ultra-builder-pro-cli 0.25.1 → 0.26.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 (241) hide show
  1. package/.ultra-template/contexts/TEMPLATE.md +87 -0
  2. package/.ultra-template/decisions/.gitkeep +1 -0
  3. package/.ultra-template/evidence/.gitkeep +1 -0
  4. package/.ultra-template/north-star.md +46 -0
  5. package/.ultra-template/research/.gitkeep +1 -0
  6. package/.ultra-template/specs/discovery.md +2 -2
  7. package/.ultra-template/tasks.json +3 -0
  8. package/.ultra-template/test-report.json +27 -0
  9. package/CHANGELOG.md +40 -0
  10. package/README.md +217 -579
  11. package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
  12. package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
  13. package/adapters/_shared/file-ops.cjs +76 -2
  14. package/adapters/_shared/host-profile.cjs +97 -0
  15. package/adapters/_shared/plugin-core.cjs +204 -0
  16. package/adapters/_shared/runtime-assets.cjs +31 -96
  17. package/adapters/claude.js +92 -291
  18. package/adapters/codex.js +309 -842
  19. package/adapters/grok.js +143 -1469
  20. package/adapters/kimi.js +124 -1053
  21. package/adapters/opencode.js +230 -742
  22. package/bin/delegate-worker.cjs +323 -0
  23. package/bin/delegate.cjs +456 -0
  24. package/bin/install.js +38 -11
  25. package/docs/ARCHITECTURE.md +122 -216
  26. package/docs/ARTIFACT-AUTHORITY.md +90 -268
  27. package/docs/DECISIONS.md +75 -87
  28. package/docs/PHILOSOPHY.md +177 -0
  29. package/docs/PLUGIN-ISOLATION-CONTRACT.md +62 -39
  30. package/docs/RUNTIME-COMPAT-MATRIX.md +99 -156
  31. package/docs/SKILL-AUTHORING.md +94 -73
  32. package/docs/WORKFLOW-LIFECYCLE.md +93 -189
  33. package/hooks/README.md +17 -37
  34. package/hooks/_common.py +113 -0
  35. package/hooks/adapters/codex.py +9 -16
  36. package/hooks/adapters/grok.py +5 -5
  37. package/hooks/adapters/kimi.py +8 -14
  38. package/hooks/block_dangerous_commands.py +95 -0
  39. package/hooks/compact_context.py +65 -0
  40. package/hooks/mid_workflow_recall.py +25 -0
  41. package/hooks/post_edit_guard.py +105 -0
  42. package/hooks/session_context.py +30 -0
  43. package/package.json +17 -52
  44. package/skills/ultra-change/SKILL.md +97 -50
  45. package/skills/ultra-change/references/change-contract.md +40 -1
  46. package/skills/ultra-delegate/SKILL.md +82 -0
  47. package/skills/ultra-delegate/references/delegation-contract.md +51 -0
  48. package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
  49. package/skills/ultra-deliver/SKILL.md +61 -42
  50. package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
  51. package/skills/ultra-dev/SKILL.md +96 -62
  52. package/skills/ultra-dev/references/debugging.md +22 -0
  53. package/skills/ultra-domain-modeling/SKILL.md +79 -0
  54. package/skills/ultra-grilling/SKILL.md +79 -0
  55. package/skills/ultra-init/SKILL.md +94 -37
  56. package/skills/ultra-init/references/brownfield-adoption.md +33 -83
  57. package/skills/ultra-init/scripts/init_project.cjs +84 -0
  58. package/skills/ultra-plan/SKILL.md +88 -68
  59. package/skills/ultra-research/SKILL.md +62 -48
  60. package/skills/ultra-research/references/00-problem-validation.md +5 -5
  61. package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
  62. package/skills/ultra-research/references/02-market-assessment.md +5 -4
  63. package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
  64. package/skills/ultra-research/references/04-product-strategy.md +3 -3
  65. package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
  66. package/skills/ultra-research/references/10-user-personas.md +3 -2
  67. package/skills/ultra-research/references/11-user-scenarios.md +3 -3
  68. package/skills/ultra-research/references/20-user-stories.md +4 -3
  69. package/skills/ultra-research/references/21-features-scope.md +3 -2
  70. package/skills/ultra-research/references/22-success-metrics.md +3 -3
  71. package/skills/ultra-research/references/30-architecture-context.md +3 -3
  72. package/skills/ultra-research/references/31-solution-strategy.md +3 -3
  73. package/skills/ultra-research/references/32-building-blocks.md +3 -3
  74. package/skills/ultra-research/references/40-deployment.md +3 -3
  75. package/skills/ultra-research/references/41-quality-risks.md +3 -3
  76. package/skills/ultra-research/references/99-synthesis.md +7 -7
  77. package/skills/ultra-review/SKILL.md +59 -32
  78. package/skills/ultra-review/references/code.md +17 -0
  79. package/skills/ultra-review/references/comments.md +12 -0
  80. package/skills/ultra-review/references/design.md +12 -0
  81. package/skills/ultra-review/references/errors.md +13 -0
  82. package/skills/ultra-review/references/spec.md +13 -0
  83. package/skills/ultra-review/references/tests.md +12 -0
  84. package/skills/ultra-review/references/worker-packet.md +40 -0
  85. package/skills/ultra-review/scripts/review_wait.py +26 -15
  86. package/skills/ultra-status/SKILL.md +61 -36
  87. package/skills/ultra-tdd/SKILL.md +81 -0
  88. package/skills/ultra-tdd/references/templates/README.md +20 -0
  89. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
  90. package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
  91. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
  92. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
  93. package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
  94. package/skills/ultra-tdd/references/test-execution.md +18 -0
  95. package/skills/ultra-test/SKILL.md +70 -30
  96. package/skills/ultra-test/references/export-syntax.md +18 -0
  97. package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
  98. package/skills/ultra-think/SKILL.md +49 -27
  99. package/skills/ultra-think/references/autonomy-boundary.md +48 -0
  100. package/skills/ultra-think/references/interaction-boundary.md +18 -33
  101. package/.ultra-template/docs/research/README.md +0 -55
  102. package/.ultra-template/reports/templates/delivery-report.json +0 -17
  103. package/.ultra-template/reports/templates/test-report.json +0 -18
  104. package/.ultra-template/tasks/tasks.json +0 -15
  105. package/.ultra-template/templates/task-context.md +0 -19
  106. package/adapters/_shared/codex-assets.cjs +0 -725
  107. package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
  108. package/adapters/_shared/interaction-contract.cjs +0 -129
  109. package/adapters/_shared/path-rewrite.cjs +0 -79
  110. package/adapters/_shared/runtime-native.cjs +0 -132
  111. package/adapters/_shared/settings-merge.cjs +0 -76
  112. package/agents/code-reviewer.md +0 -42
  113. package/agents/debugger.md +0 -40
  114. package/agents/review-code.md +0 -49
  115. package/agents/review-comments.md +0 -38
  116. package/agents/review-coordinator.md +0 -56
  117. package/agents/review-design.md +0 -40
  118. package/agents/review-errors.md +0 -43
  119. package/agents/review-spec.md +0 -39
  120. package/agents/review-tests.md +0 -40
  121. package/agents/tdd-runner.md +0 -38
  122. package/bin/orchestrator.js +0 -383
  123. package/commands/ultra-change.md +0 -10
  124. package/commands/ultra-deliver.md +0 -10
  125. package/commands/ultra-dev.md +0 -10
  126. package/commands/ultra-doctor.md +0 -10
  127. package/commands/ultra-init.md +0 -10
  128. package/commands/ultra-plan.md +0 -10
  129. package/commands/ultra-research.md +0 -10
  130. package/commands/ultra-review.md +0 -10
  131. package/commands/ultra-status.md +0 -10
  132. package/commands/ultra-test.md +0 -10
  133. package/commands/ultra-think.md +0 -10
  134. package/docs/AGENT-CONTEXT.md +0 -191
  135. package/docs/COMMIT-HASH-BACKFILL.md +0 -51
  136. package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
  137. package/docs/ROADMAP.md +0 -101
  138. package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
  139. package/hooks/active_task_context.py +0 -152
  140. package/hooks/context_envelope.py +0 -100
  141. package/hooks/health_check.py +0 -196
  142. package/hooks/pre_stop_check.py +0 -66
  143. package/hooks/runtime_paths.py +0 -396
  144. package/hooks/subagent_tracker.py +0 -88
  145. package/hooks/workflow_checkpoint.py +0 -76
  146. package/hooks/workflow_context.py +0 -48
  147. package/hooks/workflow_resume.py +0 -84
  148. package/mcp-server/hook-context.cjs +0 -38
  149. package/mcp-server/hook-event.cjs +0 -34
  150. package/mcp-server/lib/archive-journal.cjs +0 -1119
  151. package/mcp-server/lib/archive-mutation-worker.py +0 -365
  152. package/mcp-server/lib/artifact-registry.cjs +0 -1232
  153. package/mcp-server/lib/baseline-workflow.cjs +0 -1193
  154. package/mcp-server/lib/canonical-json.cjs +0 -19
  155. package/mcp-server/lib/change-authority.cjs +0 -26
  156. package/mcp-server/lib/change-packet.cjs +0 -790
  157. package/mcp-server/lib/change-workflow.cjs +0 -65
  158. package/mcp-server/lib/context-envelope.cjs +0 -581
  159. package/mcp-server/lib/context-paths.cjs +0 -142
  160. package/mcp-server/lib/decision-records.cjs +0 -423
  161. package/mcp-server/lib/delivery-transaction.cjs +0 -471
  162. package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
  163. package/mcp-server/lib/doctor.cjs +0 -627
  164. package/mcp-server/lib/git-bootstrap.cjs +0 -651
  165. package/mcp-server/lib/init-project.cjs +0 -1021
  166. package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
  167. package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
  168. package/mcp-server/lib/managed-file-write.cjs +0 -172
  169. package/mcp-server/lib/plan-checkpoint.cjs +0 -177
  170. package/mcp-server/lib/plan-publication-worker.cjs +0 -830
  171. package/mcp-server/lib/plan-store.cjs +0 -794
  172. package/mcp-server/lib/prd-parser.cjs +0 -83
  173. package/mcp-server/lib/pricing.cjs +0 -44
  174. package/mcp-server/lib/project-context.cjs +0 -178
  175. package/mcp-server/lib/projector.cjs +0 -386
  176. package/mcp-server/lib/provider-refs.cjs +0 -42
  177. package/mcp-server/lib/runtime-paths.cjs +0 -1714
  178. package/mcp-server/lib/runtime-state.cjs +0 -222
  179. package/mcp-server/lib/safe-project-file.cjs +0 -385
  180. package/mcp-server/lib/stage-checkpoints.cjs +0 -463
  181. package/mcp-server/lib/state-authority.cjs +0 -95
  182. package/mcp-server/lib/state-db.cjs +0 -1580
  183. package/mcp-server/lib/state-ops.cjs +0 -1428
  184. package/mcp-server/lib/task-contract.cjs +0 -404
  185. package/mcp-server/lib/task-expander.cjs +0 -105
  186. package/mcp-server/lib/task-ledger.cjs +0 -1964
  187. package/mcp-server/lib/telemetry.cjs +0 -85
  188. package/mcp-server/lib/topo.cjs +0 -131
  189. package/mcp-server/lib/ultra-facade.cjs +0 -2684
  190. package/mcp-server/lib/worker-packet.cjs +0 -358
  191. package/mcp-server/server.cjs +0 -437
  192. package/orchestrator/auto-merge.cjs +0 -128
  193. package/orchestrator/daemon.cjs +0 -264
  194. package/orchestrator/dispatch-rules.cjs +0 -78
  195. package/orchestrator/parallel-orchestrator.cjs +0 -903
  196. package/orchestrator/planner/plan-builder.cjs +0 -125
  197. package/orchestrator/recovery.cjs +0 -188
  198. package/orchestrator/session-close-journal-worker.cjs +0 -5
  199. package/orchestrator/session-close-journal.cjs +0 -1014
  200. package/orchestrator/session-runner.cjs +0 -1153
  201. package/orchestrator/worktree-manager.cjs +0 -282
  202. package/settings.json +0 -7
  203. package/skills/cc-collab/SKILL.md +0 -42
  204. package/skills/code-review-expert/SKILL.md +0 -43
  205. package/skills/code-review-expert/references/correctness-reliability.md +0 -30
  206. package/skills/code-review-expert/references/design-boundaries.md +0 -26
  207. package/skills/code-review-expert/references/removal-plan.md +0 -24
  208. package/skills/codex-collab/SKILL.md +0 -49
  209. package/skills/codex-collab/evals/evals.json +0 -35
  210. package/skills/integration-rules/SKILL.md +0 -32
  211. package/skills/security-rules/SKILL.md +0 -38
  212. package/skills/testing-rules/SKILL.md +0 -32
  213. package/skills/ultra-doctor/SKILL.md +0 -41
  214. package/skills/ultra-plan/references/semantic-preflight.md +0 -20
  215. package/skills/ultra-verify/SKILL.md +0 -46
  216. package/skills/ultra-verify/evals/evals.json +0 -46
  217. package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
  218. package/skills/ultra-verify/references/evidence-status.md +0 -21
  219. package/skills/ultra-verify/scripts/verify_wait.py +0 -94
  220. package/spec/cli-protocol.md +0 -102
  221. package/spec/mcp-tools.yaml +0 -312
  222. package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
  223. package/spec/schemas/change-delta.v1.schema.json +0 -153
  224. package/spec/schemas/command-manifest.schema.json +0 -29
  225. package/spec/schemas/context-file.v4.5.schema.json +0 -54
  226. package/spec/schemas/delivery-report.v1.schema.json +0 -66
  227. package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
  228. package/spec/schemas/mcp-tools.schema.json +0 -136
  229. package/spec/schemas/skill-manifest.schema.json +0 -24
  230. package/spec/schemas/state-db.sql +0 -641
  231. package/spec/schemas/task-ledger.v2.schema.json +0 -296
  232. package/spec/schemas/tasks.v4.5.schema.json +0 -96
  233. package/spec/schemas/test-report.v1.schema.json +0 -225
  234. package/ultra-tools/cli.cjs +0 -129
  235. package/ultra-tools/commands/db.cjs +0 -226
  236. package/ultra-tools/commands/legacy-memory.cjs +0 -159
  237. package/ultra-tools/commands/migrate.cjs +0 -1386
  238. package/ultra-tools/commands/session.cjs +0 -213
  239. package/ultra-tools/commands/status.cjs +0 -559
  240. package/ultra-tools/commands/system.cjs +0 -498
  241. package/ultra-tools/commands/task.cjs +0 -105
package/adapters/codex.js CHANGED
@@ -1,21 +1,5 @@
1
1
  'use strict';
2
2
 
3
- /**
4
- * Codex adapter.
5
- *
6
- * Codex-native mapping:
7
- * commands/*.md -> explicit plugin skills ($ultra-builder-pro:<name>)
8
- * skills/** -> ~/plugins/ultra-builder-pro/skills/**
9
- * agents/*.md -> ~/.codex/agents/*.toml
10
- * hooks/*.py -> plugin hooks with a Codex wire adapter
11
- * mcp-server -> plugin .mcp.json (project cwd owns .ultra/.runtime/state.db)
12
- * plugin marketplace -> ~/.agents/plugins/marketplace.json
13
- *
14
- * Deprecated ~/.codex/prompts and ~/.codex/skills projection is intentionally
15
- * not used. The plugin remains the single distribution boundary for skills,
16
- * hooks, and MCP; custom agents use Codex's standalone TOML surface.
17
- */
18
-
19
3
  const fs = require('node:fs');
20
4
  const os = require('node:os');
21
5
  const path = require('node:path');
@@ -23,43 +7,35 @@ const crypto = require('node:crypto');
23
7
  const { spawnSync } = require('node:child_process');
24
8
 
25
9
  const {
10
+ copyHooks,
11
+ copySkills,
12
+ inspectPlugin,
13
+ markManaged,
14
+ publishManagedTrees,
15
+ removeManaged,
16
+ writePluginProvenance,
17
+ } = require('./_shared/plugin-core.cjs');
18
+ const {
19
+ captureAbsent,
26
20
  ensureDir,
27
21
  isManaged,
22
+ listRelative,
23
+ managedMetadata,
24
+ pruneCreatedEmpty,
28
25
  removeTree,
29
26
  writeAtomic,
30
27
  } = require('./_shared/file-ops.cjs');
31
- const {
32
- PLUGIN_NAME,
33
- MANAGED_MARKER,
34
- applyNativeDoctor,
35
- buildPlugin,
36
- installAgents,
37
- } = require('./_shared/codex-assets.cjs');
38
- const provenance = require('./_shared/provenance.cjs');
39
28
 
40
- const MCP_SERVER_NAME = PLUGIN_NAME;
41
- const SOURCE_TAG = 'ubp';
42
- const MARKER_BEGIN = '# >>> ultra-builder-pro managed block — do not edit by hand';
43
- const MARKER_END = '# <<< ultra-builder-pro managed block';
44
- const RUNTIME_MANIFEST_DIR = 'ultra-builder-pro';
45
- const RUNTIME_MANIFEST_FILE = 'install-manifest.json';
46
- const PROVENANCE_FILE = 'provenance.json';
47
- const HOOK_ADAPTER_RELATIVE = path.join('hooks', 'adapters', 'codex.py');
29
+ const PLUGIN_NAME = 'ultra-builder-pro';
48
30
 
49
31
  function resolveTarget(ctx = {}) {
50
32
  if (ctx.configDir) return path.resolve(ctx.configDir);
51
- if (ctx.scope === 'global') {
52
- return process.env.CODEX_HOME || path.join(ctx.homeDir || os.homedir(), '.codex');
53
- }
33
+ if (ctx.scope === 'global') return process.env.CODEX_HOME || path.join(ctx.homeDir || os.homedir(), '.codex');
54
34
  return path.join(ctx.cwd || process.cwd(), '.codex');
55
35
  }
56
36
 
57
37
  function resolveHomeDir(ctx = {}) {
58
- return path.resolve(ctx.homeDir || os.homedir());
59
- }
60
-
61
- function resolveRepoRoot(ctx = {}) {
62
- return ctx.repoRoot || path.resolve(__dirname, '..');
38
+ return path.resolve(ctx.configDir || ctx.homeDir || os.homedir());
63
39
  }
64
40
 
65
41
  function resolvePluginRoot(ctx = {}) {
@@ -70,896 +46,387 @@ function resolveMarketplaceFile(ctx = {}) {
70
46
  return path.join(resolveHomeDir(ctx), '.agents', 'plugins', 'marketplace.json');
71
47
  }
72
48
 
73
- function runtimeManifestFile(configDir) {
74
- return path.join(configDir, RUNTIME_MANIFEST_DIR, RUNTIME_MANIFEST_FILE);
75
- }
76
-
77
- function stripManagedBlock(text) {
78
- const begin = text.indexOf(MARKER_BEGIN);
79
- if (begin === -1) return text;
80
- const end = text.indexOf(MARKER_END, begin);
81
- if (end === -1) return text;
82
- const endLine = text.indexOf('\n', end);
83
- const after = endLine === -1 ? text.length : endLine + 1;
84
- const leading = text.slice(0, begin).replace(/\n+$/, '\n');
85
- return leading + text.slice(after);
86
- }
87
-
88
- function hasManagedBlock(text) {
89
- return text.includes(MARKER_BEGIN);
49
+ function resolveRepoRoot(ctx = {}) {
50
+ return ctx.repoRoot || path.resolve(__dirname, '..');
90
51
  }
91
52
 
92
- function cleanupLegacyConfig(configDir) {
93
- const configFile = path.join(configDir, 'config.toml');
94
- let configUpdated = false;
95
- if (fs.existsSync(configFile)) {
96
- const existing = fs.readFileSync(configFile, 'utf8');
97
- if (hasManagedBlock(existing)) {
98
- const stripped = stripManagedBlock(existing);
99
- if (stripped.trim()) writeAtomic(configFile, stripped);
100
- else fs.unlinkSync(configFile);
101
- configUpdated = true;
102
- }
103
- }
104
-
105
- const removed = [];
106
- for (const sub of ['skills', 'prompts']) {
107
- const dir = path.join(configDir, sub);
108
- if (fs.existsSync(dir) && isManaged(dir)) {
109
- removeTree(dir);
110
- removed.push(sub);
111
- }
112
- }
113
- return { configUpdated, removed };
53
+ function currentCleanupRoots(ctx = {}) {
54
+ return new Set([resolveHomeDir(ctx), resolveTarget(ctx)].map((root) => path.resolve(root)));
114
55
  }
115
56
 
116
- function loadMarketplace(file) {
117
- if (!fs.existsSync(file)) {
118
- return {
119
- name: 'personal',
120
- interface: { displayName: 'Personal' },
121
- plugins: [],
122
- };
123
- }
124
- const payload = JSON.parse(fs.readFileSync(file, 'utf8'));
125
- if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
126
- throw new Error(`${file} must contain a JSON object`);
127
- }
128
- if (typeof payload.name !== 'string' || !payload.name.trim()) {
129
- throw new Error(`${file} must contain a non-empty marketplace name`);
130
- }
131
- if (!payload.interface || typeof payload.interface !== 'object' || Array.isArray(payload.interface)) {
132
- payload.interface = { displayName: 'Personal' };
133
- }
134
- if (!Array.isArray(payload.plugins)) {
135
- throw new Error(`${file} field plugins must be an array`);
57
+ function installCleanupRoots(ctx, pluginRoot) {
58
+ const previous = managedMetadata(pluginRoot)?.cleanup_roots;
59
+ const allowed = currentCleanupRoots(ctx);
60
+ if (previous !== undefined) {
61
+ if (!Array.isArray(previous) || previous.some((entry) => (
62
+ !entry || !allowed.has(path.resolve(entry.root || '')) || !Array.isArray(entry.absent)
63
+ ))) throw new Error(`invalid Codex cleanup ownership in ${pluginRoot}`);
64
+ return previous;
136
65
  }
137
- return payload;
138
- }
139
66
 
140
- function marketplaceEntry() {
141
- return {
142
- name: PLUGIN_NAME,
143
- source: { source: 'local', path: `./plugins/${PLUGIN_NAME}` },
144
- policy: { installation: 'AVAILABLE', authentication: 'ON_INSTALL' },
145
- category: 'Developer Tools',
67
+ const groups = new Map();
68
+ const add = (root, candidates) => {
69
+ root = path.resolve(root);
70
+ if (!groups.has(root)) groups.set(root, new Set());
71
+ for (const candidate of candidates) groups.get(root).add(candidate);
146
72
  };
147
- }
148
-
149
- function upsertMarketplace(file) {
150
- const payload = mergedMarketplace(file);
151
- writeAtomic(file, JSON.stringify(payload, null, 2) + '\n');
152
- return { file, name: payload.name, entry: marketplaceEntry() };
153
- }
154
-
155
- function mergedMarketplace(file) {
156
- const payload = loadMarketplace(file);
157
- const entry = marketplaceEntry();
158
- const index = payload.plugins.findIndex((plugin) => plugin && plugin.name === PLUGIN_NAME);
159
- if (index === -1) payload.plugins.push(entry);
160
- else payload.plugins[index] = entry;
161
- return payload;
162
- }
163
-
164
- function removeMarketplaceEntry(file) {
165
- if (!fs.existsSync(file)) return { file, updated: false, name: 'personal' };
166
- const payload = loadMarketplace(file);
167
- const next = payload.plugins.filter((plugin) => !plugin || plugin.name !== PLUGIN_NAME);
168
- if (next.length === payload.plugins.length) {
169
- return { file, updated: false, name: payload.name };
170
- }
171
- payload.plugins = next;
172
- writeAtomic(file, JSON.stringify(payload, null, 2) + '\n');
173
- return { file, updated: true, name: payload.name };
174
- }
175
-
176
- function shouldRunPluginCli(ctx = {}) {
177
- if (typeof ctx.runPluginCli === 'boolean') return ctx.runPluginCli;
178
- return ctx.scope === 'global' && !ctx.configDir;
179
- }
180
-
181
- function shouldRunHostCli(ctx = {}) {
182
- if (typeof ctx.runHostCli === 'boolean') return ctx.runHostCli;
183
- return shouldRunPluginCli(ctx);
184
- }
185
-
186
- function runCodexCli(args, ctx = {}) {
187
- const result = spawnSync(ctx.codexBin || 'codex', args, {
188
- encoding: 'utf8',
189
- timeout: ctx.hostCliTimeoutMs || 30000,
190
- killSignal: 'SIGKILL',
191
- stdio: ['ignore', 'pipe', 'pipe'],
192
- env: {
193
- ...process.env,
194
- HOME: resolveHomeDir(ctx),
195
- CODEX_HOME: resolveTarget(ctx),
196
- },
197
- });
198
- if (result.error) {
199
- if (result.error.code === 'ETIMEDOUT') {
200
- const error = new Error(`codex ${args.join(' ')} timed out`);
201
- error.code = 'HOST_CLI_TIMEOUT';
202
- throw error;
73
+ add(resolveHomeDir(ctx), [
74
+ '.', 'plugins', '.agents', path.join('.agents', 'plugins'),
75
+ path.join('.agents', 'plugins', 'marketplace.json'),
76
+ ]);
77
+ add(resolveTarget(ctx), [
78
+ '.', 'config.toml', 'plugins', path.join('plugins', 'cache'),
79
+ path.join('plugins', 'cache', 'personal'), 'tmp', path.join('tmp', 'arg0'),
80
+ ]);
81
+ return [...groups].map(([root, candidates]) => ({
82
+ root,
83
+ absent: captureAbsent(root, [...candidates]),
84
+ }));
85
+ }
86
+
87
+ function pruneInstallCleanup(ctx, cleanupRoots) {
88
+ const allowed = currentCleanupRoots(ctx);
89
+ const removed = [];
90
+ for (const entry of cleanupRoots || []) {
91
+ const root = path.resolve(entry.root || '');
92
+ if (!allowed.has(root) || !Array.isArray(entry.absent)) {
93
+ throw new Error('refusing Codex cleanup outside the current config and sidecar roots');
203
94
  }
204
- throw result.error;
205
- }
206
- if (result.status !== 0) {
207
- const detail = (result.stderr || result.stdout || '').trim();
208
- throw new Error(`codex ${args.join(' ')} failed${detail ? `: ${detail}` : ''}`);
95
+ removed.push(...pruneCreatedEmpty(root, entry.absent).map((item) => `${root}:${item}`));
209
96
  }
210
- return result.stdout;
97
+ return removed;
211
98
  }
212
99
 
213
- function runPluginCli(action, marketplaceName, ctx = {}) {
214
- const selector = `${PLUGIN_NAME}@${marketplaceName}`;
215
- const args = action === 'add'
216
- ? ['plugin', 'add', selector, '--json']
217
- : ['plugin', 'remove', selector, '--json'];
218
- return { selector, stdout: runCodexCli(args, ctx).trim() };
100
+ function shellQuote(value) {
101
+ return `'${String(value).replaceAll("'", "'\"'\"'")}'`;
219
102
  }
220
103
 
221
- function snapshotPluginRegistration(marketplaceName, ctx = {}) {
222
- const selector = `${PLUGIN_NAME}@${marketplaceName}`;
223
- const payload = JSON.parse(runCodexCli(['plugin', 'list', '--json'], ctx));
224
- if (!payload || !Array.isArray(payload.installed)) {
225
- throw new Error('codex plugin list --json did not return an installed array');
226
- }
227
- const entry = payload.installed.find((plugin) => (
228
- plugin?.pluginId === selector
229
- || (plugin?.name === PLUGIN_NAME && plugin?.marketplaceName === marketplaceName)
230
- )) || null;
104
+ function hookCommand(pluginRoot, name) {
105
+ const adapter = path.join(pluginRoot, 'hooks', 'adapters', 'codex.py');
231
106
  return {
232
- selector,
233
- marketplaceName,
234
- installed: !!entry && entry.installed !== false,
235
- enabled: !!entry && entry.enabled === true,
236
- entry,
237
- config: captureFiles([path.join(resolveTarget(ctx), 'config.toml')])[0],
107
+ type: 'command',
108
+ command: `python3 ${shellQuote(adapter)} ${shellQuote(name)}`,
109
+ timeout: 10,
238
110
  };
239
111
  }
240
112
 
241
- function restorePluginRegistration(snapshot, ctx = {}) {
242
- const action = snapshot.installed ? 'add' : 'remove';
243
- let actionError = null;
244
- let result = null;
245
- try {
246
- result = runPluginCli(action, snapshot.marketplaceName, ctx);
247
- } catch (error) {
248
- actionError = error;
249
- }
250
- let configError = null;
251
- try {
252
- restoreFiles([snapshot.config]);
253
- } catch (error) {
254
- configError = error;
255
- }
256
- if (actionError || configError) {
257
- throw new AggregateError(
258
- [actionError, configError].filter(Boolean),
259
- `Codex ${action} registration rollback failed`,
260
- );
261
- }
113
+ function buildHooksManifest(pluginRoot = '${CODEX_PLUGIN_ROOT}') {
262
114
  return {
263
- ...result,
264
- restored: {
265
- installed: snapshot.installed,
266
- enabled: snapshot.enabled,
115
+ hooks: {
116
+ SessionStart: [
117
+ { matcher: 'startup|resume|clear', hooks: [hookCommand(pluginRoot, 'session_context.py')] },
118
+ { matcher: 'compact', hooks: [hookCommand(pluginRoot, 'compact_context.py')] },
119
+ ],
120
+ PreToolUse: [
121
+ { matcher: 'Write|Edit|Grep|apply_patch', hooks: [hookCommand(pluginRoot, 'mid_workflow_recall.py')] },
122
+ { matcher: 'Bash', hooks: [hookCommand(pluginRoot, 'block_dangerous_commands.py')] },
123
+ ],
124
+ PostToolUse: [
125
+ { matcher: 'Write|Edit|apply_patch', hooks: [hookCommand(pluginRoot, 'post_edit_guard.py')] },
126
+ ],
127
+ PreCompact: [{ matcher: 'manual|auto', hooks: [hookCommand(pluginRoot, 'compact_context.py')] }],
128
+ PostCompact: [{ hooks: [hookCommand(pluginRoot, 'compact_context.py')] }],
267
129
  },
268
130
  };
269
131
  }
270
132
 
271
- function inspectCodexHost(ctx = {}, runtimeManifest = null, expectedPluginVersion = null) {
272
- const payload = JSON.parse(runCodexCli(['plugin', 'list', '--json'], ctx));
273
- if (!payload || !Array.isArray(payload.installed)) {
274
- throw new Error('codex plugin list --json did not return an installed array');
275
- }
276
- const marketplaceName = runtimeManifest?.marketplace?.name || 'personal';
277
- const pluginId = `${PLUGIN_NAME}@${marketplaceName}`;
278
- const plugin = payload.installed.find((entry) => (
279
- entry?.pluginId === pluginId
280
- || (entry?.name === PLUGIN_NAME && entry?.marketplaceName === marketplaceName)
281
- ));
282
- const pluginOk = !!plugin
283
- && plugin.installed === true
284
- && plugin.enabled === true
285
- && (!(expectedPluginVersion || runtimeManifest?.plugin?.version)
286
- || plugin.version === (expectedPluginVersion || runtimeManifest.plugin.version));
287
- const mcpPayload = JSON.parse(runCodexCli(['mcp', 'list', '--json'], ctx));
288
- if (!Array.isArray(mcpPayload)) {
289
- throw new Error('codex mcp list --json did not return an array');
290
- }
291
- const expected = JSON.parse(
292
- fs.readFileSync(path.join(resolvePluginRoot(ctx), '.mcp.json'), 'utf8'),
293
- ).mcpServers?.[MCP_SERVER_NAME];
294
- const mcp = mcpPayload.find((entry) => entry?.name === MCP_SERVER_NAME);
295
- const mcpOk = !!mcp
296
- && mcp.enabled === true
297
- && mcp.disabled_reason === null
298
- && mcp.transport?.type === 'stdio'
299
- && mcp.transport.command === expected?.command
300
- && JSON.stringify(mcp.transport.args) === JSON.stringify(expected?.args);
301
- return { pluginId, plugin, pluginOk, mcp, mcpOk };
302
- }
303
-
304
- function pluginCacheRoot(configDir, marketplaceName) {
305
- if (typeof marketplaceName !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(marketplaceName)) {
306
- throw new Error(`invalid Codex marketplace name: ${marketplaceName}`);
133
+ function contentHash(root, version) {
134
+ const hash = crypto.createHash('sha256').update(version);
135
+ for (const relative of listRelative(root).sort()) {
136
+ if (relative.startsWith('.codex-plugin') || relative === '.ubp-managed') continue;
137
+ hash.update(relative).update(fs.readFileSync(path.join(root, relative)));
307
138
  }
308
- return path.join(configDir, 'plugins', 'cache', marketplaceName, PLUGIN_NAME);
309
- }
310
-
311
- function normalizeHookCacheVersions(versions) {
312
- const normalized = new Set();
313
- for (const version of versions) {
314
- if (typeof version !== 'string'
315
- || !/^[A-Za-z0-9][A-Za-z0-9.+_-]*$/.test(version)) {
316
- throw new Error(`invalid Codex plugin cache version: ${String(version)}`);
317
- }
318
- normalized.add(version);
319
- }
320
- return [...normalized].sort();
321
- }
322
-
323
- function listHookCacheVersionsOnDisk(configDir, marketplaceName) {
324
- const root = pluginCacheRoot(configDir, marketplaceName);
325
- if (!fs.existsSync(root)) return [];
326
- return normalizeHookCacheVersions(
327
- fs.readdirSync(root, { withFileTypes: true })
328
- .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
329
- .map((entry) => entry.name),
330
- );
139
+ return hash.digest('hex').slice(0, 12);
331
140
  }
332
141
 
333
- function listKnownHookCacheVersions(configDir, marketplaceName, manifest = null) {
334
- const versions = listHookCacheVersionsOnDisk(configDir, marketplaceName);
335
-
336
- if (manifest && manifest.source === SOURCE_TAG && manifest.adapter === 'codex') {
337
- if (manifest.plugin && manifest.plugin.version !== undefined) {
338
- versions.push(manifest.plugin.version);
339
- }
340
- if (manifest.hook_cache_versions !== undefined) {
341
- if (!Array.isArray(manifest.hook_cache_versions)) {
342
- throw new Error('invalid Codex runtime manifest hook_cache_versions');
343
- }
344
- versions.push(...manifest.hook_cache_versions);
345
- }
142
+ function buildStaging(repoRoot, staging, publishedRoot) {
143
+ const skillRoot = path.join(staging, 'skills');
144
+ const hookRoot = path.join(staging, 'hooks');
145
+ const skills = copySkills({ runtime: 'codex', repoRoot, skillRoot });
146
+ copyHooks({
147
+ runtime: 'codex', repoRoot, hookRoot,
148
+ hooksManifest: buildHooksManifest(publishedRoot),
149
+ });
150
+ if (fs.existsSync(path.join(repoRoot, 'LICENSE'))) {
151
+ fs.copyFileSync(path.join(repoRoot, 'LICENSE'), path.join(staging, 'LICENSE'));
346
152
  }
347
- return normalizeHookCacheVersions(versions);
153
+ const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
154
+ const version = `${pkg.version}+codex.${contentHash(staging, pkg.version)}`;
155
+ writeAtomic(path.join(staging, '.codex-plugin', 'plugin.json'), `${JSON.stringify({
156
+ name: PLUGIN_NAME,
157
+ version,
158
+ description: 'File-first Ultra Builder Pro workflows for Codex.',
159
+ author: { name: pkg.author },
160
+ homepage: pkg.homepage,
161
+ repository: pkg.repository?.url || pkg.repository,
162
+ license: pkg.license,
163
+ keywords: ['codex', 'skills', 'hooks', 'ultra-builder-pro'],
164
+ skills: './skills/',
165
+ interface: {
166
+ displayName: 'Ultra Builder Pro',
167
+ shortDescription: 'File-first project workflows and evidence.',
168
+ longDescription: 'Resume product and engineering work across sessions and coding-agent hosts through portable Skills, repository files, and Git.',
169
+ developerName: pkg.author,
170
+ category: 'Developer Tools',
171
+ capabilities: ['Interactive', 'Write'],
172
+ websiteURL: pkg.homepage,
173
+ defaultPrompt: [
174
+ 'Start a new project with $ultra-builder-pro:ultra-init.',
175
+ 'Reconcile this change with $ultra-builder-pro:ultra-change.',
176
+ 'Show workflow status with $ultra-builder-pro:ultra-status.',
177
+ ],
178
+ },
179
+ }, null, 2)}\n`);
180
+ markManaged(staging, { adapter: 'codex', plugin: PLUGIN_NAME, version });
181
+ writePluginProvenance({
182
+ adapter: 'codex', repoRoot, stagingRoot: staging, publishedRoot,
183
+ contracts: {
184
+ plugin_manifest: { root: 'plugin', path: '.codex-plugin/plugin.json' },
185
+ hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
186
+ hook_adapter: { root: 'plugin', path: 'hooks/adapters/codex.py' },
187
+ },
188
+ });
189
+ return { skills, version };
348
190
  }
349
191
 
350
- function readPluginManifest(pluginRoot) {
351
- const file = path.join(pluginRoot, '.codex-plugin', 'plugin.json');
352
- const contents = fs.readFileSync(file);
353
- const manifest = JSON.parse(contents.toString('utf8'));
354
- if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)
355
- || manifest.name !== PLUGIN_NAME) {
356
- throw new Error(`invalid Codex plugin manifest: ${file}`);
192
+ function loadMarketplace(file) {
193
+ if (!fs.existsSync(file)) return { name: 'personal', interface: { displayName: 'Personal' }, plugins: [] };
194
+ const value = JSON.parse(fs.readFileSync(file, 'utf8'));
195
+ if (!value || typeof value !== 'object' || Array.isArray(value) || !Array.isArray(value.plugins)) {
196
+ throw new Error(`invalid Codex marketplace: ${file}`);
357
197
  }
358
- normalizeHookCacheVersions([manifest.version]);
359
- return { file, contents, manifest };
360
- }
361
-
362
- function codexVersionParts(version) {
363
- if (typeof version !== 'string') return null;
364
- const match = /^(.+)\+codex\.([A-Za-z0-9][A-Za-z0-9._-]*)$/.exec(version);
365
- return match ? { base: match[1], cachebuster: match[2] } : null;
198
+ return value;
366
199
  }
367
200
 
368
- function bindCachebusterProvenance({
369
- report,
370
- runtimeManifest,
371
- sourceManifest,
372
- host,
373
- configDir,
374
- marketplaceName,
375
- }) {
376
- const previousVersion = runtimeManifest?.plugin?.version;
377
- const currentVersion = sourceManifest?.manifest?.version;
378
- const previous = codexVersionParts(previousVersion);
379
- const current = codexVersionParts(currentVersion);
380
- if (!previous || !current || previous.base !== current.base
381
- || previousVersion === currentVersion) {
382
- return false;
383
- }
384
-
385
- const mismatch = report.issues.filter((issue) => (
386
- issue.code === 'ASSET_HASH_MISMATCH'
387
- && issue.root === 'plugin'
388
- && issue.path === path.join('.codex-plugin', 'plugin.json')
389
- ));
390
- if (mismatch.length !== 1
391
- || !host?.pluginOk
392
- || host.plugin?.version !== currentVersion) {
393
- report.checks.cachebuster_binding = { status: 'fail' };
394
- return false;
395
- }
396
-
397
- const originalManifest = {
398
- ...sourceManifest.manifest,
399
- version: previousVersion,
400
- };
401
- const originalHash = crypto.createHash('sha256')
402
- .update(`${JSON.stringify(originalManifest, null, 2)}\n`)
403
- .digest('hex');
404
- const cacheManifestFile = path.join(
405
- pluginCacheRoot(configDir, marketplaceName),
406
- currentVersion,
407
- '.codex-plugin',
408
- 'plugin.json',
409
- );
410
- let cacheMatchesSource = false;
411
- try {
412
- cacheMatchesSource = crypto.createHash('sha256')
413
- .update(fs.readFileSync(cacheManifestFile))
414
- .digest('hex')
415
- === crypto.createHash('sha256')
416
- .update(sourceManifest.contents)
417
- .digest('hex');
418
- } catch (error) {
419
- if (error.code !== 'ENOENT') throw error;
420
- }
421
- if (originalHash !== mismatch[0].expected || !cacheMatchesSource) {
422
- report.checks.cachebuster_binding = { status: 'fail' };
423
- return false;
424
- }
425
-
426
- report.issues = report.issues.filter((issue) => issue !== mismatch[0]);
427
- report.checks.assets.failed -= 1;
428
- if (report.checks.assets.failed === 0) report.checks.assets.status = 'pass';
429
- report.checks.cachebuster_binding = {
430
- status: 'pass',
431
- previous_version: previousVersion,
432
- current_version: currentVersion,
433
- cache_manifest: cacheManifestFile,
201
+ function marketplaceEntry() {
202
+ return {
203
+ name: PLUGIN_NAME,
204
+ source: { source: 'local', path: `./plugins/${PLUGIN_NAME}` },
205
+ policy: { installation: 'AVAILABLE', authentication: 'ON_INSTALL' },
206
+ category: 'Developer Tools',
434
207
  };
435
- return true;
436
208
  }
437
209
 
438
- function hookAdaptersForVersions(configDir, marketplaceName, versions) {
439
- const root = pluginCacheRoot(configDir, marketplaceName);
440
- return normalizeHookCacheVersions(versions)
441
- .map((version) => path.join(root, version, HOOK_ADAPTER_RELATIVE));
210
+ function writeMarketplace(file) {
211
+ const value = loadMarketplace(file);
212
+ const next = value.plugins.filter((entry) => entry?.name !== PLUGIN_NAME);
213
+ next.push(marketplaceEntry());
214
+ value.plugins = next;
215
+ writeAtomic(file, `${JSON.stringify(value, null, 2)}\n`);
216
+ return value.name;
442
217
  }
443
218
 
444
- function listCachedHookAdapters(configDir, marketplaceName, manifest = null) {
445
- return hookAdaptersForVersions(
446
- configDir,
447
- marketplaceName,
448
- listKnownHookCacheVersions(configDir, marketplaceName, manifest),
449
- );
450
- }
451
-
452
- function hookForwarder(target) {
453
- return `#!/usr/bin/env python3
454
- """Forward a live Codex task from a retired plugin cache to the current adapter."""
455
-
456
- import runpy
457
-
458
- runpy.run_path(${JSON.stringify(path.resolve(target))}, run_name="__main__")
459
- `;
460
- }
461
-
462
- function prepareHookAdapterPath(file) {
463
- const versionRoot = path.dirname(path.dirname(path.dirname(file)));
464
- try {
465
- const stat = fs.lstatSync(versionRoot);
466
- if (stat.isSymbolicLink()) {
467
- fs.unlinkSync(versionRoot);
468
- } else if (!stat.isDirectory()) {
469
- throw new Error(`Codex plugin cache version is not a directory: ${versionRoot}`);
470
- }
471
- } catch (error) {
472
- if (error.code !== 'ENOENT') throw error;
473
- }
474
- }
475
-
476
- function restoreCachedHookAdapters(previousAdapters, target) {
477
- const resolvedTarget = path.resolve(target);
478
- if (!fs.existsSync(resolvedTarget)) {
479
- throw new Error(`current Codex hook adapter is missing: ${resolvedTarget}`);
480
- }
481
- const restored = [];
482
- for (const previous of previousAdapters) {
483
- if (path.resolve(previous) === resolvedTarget || fs.existsSync(previous)) continue;
484
- prepareHookAdapterPath(previous);
485
- writeAtomic(previous, hookForwarder(resolvedTarget));
486
- restored.push(previous);
487
- }
488
- return { target: resolvedTarget, restored };
489
- }
490
-
491
- function currentHookAdapter(configDir, marketplaceName, plugin) {
492
- const cached = path.join(pluginCacheRoot(configDir, marketplaceName), plugin.version, HOOK_ADAPTER_RELATIVE);
493
- if (fs.existsSync(cached)) return cached;
494
- return path.join(plugin.root, HOOK_ADAPTER_RELATIVE);
495
- }
496
-
497
- function writeRuntimeManifest(configDir, data) {
498
- const file = runtimeManifestFile(configDir);
499
- writeAtomic(file, JSON.stringify({
500
- source: SOURCE_TAG,
501
- adapter: 'codex',
502
- plugin: data.plugin,
503
- marketplace: data.marketplace,
504
- agents: data.agents,
505
- hook_cache_versions: data.hookCacheVersions,
506
- }, null, 2) + '\n');
507
- return file;
219
+ function runCodex(args, ctx) {
220
+ const result = spawnSync(ctx.codexBin || 'codex', args, {
221
+ encoding: 'utf8', timeout: ctx.hostCliTimeoutMs || 30000,
222
+ env: { ...process.env, HOME: resolveHomeDir(ctx), CODEX_HOME: resolveTarget(ctx) },
223
+ });
224
+ if (result.error) throw result.error;
225
+ if (result.status !== 0) throw new Error((result.stderr || result.stdout || '').trim() || `codex ${args.join(' ')} failed`);
226
+ return result.stdout;
508
227
  }
509
228
 
510
- function captureFiles(files) {
511
- return [...new Set(files)].map((file) => {
512
- try {
513
- const stat = fs.statSync(file);
514
- return { file, exists: true, contents: fs.readFileSync(file), mode: stat.mode & 0o7777 };
515
- } catch (error) {
516
- if (error.code === 'ENOENT') return { file, exists: false };
517
- throw error;
518
- }
519
- });
229
+ function shouldRunPluginCli(ctx = {}) {
230
+ if (typeof ctx.runPluginCli === 'boolean') return ctx.runPluginCli;
231
+ return ctx.scope === 'global';
520
232
  }
521
233
 
522
- function restoreFiles(snapshots) {
523
- for (const snapshot of snapshots) {
524
- if (!snapshot.exists) {
525
- try {
526
- fs.unlinkSync(snapshot.file);
527
- } catch (error) {
528
- if (error.code !== 'ENOENT') throw error;
529
- }
530
- continue;
531
- }
532
- writeAtomic(snapshot.file, snapshot.contents);
533
- fs.chmodSync(snapshot.file, snapshot.mode);
234
+ function installedPlugin(ctx) {
235
+ const value = JSON.parse(runCodex(['plugin', 'list', '--json'], ctx));
236
+ if (!Array.isArray(value.installed)) {
237
+ throw new Error('codex plugin list --json must include an installed array');
534
238
  }
239
+ return value.installed.find((entry) => entry?.name === PLUGIN_NAME) || null;
535
240
  }
536
241
 
537
- function publishStagedAgents(staged, configDir) {
538
- const targetRoot = path.join(configDir, 'agents');
539
- ensureDir(targetRoot);
540
- for (const file of staged.installed) {
541
- const target = path.join(targetRoot, file);
542
- if (fs.existsSync(target)) {
543
- const existing = fs.readFileSync(target, 'utf8');
544
- if (!existing.startsWith(`# ${MANAGED_MARKER}`)) {
545
- throw new Error(`refusing to overwrite unmanaged Codex agent: ${target}`);
546
- }
547
- }
548
- }
549
- for (const file of staged.installed) {
550
- writeAtomic(
551
- path.join(targetRoot, file),
552
- fs.readFileSync(path.join(staged.root, file)),
553
- );
242
+ function restoreMarketplace(file, previous) {
243
+ if (previous !== null) {
244
+ writeAtomic(file, previous);
245
+ } else if (fs.existsSync(file)) {
246
+ removeMarketplace(file, { deleteIfEmpty: true });
554
247
  }
555
- return { root: targetRoot, installed: staged.installed };
556
248
  }
557
249
 
558
250
  function install(ctx = {}) {
559
- const configDir = resolveTarget(ctx);
560
251
  const repoRoot = resolveRepoRoot(ctx);
561
252
  const pluginRoot = resolvePluginRoot(ctx);
562
253
  const marketplaceFile = resolveMarketplaceFile(ctx);
563
- ensureDir(configDir);
564
-
565
- const previousManifest = readRuntimeManifest(configDir);
254
+ const previousMetadata = managedMetadata(pluginRoot) || {};
255
+ const cleanupRoots = installCleanupRoots(ctx, pluginRoot);
566
256
  if (fs.existsSync(pluginRoot) && !isManaged(pluginRoot)) {
567
- throw new Error(`refusing to replace unmanaged plugin directory: ${pluginRoot}`);
568
- }
257
+ throw new Error(`refusing to replace unmanaged Codex plugin: ${pluginRoot}`);
258
+ }
259
+ ensureDir(resolveTarget(ctx));
260
+ const runNative = shouldRunPluginCli(ctx);
261
+ const previousRegistration = runNative ? installedPlugin(ctx) : null;
262
+ const previousMarketplace = fs.existsSync(marketplaceFile)
263
+ ? fs.readFileSync(marketplaceFile, 'utf8')
264
+ : null;
265
+ const marketplaceCreated = previousMetadata.marketplace_created === true
266
+ || (previousMetadata.marketplace_created === undefined && previousMarketplace === null);
267
+ const previousMarketplaceName = fs.existsSync(marketplaceFile)
268
+ ? loadMarketplace(marketplaceFile).name
269
+ : 'personal';
569
270
  ensureDir(path.dirname(pluginRoot));
570
- const stagingRoot = fs.mkdtempSync(path.join(path.dirname(pluginRoot), `.${PLUGIN_NAME}-staging-`));
571
- const backupRoot = `${stagingRoot}-previous`;
572
- const agentStagingConfig = fs.mkdtempSync(path.join(configDir, `.${PLUGIN_NAME}-agents-`));
573
- let plugin = null;
574
- let stagedAgents = null;
575
- let agents = null;
576
- let marketplace = null;
577
- let previousVersions = [];
578
- let previousAdapters = [];
579
- let snapshots = [];
580
- let movedPrevious = false;
271
+ const staging = fs.mkdtempSync(path.join(path.dirname(pluginRoot), `.${PLUGIN_NAME}-staging-`));
272
+ const recovery = fs.existsSync(pluginRoot)
273
+ ? `${pluginRoot}.ubp-recovery-${crypto.randomUUID()}`
274
+ : null;
275
+ if (recovery) fs.cpSync(pluginRoot, recovery, { recursive: true });
276
+ let marketplaceName;
581
277
  let published = false;
582
- let registration = null;
583
- let registrationSnapshot = null;
584
- let registrationAttempted = false;
585
- let hookCompatibility = null;
586
- let legacy = { configUpdated: false, removed: [] };
278
+ let marketplaceWritten = false;
279
+ let nativeAttempted = false;
587
280
  try {
588
- plugin = buildPlugin({ repoRoot, pluginRoot: stagingRoot, publishedRoot: pluginRoot });
589
- stagedAgents = installAgents({ repoRoot, configDir: agentStagingConfig });
590
- const marketplacePayload = mergedMarketplace(marketplaceFile);
591
- marketplace = {
592
- file: marketplaceFile,
593
- name: marketplacePayload.name,
594
- entry: marketplaceEntry(),
595
- };
596
- previousVersions = listKnownHookCacheVersions(
597
- configDir,
598
- marketplace.name,
599
- previousManifest,
600
- );
601
- previousAdapters = hookAdaptersForVersions(
602
- configDir,
603
- marketplace.name,
604
- previousVersions,
605
- );
606
- snapshots = captureFiles([
607
- marketplaceFile,
608
- runtimeManifestFile(configDir),
609
- path.join(configDir, RUNTIME_MANIFEST_DIR, PROVENANCE_FILE),
610
- ...stagedAgents.installed.map((file) => path.join(configDir, 'agents', file)),
611
- ...previousAdapters,
612
- ]);
613
-
614
- if (fs.existsSync(pluginRoot)) {
615
- fs.renameSync(pluginRoot, backupRoot);
616
- movedPrevious = true;
617
- }
618
- fs.renameSync(stagingRoot, pluginRoot);
281
+ const copied = buildStaging(repoRoot, staging, pluginRoot);
282
+ markManaged(staging, {
283
+ cleanup_roots: cleanupRoots,
284
+ marketplace_created: marketplaceCreated,
285
+ });
286
+ publishManagedTrees([{ source: staging, target: pluginRoot, label: 'Codex plugin' }]);
619
287
  published = true;
620
- agents = publishStagedAgents(stagedAgents, configDir);
621
- writeAtomic(marketplaceFile, JSON.stringify(marketplacePayload, null, 2) + '\n');
622
-
623
- if (shouldRunPluginCli(ctx)) {
624
- registrationSnapshot = snapshotPluginRegistration(marketplace.name, ctx);
625
- registrationAttempted = true;
626
- registration = runPluginCli('add', marketplace.name, ctx);
627
- if (typeof ctx.afterPluginRegistration === 'function') {
628
- ctx.afterPluginRegistration({
629
- action: 'add',
630
- before: registrationSnapshot,
631
- registration,
632
- });
633
- }
634
- hookCompatibility = restoreCachedHookAdapters(
635
- previousAdapters,
636
- currentHookAdapter(configDir, marketplace.name, plugin),
637
- );
288
+ marketplaceName = writeMarketplace(marketplaceFile);
289
+ marketplaceWritten = true;
290
+ if (runNative) {
291
+ nativeAttempted = true;
292
+ runCodex(['plugin', 'add', `${PLUGIN_NAME}@${marketplaceName}`, '--json'], ctx);
638
293
  }
639
- const hookCacheVersions = normalizeHookCacheVersions([
640
- ...previousVersions,
641
- plugin.version,
642
- ]);
643
- const manifestFile = writeRuntimeManifest(configDir, {
644
- plugin: { root: plugin.root, version: plugin.version },
645
- marketplace: { file: marketplace.file, name: marketplace.name },
646
- agents: agents.installed,
647
- hookCacheVersions,
648
- });
649
- const source = provenance.packageSource(repoRoot);
650
- const provenanceFile = path.join(configDir, RUNTIME_MANIFEST_DIR, PROVENANCE_FILE);
651
- const provenanceManifest = provenance.writeProvenance({
652
- file: provenanceFile,
653
- adapter: 'codex',
654
- ...source,
655
- roots: { plugin: pluginRoot, config: configDir },
656
- assets: [
657
- ...provenance.assetRefsForTree('plugin', pluginRoot, {
658
- exclude: ['.ubp-managed'],
659
- }),
660
- ...agents.installed.map((file) => ({ root: 'config', path: path.join('agents', file) })),
661
- { root: 'config', path: path.join(RUNTIME_MANIFEST_DIR, RUNTIME_MANIFEST_FILE) },
662
- ],
663
- contracts: {
664
- plugin_manifest: { root: 'plugin', path: '.codex-plugin/plugin.json' },
665
- mcp_registration: { root: 'plugin', path: '.mcp.json' },
666
- mcp_launcher: { root: 'plugin', path: 'runtime/launch.cjs' },
667
- native_runtime: { root: 'plugin', path: 'runtime/native-runtime.json' },
668
- context_envelope_helper: { root: 'plugin', path: 'runtime/hook-context.cjs' },
669
- hook_event_helper: { root: 'plugin', path: 'runtime/hook-event.cjs' },
670
- hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
671
- hook_adapter: { root: 'plugin', path: HOOK_ADAPTER_RELATIVE },
672
- runtime_manifest: { root: 'config', path: path.join(RUNTIME_MANIFEST_DIR, RUNTIME_MANIFEST_FILE) },
673
- },
674
- });
675
- provenanceManifest.file = provenanceFile;
676
- legacy = cleanupLegacyConfig(configDir);
677
- if (movedPrevious) removeTree(backupRoot);
678
- removeTree(agentStagingConfig);
294
+ if (recovery) removeTree(recovery);
679
295
  return {
680
- target: configDir,
681
- plugin,
682
- agents,
683
- marketplace,
684
- registration,
685
- hookCompatibility,
686
- legacy,
687
- manifestFile,
688
- provenance: provenanceManifest,
689
- config: { updated: legacy.configUpdated },
296
+ target: resolveTarget(ctx),
297
+ pluginRoot,
298
+ skillRoot: path.join(pluginRoot, 'skills'),
299
+ hookRoot: path.join(pluginRoot, 'hooks'),
300
+ marketplaceFile,
301
+ copied,
690
302
  };
691
303
  } catch (error) {
692
- if (published && fs.existsSync(pluginRoot)) removeTree(pluginRoot);
693
- else if (fs.existsSync(stagingRoot)) removeTree(stagingRoot);
694
- if (movedPrevious && fs.existsSync(backupRoot)) fs.renameSync(backupRoot, pluginRoot);
695
- let rollbackError = null;
304
+ const rollback = [];
696
305
  try {
697
- const currentCache = marketplace && plugin
698
- ? path.join(pluginCacheRoot(configDir, marketplace.name), plugin.version)
699
- : null;
700
- if (currentCache && !previousVersions.includes(plugin.version) && fs.existsSync(currentCache)) {
701
- removeTree(currentCache);
306
+ if (published) {
307
+ if (fs.existsSync(pluginRoot)) removeTree(pluginRoot);
308
+ if (recovery && fs.existsSync(recovery)) fs.renameSync(recovery, pluginRoot);
309
+ } else if (recovery && fs.existsSync(recovery)) {
310
+ removeTree(recovery);
702
311
  }
703
- if (registrationAttempted && registrationSnapshot) {
704
- restorePluginRegistration(registrationSnapshot, ctx);
312
+ } catch (rollbackError) {
313
+ rollback.push(rollbackError);
314
+ }
315
+ try {
316
+ if (marketplaceWritten) restoreMarketplace(marketplaceFile, previousMarketplace);
317
+ } catch (rollbackError) {
318
+ rollback.push(rollbackError);
319
+ }
320
+ if (nativeAttempted) {
321
+ try {
322
+ const currentRegistration = installedPlugin(ctx);
323
+ if (previousRegistration && !currentRegistration) {
324
+ runCodex([
325
+ 'plugin', 'add', `${PLUGIN_NAME}@${previousMarketplaceName}`, '--json',
326
+ ], ctx);
327
+ } else if (!previousRegistration && currentRegistration) {
328
+ runCodex([
329
+ 'plugin', 'remove', `${PLUGIN_NAME}@${marketplaceName}`, '--json',
330
+ ], ctx);
331
+ }
332
+ } catch (rollbackError) {
333
+ rollback.push(rollbackError);
705
334
  }
706
- restoreFiles(snapshots);
707
- } catch (caught) {
708
- rollbackError = caught;
709
- } finally {
710
- if (fs.existsSync(agentStagingConfig)) removeTree(agentStagingConfig);
711
335
  }
712
- if (rollbackError) {
713
- throw new AggregateError(
714
- [error, rollbackError],
715
- 'Codex install and rollback both failed',
716
- );
336
+ if (fs.existsSync(staging)) removeTree(staging);
337
+ if (recovery && fs.existsSync(recovery)) removeTree(recovery);
338
+ try { pruneInstallCleanup(ctx, cleanupRoots); } catch (cleanupError) { rollback.push(cleanupError); }
339
+ if (rollback.length) {
340
+ throw new AggregateError([error, ...rollback], 'Codex install and rollback failed');
717
341
  }
718
342
  throw error;
719
343
  }
720
344
  }
721
345
 
722
346
  function doctor(ctx = {}) {
723
- const configDir = resolveTarget(ctx);
724
- const pluginRoot = resolvePluginRoot(ctx);
725
347
  const repoRoot = resolveRepoRoot(ctx);
726
- const marketplaceFile = resolveMarketplaceFile(ctx);
727
- const source = provenance.packageSource(repoRoot);
728
- const report = provenance.inspectProvenance({
729
- file: path.join(configDir, RUNTIME_MANIFEST_DIR, PROVENANCE_FILE),
730
- expectedAdapter: 'codex',
731
- expectedPackageVersion: source.packageInfo.version,
348
+ const pluginRoot = resolvePluginRoot(ctx);
349
+ const report = inspectPlugin({
350
+ adapter: 'codex', repoRoot, pluginRoot,
351
+ skillRoot: path.join(pluginRoot, 'skills'),
352
+ hookRoot: path.join(pluginRoot, 'hooks'),
353
+ manifestFile: path.join(pluginRoot, '.codex-plugin', 'plugin.json'),
732
354
  });
733
-
734
- let runtimeManifest = null;
735
- try {
736
- runtimeManifest = readRuntimeManifest(configDir);
737
- } catch (error) {
738
- report.issues.push({ code: 'RUNTIME_MANIFEST_INVALID', message: error.message });
739
- }
740
- const runtimeManifestOk = runtimeManifest?.source === SOURCE_TAG
741
- && runtimeManifest.adapter === 'codex'
742
- && runtimeManifest.plugin?.root === pluginRoot
743
- && typeof runtimeManifest.plugin?.version === 'string';
744
- if (!runtimeManifestOk && !report.issues.some((entry) => entry.code === 'RUNTIME_MANIFEST_INVALID')) {
745
- report.issues.push({ code: 'RUNTIME_MANIFEST_INVALID', path: runtimeManifestFile(configDir) });
746
- }
747
- report.checks.runtime_manifest = { status: runtimeManifestOk ? 'pass' : 'fail' };
748
-
749
355
  let marketplaceOk = false;
750
356
  try {
751
- const marketplace = loadMarketplace(marketplaceFile);
752
- const entry = marketplace.plugins.find((plugin) => plugin?.name === PLUGIN_NAME);
753
- marketplaceOk = entry?.source?.source === 'local'
754
- && entry.source.path === `./plugins/${PLUGIN_NAME}`;
755
- } catch (error) {
756
- report.issues.push({ code: 'MARKETPLACE_REGISTRATION_INVALID', message: error.message });
757
- }
758
- if (!marketplaceOk && !report.issues.some((entry) => entry.code === 'MARKETPLACE_REGISTRATION_INVALID')) {
759
- report.issues.push({ code: 'MARKETPLACE_REGISTRATION_INVALID', path: marketplaceFile });
760
- }
357
+ marketplaceOk = loadMarketplace(resolveMarketplaceFile(ctx)).plugins.some((entry) => (
358
+ entry?.name === PLUGIN_NAME && entry.source?.path === `./plugins/${PLUGIN_NAME}`
359
+ ));
360
+ } catch {}
761
361
  report.checks.marketplace = { status: marketplaceOk ? 'pass' : 'fail' };
762
-
763
- let sourceManifest = null;
764
- try {
765
- sourceManifest = readPluginManifest(pluginRoot);
766
- } catch (error) {
767
- report.issues.push({ code: 'PLUGIN_MANIFEST_INVALID', message: error.message });
768
- }
769
-
770
- let host = null;
771
- if (shouldRunHostCli(ctx)) {
772
- try {
773
- host = inspectCodexHost(
774
- ctx,
775
- runtimeManifest,
776
- sourceManifest?.manifest?.version || null,
777
- );
778
- report.checks.host_plugin = { status: host.pluginOk ? 'pass' : 'fail' };
779
- report.checks.host_mcp = { status: host.mcpOk ? 'pass' : 'fail' };
780
- if (!host.pluginOk) {
781
- report.issues.push({
782
- code: 'HOST_PLUGIN_NOT_DISCOVERED',
783
- plugin_id: host.pluginId,
784
- });
785
- }
786
- if (!host.mcpOk) {
787
- report.issues.push({
788
- code: 'HOST_MCP_NOT_DISCOVERED',
789
- plugin_id: host.pluginId,
790
- server: MCP_SERVER_NAME,
791
- });
792
- }
793
- } catch (error) {
794
- report.checks.host_plugin = { status: 'fail' };
795
- report.checks.host_mcp = { status: 'fail' };
796
- report.issues.push({
797
- code: error.code === 'HOST_CLI_TIMEOUT'
798
- ? 'HOST_CLI_TIMEOUT'
799
- : 'HOST_CLI_INSPECTION_FAILED',
800
- message: error.message,
801
- });
802
- }
803
- }
804
-
805
- const marketplaceName = runtimeManifest?.marketplace?.name || 'personal';
806
- if (runtimeManifestOk && sourceManifest && host) {
807
- bindCachebusterProvenance({
808
- report,
809
- runtimeManifest,
810
- sourceManifest,
811
- host,
812
- configDir,
813
- marketplaceName,
814
- });
815
- }
816
-
817
- let hookTargetsOk = runtimeManifestOk;
818
- if (runtimeManifestOk) {
819
- const cacheRoot = pluginCacheRoot(configDir, marketplaceName);
820
- let requiredVersions = [];
362
+ if (!marketplaceOk) report.issues.push({ code: 'MARKETPLACE_REGISTRATION_INVALID' });
363
+ if (shouldRunPluginCli(ctx)) {
364
+ let discovered = false;
821
365
  try {
822
- requiredVersions = listHookCacheVersionsOnDisk(configDir, marketplaceName);
823
- const activeVersion = host?.plugin?.installed === true
824
- ? host.plugin.version
825
- : (shouldRunPluginCli(ctx)
826
- ? sourceManifest?.manifest?.version || runtimeManifest.plugin.version
827
- : null);
828
- if (activeVersion) {
829
- requiredVersions = normalizeHookCacheVersions([
830
- ...requiredVersions,
831
- activeVersion,
832
- ]);
833
- }
834
- } catch (error) {
835
- hookTargetsOk = false;
836
- report.issues.push({ code: 'HOOK_CACHE_INVALID', message: error.message });
837
- }
838
- for (const version of requiredVersions) {
839
- const adapter = path.join(cacheRoot, version, HOOK_ADAPTER_RELATIVE);
840
- if (!fs.existsSync(adapter)) {
841
- hookTargetsOk = false;
842
- report.issues.push({ code: 'HOOK_TARGET_MISSING', version, path: adapter });
843
- }
844
- }
845
- }
846
- report.checks.hook_targets = { status: hookTargetsOk ? 'pass' : 'fail' };
847
- return applyNativeDoctor(report, path.join(pluginRoot, 'runtime'));
848
- }
849
-
850
- function removeManagedAgents(configDir, manifest) {
851
- const agentRoot = path.join(configDir, 'agents');
852
- const requested = manifest && Array.isArray(manifest.agents)
853
- ? manifest.agents
854
- : (fs.existsSync(agentRoot) ? fs.readdirSync(agentRoot).filter((name) => name.endsWith('.toml')) : []);
855
- const removed = [];
856
- for (const file of requested) {
857
- const target = path.join(agentRoot, path.basename(file));
858
- if (!fs.existsSync(target)) continue;
859
- const contents = fs.readFileSync(target, 'utf8');
860
- if (!contents.startsWith(`# ${MANAGED_MARKER}`)) continue;
861
- fs.unlinkSync(target);
862
- removed.push(path.basename(target));
863
- }
864
- return removed;
865
- }
866
-
867
- function readRuntimeManifest(configDir) {
868
- const file = runtimeManifestFile(configDir);
869
- if (!fs.existsSync(file)) return null;
870
- try {
871
- return JSON.parse(fs.readFileSync(file, 'utf8'));
872
- } catch (error) {
873
- throw new Error(`invalid Codex runtime manifest ${file}: ${error.message}`, { cause: error });
366
+ const plugin = installedPlugin(ctx);
367
+ discovered = !!plugin && plugin.installed !== false && plugin.enabled === true;
368
+ } catch {}
369
+ report.checks.host_plugin = { status: discovered ? 'pass' : 'fail' };
370
+ if (!discovered) report.issues.push({ code: 'HOST_PLUGIN_NOT_DISCOVERED' });
371
+ }
372
+ report.checks.hook_activation = {
373
+ status: 'user_review_required',
374
+ reason: 'Codex does not trust non-managed plugin hooks merely because the plugin is installed or enabled.',
375
+ };
376
+ report.actions = [{
377
+ code: 'REVIEW_CODEX_HOOK_TRUST',
378
+ message: 'Open a new Codex session, review the current Ultra hook definition, and trust it before relying on hook acceleration.',
379
+ }];
380
+ report.status = report.issues.length === 0 ? 'healthy' : 'degraded';
381
+ return report;
382
+ }
383
+
384
+ function removeMarketplace(file, { deleteIfEmpty = false } = {}) {
385
+ if (!fs.existsSync(file)) return false;
386
+ const value = loadMarketplace(file);
387
+ const before = value.plugins.length;
388
+ value.plugins = value.plugins.filter((entry) => entry?.name !== PLUGIN_NAME);
389
+ if (value.plugins.length === before) return false;
390
+ if (deleteIfEmpty && value.plugins.length === 0 && value.name === 'personal') {
391
+ fs.unlinkSync(file);
392
+ } else {
393
+ writeAtomic(file, `${JSON.stringify(value, null, 2)}\n`);
874
394
  }
395
+ return true;
875
396
  }
876
397
 
877
398
  function uninstall(ctx = {}) {
878
- const configDir = resolveTarget(ctx);
879
- const pluginRoot = resolvePluginRoot(ctx);
880
399
  const marketplaceFile = resolveMarketplaceFile(ctx);
881
- const manifest = readRuntimeManifest(configDir);
882
- const marketplaceBefore = loadMarketplace(marketplaceFile);
883
- let registration = null;
884
- let registrationSnapshot = null;
885
- let registrationAttempted = false;
886
- try {
887
- if (shouldRunPluginCli(ctx)) {
888
- registrationSnapshot = snapshotPluginRegistration(marketplaceBefore.name, ctx);
889
- registrationAttempted = true;
890
- registration = runPluginCli('remove', marketplaceBefore.name, ctx);
891
- if (typeof ctx.afterPluginRegistration === 'function') {
892
- ctx.afterPluginRegistration({
893
- action: 'remove',
894
- before: registrationSnapshot,
895
- registration,
896
- });
897
- }
898
- }
899
- const agents = removeManagedAgents(configDir, manifest);
900
-
901
- let pluginRemoved = false;
902
- if (fs.existsSync(pluginRoot) && fs.existsSync(path.join(pluginRoot, '.ubp-managed'))) {
903
- removeTree(pluginRoot);
904
- pluginRemoved = true;
905
- }
906
- const marketplace = removeMarketplaceEntry(marketplaceFile);
907
- const legacy = cleanupLegacyConfig(configDir);
908
- const manifestDir = path.join(configDir, RUNTIME_MANIFEST_DIR);
909
- if (fs.existsSync(manifestDir)) removeTree(manifestDir);
910
-
911
- return {
912
- target: configDir,
913
- removed: { plugin: pluginRemoved, agents },
914
- marketplace,
915
- registration,
916
- legacy,
917
- config: { updated: legacy.configUpdated },
918
- };
919
- } catch (error) {
920
- if (registrationAttempted && registrationSnapshot) {
921
- try {
922
- restorePluginRegistration(registrationSnapshot, ctx);
923
- } catch (rollbackError) {
924
- throw new AggregateError(
925
- [error, rollbackError],
926
- 'Codex uninstall and registration rollback both failed',
927
- );
928
- }
929
- }
930
- throw error;
931
- }
400
+ const pluginRoot = resolvePluginRoot(ctx);
401
+ const metadata = managedMetadata(pluginRoot) || {};
402
+ const cleanupRoots = metadata.cleanup_roots || [];
403
+ const marketplaceCreated = metadata.marketplace_created === true;
404
+ const marketplace = fs.existsSync(marketplaceFile) ? loadMarketplace(marketplaceFile) : { name: 'personal' };
405
+ if (shouldRunPluginCli(ctx)) {
406
+ runCodex(['plugin', 'remove', `${PLUGIN_NAME}@${marketplace.name}`, '--json'], ctx);
407
+ }
408
+ const removedPlugin = removeManaged(pluginRoot, 'Codex plugin');
409
+ const removedMarketplace = removeMarketplace(marketplaceFile, { deleteIfEmpty: marketplaceCreated });
410
+ const cleaned = pruneInstallCleanup(ctx, cleanupRoots);
411
+ return {
412
+ target: resolveTarget(ctx), pluginRoot,
413
+ removed: {
414
+ plugin: removedPlugin,
415
+ marketplace: removedMarketplace,
416
+ },
417
+ cleaned,
418
+ };
932
419
  }
933
420
 
934
421
  module.exports = {
935
422
  name: 'codex',
936
- MCP_SERVER_NAME,
937
- MARKER_BEGIN,
938
- MARKER_END,
939
- SOURCE_TAG,
423
+ PLUGIN_NAME,
424
+ buildHooksManifest,
940
425
  resolveTarget,
941
426
  resolveHomeDir,
942
427
  resolvePluginRoot,
943
428
  resolveMarketplaceFile,
944
429
  install,
945
- uninstall,
946
430
  doctor,
947
- _internal: {
948
- stripManagedBlock,
949
- hasManagedBlock,
950
- cleanupLegacyConfig,
951
- loadMarketplace,
952
- upsertMarketplace,
953
- removeMarketplaceEntry,
954
- runPluginCli,
955
- runCodexCli,
956
- snapshotPluginRegistration,
957
- restorePluginRegistration,
958
- inspectCodexHost,
959
- pluginCacheRoot,
960
- listKnownHookCacheVersions,
961
- listCachedHookAdapters,
962
- prepareHookAdapterPath,
963
- restoreCachedHookAdapters,
964
- },
431
+ uninstall,
965
432
  };