openplanr 3.2.0 → 3.4.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 (411) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/cli/commands/artifact-design-share.d.ts +9 -0
  3. package/dist/cli/commands/artifact-design-share.d.ts.map +1 -0
  4. package/dist/cli/commands/artifact-design-share.js +200 -0
  5. package/dist/cli/commands/artifact-design-share.js.map +1 -0
  6. package/dist/cli/commands/artifact.d.ts.map +1 -1
  7. package/dist/cli/commands/artifact.js +5 -18
  8. package/dist/cli/commands/artifact.js.map +1 -1
  9. package/dist/cli/commands/ship.d.ts.map +1 -1
  10. package/dist/cli/commands/ship.js +2 -0
  11. package/dist/cli/commands/ship.js.map +1 -1
  12. package/dist/cli/commands/spec.d.ts +2 -1
  13. package/dist/cli/commands/spec.d.ts.map +1 -1
  14. package/dist/cli/commands/spec.js +45 -5
  15. package/dist/cli/commands/spec.js.map +1 -1
  16. package/dist/dashboard/.vite/manifest.json +2 -2
  17. package/dist/dashboard/assets/index-Cv4Vwhuw.css +2 -0
  18. package/dist/dashboard/assets/index-CvQAlBy3.js +75 -0
  19. package/dist/dashboard/assets/index-CvQAlBy3.js.map +1 -0
  20. package/dist/dashboard/dashboard-manifest.json +14 -14
  21. package/dist/dashboard/index.html +2 -2
  22. package/dist/services/claude-plugin-service.d.ts +5 -0
  23. package/dist/services/claude-plugin-service.d.ts.map +1 -1
  24. package/dist/services/claude-plugin-service.js +18 -2
  25. package/dist/services/claude-plugin-service.js.map +1 -1
  26. package/dist/services/codex-plugin-service.d.ts.map +1 -1
  27. package/dist/services/codex-plugin-service.js +11 -3
  28. package/dist/services/codex-plugin-service.js.map +1 -1
  29. package/dist/services/design-artifact-service.d.ts +20 -1
  30. package/dist/services/design-artifact-service.d.ts.map +1 -1
  31. package/dist/services/design-artifact-service.js +137 -18
  32. package/dist/services/design-artifact-service.js.map +1 -1
  33. package/dist/services/runtime-manager/codex-discovery.d.ts +1 -3
  34. package/dist/services/runtime-manager/codex-discovery.d.ts.map +1 -1
  35. package/dist/services/runtime-manager/codex-discovery.js +3 -16
  36. package/dist/services/runtime-manager/codex-discovery.js.map +1 -1
  37. package/dist/services/runtime-manager/doctor.d.ts +25 -0
  38. package/dist/services/runtime-manager/doctor.d.ts.map +1 -1
  39. package/dist/services/runtime-manager/doctor.js +480 -0
  40. package/dist/services/runtime-manager/doctor.js.map +1 -1
  41. package/dist/services/runtime-manager/errors.d.ts +12 -0
  42. package/dist/services/runtime-manager/errors.d.ts.map +1 -0
  43. package/dist/services/runtime-manager/errors.js +14 -0
  44. package/dist/services/runtime-manager/errors.js.map +1 -0
  45. package/dist/services/runtime-manager/global-state.d.ts +7 -0
  46. package/dist/services/runtime-manager/global-state.d.ts.map +1 -1
  47. package/dist/services/runtime-manager/global-state.js +116 -0
  48. package/dist/services/runtime-manager/global-state.js.map +1 -1
  49. package/dist/services/runtime-manager/host-version-probe.d.ts +8 -0
  50. package/dist/services/runtime-manager/host-version-probe.d.ts.map +1 -0
  51. package/dist/services/runtime-manager/host-version-probe.js +14 -0
  52. package/dist/services/runtime-manager/host-version-probe.js.map +1 -0
  53. package/dist/services/runtime-manager/install-plan.d.ts +25 -0
  54. package/dist/services/runtime-manager/install-plan.d.ts.map +1 -1
  55. package/dist/services/runtime-manager/install-plan.js +305 -0
  56. package/dist/services/runtime-manager/install-plan.js.map +1 -1
  57. package/dist/services/runtime-manager/inventory.d.ts +6 -0
  58. package/dist/services/runtime-manager/inventory.d.ts.map +1 -1
  59. package/dist/services/runtime-manager/inventory.js +16 -1
  60. package/dist/services/runtime-manager/inventory.js.map +1 -1
  61. package/dist/services/runtime-manager/legacy-adapters.d.ts +9 -0
  62. package/dist/services/runtime-manager/legacy-adapters.d.ts.map +1 -0
  63. package/dist/services/runtime-manager/legacy-adapters.js +187 -0
  64. package/dist/services/runtime-manager/legacy-adapters.js.map +1 -0
  65. package/dist/services/runtime-manager/paths.d.ts +18 -0
  66. package/dist/services/runtime-manager/paths.d.ts.map +1 -0
  67. package/dist/services/runtime-manager/paths.js +83 -0
  68. package/dist/services/runtime-manager/paths.js.map +1 -0
  69. package/dist/services/runtime-manager/runtime-package.d.ts +21 -0
  70. package/dist/services/runtime-manager/runtime-package.d.ts.map +1 -1
  71. package/dist/services/runtime-manager/runtime-package.js +77 -0
  72. package/dist/services/runtime-manager/runtime-package.js.map +1 -1
  73. package/dist/services/runtime-manager/storage.d.ts +47 -0
  74. package/dist/services/runtime-manager/storage.d.ts.map +1 -0
  75. package/dist/services/runtime-manager/storage.js +604 -0
  76. package/dist/services/runtime-manager/storage.js.map +1 -0
  77. package/dist/services/runtime-manager-service.d.ts +7 -17
  78. package/dist/services/runtime-manager-service.d.ts.map +1 -1
  79. package/dist/services/runtime-manager-service.js +32 -1695
  80. package/dist/services/runtime-manager-service.js.map +1 -1
  81. package/dist/services/ship-checks/contract.d.ts +15 -1
  82. package/dist/services/ship-checks/contract.d.ts.map +1 -1
  83. package/dist/services/ship-checks/contract.js +1 -1
  84. package/dist/services/ship-checks/contract.js.map +1 -1
  85. package/dist/services/ship-checks/coverage.d.ts +4 -0
  86. package/dist/services/ship-checks/coverage.d.ts.map +1 -0
  87. package/dist/services/ship-checks/coverage.js +113 -0
  88. package/dist/services/ship-checks/coverage.js.map +1 -0
  89. package/dist/services/ship-checks/discover.d.ts.map +1 -1
  90. package/dist/services/ship-checks/discover.js +5 -1
  91. package/dist/services/ship-checks/discover.js.map +1 -1
  92. package/dist/services/ship-checks/package-contents.d.ts +4 -0
  93. package/dist/services/ship-checks/package-contents.d.ts.map +1 -0
  94. package/dist/services/ship-checks/package-contents.js +91 -0
  95. package/dist/services/ship-checks/package-contents.js.map +1 -0
  96. package/dist/services/ship-checks/packages.d.ts +2 -0
  97. package/dist/services/ship-checks/packages.d.ts.map +1 -1
  98. package/dist/services/ship-checks/packages.js +4 -0
  99. package/dist/services/ship-checks/packages.js.map +1 -1
  100. package/dist/services/ship-checks/scope.js +2 -1
  101. package/dist/services/ship-checks/scope.js.map +1 -1
  102. package/dist/services/spec-service.d.ts +18 -0
  103. package/dist/services/spec-service.d.ts.map +1 -1
  104. package/dist/services/spec-service.js +174 -9
  105. package/dist/services/spec-service.js.map +1 -1
  106. package/dist/templates/spec/spec-shaped.md.hbs +1 -1
  107. package/dist/templates/spec/spec.md.hbs +2 -2
  108. package/dist/utils/splice-managed-block.js +1 -1
  109. package/dist/utils/splice-managed-block.js.map +1 -1
  110. package/docs/CLI.md +86 -2
  111. package/lib/host-packages/adapter-registry.json +5 -5
  112. package/lib/host-packages/capability-map.json +3 -3
  113. package/lib/host-packages/claude/.claude-plugin/marketplace.json +3 -3
  114. package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +6 -6
  115. package/lib/host-packages/claude/openplanr/.openplanr-content.json +94 -146
  116. package/lib/host-packages/claude/openplanr/README.md +12 -12
  117. package/lib/host-packages/claude/openplanr/agents/planr-qa.md +5 -3
  118. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/shared/task-context-resolution.md +4 -4
  119. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-artifact-shell.mjs +2 -4
  120. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-document.mjs +110 -25
  121. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-handoff.mjs +851 -57
  122. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review-state-store.mjs +45 -0
  123. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review.mjs +65 -74
  124. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -1
  125. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +321 -0
  126. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs +1652 -0
  127. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs → claude/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs} +537 -5
  128. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +137 -0
  129. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design.mjs +374 -61
  130. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +32 -59
  131. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +138 -133
  132. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +1 -5608
  133. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +2568 -0
  134. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +10 -74
  135. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/package.json +4 -4
  136. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +8 -2
  137. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +2 -2
  138. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +5 -5
  139. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +72 -2
  140. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +14 -7
  141. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +71 -34
  142. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +28 -14
  143. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +12 -12
  144. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +6 -2
  145. package/lib/host-packages/{openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.20.0 → claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0}/design-document.schema.json +131 -4
  146. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-review-bundle.schema.json +822 -0
  147. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +616 -4775
  148. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design.mjs +219 -424
  149. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +6 -2
  150. package/lib/host-packages/claude/openplanr/skills/delegate/SKILL.md +2 -1
  151. package/lib/host-packages/claude/openplanr/skills/delegate/references/integration-review.md +26 -20
  152. package/lib/host-packages/claude/openplanr/skills/delegate/references/operator-guide.md +25 -1
  153. package/lib/host-packages/claude/openplanr/skills/delegate/references/run-handoff.md +2 -2
  154. package/lib/host-packages/claude/openplanr/skills/delegate/references/worktree-custody.md +17 -9
  155. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/adapters/claude.mjs +15 -3
  156. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/adapters/codex.mjs +38 -2
  157. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/adapters/native.mjs +5 -0
  158. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/custody.mjs +217 -10
  159. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/handoff.mjs +1080 -0
  160. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/integrate.mjs +96 -201
  161. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/integration-files.mjs +3 -382
  162. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/presentation.mjs +29 -12
  163. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-contract.mjs +11 -0
  164. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-execution.mjs +1 -1
  165. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-lifecycle.mjs +14 -10
  166. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-preparation.mjs +4 -0
  167. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-record.mjs +2 -2
  168. package/lib/host-packages/claude/openplanr/skills/design/references/design-system.md +17 -2
  169. package/lib/host-packages/claude/openplanr/skills/design/references/generation.md +60 -2
  170. package/lib/host-packages/claude/openplanr/skills/design/references/utilities.md +23 -7
  171. package/lib/host-packages/claude/openplanr/skills/design/schemas/design-document.schema.json +131 -4
  172. package/lib/host-packages/claude/openplanr/skills/design-loop/references/design-system.md +17 -2
  173. package/lib/host-packages/claude/openplanr/skills/design-loop/references/generation.md +60 -2
  174. package/lib/host-packages/claude/openplanr/skills/design-loop/references/utilities.md +23 -7
  175. package/lib/host-packages/claude/openplanr/skills/design-loop/schemas/design-document.schema.json +131 -4
  176. package/lib/host-packages/claude/openplanr/skills/design-review/references/design-system.md +17 -2
  177. package/lib/host-packages/claude/openplanr/skills/design-review/references/generation.md +60 -2
  178. package/lib/host-packages/claude/openplanr/skills/design-review/references/utilities.md +23 -7
  179. package/lib/host-packages/claude/openplanr/skills/design-review/schemas/design-document.schema.json +131 -4
  180. package/lib/host-packages/claude/openplanr/skills/investigate/SKILL.md +2 -1
  181. package/lib/host-packages/claude/openplanr/skills/investigate/references/fail-first.md +40 -0
  182. package/lib/host-packages/claude/openplanr/skills/plan/SKILL.md +21 -11
  183. package/lib/host-packages/claude/openplanr/skills/plan/schemas/design-document.schema.json +131 -4
  184. package/lib/host-packages/claude/openplanr/skills/ship/SKILL.md +51 -18
  185. package/lib/host-packages/claude/openplanr/skills/ship/references/fail-first.md +40 -0
  186. package/lib/host-packages/claude/openplanr/skills/ship/references/result-contract.md +7 -3
  187. package/lib/host-packages/claude/openplanr/skills/spec/SKILL.md +6 -0
  188. package/lib/host-packages/claude/openplanr/skills/spec/references/specification-contract.md +3 -1
  189. package/lib/host-packages/cursor/openplanr/.openplanr-content.json +91 -143
  190. package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
  191. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/integration-review.md +26 -20
  192. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/operator-guide.md +25 -1
  193. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/run-handoff.md +2 -2
  194. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/worktree-custody.md +17 -9
  195. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/adapters/claude.mjs +15 -3
  196. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/adapters/codex.mjs +38 -2
  197. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/adapters/native.mjs +5 -0
  198. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/custody.mjs +217 -10
  199. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/handoff.mjs +1080 -0
  200. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/integrate.mjs +96 -201
  201. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/integration-files.mjs +3 -382
  202. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/presentation.mjs +29 -12
  203. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-contract.mjs +11 -0
  204. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-execution.mjs +1 -1
  205. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-lifecycle.mjs +14 -10
  206. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-preparation.mjs +4 -0
  207. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-record.mjs +2 -2
  208. package/lib/host-packages/cursor/openplanr/rules/planr-delegate.mdc +2 -1
  209. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/design-system.md +17 -2
  210. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/generation.md +60 -2
  211. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/utilities.md +23 -7
  212. package/lib/host-packages/cursor/openplanr/rules/planr-design/schemas/design-document.schema.json +131 -4
  213. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/design-system.md +17 -2
  214. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/generation.md +60 -2
  215. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/utilities.md +23 -7
  216. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/schemas/design-document.schema.json +131 -4
  217. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/design-system.md +17 -2
  218. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/generation.md +60 -2
  219. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/utilities.md +23 -7
  220. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/schemas/design-document.schema.json +131 -4
  221. package/lib/host-packages/cursor/openplanr/rules/planr-investigate/references/fail-first.md +40 -0
  222. package/lib/host-packages/cursor/openplanr/rules/planr-investigate.mdc +2 -1
  223. package/lib/host-packages/cursor/openplanr/rules/planr-plan/schemas/design-document.schema.json +131 -4
  224. package/lib/host-packages/cursor/openplanr/rules/planr-plan.mdc +21 -11
  225. package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/fail-first.md +40 -0
  226. package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/result-contract.md +7 -3
  227. package/lib/host-packages/cursor/openplanr/rules/planr-ship.mdc +51 -18
  228. package/lib/host-packages/cursor/openplanr/rules/planr-spec/references/specification-contract.md +3 -1
  229. package/lib/host-packages/cursor/openplanr/rules/planr-spec.mdc +6 -0
  230. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-artifact-shell.mjs +2 -4
  231. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-document.mjs +110 -25
  232. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-handoff.mjs +851 -57
  233. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review-state-store.mjs +45 -0
  234. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review.mjs +65 -74
  235. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -1
  236. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +321 -0
  237. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs +1652 -0
  238. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/{design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs → design-shared-artifact-support-protocol-contracts-9f6918b0.mjs} +537 -5
  239. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +137 -0
  240. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design.mjs +374 -61
  241. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +32 -59
  242. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +138 -133
  243. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +1 -5608
  244. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +2568 -0
  245. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +10 -74
  246. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/package.json +4 -4
  247. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +8 -2
  248. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +2 -2
  249. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +5 -5
  250. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +72 -2
  251. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +14 -7
  252. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +71 -34
  253. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +28 -14
  254. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +12 -12
  255. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +6 -2
  256. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/{v1.20.0 → v1.22.0}/design-document.schema.json +131 -4
  257. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-review-bundle.schema.json +822 -0
  258. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +616 -4775
  259. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design.mjs +219 -424
  260. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +6 -2
  261. package/lib/host-packages/openai/.claude-plugin/marketplace.json +3 -3
  262. package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +2 -2
  263. package/lib/host-packages/openai/openplanr/.openplanr-content.json +98 -150
  264. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-artifact-shell.mjs +2 -4
  265. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-document.mjs +110 -25
  266. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-handoff.mjs +851 -57
  267. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review-state-store.mjs +45 -0
  268. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review.mjs +65 -74
  269. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -1
  270. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +321 -0
  271. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs +1652 -0
  272. package/lib/host-packages/{claude/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs → openai/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs} +537 -5
  273. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +137 -0
  274. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design.mjs +374 -61
  275. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +32 -59
  276. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +138 -133
  277. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +1 -5608
  278. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +2568 -0
  279. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +10 -74
  280. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/package.json +4 -4
  281. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +8 -2
  282. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +2 -2
  283. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +5 -5
  284. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +72 -2
  285. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +14 -7
  286. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +71 -34
  287. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +28 -14
  288. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +12 -12
  289. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +6 -2
  290. package/lib/host-packages/{claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.20.0 → openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0}/design-document.schema.json +131 -4
  291. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-review-bundle.schema.json +822 -0
  292. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +616 -4775
  293. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design.mjs +219 -424
  294. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +6 -2
  295. package/lib/host-packages/openai/openplanr/skills/delegate/SKILL.md +2 -1
  296. package/lib/host-packages/openai/openplanr/skills/delegate/openplanr.skill.json +1 -1
  297. package/lib/host-packages/openai/openplanr/skills/delegate/references/integration-review.md +26 -20
  298. package/lib/host-packages/openai/openplanr/skills/delegate/references/operator-guide.md +25 -1
  299. package/lib/host-packages/openai/openplanr/skills/delegate/references/run-handoff.md +2 -2
  300. package/lib/host-packages/openai/openplanr/skills/delegate/references/worktree-custody.md +17 -9
  301. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/adapters/claude.mjs +15 -3
  302. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/adapters/codex.mjs +38 -2
  303. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/adapters/native.mjs +5 -0
  304. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/custody.mjs +217 -10
  305. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/handoff.mjs +1080 -0
  306. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/integrate.mjs +96 -201
  307. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/integration-files.mjs +3 -382
  308. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/presentation.mjs +29 -12
  309. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-contract.mjs +11 -0
  310. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-execution.mjs +1 -1
  311. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-lifecycle.mjs +14 -10
  312. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-preparation.mjs +4 -0
  313. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-record.mjs +2 -2
  314. package/lib/host-packages/openai/openplanr/skills/design/references/design-system.md +17 -2
  315. package/lib/host-packages/openai/openplanr/skills/design/references/generation.md +60 -2
  316. package/lib/host-packages/openai/openplanr/skills/design/references/utilities.md +23 -7
  317. package/lib/host-packages/openai/openplanr/skills/design/schemas/design-document.schema.json +131 -4
  318. package/lib/host-packages/openai/openplanr/skills/design-loop/references/design-system.md +17 -2
  319. package/lib/host-packages/openai/openplanr/skills/design-loop/references/generation.md +60 -2
  320. package/lib/host-packages/openai/openplanr/skills/design-loop/references/utilities.md +23 -7
  321. package/lib/host-packages/openai/openplanr/skills/design-loop/schemas/design-document.schema.json +131 -4
  322. package/lib/host-packages/openai/openplanr/skills/design-review/references/design-system.md +17 -2
  323. package/lib/host-packages/openai/openplanr/skills/design-review/references/generation.md +60 -2
  324. package/lib/host-packages/openai/openplanr/skills/design-review/references/utilities.md +23 -7
  325. package/lib/host-packages/openai/openplanr/skills/design-review/schemas/design-document.schema.json +131 -4
  326. package/lib/host-packages/openai/openplanr/skills/investigate/SKILL.md +2 -1
  327. package/lib/host-packages/openai/openplanr/skills/investigate/agents/openai.yaml +1 -1
  328. package/lib/host-packages/openai/openplanr/skills/investigate/openplanr.skill.json +12 -1
  329. package/lib/host-packages/openai/openplanr/skills/investigate/references/fail-first.md +40 -0
  330. package/lib/host-packages/openai/openplanr/skills/plan/SKILL.md +21 -11
  331. package/lib/host-packages/openai/openplanr/skills/plan/openplanr.skill.json +1 -1
  332. package/lib/host-packages/openai/openplanr/skills/plan/schemas/design-document.schema.json +131 -4
  333. package/lib/host-packages/openai/openplanr/skills/ship/SKILL.md +51 -18
  334. package/lib/host-packages/openai/openplanr/skills/ship/agents/openai.yaml +1 -1
  335. package/lib/host-packages/openai/openplanr/skills/ship/openplanr.skill.json +12 -1
  336. package/lib/host-packages/openai/openplanr/skills/ship/references/fail-first.md +40 -0
  337. package/lib/host-packages/openai/openplanr/skills/ship/references/result-contract.md +7 -3
  338. package/lib/host-packages/openai/openplanr/skills/spec/SKILL.md +6 -0
  339. package/lib/host-packages/openai/openplanr/skills/spec/openplanr.skill.json +1 -1
  340. package/lib/host-packages/openai/openplanr/skills/spec/references/specification-contract.md +3 -1
  341. package/lib/json-schema.mjs +4 -0
  342. package/package.json +16 -16
  343. package/dist/dashboard/assets/index-BU5fzjyz.css +0 -1
  344. package/dist/dashboard/assets/index-L8qtwqv6.js +0 -572
  345. package/dist/dashboard/assets/index-L8qtwqv6.js.map +0 -1
  346. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +0 -555
  347. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-compression.mjs +0 -4180
  348. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-encrypted-workspace-client.mjs +0 -647
  349. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-owner-custody.mjs +0 -129
  350. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-resource-pack.mjs +0 -698
  351. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-runtime-support-6774fd3d5d02.mjs +0 -24
  352. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-share.mjs +0 -1993
  353. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs +0 -1020
  354. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-protocol-contracts-75a938cc.mjs +0 -222
  355. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-protocol-contracts-b5f28f94.mjs +0 -5778
  356. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-upload-spool.mjs +0 -198
  357. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-compression.js +0 -4175
  358. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/05-artifact-sharing.js +0 -65
  359. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +0 -1755
  360. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/07-artifact-sharing.js +0 -62
  361. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +0 -2633
  362. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-paste.schema.json +0 -49
  363. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-workspace.schema.json +0 -108
  364. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-create.schema.json +0 -170
  365. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-event.schema.json +0 -97
  366. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-revision.schema.json +0 -56
  367. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/integration-transaction.mjs +0 -958
  368. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/integration-transaction.mjs +0 -958
  369. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +0 -555
  370. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-compression.mjs +0 -4180
  371. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-encrypted-workspace-client.mjs +0 -647
  372. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-owner-custody.mjs +0 -129
  373. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-resource-pack.mjs +0 -698
  374. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-runtime-support-6774fd3d5d02.mjs +0 -24
  375. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-share.mjs +0 -1993
  376. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs +0 -1020
  377. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-protocol-contracts-75a938cc.mjs +0 -222
  378. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-protocol-contracts-b5f28f94.mjs +0 -5778
  379. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-upload-spool.mjs +0 -198
  380. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-compression.js +0 -4175
  381. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/05-artifact-sharing.js +0 -65
  382. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +0 -1755
  383. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/07-artifact-sharing.js +0 -62
  384. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +0 -2633
  385. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-paste.schema.json +0 -49
  386. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-workspace.schema.json +0 -108
  387. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-create.schema.json +0 -170
  388. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-event.schema.json +0 -97
  389. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-revision.schema.json +0 -56
  390. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +0 -555
  391. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-compression.mjs +0 -4180
  392. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-encrypted-workspace-client.mjs +0 -647
  393. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-owner-custody.mjs +0 -129
  394. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-resource-pack.mjs +0 -698
  395. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-runtime-support-6774fd3d5d02.mjs +0 -24
  396. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-share.mjs +0 -1993
  397. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs +0 -1020
  398. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-protocol-contracts-75a938cc.mjs +0 -222
  399. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-protocol-contracts-b5f28f94.mjs +0 -5778
  400. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-upload-spool.mjs +0 -198
  401. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-compression.js +0 -4175
  402. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/05-artifact-sharing.js +0 -65
  403. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +0 -1755
  404. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/07-artifact-sharing.js +0 -62
  405. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +0 -2633
  406. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-paste.schema.json +0 -49
  407. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-workspace.schema.json +0 -108
  408. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-create.schema.json +0 -170
  409. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-event.schema.json +0 -97
  410. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-revision.schema.json +0 -56
  411. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/integration-transaction.mjs +0 -958
@@ -5,596 +5,28 @@
5
5
  * drift. Entry points: `previewSetup`, `applySetup`, `rollbackRuntime`, `removeRuntime`,
6
6
  * `runtimeDoctor`. Host plugin commands belong to the `claude-plugin` and `codex-plugin` services.
7
7
  */
8
- import { spawnSync } from 'node:child_process';
9
- import { createHash, randomUUID } from 'node:crypto';
10
- import { existsSync, constants as fsConstants, lstatSync, readdirSync, readFileSync, realpathSync, } from 'node:fs';
11
- import { mkdir, open, readFile, rename, rm, unlink, writeFile, } from 'node:fs/promises';
8
+ import { existsSync, lstatSync, readFileSync } from 'node:fs';
9
+ import { mkdir, open, readFile, rm, unlink } from 'node:fs/promises';
12
10
  import path from 'node:path';
13
- import { validateJson } from '../../lib/json-schema.mjs';
14
11
  import { CLI_NODE_REMEDIATION, cliNodeVersionMessage, supportsCliNodeVersion, } from '../../lib/node-runtime.mjs';
15
- import { planrHome, userHome } from '../../lib/planr-home.mjs';
16
- import { CLI_COMMAND, getPackageRoot, PLANNING_FOLDER } from '../utils/constants.js';
17
- import { spliceManagedBlock } from '../utils/splice-managed-block.js';
18
- import { applyBundledClaudePluginIntegration, claudePluginUninstallCommand, inspectBundledClaudePluginIntegration, } from './claude-plugin-service.js';
12
+ import { CLI_COMMAND } from '../utils/constants.js';
13
+ import { applyBundledClaudePluginIntegration, duplicateClaudePluginDiagnostic, inspectBundledClaudePluginIntegration, } from './claude-plugin-service.js';
19
14
  import { applyCodexPluginIntegration, inspectCodexPluginIntegration, } from './codex-plugin-service.js';
20
15
  import { resolvePipelinePackage } from './pipeline-package-service.js';
21
- import { readOpenPlanrVersion } from './provenance-service.js';
22
- import { effectiveCodexHome, inspectCodexSkillDiscovery, } from './runtime-manager/codex-discovery.js';
23
- import { classifyComponentDrift as classifyRuntimeComponentDrift, diagnoseManagedRuntimeFiles, diagnoseRetiredCommandSkills, diagnoseRuntimeProvenance, pluginDocuments, } from './runtime-manager/doctor.js';
24
- import { migrateLegacyGlobalRuntimeState, } from './runtime-manager/global-state.js';
25
- import { inventoryManagedSkillFiles, planCodexUserBundleTransition, } from './runtime-manager/install-plan.js';
26
- import { detectInstalledRuntimes, inspectRuntimeProjectContext, listInstalledRuntimeAdapters, planningFolderConflict, } from './runtime-manager/inventory.js';
27
- import { caseVariantOf, describePackageDrift, inspectRuntimeLocator, inspectRuntimePackage, inspectThinRule, retainedPackageDrift, runtimeLocator, thinDiscoveryMetadata, thinSkillEntry, } from './runtime-manager/runtime-package.js';
16
+ import { effectiveCodexHome } from './runtime-manager/codex-discovery.js';
17
+ import { runtimeDoctor as inspectRuntimeDoctor } from './runtime-manager/doctor.js';
18
+ import { RuntimeManagerError } from './runtime-manager/errors.js';
19
+ import { decodeStoredRuntimeState, inferRuntimeScope, loadState, projectUsesUserRuntime, runtimeStateBytes, } from './runtime-manager/global-state.js';
20
+ import { actionBytes, buildActions, normalizeInstallScope, planCodexUserBundleTransition, runtimeLockManifestDigest, skillInstallMode, skillInstallModes, } from './runtime-manager/install-plan.js';
21
+ import { detectInstalledRuntimes, inspectRuntimeProjectContext, listInstalledRuntimeAdapters, planningFolderConflict, readRegistry, } from './runtime-manager/inventory.js';
22
+ import { planRetiredClaudeProjectAgents, planRetiredCursorProjectRules, } from './runtime-manager/legacy-adapters.js';
23
+ import { canonicalProjectPath, codexSkillsRoot, pathIsWithin, planrCustodyAnchor, projectKey, runtimeRoot, statePath, userHome, } from './runtime-manager/paths.js';
24
+ import { bundledHostRoot as resolveBundledHostRoot, } from './runtime-manager/runtime-package.js';
25
+ import { assertActionCustody, assertApprovedTargetCustody, assertBackupDirectoryCustody, assertClosedBackupManifest, assertClosedBackupTree, assertMutableOwnedTarget, assertProfessionalSkillTransition, assertRequestedProjectBinding, assertUserOwnedTarget, atomicWrite, backupManifestIdentity, createBackup, hash, immutableRuntimeTarget, ownershipHash, readBackupEntryBytes, readCustodiedRegularFile, restoreBackup, } from './runtime-manager/storage.js';
28
26
  export { classifyComponentDrift } from './runtime-manager/doctor.js';
29
- const runtimeLockSchema = JSON.parse(readFileSync(new URL('../../lib/runtime-lock.schema.json', import.meta.url), 'utf8'));
30
- // Setup and global help remain available when the optional pipeline package is absent.
31
- function validateRuntimeLockContract(value) {
32
- return validateJson(value, runtimeLockSchema);
33
- }
34
- const RETIRED_CURSOR_WORKFLOW_RULES = [
35
- 'planr-pipeline.mdc',
36
- 'planr-pipeline-plan.mdc',
37
- 'planr-pipeline-ship.mdc',
38
- ];
39
- const RETIRED_CURSOR_AGENT_RULES = [
40
- 'backend-agent.md',
41
- 'db-agent.md',
42
- 'designer-agent.md',
43
- 'devops-agent.md',
44
- 'doc-gen-agent.md',
45
- 'entity-scaffold-agent.md',
46
- 'frontend-agent.md',
47
- 'qa-agent.md',
48
- 'specification-agent.md',
49
- ];
50
- export class RuntimeManagerError extends Error {
51
- code;
52
- recovery;
53
- constructor(code, message, recovery) {
54
- super(message);
55
- this.code = code;
56
- this.recovery = recovery;
57
- this.name = 'RuntimeManagerError';
58
- }
59
- toJSON() {
60
- return { ok: false, code: this.code, problem: this.message, recovery: this.recovery };
61
- }
62
- }
63
- function hash(value) {
64
- return createHash('sha256').update(value).digest('hex');
65
- }
66
- function managedBlockBytes(content, marker = 'runtime') {
67
- const text = Buffer.isBuffer(content) ? content.toString('utf8') : content;
68
- const begin = `<!-- ##planr-${marker}:begin##`;
69
- const end = `<!-- ##planr-${marker}:end## -->`;
70
- const start = text.indexOf(begin);
71
- const finish = text.indexOf(end, start);
72
- if (start === -1 || finish === -1)
73
- return Buffer.alloc(0);
74
- return Buffer.from(text.slice(start, finish + end.length));
75
- }
76
- function ownershipHash(content, kind, marker) {
77
- return hash(kind === 'managed-block' ? managedBlockBytes(content, marker) : content);
78
- }
79
- function canonicalProjectPath(projectDir) {
80
- const resolved = path.resolve(projectDir);
81
- try {
82
- return realpathSync(resolved);
83
- }
84
- catch (cause) {
85
- if (cause.code === 'ENOENT')
86
- return resolved;
87
- throw cause;
88
- }
89
- }
90
- function projectKey(projectDir) {
91
- return hash(canonicalProjectPath(projectDir)).slice(0, 16);
92
- }
93
- /** Canonical native homes select only Codex ownership, never another agent's state. */
94
- function codexProfileKey() {
95
- const selected = effectiveCodexHome();
96
- const defaultHome = path.resolve(userHome(), '.codex');
97
- return selected === defaultHome ? undefined : hash(selected).slice(0, 16);
98
- }
99
- export function runtimeRoot() {
100
- return path.join(planrHome(), 'runtime');
101
- }
102
- function codexRuntimeRoot(nativeHome = effectiveCodexHome()) {
103
- const defaultHome = path.resolve(userHome(), '.codex');
104
- return nativeHome === defaultHome
105
- ? runtimeRoot()
106
- : path.join(runtimeRoot(), 'profiles', `codex-${hash(nativeHome).slice(0, 16)}`);
107
- }
108
- function codexBookkeepingTarget(target, nativeHome) {
109
- const root = codexRuntimeRoot(nativeHome);
110
- return pathIsWithin(target, root) && !pathIsWithin(target, path.join(root, 'profiles'));
111
- }
112
- function statePath() {
113
- return path.join(runtimeRoot(), 'state.json');
114
- }
115
- function codexSkillsRoot() {
116
- return path.join(effectiveCodexHome(), 'skills');
117
- }
118
- /** Explicit OpenPlanr homes may be outside the native user's home; custody starts at their real existing boundary. */
119
- function planrCustodyAnchor() {
120
- const configured = path.resolve(planrHome());
121
- if (pathIsWithin(configured, userHome()))
122
- return userHome();
123
- let boundary = configured;
124
- while (!existsSync(boundary) && path.dirname(boundary) !== boundary)
125
- boundary = path.dirname(boundary);
126
- return boundary;
127
- }
128
- function backupsRoot() {
129
- return path.join(planrHome(), 'backups');
130
- }
131
- function projectBackupsRoot(projectDir) {
132
- return path.join(backupsRoot(), projectKey(projectDir));
133
- }
134
- function pathIsWithin(candidate, root) {
135
- const relative = path.relative(path.resolve(root), path.resolve(candidate));
136
- return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
137
- }
138
- function assertApprovedTargetCustody(target, approvedRoot, anchor, code, label) {
139
- const selected = path.resolve(target);
140
- const root = path.resolve(approvedRoot);
141
- const boundary = path.resolve(anchor);
142
- if (!pathIsWithin(selected, root) || !pathIsWithin(root, boundary)) {
143
- throw new RuntimeManagerError(code, `${label} target ${selected} leaves its approved root ${root}.`, 'Restore valid runtime ownership state and keep managed targets inside the exact approved bundle root.');
144
- }
145
- let canonicalBoundary;
146
- try {
147
- const boundaryMetadata = lstatSync(boundary);
148
- if (boundaryMetadata.isSymbolicLink() || !boundaryMetadata.isDirectory())
149
- throw new Error('invalid boundary');
150
- canonicalBoundary = realpathSync(boundary);
151
- }
152
- catch (cause) {
153
- if (cause.code === 'ENOENT')
154
- return;
155
- throw new RuntimeManagerError(code, `${label} boundary ${boundary} is unavailable, non-directory, or symbolic.`, 'Use one real user/project root and replace symbolic managed-root parents before retrying.');
156
- }
157
- let current = boundary;
158
- const segments = path.relative(boundary, selected).split(path.sep).filter(Boolean);
159
- for (const [index, segment] of segments.entries()) {
160
- current = path.join(current, segment);
161
- let metadata;
162
- try {
163
- metadata = lstatSync(current);
164
- }
165
- catch (cause) {
166
- if (cause.code === 'ENOENT')
167
- return;
168
- throw cause;
169
- }
170
- if (metadata.isSymbolicLink()) {
171
- throw new RuntimeManagerError(code, `${label} target ${selected} traverses symbolic component ${current}.`, 'Replace symbolic managed-root parents with real directories before retrying.');
172
- }
173
- const isLeaf = index === segments.length - 1;
174
- if (!isLeaf && !metadata.isDirectory()) {
175
- throw new RuntimeManagerError(code, `${label} target ${selected} traverses non-directory component ${current}.`, 'Repair the managed bundle root before retrying.');
176
- }
177
- const canonical = realpathSync(current);
178
- if (canonical !== canonicalBoundary &&
179
- !canonical.startsWith(`${canonicalBoundary}${path.sep}`)) {
180
- throw new RuntimeManagerError(code, `${label} target ${selected} escapes its canonical boundary.`, 'Repair the managed bundle root before retrying.');
181
- }
182
- }
183
- }
184
- function assertMutableOwnedTarget(target, projectDir, code) {
185
- if (pathIsWithin(target, runtimeRoot())) {
186
- assertApprovedTargetCustody(target, runtimeRoot(), planrCustodyAnchor(), code, 'Global runtime ownership');
187
- return;
188
- }
189
- if (pathIsWithin(target, codexSkillsRoot())) {
190
- assertApprovedTargetCustody(target, codexSkillsRoot(), effectiveCodexHome(), code, 'Global Codex bundle ownership');
191
- return;
192
- }
193
- if (pathIsWithin(target, projectDir)) {
194
- assertApprovedTargetCustody(target, projectDir, projectDir, code, 'Project runtime ownership');
195
- return;
196
- }
197
- throw new RuntimeManagerError(code, `Managed mutation target ${path.resolve(target)} is outside every approved runtime root.`, 'Restore a valid ownership/backup manifest before retrying.');
198
- }
199
- function assertBackupDirectoryCustody(selected, projectDir, code, requireExisting = false) {
200
- const directory = path.resolve(selected);
201
- const approvedRoot = path.resolve(projectBackupsRoot(projectDir));
202
- if (path.dirname(directory) !== approvedRoot) {
203
- throw new RuntimeManagerError(code, `Runtime backup ${directory} is not one direct child of ${approvedRoot}.`, 'Select only the exact project backup created by OpenPlanr.');
204
- }
205
- assertApprovedTargetCustody(directory, approvedRoot, planrCustodyAnchor(), code, 'Runtime backup');
206
- if (requireExisting) {
207
- let metadata;
208
- try {
209
- metadata = lstatSync(directory);
210
- }
211
- catch {
212
- throw new RuntimeManagerError(code, `Runtime backup ${directory} is unavailable.`);
213
- }
214
- if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
215
- throw new RuntimeManagerError(code, `Runtime backup ${directory} is not one real directory.`);
216
- }
217
- }
218
- return directory;
219
- }
220
- function backupEntryPath(directory, index, target) {
221
- return path.join(directory, 'files', `${String(index).padStart(3, '0')}-${path.basename(target)}`);
222
- }
223
- function backupManifestIdentity(manifest) {
224
- return `sha256:${hash(JSON.stringify({
225
- schemaVersion: manifest.schemaVersion,
226
- projectDir: manifest.projectDir,
227
- createdAt: manifest.createdAt,
228
- files: manifest.files.map(({ target, backup, existed, beforeHash, afterHash }) => ({
229
- target,
230
- existed,
231
- ...(backup ? { backup } : {}),
232
- ...(beforeHash ? { beforeHash } : {}),
233
- ...(path.resolve(target) === path.resolve(statePath())
234
- ? {}
235
- : { expectedAfterHash: afterHash ?? null }),
236
- })),
237
- }))}`;
238
- }
239
- function assertClosedBackupManifest(value, directory, projectDir, code, requireFinalStateHash = false) {
240
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
241
- throw new RuntimeManagerError(code, 'Runtime backup manifest is not one closed object.');
242
- }
243
- const manifest = value;
244
- if (JSON.stringify(Object.keys(manifest).sort()) !==
245
- JSON.stringify(['createdAt', 'files', 'projectDir', 'schemaVersion']) ||
246
- manifest.schemaVersion !== '1.0.0' ||
247
- typeof manifest.projectDir !== 'string' ||
248
- canonicalProjectPath(manifest.projectDir) !== canonicalProjectPath(projectDir) ||
249
- typeof manifest.createdAt !== 'string' ||
250
- manifest.createdAt.length > 64 ||
251
- Number.isNaN(Date.parse(manifest.createdAt)) ||
252
- !Array.isArray(manifest.files) ||
253
- manifest.files.length > 10_000) {
254
- throw new RuntimeManagerError(code, 'Runtime backup manifest identity is invalid.');
255
- }
256
- const seenTargets = new Set();
257
- const files = manifest.files.map((candidate, index) => {
258
- if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate)) {
259
- throw new RuntimeManagerError(code, 'Runtime backup manifest contains a non-object entry.');
260
- }
261
- const entry = candidate;
262
- const keys = Object.keys(entry).sort();
263
- if (keys.some((key) => !['afterHash', 'backup', 'beforeHash', 'existed', 'target'].includes(key)) ||
264
- !keys.includes('existed') ||
265
- !keys.includes('target') ||
266
- typeof entry.target !== 'string' ||
267
- path.resolve(entry.target) !== entry.target ||
268
- typeof entry.existed !== 'boolean' ||
269
- (entry.afterHash !== undefined &&
270
- (typeof entry.afterHash !== 'string' || !/^[a-f0-9]{64}$/u.test(entry.afterHash)))) {
271
- throw new RuntimeManagerError(code, 'Runtime backup manifest entry is invalid.');
272
- }
273
- assertMutableOwnedTarget(entry.target, projectDir, code);
274
- if (seenTargets.has(entry.target)) {
275
- throw new RuntimeManagerError(code, `Runtime backup repeats target ${entry.target}.`);
276
- }
277
- seenTargets.add(entry.target);
278
- if (entry.existed) {
279
- const expected = backupEntryPath(directory, index, entry.target);
280
- if (typeof entry.backup !== 'string' ||
281
- path.resolve(entry.backup) !== expected ||
282
- typeof entry.beforeHash !== 'string' ||
283
- !/^[a-f0-9]{64}$/u.test(entry.beforeHash)) {
284
- throw new RuntimeManagerError(code, `Runtime backup source for ${entry.target} is not exactly bound to its manifest slot.`);
285
- }
286
- assertApprovedTargetCustody(entry.backup, path.join(directory, 'files'), directory, code, 'Runtime backup file');
287
- }
288
- else if (entry.backup !== undefined || entry.beforeHash !== undefined) {
289
- throw new RuntimeManagerError(code, `Runtime backup records bytes for absent target ${entry.target}.`);
290
- }
291
- return {
292
- target: entry.target,
293
- existed: entry.existed,
294
- ...(typeof entry.backup === 'string' ? { backup: entry.backup } : {}),
295
- ...(typeof entry.beforeHash === 'string' ? { beforeHash: entry.beforeHash } : {}),
296
- ...(typeof entry.afterHash === 'string' ? { afterHash: entry.afterHash } : {}),
297
- };
298
- });
299
- const stateEntries = files.filter((entry) => path.resolve(entry.target) === path.resolve(statePath()));
300
- if (files.length === 0 || stateEntries.length !== 1) {
301
- throw new RuntimeManagerError(code, 'Runtime backup manifest must bind exactly one global ownership-state target.');
302
- }
303
- const [stateEntry] = stateEntries;
304
- if ((requireFinalStateHash && !stateEntry.afterHash) ||
305
- (!stateEntry.existed && files.every((entry) => entry === stateEntry))) {
306
- throw new RuntimeManagerError(code, 'Runtime backup manifest does not bind one complete setup transition.');
307
- }
308
- return {
309
- schemaVersion: '1.0.0',
310
- projectDir: manifest.projectDir,
311
- createdAt: manifest.createdAt,
312
- files,
313
- };
314
- }
315
- function assertClosedBackupTree(directory, manifest, projectDir, code) {
316
- assertBackupDirectoryCustody(directory, projectDir, code, true);
317
- const expectedFiles = new Set([
318
- path.join(directory, 'migration-manifest.json'),
319
- ...manifest.files.flatMap((entry) => (entry.backup ? [entry.backup] : [])),
320
- ].map((entry) => path.resolve(entry)));
321
- const actualFiles = new Set();
322
- const walk = (current) => {
323
- assertApprovedTargetCustody(current, directory, planrCustodyAnchor(), code, 'Runtime backup tree');
324
- for (const entry of readdirSync(current, { withFileTypes: true })) {
325
- const absolute = path.join(current, entry.name);
326
- const metadata = lstatSync(absolute);
327
- if (metadata.isSymbolicLink() || (!metadata.isDirectory() && !metadata.isFile())) {
328
- throw new RuntimeManagerError(code, `Runtime backup tree contains an unsupported entry at ${absolute}.`);
329
- }
330
- if (metadata.isDirectory())
331
- walk(absolute);
332
- else
333
- actualFiles.add(path.resolve(absolute));
334
- }
335
- };
336
- walk(directory);
337
- if (actualFiles.size !== expectedFiles.size ||
338
- [...actualFiles].some((entry) => !expectedFiles.has(entry))) {
339
- throw new RuntimeManagerError(code, 'Runtime backup tree does not exactly match its closed manifest.');
340
- }
341
- }
342
- async function readCustodiedRegularFile(target, custody, code, label, maxBytes = 64 * 1024 * 1024) {
343
- custody();
344
- let handle;
345
- try {
346
- handle = await open(target, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
347
- }
348
- catch (cause) {
349
- throw new RuntimeManagerError(code, `${label} ${target} cannot be opened as a real file: ${cause instanceof Error ? cause.message : String(cause)}`);
350
- }
351
- try {
352
- const metadata = await handle.stat();
353
- if (!metadata.isFile() || metadata.size > maxBytes) {
354
- throw new RuntimeManagerError(code, `${label} ${target} is not one bounded regular file.`);
355
- }
356
- const bytes = await handle.readFile();
357
- custody();
358
- return bytes;
359
- }
360
- finally {
361
- await handle.close();
362
- }
363
- }
364
- async function readBackupEntryBytes(directory, entry, _projectDir, code) {
365
- if (!entry.backup || !entry.beforeHash) {
366
- throw new RuntimeManagerError(code, `Runtime backup for ${entry.target} has no byte custody.`);
367
- }
368
- const bytes = await readCustodiedRegularFile(entry.backup, () => assertApprovedTargetCustody(entry.backup, path.join(directory, 'files'), directory, code, 'Runtime backup file'), code, 'Runtime backup file');
369
- if (hash(bytes) !== entry.beforeHash) {
370
- throw new RuntimeManagerError(code, `Runtime backup bytes for ${entry.target} do not match their recorded hash.`);
371
- }
372
- return bytes;
373
- }
374
- function assertUserOwnedTarget(file, runtime, code = 'E_RUNTIME_STATE_INVALID', nativeHome = effectiveCodexHome()) {
375
- const approvedRoot = runtime === 'codex' ? codexRuntimeRoot(nativeHome) : runtimeRoot();
376
- if (runtime === 'codex'
377
- ? codexBookkeepingTarget(file.target, nativeHome)
378
- : pathIsWithin(file.target, approvedRoot)) {
379
- assertApprovedTargetCustody(file.target, approvedRoot, planrCustodyAnchor(), code, 'Global runtime ownership');
380
- return;
381
- }
382
- if (runtime === 'codex' && pathIsWithin(file.target, path.join(nativeHome, 'skills'))) {
383
- assertApprovedTargetCustody(file.target, path.join(nativeHome, 'skills'), nativeHome, code, 'Global Codex bundle ownership');
384
- return;
385
- }
386
- throw new RuntimeManagerError(code, `Global ${runtime} ownership target ${path.resolve(file.target)} is outside every approved bundle root.`, 'Restore valid global ownership state before setup, rollback, retirement, or removal.');
387
- }
388
- function assertActionCustody(actions, projectDir) {
389
- for (const action of actions) {
390
- if (action.scope === 'user') {
391
- assertUserOwnedTarget(action, action.runtime, 'E_MIGRATION_CONFLICT');
392
- }
393
- else {
394
- assertApprovedTargetCustody(action.target, projectDir, projectDir, 'E_MIGRATION_CONFLICT', 'Project runtime ownership');
395
- }
396
- }
397
- }
398
- function assertRuntimeStateCustody(state, nativeHome = effectiveCodexHome()) {
399
- for (const runtime of ['claude-code', 'codex', 'cursor']) {
400
- for (const file of state.userBundles?.[runtime]?.ownedFiles ?? []) {
401
- assertUserOwnedTarget(file, runtime, 'E_RUNTIME_STATE_INVALID', nativeHome);
402
- }
403
- }
404
- for (const project of Object.values(state.projects)) {
405
- for (const file of project.ownedFiles) {
406
- const runtime = file.runtime;
407
- if (file.scope === 'user' || (runtime !== 'core' && isUserOwnedFile(file, runtime))) {
408
- assertUserOwnedTarget(file, runtime, 'E_RUNTIME_STATE_INVALID', nativeHome);
409
- }
410
- else {
411
- assertApprovedTargetCustody(file.target, project.projectDir, project.projectDir, 'E_RUNTIME_STATE_INVALID', 'Project runtime ownership');
412
- }
413
- }
414
- }
415
- }
416
- function assertRuntimeStateValue(value, code = 'E_RUNTIME_STATE_INVALID', allowLegacyProjectKeys = false, nativeHome = effectiveCodexHome(), inspectCustody = true) {
417
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
418
- throw new RuntimeManagerError(code, 'Runtime ownership state is not one closed object.');
419
- }
420
- const state = value;
421
- if (Object.keys(state).some((key) => !['projects', 'schemaVersion', 'userBundles', 'codexProfiles'].includes(key)) ||
422
- !['1.0.0', '2.0.0'].includes(String(state.schemaVersion)) ||
423
- state.projects === null ||
424
- typeof state.projects !== 'object' ||
425
- Array.isArray(state.projects) ||
426
- (state.userBundles !== undefined &&
427
- (state.userBundles === null ||
428
- typeof state.userBundles !== 'object' ||
429
- Array.isArray(state.userBundles)))) {
430
- throw new RuntimeManagerError(code, 'Runtime ownership state identity is invalid.');
431
- }
432
- const legacyProjectKeysAllowed = allowLegacyProjectKeys && state.schemaVersion === '1.0.0';
433
- const runtimeIds = new Set(['claude-code', 'codex', 'cursor']);
434
- const assertOwnedFile = (candidate) => {
435
- if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate))
436
- return false;
437
- const file = candidate;
438
- return (Object.keys(file).every((key) => ['hash', 'kind', 'marker', 'runtime', 'scope', 'target'].includes(key)) &&
439
- ['claude-code', 'codex', 'core', 'cursor'].includes(String(file.runtime)) &&
440
- ['file', 'managed-block'].includes(String(file.kind)) &&
441
- (file.scope === undefined || ['user', 'project'].includes(String(file.scope))) &&
442
- (file.marker === undefined || typeof file.marker === 'string') &&
443
- typeof file.target === 'string' &&
444
- path.resolve(file.target) === file.target &&
445
- typeof file.hash === 'string' &&
446
- /^[a-f0-9]{64}$/u.test(file.hash));
447
- };
448
- const canonicalProjects = new Set();
449
- for (const [key, project] of Object.entries(state.projects)) {
450
- if (project === null || typeof project !== 'object' || Array.isArray(project)) {
451
- throw new RuntimeManagerError(code, 'Runtime project ownership record is invalid.');
452
- }
453
- const record = project;
454
- const canonicalProject = typeof record.projectDir === 'string' ? canonicalProjectPath(record.projectDir) : '';
455
- if (Object.keys(record).some((key) => ![
456
- 'activeRuntime',
457
- 'backupDir',
458
- 'backupManifestHash',
459
- 'commandPrefix',
460
- 'codexHome',
461
- 'ownedFiles',
462
- 'projectDir',
463
- 'runtimeScopes',
464
- 'runtimes',
465
- 'skillModes',
466
- 'updatedAt',
467
- ].includes(key)) ||
468
- typeof record.projectDir !== 'string' ||
469
- path.resolve(record.projectDir) !== record.projectDir ||
470
- (!legacyProjectKeysAllowed && key !== projectKey(canonicalProject)) ||
471
- canonicalProjects.has(canonicalProject) ||
472
- typeof record.updatedAt !== 'string' ||
473
- (record.codexHome !== undefined &&
474
- (typeof record.codexHome !== 'string' ||
475
- path.resolve(record.codexHome) !== record.codexHome)) ||
476
- !Array.isArray(record.runtimes) ||
477
- record.runtimes.some((runtime) => !runtimeIds.has(runtime)) ||
478
- !Array.isArray(record.ownedFiles) ||
479
- record.ownedFiles.some((file) => !assertOwnedFile(file)) ||
480
- (record.backupDir !== undefined && typeof record.backupDir !== 'string') ||
481
- (record.backupManifestHash !== undefined &&
482
- (typeof record.backupManifestHash !== 'string' ||
483
- !/^sha256:[a-f0-9]{64}$/u.test(record.backupManifestHash))) ||
484
- (record.commandPrefix !== undefined &&
485
- !['bare', 'namespaced'].includes(String(record.commandPrefix))) ||
486
- (record.skillModes !== undefined &&
487
- (record.skillModes === null ||
488
- typeof record.skillModes !== 'object' ||
489
- Array.isArray(record.skillModes) ||
490
- Object.entries(record.skillModes).some(([runtime, mode]) => !runtimeIds.has(runtime) ||
491
- !['direct', 'unified-plugin', 'project-rule'].includes(String(mode))))) ||
492
- (record.activeRuntime !== undefined && !runtimeIds.has(record.activeRuntime))) {
493
- throw new RuntimeManagerError(code, 'Runtime project ownership record is invalid.');
494
- }
495
- canonicalProjects.add(canonicalProject);
496
- }
497
- const bundles = (state.userBundles ?? {});
498
- if (Object.keys(bundles).some((runtime) => !runtimeIds.has(runtime))) {
499
- throw new RuntimeManagerError(code, 'Runtime global ownership bundle identity is invalid.');
500
- }
501
- for (const [runtime, candidate] of Object.entries(bundles)) {
502
- if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate)) {
503
- throw new RuntimeManagerError(code, 'Runtime global ownership bundle is invalid.');
504
- }
505
- const bundle = candidate;
506
- if (Object.keys(bundle).some((key) => ![
507
- 'commandPrefix',
508
- 'installMode',
509
- 'ownedFiles',
510
- 'pipelineVersion',
511
- 'runtime',
512
- 'scope',
513
- 'updatedAt',
514
- ].includes(key)) ||
515
- bundle.runtime !== runtime ||
516
- bundle.scope !== 'user' ||
517
- typeof bundle.updatedAt !== 'string' ||
518
- typeof bundle.pipelineVersion !== 'string' ||
519
- !['bare', 'namespaced'].includes(String(bundle.commandPrefix)) ||
520
- (bundle.installMode !== undefined &&
521
- !['direct', 'unified-plugin', 'project-rule'].includes(String(bundle.installMode))) ||
522
- !Array.isArray(bundle.ownedFiles) ||
523
- bundle.ownedFiles.some((file) => !assertOwnedFile(file)) ||
524
- (runtime === 'codex' &&
525
- bundle.ownedFiles.some((file) => {
526
- const target = file.target;
527
- return (!codexBookkeepingTarget(target, nativeHome) &&
528
- !pathIsWithin(target, path.join(nativeHome, 'skills')));
529
- }))) {
530
- throw new RuntimeManagerError(code, 'Runtime global ownership bundle is invalid.');
531
- }
532
- }
533
- if (state.codexProfiles !== undefined) {
534
- if (!state.codexProfiles ||
535
- typeof state.codexProfiles !== 'object' ||
536
- Array.isArray(state.codexProfiles))
537
- throw new RuntimeManagerError(code, 'Codex profile ownership is invalid.');
538
- for (const [key, candidate] of Object.entries(state.codexProfiles)) {
539
- if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
540
- throw new RuntimeManagerError(code, 'Codex profile ownership is invalid.');
541
- const profile = candidate;
542
- if (Object.keys(profile).some((field) => !['home', 'bundle'].includes(field)) ||
543
- typeof profile.home !== 'string' ||
544
- path.resolve(profile.home) !== profile.home ||
545
- hash(profile.home).slice(0, 16) !== key)
546
- throw new RuntimeManagerError(code, 'Codex profile identity is invalid.');
547
- // Decode every record, but only inspect the selected native home's filesystem.
548
- if (profile.bundle !== undefined)
549
- assertRuntimeStateValue({ schemaVersion: '2.0.0', projects: {}, userBundles: { codex: profile.bundle } }, code, false, profile.home, profile.home === effectiveCodexHome());
550
- }
551
- }
552
- const decoded = value;
553
- if (inspectCustody)
554
- assertRuntimeStateCustody(decoded, nativeHome);
555
- return decoded;
556
- }
557
- function isUserOwnedFile(file, runtime, nativeHome = effectiveCodexHome()) {
558
- if (file.scope)
559
- return file.scope === 'user';
560
- if (pathIsWithin(file.target, runtimeRoot()))
561
- return true;
562
- return runtime === 'codex' && pathIsWithin(file.target, path.join(nativeHome, 'skills'));
563
- }
564
- function projectUsesUserRuntime(project, runtime, nativeHome = effectiveCodexHome()) {
565
- if (runtime === 'codex' &&
566
- (project.codexHome ?? path.resolve(userHome(), '.codex')) !== nativeHome)
567
- return false;
568
- if (!project.runtimes.includes(runtime))
569
- return false;
570
- const recorded = project.runtimeScopes?.[runtime];
571
- if (recorded)
572
- return recorded === 'user' || recorded === 'both';
573
- return project.ownedFiles.some((file) => file.runtime === runtime && isUserOwnedFile(file, runtime));
574
- }
575
- function assertRequestedProjectBinding(projectDir, key, project) {
576
- const canonical = canonicalProjectPath(projectDir);
577
- if (key !== projectKey(canonical) || canonicalProjectPath(project.projectDir) !== canonical) {
578
- throw new RuntimeManagerError('E_RUNTIME_STATE_INVALID', 'Runtime ownership state is bound to a different canonical project.', 'Repair the project ownership record before preview, rollback, removal, or cleanup.');
579
- }
580
- }
581
- function inferLegacyCodexPrefix(project, nativeHome = effectiveCodexHome()) {
582
- if (!projectUsesUserRuntime(project, 'codex', nativeHome))
583
- return undefined;
584
- if (project.commandPrefix)
585
- return project.commandPrefix;
586
- const roots = project.ownedFiles
587
- .filter((file) => file.runtime === 'codex' && pathIsWithin(file.target, path.join(nativeHome, 'skills')))
588
- .map((file) => path.relative(path.join(nativeHome, 'skills'), file.target).split(path.sep)[0])
589
- .filter(Boolean);
590
- if (roots.length === 0)
591
- return 'namespaced';
592
- const modes = new Set(roots.map((name) => (name.startsWith('planr-') ? 'namespaced' : 'bare')));
593
- if (modes.size !== 1) {
594
- throw new RuntimeManagerError('E_RUNTIME_PREFIX_CONFLICT', `Legacy Codex ownership for ${project.projectDir} mixes bare and namespaced skill targets.`, 'Choose one global Codex prefix, move the other skill tree aside, and rerun setup.');
595
- }
596
- return [...modes][0];
597
- }
27
+ export { RuntimeManagerError } from './runtime-manager/errors.js';
28
+ export { previewHomeProjectCleanup } from './runtime-manager/global-state.js';
29
+ export { runtimeRoot } from './runtime-manager/paths.js';
598
30
  export function inspectProjectContext(projectDir) {
599
31
  return inspectRuntimeProjectContext(projectDir);
600
32
  }
@@ -630,446 +62,11 @@ function chooseRuntimes(choice) {
630
62
  }
631
63
  return detected;
632
64
  }
633
- function readRegistry() {
634
- const root = getPackageRoot();
635
- const registryPath = path.join(root, 'lib', 'host-packages', 'adapter-registry.json');
636
- if (!existsSync(registryPath)) {
637
- throw new RuntimeManagerError('E_HOST_PACKAGE_MISSING', 'The OpenPlanr host-package inventory is missing.', 'Reinstall the OpenPlanr utility CLI or run the workspace skill generator.');
638
- }
639
- const document = JSON.parse(readFileSync(registryPath, 'utf8'));
640
- return {
641
- root,
642
- version: document.pipelineVersion,
643
- pluginVersion: document.pluginVersion,
644
- registry: document,
645
- };
646
- }
647
- export function bundledHostRoot(host) {
648
- const cliRoot = getPackageRoot();
649
- const root = path.join(cliRoot, 'lib', 'host-packages', host);
650
- if (!existsSync(root) || lstatSync(root).isSymbolicLink() || !lstatSync(root).isDirectory()) {
651
- throw new RuntimeManagerError('E_HOST_PACKAGE_MISSING', `The bundled ${host} OpenPlanr package is missing or unsafe.`, 'Reinstall the OpenPlanr utility CLI or run the workspace skill generator.');
652
- }
653
- return root;
654
- }
655
- /** Setup and doctor word a second `planr` plugin identically; neither removes it. */
656
- function duplicateClaudePluginDiagnostic(inspection) {
657
- const managed = inspection.plugins.map((plugin) => plugin.id).join(', ');
658
- const duplicates = inspection.duplicatePluginIds;
659
- return {
660
- message: `Duplicate Claude plugin detected: ${duplicates.join(', ')} installed beside the setup-managed ${managed}; both expose the same /planr commands`,
661
- fix: `Keep one plugin. To keep the setup-managed ${managed}, run: ${duplicates
662
- .map(claudePluginUninstallCommand)
663
- .join(' && ')}`,
664
- };
665
- }
666
- function bundledRuntimePackage(runtime) {
667
- const host = runtime === 'claude-code' ? 'claude' : runtime === 'codex' ? 'openai' : 'cursor';
668
- try {
669
- return inspectRuntimePackage(path.join(bundledHostRoot(host), 'openplanr'), path.join(runtimeRoot(), 'packages'), host, readRegistry().pluginVersion, false);
670
- }
671
- catch (cause) {
672
- throw new RuntimeManagerError('E_RUNTIME_PACKAGE_INVALID', `The exact ${runtime} runtime package could not be verified: ${cause instanceof Error ? cause.message : String(cause)}`, 'Reinstall the OpenPlanr CLI or regenerate its host packages.');
673
- }
674
- }
675
- /** Compares the retained copy of the bundled package with its reviewed bytes. */
676
- function retainedRuntimePackageDrift(runtime, runtimePackage) {
677
- try {
678
- return retainedPackageDrift(runtimePackage);
679
- }
680
- catch (cause) {
681
- throw new RuntimeManagerError('E_RUNTIME_PACKAGE_INVALID', `The ${runtime} runtime package ${runtimePackage.version} at ${runtimePackage.root} could not be inspected: ${cause instanceof Error ? cause.message : String(cause)}`, 'Remove the symbolic link or other unsupported entry it names, then rerun the command.');
682
- }
683
- }
684
- function bundledSkillAssets(runtime, runtimePackage = bundledRuntimePackage(runtime)) {
685
- const assets = [];
686
- const rootName = runtime === 'cursor' ? 'rules' : 'skills';
687
- for (const file of runtimePackage.files) {
688
- const segments = file.relativePath.split('/');
689
- if (segments[0] !== rootName)
690
- continue;
691
- if (runtime === 'cursor') {
692
- if (segments.length !== 2 || !segments[1].endsWith('.mdc'))
693
- continue;
694
- const skillId = segments[1].slice(0, -'.mdc'.length);
695
- assets.push({
696
- skillId,
697
- targetName: skillId,
698
- relativePath: segments[1],
699
- content: thinSkillEntry(file.content, runtimeLocator(runtimePackage, file.relativePath)),
700
- });
701
- continue;
702
- }
703
- const [, skillId, ...relative] = segments;
704
- const relativePath = relative.join('/');
705
- if (relativePath === 'SKILL.md') {
706
- const locator = runtimeLocator(runtimePackage, file.relativePath);
707
- assets.push({
708
- skillId,
709
- targetName: skillId,
710
- relativePath,
711
- content: thinSkillEntry(file.content, locator),
712
- });
713
- assets.push({
714
- skillId,
715
- targetName: skillId,
716
- relativePath: 'openplanr.install.json',
717
- content: Buffer.from(`${JSON.stringify(locator, null, 2)}\n`),
718
- });
719
- }
720
- else if (relativePath === 'openplanr.skill.json' || relativePath === 'agents/openai.yaml') {
721
- assets.push({
722
- skillId,
723
- targetName: skillId,
724
- relativePath,
725
- content: thinDiscoveryMetadata(relativePath, file.content),
726
- });
727
- }
728
- }
729
- return assets;
730
- }
731
- function actionBytes(action) {
732
- if (action.kind === 'file')
733
- return action.content;
734
- const existing = existsSync(action.target) ? readFileSync(action.target, 'utf8') : '';
735
- const spliced = spliceManagedBlock(existing, action.marker ?? 'runtime', action.content.toString('utf8'));
736
- return Buffer.from(spliced.endsWith('\n') ? spliced : `${spliced}\n`);
737
- }
738
- function runtimeMarker(runtime, pipelineVersion) {
739
- return Buffer.from(`${JSON.stringify({ schemaVersion: '1.0.0', runtime, pipelineVersion, managedBy: 'openplanr' }, null, 2)}\n`);
740
- }
741
- function normalizeInstallScope(adapter, requested) {
742
- const supportsUser = adapter.installScopes.includes('user');
743
- const supportsProject = adapter.installScopes.includes('project');
744
- if (requested === 'user' && !supportsUser) {
745
- throw new RuntimeManagerError('E_SCOPE_UNSUPPORTED', `OpenPlanr's ${adapter.id} integration currently requires project scope.`, `Change into your project, then run \`${CLI_COMMAND} setup --runtime ${adapter.id} --scope project\`. To initialize a new project there, run \`${CLI_COMMAND} init\` first.`);
746
- }
747
- if (requested === 'project' && !supportsProject) {
748
- throw new RuntimeManagerError('E_SCOPE_UNSUPPORTED', `${adapter.id} does not support project-scope installation.`);
749
- }
750
- if (requested !== 'both')
751
- return requested;
752
- if (supportsUser && supportsProject)
753
- return 'both';
754
- return supportsProject ? 'project' : 'user';
755
- }
756
- function inferRuntimeScope(files, runtime, nativeHome = effectiveCodexHome()) {
757
- const owned = files.filter((file) => file.runtime === runtime);
758
- const hasUser = owned.some((file) => isUserOwnedFile(file, runtime, nativeHome));
759
- const hasProject = owned.some((file) => !isUserOwnedFile(file, runtime, nativeHome));
760
- return hasUser && hasProject ? 'both' : hasUser ? 'user' : 'project';
761
- }
762
- function skillInstallMode(runtime, options, scope) {
763
- if (runtime === 'claude-code')
764
- return 'unified-plugin';
765
- if (runtime === 'cursor')
766
- return 'project-rule';
767
- if (scope === 'project' && options.projectSkillMode)
768
- return options.projectSkillMode;
769
- return options.skillMode ?? (options.scope === 'project' ? 'project-rule' : 'direct');
770
- }
771
- function skillInstallModes(runtimes, options, scope) {
772
- return Object.fromEntries(runtimes.map((runtime) => [runtime, skillInstallMode(runtime, options, scope)]));
773
- }
774
- function runtimeLockManifestDigest(input) {
775
- return `sha256:${hash(JSON.stringify({
776
- protocol: input.protocolVersion,
777
- pipelineVersion: input.pipelineVersion,
778
- adapters: input.adapters,
779
- ...(input.skillModes ? { skillModes: input.skillModes } : {}),
780
- }))}`;
781
- }
782
- function buildRuntimeLock(options, runtimes, runtimeScopes, registry, pipelineVersion) {
783
- const adapters = runtimes.map((runtime) => {
784
- const adapter = registry.adapters.find((entry) => entry.id === runtime);
785
- if (!adapter)
786
- throw new RuntimeManagerError('E_ADAPTER_MISSING', `Adapter ${runtime} is absent.`);
787
- const installScope = normalizeInstallScope(adapter, runtimeScopes[runtime] ?? options.scope ?? 'user');
788
- return {
789
- runtime,
790
- version: adapter.version,
791
- capabilityLevel: adapter.capabilityLevel,
792
- installScope,
793
- };
794
- });
795
- const skillModes = skillInstallModes(runtimes, options, 'project');
796
- const components = {
797
- cli: options.cliVersion,
798
- pipeline: pipelineVersion,
799
- skills: readRegistry().pluginVersion,
800
- };
801
- const manifestDigest = runtimeLockManifestDigest({
802
- protocolVersion: registry.protocolVersion,
803
- pipelineVersion,
804
- adapters,
805
- skillModes,
806
- });
807
- const lockPath = path.join(options.projectDir, PLANNING_FOLDER, 'runtime-lock.json');
808
- if (existsSync(lockPath)) {
809
- try {
810
- const existing = JSON.parse(readFileSync(lockPath, 'utf8'));
811
- if (existing.manifestDigest === manifestDigest &&
812
- existing.protocolVersion === registry.protocolVersion &&
813
- JSON.stringify(existing.components) === JSON.stringify(components) &&
814
- JSON.stringify(existing.adapters) === JSON.stringify(adapters) &&
815
- validateRuntimeLockContract(existing).length === 0) {
816
- return readFileSync(lockPath);
817
- }
818
- }
819
- catch {
820
- // Invalid locks are replaced after backup during setup.
821
- }
822
- }
823
- const lock = {
824
- schemaVersion: '1.0.0',
825
- generatedAt: new Date().toISOString(),
826
- manifestDigest,
827
- protocolVersion: registry.protocolVersion,
828
- components,
829
- adapters,
830
- skillModes,
831
- };
832
- const errors = validateRuntimeLockContract(lock);
833
- if (errors.length) {
834
- throw new RuntimeManagerError('E_RUNTIME_LOCK_INVALID', `Generated runtime lock does not match the supported contract: ${errors[0].path} ${errors[0].detail}`, 'Reinstall the OpenPlanr CLI or regenerate its bundled runtime adapters.');
835
- }
836
- return Buffer.from(`${JSON.stringify(lock, null, 2)}\n`);
837
- }
838
- function buildActions(options, runtimes, runtimeScopes, retainedProject, packageRestores = []) {
839
- if (options.minimal)
840
- return [];
841
- const { version, registry } = readRegistry();
842
- if (options.version && options.version !== version) {
843
- throw new RuntimeManagerError('E_VERSION_UNAVAILABLE', `Installed pipeline ${version} does not match requested ${options.version}.`, `Install an OpenPlanr CLI release that bundles workflow assets ${options.version}, then rerun setup.`);
844
- }
845
- const actions = [];
846
- for (const runtime of runtimes) {
847
- const adapter = registry.adapters.find((entry) => entry.id === runtime);
848
- if (!adapter)
849
- throw new RuntimeManagerError('E_ADAPTER_MISSING', `Adapter ${runtime} is absent.`);
850
- const scope = normalizeInstallScope(adapter, runtimeScopes[runtime] ?? options.scope ?? 'user');
851
- const installUser = (scope === 'user' || scope === 'both' || options.userScopeRuntimes?.includes(runtime)) &&
852
- adapter.installScopes.includes('user');
853
- const installProject = (scope === 'project' || scope === 'both') && adapter.installScopes.includes('project');
854
- const userDiscovery = installUser &&
855
- (runtime === 'claude-code' ||
856
- (runtime === 'codex' && skillInstallMode(runtime, options) !== 'project-rule'));
857
- const usesThinEntries = (installProject && !userDiscovery) ||
858
- (installUser && runtime === 'codex' && skillInstallMode(runtime, options) === 'direct');
859
- let skillAssets;
860
- let runtimePackage;
861
- try {
862
- runtimePackage = bundledRuntimePackage(runtime);
863
- skillAssets = bundledSkillAssets(runtime, runtimePackage);
864
- }
865
- catch (cause) {
866
- throw new RuntimeManagerError('E_SKILL_DISTRIBUTION_INVALID', `The generated ${runtime} skill distribution is invalid: ${cause instanceof Error ? cause.message : String(cause)}`, 'Reinstall the OpenPlanr utility CLI or regenerate the host packages.');
867
- }
868
- if (usesThinEntries) {
869
- const drift = retainedRuntimePackageDrift(runtime, runtimePackage);
870
- if (drift.changed.length > 0 || drift.unexpected.length > 0) {
871
- if (!options.restoreRuntimePackages) {
872
- throw new RuntimeManagerError('E_RUNTIME_PACKAGE_CHANGED', `Files in the ${runtime} runtime package ${runtimePackage.version} were changed outside OpenPlanr: ${describePackageDrift(drift)}.`, `Run \`${CLI_COMMAND} doctor --fix\` to restore the package from this CLI; the changed files are backed up first.`);
873
- }
874
- // A restore never deletes a name that could open a packaged file.
875
- const reviewed = runtimePackage.files.map((file) => file.relativePath);
876
- const caseVariants = drift.unexpected.filter((file) => caseVariantOf(file, reviewed));
877
- if (caseVariants.length > 0) {
878
- throw new RuntimeManagerError('E_RUNTIME_PACKAGE_CHANGED', `Files in the ${runtime} runtime package ${runtimePackage.version} differ from packaged files only by letter case: ${caseVariants.join(', ')}.`, `OpenPlanr does not delete them. Remove them by hand, then rerun \`${CLI_COMMAND} doctor --fix\`.`);
879
- }
880
- packageRestores.push({
881
- runtime,
882
- version: runtimePackage.version,
883
- root: runtimePackage.root,
884
- ...drift,
885
- });
886
- }
887
- for (const file of runtimePackage.files) {
888
- actions.push({
889
- runtime: 'core',
890
- scope: 'user',
891
- target: path.join(runtimePackage.root, file.relativePath),
892
- content: file.content,
893
- kind: 'file',
894
- custody: 'runtime-package',
895
- description: `Retain exact ${runtime} runtime package ${file.relativePath}`,
896
- });
897
- }
898
- }
899
- if (installUser) {
900
- if (runtime === 'codex') {
901
- const mode = skillInstallMode(runtime, options);
902
- if (mode === 'direct') {
903
- for (const asset of skillAssets) {
904
- actions.push({
905
- runtime,
906
- scope: 'user',
907
- target: path.join(codexSkillsRoot(), asset.targetName, asset.relativePath),
908
- content: asset.content,
909
- kind: 'file',
910
- description: `Install Codex skill asset ${asset.targetName}/${asset.relativePath}`,
911
- });
912
- }
913
- }
914
- if (mode !== 'project-rule') {
915
- actions.push({
916
- runtime,
917
- scope: 'user',
918
- target: path.join(codexRuntimeRoot(), 'install-modes', 'codex.json'),
919
- content: Buffer.from(`${JSON.stringify({ schemaVersion: '1.0.0', runtime, mode, sourceVersion: version }, null, 2)}\n`),
920
- kind: 'file',
921
- description: `Record Codex ${mode} skill installation mode`,
922
- });
923
- }
924
- }
925
- actions.push({
926
- runtime,
927
- scope: 'user',
928
- target: path.join(runtime === 'codex' ? codexRuntimeRoot() : runtimeRoot(), 'adapters', `${runtime}.json`),
929
- content: runtimeMarker(runtime, version),
930
- kind: 'file',
931
- description: `Record ${runtime} adapter installation`,
932
- });
933
- }
934
- if (installProject) {
935
- if (userDiscovery) {
936
- actions.push({
937
- runtime,
938
- scope: 'project',
939
- target: path.join(options.projectDir, PLANNING_FOLDER, 'runtime-policy', `${runtime}.json`),
940
- content: Buffer.from(`${JSON.stringify({ schemaVersion: '1.0.0', runtime, discovery: 'user', mode: skillInstallMode(runtime, options), packageDigest: runtimePackage.digest, packageVersion: runtimePackage.version }, null, 2)}\n`),
941
- kind: 'file',
942
- custody: 'professional-skill',
943
- description: `Bind project policy to the existing ${runtime} user discovery`,
944
- });
945
- continue;
946
- }
947
- if (runtime === 'codex') {
948
- for (const asset of skillAssets) {
949
- actions.push({
950
- runtime,
951
- scope: 'project',
952
- target: path.join(options.projectDir, '.agents', 'skills', asset.targetName, asset.relativePath),
953
- content: asset.content,
954
- kind: 'file',
955
- custody: 'professional-skill',
956
- description: `Install project Codex skill asset ${asset.targetName}/${asset.relativePath}`,
957
- });
958
- }
959
- }
960
- else if (runtime === 'cursor') {
961
- for (const asset of skillAssets) {
962
- actions.push({
963
- runtime,
964
- scope: 'project',
965
- target: path.join(options.projectDir, '.cursor', 'rules', 'openplanr', asset.relativePath),
966
- content: asset.content,
967
- kind: 'file',
968
- custody: 'professional-skill',
969
- description: `Install Cursor rule asset ${asset.relativePath}`,
970
- });
971
- }
972
- }
973
- else {
974
- for (const asset of skillAssets) {
975
- actions.push({
976
- runtime,
977
- scope: 'project',
978
- target: path.join(options.projectDir, '.claude', 'skills', asset.targetName, asset.relativePath),
979
- content: asset.content,
980
- kind: 'file',
981
- custody: 'professional-skill',
982
- description: `Install project Claude skill asset ${asset.targetName}/${asset.relativePath}`,
983
- });
984
- }
985
- const agentsRoot = path.join(bundledHostRoot('claude'), 'openplanr', 'agents');
986
- for (const absolutePath of inventoryRegularFiles(agentsRoot)) {
987
- const relativePath = path.relative(agentsRoot, absolutePath);
988
- actions.push({
989
- runtime,
990
- scope: 'project',
991
- target: path.join(options.projectDir, '.claude', 'agents', relativePath),
992
- content: Buffer.from(readFileSync(absolutePath, 'utf8').replaceAll('${CLAUDE_PLUGIN_ROOT}', runtimePackage.root)),
993
- kind: 'file',
994
- custody: 'professional-skill',
995
- description: `Install project Claude role agent ${relativePath}`,
996
- });
997
- }
998
- }
999
- }
1000
- }
1001
- const lockRuntimes = [...new Set([...(retainedProject?.runtimes ?? []), ...runtimes])];
1002
- const lockScopes = { ...retainedProject?.runtimeScopes, ...runtimeScopes };
1003
- if (lockRuntimes.some((runtime) => {
1004
- const scope = lockScopes[runtime] ?? options.scope ?? 'user';
1005
- return scope === 'project' || scope === 'both';
1006
- })) {
1007
- actions.push({
1008
- runtime: 'core',
1009
- scope: 'project',
1010
- target: path.join(options.projectDir, PLANNING_FOLDER, 'runtime-lock.json'),
1011
- content: buildRuntimeLock({
1012
- ...options,
1013
- projectSkillMode: options.projectSkillMode ?? retainedProject?.skillModes?.codex,
1014
- }, lockRuntimes, lockScopes, registry, version),
1015
- kind: 'file',
1016
- description: 'Write exact runtime compatibility lock',
1017
- });
1018
- }
1019
- return actions;
1020
- }
1021
65
  function operationFor(action) {
1022
66
  if (!existsSync(action.target))
1023
67
  return 'create';
1024
68
  return hash(readFileSync(action.target)) === hash(actionBytes(action)) ? 'unchanged' : 'update';
1025
69
  }
1026
- function isRetiredGeneratedCursorRule(target, content) {
1027
- const source = content.toString('utf8');
1028
- const parent = path.basename(path.dirname(target));
1029
- if (parent === 'agents') {
1030
- const [header, provenance] = source.split(/\r?\n/u);
1031
- return (header ===
1032
- '> **Cursor adapter — synthesized from planr-pipeline.** Agent role system prompt (body-only). Used by `/cursor/rules/planr-pipeline.mdc` for Composer subagent dispatch.' &&
1033
- provenance.startsWith('> Source: `planr-pipeline/agents/'));
1034
- }
1035
- const generatedFooter = source.trimEnd().split(/\r?\n/u).at(-1) ?? '';
1036
- const deprecatedAlias = '---\ndescription: Deprecated OpenPlanr pipeline alias\nalwaysApply: false\n---\n\nThis compatibility alias is deprecated. Use the portable `openplanr.mdc` rule.\n';
1037
- return ((generatedFooter.startsWith(`*Generated by \`${CLI_COMMAND} rules generate --target cursor --scope pipeline\`.`) &&
1038
- generatedFooter.endsWith('*')) ||
1039
- source === deprecatedAlias);
1040
- }
1041
- function planRetiredCursorProjectRules(projectDir, runtimes, runtimeScopes) {
1042
- if (!runtimes.includes('cursor') ||
1043
- !['project', 'both'].includes(runtimeScopes.cursor ?? 'project')) {
1044
- return [];
1045
- }
1046
- const rulesRoot = path.join(projectDir, '.cursor', 'rules');
1047
- const candidates = [
1048
- ...RETIRED_CURSOR_WORKFLOW_RULES.map((name) => path.join(rulesRoot, name)),
1049
- ...RETIRED_CURSOR_AGENT_RULES.map((name) => path.join(rulesRoot, 'agents', name)),
1050
- ];
1051
- return candidates.flatMap((target) => {
1052
- if (!existsSync(target))
1053
- return [];
1054
- const metadata = lstatSync(target);
1055
- if (metadata.isSymbolicLink() || !metadata.isFile()) {
1056
- throw new RuntimeManagerError('E_MIGRATION_CONFLICT', `Retired Cursor workflow path is not one regular generated file: ${target}.`, 'Move the foreign path aside and rerun setup.');
1057
- }
1058
- const content = readFileSync(target);
1059
- if (!isRetiredGeneratedCursorRule(target, content))
1060
- return [];
1061
- return [
1062
- {
1063
- runtime: 'cursor',
1064
- scope: 'project',
1065
- target,
1066
- content,
1067
- kind: 'file',
1068
- description: 'Retire an unreachable generated Cursor workflow copy',
1069
- },
1070
- ];
1071
- });
1072
- }
1073
70
  /** Files added to a retained runtime package; a restore removes them after the backup. */
1074
71
  function planPackageRetirements(restores) {
1075
72
  return restores.flatMap((restore) => restore.unexpected.map((file) => {
@@ -1085,11 +82,6 @@ function planPackageRetirements(restores) {
1085
82
  };
1086
83
  }));
1087
84
  }
1088
- function inventoryRegularFiles(root) {
1089
- return inventoryManagedSkillFiles(root, (message, recovery) => {
1090
- throw new RuntimeManagerError('E_MIGRATION_CONFLICT', message, recovery);
1091
- });
1092
- }
1093
85
  /** A deliberate scope narrowing retires only unchanged, previously owned project bytes. */
1094
86
  function planScopeRetirements(options, project, runtimes, actions) {
1095
87
  if (!project)
@@ -1137,222 +129,9 @@ function planUserBundleTransition(state, actions) {
1137
129
  },
1138
130
  });
1139
131
  }
1140
- /** Preserve modified or unknown professional project assets for every coding agent. */
1141
- function assertProfessionalSkillTransition(state, projectDir, actions) {
1142
- const desired = actions.filter((action) => action.scope === 'project' && action.custody === 'professional-skill');
1143
- if (desired.length === 0)
1144
- return;
1145
- const tracked = new Map((state.projects[projectKey(projectDir)]?.ownedFiles ?? []).map((file) => [
1146
- path.resolve(file.target),
1147
- file,
1148
- ]));
1149
- for (const action of desired) {
1150
- assertMutableOwnedTarget(action.target, projectDir, 'E_MIGRATION_CONFLICT');
1151
- if (!existsSync(action.target))
1152
- continue;
1153
- const metadata = lstatSync(action.target);
1154
- if (metadata.isSymbolicLink() || !metadata.isFile()) {
1155
- throw new RuntimeManagerError('E_MIGRATION_CONFLICT', `Refusing to replace non-file professional skill target ${action.target}.`, 'Preserve or move the existing entry outside the managed skill directory before rerunning setup.');
1156
- }
1157
- const currentHash = ownershipHash(readFileSync(action.target), action.kind, action.marker);
1158
- const owned = tracked.get(path.resolve(action.target));
1159
- if (owned) {
1160
- if (currentHash !== owned.hash) {
1161
- throw new RuntimeManagerError('E_MIGRATION_CONFLICT', `Refusing to replace modified managed professional skill ${action.target}.`, 'Preserve the modified file or restore the last managed bytes before rerunning setup.');
1162
- }
1163
- continue;
1164
- }
1165
- const expectedHash = ownershipHash(actionBytes(action), action.kind, action.marker);
1166
- if (currentHash !== expectedHash) {
1167
- throw new RuntimeManagerError('E_MIGRATION_CONFLICT', `Refusing to overwrite unknown Cursor professional skill bytes at ${action.target}.`, 'Move the user-owned file aside or explicitly reconcile it before rerunning setup.');
1168
- }
1169
- }
1170
- }
1171
- const defaultCodexBundles = new WeakMap();
1172
- function selectCodexProfile(state) {
1173
- const key = codexProfileKey();
1174
- if (!key)
1175
- return state;
1176
- defaultCodexBundles.set(state, state.userBundles?.codex);
1177
- state.userBundles ??= {};
1178
- const bundle = state.codexProfiles?.[key]?.bundle;
1179
- if (bundle)
1180
- state.userBundles.codex = bundle;
1181
- else
1182
- delete state.userBundles.codex;
1183
- return state;
1184
- }
1185
- /** One shared ownership file and setup lock serialize all native profiles. */
1186
- function runtimeStateBytes(state) {
1187
- const key = codexProfileKey();
1188
- if (!key)
1189
- return Buffer.from(`${JSON.stringify(state, null, 2)}\n`);
1190
- if (!defaultCodexBundles.has(state))
1191
- throw new RuntimeManagerError('E_RUNTIME_STATE_INVALID', 'Codex profile state lost its default bundle.');
1192
- const stored = {
1193
- ...state,
1194
- userBundles: { ...state.userBundles },
1195
- codexProfiles: { ...state.codexProfiles },
1196
- };
1197
- stored.codexProfiles[key] = {
1198
- home: effectiveCodexHome(),
1199
- ...(state.userBundles?.codex ? { bundle: state.userBundles.codex } : {}),
1200
- };
1201
- const defaultBundle = defaultCodexBundles.get(state);
1202
- if (defaultBundle)
1203
- stored.userBundles.codex = defaultBundle;
1204
- else
1205
- delete stored.userBundles.codex;
1206
- return Buffer.from(`${JSON.stringify(stored, null, 2)}\n`);
1207
- }
1208
- function decodeStoredRuntimeState(value, allowLegacyProjectKeys = false) {
1209
- const defaultHome = path.resolve(userHome(), '.codex');
1210
- const state = assertRuntimeStateValue(value, 'E_RUNTIME_STATE_INVALID', allowLegacyProjectKeys, defaultHome);
1211
- migrateLegacyGlobalRuntimeState(state, {
1212
- inferRuntimeScope: (files, runtime) => inferRuntimeScope(files, runtime, defaultHome),
1213
- inferLegacyCodexPrefix: (project) => inferLegacyCodexPrefix(project, defaultHome),
1214
- isUserOwnedFile: (file, runtime) => isUserOwnedFile(file, runtime, defaultHome),
1215
- projectKey,
1216
- conflict: (code, message, recovery) => {
1217
- throw new RuntimeManagerError(code, message, recovery);
1218
- },
1219
- });
1220
- return assertRuntimeStateValue(selectCodexProfile(state));
1221
- }
1222
- async function loadState() {
1223
- assertApprovedTargetCustody(statePath(), runtimeRoot(), planrCustodyAnchor(), 'E_RUNTIME_STATE_INVALID', 'Runtime ownership state');
1224
- try {
1225
- return decodeStoredRuntimeState(JSON.parse(await readFile(statePath(), 'utf8')), true);
1226
- }
1227
- catch (error) {
1228
- if (error.code === 'ENOENT') {
1229
- return selectCodexProfile({ schemaVersion: '2.0.0', projects: {}, userBundles: {} });
1230
- }
1231
- if (error instanceof RuntimeManagerError)
1232
- throw error;
1233
- throw new RuntimeManagerError('E_RUNTIME_STATE_INVALID', `Runtime ownership state is invalid: ${error instanceof Error ? error.message : String(error)}`, 'Restore the recorded state backup before running setup again.');
1234
- }
1235
- }
1236
132
  async function resolveCommandPrefix(options) {
1237
133
  return skillInstallMode('codex', options) === 'direct' ? 'bare' : 'namespaced';
1238
134
  }
1239
- async function atomicWrite(target, content, verifyCurrent) {
1240
- await mkdir(path.dirname(target), { recursive: true });
1241
- verifyCurrent?.();
1242
- const stagedPackage = immutableRuntimeTarget(target);
1243
- const stagingRoot = path.join(runtimeRoot(), 'setup-staging');
1244
- if (stagedPackage) {
1245
- assertApprovedTargetCustody(stagingRoot, runtimeRoot(), planrCustodyAnchor(), 'E_MIGRATION_CONFLICT', 'Runtime package staging');
1246
- await mkdir(stagingRoot, { recursive: true });
1247
- }
1248
- const temp = stagedPackage
1249
- ? path.join(stagingRoot, `${randomUUID()}.tmp`)
1250
- : `${target}.${process.pid}.tmp`;
1251
- const verifyStaging = () => {
1252
- if (stagedPackage)
1253
- assertApprovedTargetCustody(temp, runtimeRoot(), planrCustodyAnchor(), 'E_MIGRATION_CONFLICT', 'Runtime package staging');
1254
- };
1255
- let staged = false;
1256
- try {
1257
- verifyStaging();
1258
- await writeFile(temp, content, { mode: 0o600, flag: 'wx' });
1259
- staged = true;
1260
- verifyCurrent?.();
1261
- verifyStaging();
1262
- await rename(temp, target);
1263
- staged = false;
1264
- }
1265
- finally {
1266
- if (staged) {
1267
- verifyStaging();
1268
- await unlink(temp);
1269
- }
1270
- }
1271
- }
1272
- /** Immutable packages are retained beyond discovery rollback, including pins held by other projects or runs. */
1273
- function immutableRuntimeTarget(target) {
1274
- return pathIsWithin(target, path.join(runtimeRoot(), 'packages'));
1275
- }
1276
- async function createBackup(projectDir, actions) {
1277
- const stamp = new Date().toISOString().replaceAll(':', '-');
1278
- const dir = assertBackupDirectoryCustody(path.join(projectBackupsRoot(projectDir), stamp), projectDir, 'E_BACKUP_FAILED');
1279
- const manifest = {
1280
- schemaVersion: '1.0.0',
1281
- projectDir: path.resolve(projectDir),
1282
- createdAt: new Date().toISOString(),
1283
- files: [],
1284
- };
1285
- await mkdir(dir, { recursive: true, mode: 0o700 });
1286
- assertBackupDirectoryCustody(dir, projectDir, 'E_BACKUP_FAILED', true);
1287
- for (const [index, action] of actions.entries()) {
1288
- assertMutableOwnedTarget(action.target, projectDir, 'E_MIGRATION_CONFLICT');
1289
- const entry = { target: action.target, existed: existsSync(action.target) };
1290
- if (entry.existed) {
1291
- const backup = backupEntryPath(dir, index, action.target);
1292
- const bytes = await readCustodiedRegularFile(action.target, () => assertMutableOwnedTarget(action.target, projectDir, 'E_MIGRATION_CONFLICT'), 'E_BACKUP_FAILED', 'Managed setup source');
1293
- assertApprovedTargetCustody(backup, path.join(dir, 'files'), dir, 'E_BACKUP_FAILED', 'Runtime backup file');
1294
- await mkdir(path.dirname(backup), { recursive: true, mode: 0o700 });
1295
- assertApprovedTargetCustody(path.dirname(backup), path.join(dir, 'files'), dir, 'E_BACKUP_FAILED', 'Runtime backup files directory');
1296
- await atomicWrite(backup, bytes);
1297
- assertApprovedTargetCustody(backup, path.join(dir, 'files'), dir, 'E_BACKUP_FAILED', 'Runtime backup file');
1298
- entry.backup = backup;
1299
- entry.beforeHash = hash(bytes);
1300
- }
1301
- manifest.files.push(entry);
1302
- }
1303
- const manifestPath = path.join(dir, 'migration-manifest.json');
1304
- assertApprovedTargetCustody(manifestPath, dir, planrCustodyAnchor(), 'E_BACKUP_FAILED', 'Runtime backup manifest');
1305
- await atomicWrite(manifestPath, Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`));
1306
- assertClosedBackupTree(dir, assertClosedBackupManifest(manifest, dir, projectDir, 'E_BACKUP_FAILED'), projectDir, 'E_BACKUP_FAILED');
1307
- return { dir, manifest };
1308
- }
1309
- function assertRestoreTargetIdentity(entry, retiredTargets) {
1310
- const present = existsSync(entry.target);
1311
- const currentHash = present ? hash(readFileSync(entry.target)) : undefined;
1312
- const matches = present
1313
- ? currentHash === entry.beforeHash || currentHash === entry.afterHash
1314
- : !entry.existed || retiredTargets.has(entry.target);
1315
- if (!matches)
1316
- throw new RuntimeManagerError('E_MIGRATION_CONFLICT', `Concurrent bytes prevent automatic restoration: ${entry.target}.`, 'Your current files were preserved. Compare the retained backup before retrying.');
1317
- }
1318
- async function restoreBackup(backup, retiredTargets) {
1319
- const directory = assertBackupDirectoryCustody(backup.dir, backup.manifest.projectDir, 'E_RUNTIME_STATE_INVALID', true);
1320
- const manifest = assertClosedBackupManifest(backup.manifest, directory, backup.manifest.projectDir, 'E_RUNTIME_STATE_INVALID');
1321
- assertClosedBackupTree(directory, manifest, backup.manifest.projectDir, 'E_RUNTIME_STATE_INVALID');
1322
- const restoreBytes = new Map();
1323
- for (const entry of manifest.files) {
1324
- assertMutableOwnedTarget(entry.target, manifest.projectDir, 'E_RUNTIME_STATE_INVALID');
1325
- if (immutableRuntimeTarget(entry.target))
1326
- continue;
1327
- assertRestoreTargetIdentity(entry, retiredTargets);
1328
- if (entry.existed) {
1329
- restoreBytes.set(entry.target, await readBackupEntryBytes(directory, entry, manifest.projectDir, 'E_RUNTIME_STATE_INVALID'));
1330
- }
1331
- }
1332
- const restored = [];
1333
- for (const entry of [...manifest.files].reverse()) {
1334
- if (immutableRuntimeTarget(entry.target))
1335
- continue;
1336
- const verifyCurrent = () => {
1337
- assertMutableOwnedTarget(entry.target, manifest.projectDir, 'E_RUNTIME_STATE_INVALID');
1338
- assertRestoreTargetIdentity(entry, retiredTargets);
1339
- };
1340
- verifyCurrent();
1341
- if (entry.existed && entry.backup) {
1342
- const bytes = restoreBytes.get(entry.target);
1343
- if (!bytes)
1344
- throw new RuntimeManagerError('E_RUNTIME_STATE_INVALID', 'Backup preflight lost validated bytes.');
1345
- await mkdir(path.dirname(entry.target), { recursive: true });
1346
- assertMutableOwnedTarget(entry.target, manifest.projectDir, 'E_RUNTIME_STATE_INVALID');
1347
- await atomicWrite(entry.target, bytes, verifyCurrent);
1348
- }
1349
- else if (existsSync(entry.target)) {
1350
- await unlink(entry.target);
1351
- }
1352
- restored.push(entry.target);
1353
- }
1354
- return restored;
1355
- }
1356
135
  export async function previewSetup(options) {
1357
136
  assertNodeVersion();
1358
137
  const scope = options.scope ?? 'user';
@@ -1456,7 +235,10 @@ export async function previewSetup(options) {
1456
235
  const packageRetirements = planPackageRetirements(packageRestores);
1457
236
  assertActionCustody([...actions, ...packageRetirements], options.projectDir);
1458
237
  const scopeRetirements = planScopeRetirements(options, (await loadState()).projects[projectKey(options.projectDir)], runtimes, actions);
1459
- const retiredProjectRules = planRetiredCursorProjectRules(options.projectDir, runtimes, runtimeScopes);
238
+ const retiredProjectRules = [
239
+ ...planRetiredCursorProjectRules(options.projectDir, runtimes, runtimeScopes),
240
+ ...planRetiredClaudeProjectAgents(options.projectDir, runtimes, runtimeScopes),
241
+ ];
1460
242
  const needsTransitionState = runtimes.includes('codex') ||
1461
243
  actions.some((action) => (action.runtime === 'codex' && action.scope === 'user') ||
1462
244
  action.custody === 'professional-skill');
@@ -1681,7 +463,10 @@ export async function applySetup(options) {
1681
463
  assertActionCustody([...actions, ...packageRetirements], options.projectDir);
1682
464
  const restoredPackageTargets = new Set(packageRestores.flatMap((restore) => restore.changed.map((file) => path.join(restore.root, file))));
1683
465
  const scopeRetirements = planScopeRetirements(options, state.projects[projectKey(options.projectDir)], preview.runtimes, actions);
1684
- const retiredProjectRules = planRetiredCursorProjectRules(options.projectDir, preview.runtimes, preview.runtimeScopes);
466
+ const retiredProjectRules = [
467
+ ...planRetiredCursorProjectRules(options.projectDir, preview.runtimes, preview.runtimeScopes),
468
+ ...planRetiredClaudeProjectAgents(options.projectDir, preview.runtimes, preview.runtimeScopes),
469
+ ];
1685
470
  const previousCodexMode = state.userBundles?.codex?.installMode ??
1686
471
  (state.userBundles?.codex ? 'direct' : undefined) ??
1687
472
  state.projects[projectKey(options.projectDir)]?.skillModes?.codex;
@@ -1825,7 +610,7 @@ export async function applySetup(options) {
1825
610
  if (metadata.isSymbolicLink() ||
1826
611
  !metadata.isFile() ||
1827
612
  hash(readFileSync(retired.target)) !== hash(retired.content)) {
1828
- throw new RuntimeManagerError('E_MIGRATION_CONFLICT', `Retired Cursor workflow bytes changed during setup: ${retired.target}.`, 'The setup transaction restored the pre-setup bytes; inspect the file and rerun.');
613
+ throw new RuntimeManagerError('E_MIGRATION_CONFLICT', `Retired project adapter bytes changed during setup: ${retired.target}.`, 'The setup transaction restored the pre-setup bytes; inspect the file and rerun.');
1829
614
  }
1830
615
  await unlink(retired.target);
1831
616
  retiredTargets.add(retired.target);
@@ -2200,18 +985,6 @@ export async function removeRuntime(runtime, projectDir) {
2200
985
  await atomicWrite(statePath(), runtimeStateBytes(state));
2201
986
  return { ok: true, removed, retainedShared };
2202
987
  }
2203
- function homeProjectRecord(state) {
2204
- return state.projects[projectKey(userHome())];
2205
- }
2206
- export async function previewHomeProjectCleanup() {
2207
- const state = await loadState();
2208
- const key = projectKey(userHome());
2209
- const project = homeProjectRecord(state);
2210
- if (!project || canonicalProjectPath(project.projectDir) !== canonicalProjectPath(userHome()))
2211
- return [];
2212
- assertRequestedProjectBinding(userHome(), key, project);
2213
- return project.ownedFiles.filter((file) => file.scope === 'project').map((file) => file.target);
2214
- }
2215
988
  export async function managedRuntimesForProject(projectDir) {
2216
989
  const state = await loadState();
2217
990
  const key = projectKey(projectDir);
@@ -2303,449 +1076,13 @@ export async function cleanupHomeProjectInstall() {
2303
1076
  await atomicWrite(statePath(), runtimeStateBytes(state));
2304
1077
  return { ok: true, removed };
2305
1078
  }
2306
- export async function runtimeDoctor(projectDir, options = {}) {
2307
- const diagnostics = [];
2308
- const repairs = [];
2309
- let lockedAdapters;
2310
- const supportedNode = supportsCliNodeVersion(process.versions.node);
2311
- diagnostics.push({
2312
- code: 'node-version',
2313
- status: supportedNode ? 'pass' : 'fail',
2314
- message: supportedNode
2315
- ? `Node.js ${process.versions.node}`
2316
- : cliNodeVersionMessage(process.versions.node),
2317
- ...(!supportedNode ? { fix: CLI_NODE_REMEDIATION } : {}),
2318
- });
2319
- const state = await loadState();
2320
- const installed = state.projects[projectKey(projectDir)];
2321
- const detectedRuntimes = detectRuntimes();
2322
- for (const result of detectedRuntimes) {
2323
- const configured = installed?.runtimes.includes(result.runtime) ?? false;
2324
- diagnostics.push({
2325
- code: `runtime-${result.runtime}`,
2326
- status: result.installed || !configured ? 'pass' : 'warn',
2327
- message: result.installed
2328
- ? `${result.runtime} detected`
2329
- : configured
2330
- ? `${result.runtime} is configured but not detected`
2331
- : `${result.runtime} is not installed or configured`,
2332
- ...(!result.installed && configured
2333
- ? { fix: `Install ${result.command} only if you intend to use this adapter.` }
2334
- : {}),
2335
- });
2336
- }
2337
- const pipeline = resolvePipelinePackage(false);
2338
- diagnostics.push({
2339
- code: 'pipeline-package',
2340
- status: pipeline ? 'pass' : 'warn',
2341
- message: pipeline
2342
- ? `Optional deterministic pipeline utilities ${pipeline.version}`
2343
- : 'Optional deterministic pipeline utilities are not installed',
2344
- ...(!pipeline
2345
- ? { fix: 'Install optional dependencies only if you need pipeline utilities.' }
2346
- : {}),
2347
- });
2348
- const registry = listRuntimeAdapters();
2349
- for (const adapter of registry) {
2350
- const detected = detectedRuntimes.find((entry) => entry.runtime === adapter.id)?.installed;
2351
- const configured = installed?.runtimes.includes(adapter.id) ?? false;
2352
- if (!detected && !configured)
2353
- continue;
2354
- const mode = adapter.capabilities?.interactiveQuestions;
2355
- diagnostics.push({
2356
- code: `runtime-interaction-${adapter.id}`,
2357
- status: mode ? 'pass' : 'fail',
2358
- message: mode
2359
- ? `${adapter.id} uses ${mode} host-native questions`
2360
- : `${adapter.id} does not declare a host-native question capability`,
2361
- ...(!mode ? { fix: 'Reinstall the OpenPlanr utility CLI host package.' } : {}),
2362
- });
2363
- }
2364
- try {
2365
- const counts = {
2366
- claude: new Set(bundledSkillAssets('claude-code').map(({ skillId }) => skillId)).size,
2367
- codex: new Set(bundledSkillAssets('codex').map(({ skillId }) => skillId)).size,
2368
- cursor: new Set(bundledSkillAssets('cursor').map(({ skillId }) => skillId)).size,
2369
- };
2370
- const uniqueCounts = new Set(Object.values(counts));
2371
- const current = uniqueCounts.size === 1 && counts.claude > 0;
2372
- diagnostics.push({
2373
- code: 'host-skill-packages',
2374
- status: current ? 'pass' : 'fail',
2375
- message: current
2376
- ? `Claude Code, Codex, and Cursor each expose all ${counts.claude} host-native skills`
2377
- : `Host skill package membership drifted (${JSON.stringify(counts)})`,
2378
- ...(!current ? { fix: 'Reinstall the CLI or regenerate its host packages.' } : {}),
2379
- });
2380
- }
2381
- catch (cause) {
2382
- diagnostics.push({
2383
- code: 'host-skill-packages',
2384
- status: 'fail',
2385
- message: cause instanceof Error ? cause.message : String(cause),
2386
- fix: 'Reinstall the CLI or regenerate its host packages.',
2387
- });
2388
- }
2389
- const claudeManagedAtUserScope = Object.values(state.projects).some((project) => project.runtimes.includes('claude-code') &&
2390
- ['user', 'both'].includes(project.runtimeScopes?.['claude-code'] ??
2391
- inferRuntimeScope(project.ownedFiles, 'claude-code')));
2392
- const pluginSkillDocuments = [];
2393
- const claudeDetected = Boolean(options.claudeCommandRunner) ||
2394
- (detectedRuntimes.find((runtime) => runtime.runtime === 'claude-code')?.installed ?? false);
2395
- if (claudeDetected && claudeManagedAtUserScope) {
2396
- const inspection = inspectBundledClaudePluginIntegration(bundledHostRoot('claude'), options.claudeCommandRunner);
2397
- if (inspection.error) {
2398
- diagnostics.push({
2399
- code: 'runtime-claude-plugins',
2400
- status: 'fail',
2401
- message: `Claude plugin state could not be inspected: ${inspection.error}`,
2402
- fix: `Update Claude Code, then run \`${CLI_COMMAND} runtime update claude --scope user\`.`,
2403
- });
2404
- }
2405
- else {
2406
- for (const root of [
2407
- ...inspection.plugins.flatMap((plugin) => plugin.installPath ?? []),
2408
- ...inspection.duplicatePluginPaths,
2409
- ]) {
2410
- pluginSkillDocuments.push(...pluginDocuments('claude-code', root));
2411
- }
2412
- const drift = inspection.plugins.filter((plugin) => !plugin.installed ||
2413
- !plugin.enabled ||
2414
- !plugin.identityValid ||
2415
- plugin.installedVersion !== plugin.expectedVersion);
2416
- diagnostics.push({
2417
- code: 'runtime-claude-plugins',
2418
- status: inspection.ready ? 'pass' : 'fail',
2419
- message: inspection.ready
2420
- ? `Claude plugins match compatibility versions (${inspection.plugins
2421
- .map((plugin) => `${plugin.name} ${plugin.expectedVersion}`)
2422
- .join(', ')})`
2423
- : `Claude plugin drift detected: ${drift
2424
- .map((plugin) => `${plugin.id} ${plugin.installedVersion ?? 'missing'} → ${plugin.expectedVersion}${!plugin.identityValid ? ' (invalid identity)' : ''}`)
2425
- .join(', ')}`,
2426
- ...(!inspection.ready
2427
- ? {
2428
- fix: `Run \`${CLI_COMMAND} runtime update claude --scope user\` and restart Claude Code.`,
2429
- }
2430
- : {}),
2431
- });
2432
- if (inspection.staleMarketplacePath) {
2433
- diagnostics.push({
2434
- code: 'runtime-claude-marketplace-path',
2435
- status: 'fail',
2436
- message: `The local OpenPlanr Claude marketplace points to another install: ${inspection.staleMarketplacePath}, not ${bundledHostRoot('claude')}`,
2437
- fix: `Run \`${CLI_COMMAND} setup --runtime claude --scope user\` to point it at this install.`,
2438
- });
2439
- }
2440
- if (inspection.legacyPluginIds.length > 0) {
2441
- diagnostics.push({
2442
- code: 'runtime-claude-legacy-plugin',
2443
- status: 'warn',
2444
- message: `Legacy Claude plugin installation detected: ${inspection.legacyPluginIds.join(', ')}`,
2445
- fix: 'Verify `planr@openplanr-local`, then remove the legacy plugin from Claude Code.',
2446
- });
2447
- }
2448
- if (inspection.duplicatePluginIds.length > 0) {
2449
- diagnostics.push({
2450
- code: 'runtime-claude-duplicate-plugin',
2451
- status: 'warn',
2452
- ...duplicateClaudePluginDiagnostic(inspection),
2453
- });
2454
- }
2455
- }
2456
- }
2457
- const expectsProjectLock = installed?.ownedFiles.some((file) => file.scope === 'project') ?? false;
2458
- const lock = path.join(projectDir, PLANNING_FOLDER, 'runtime-lock.json');
2459
- if (!existsSync(lock)) {
2460
- // User-scope setup is recorded under the directory it ran in but serves every project.
2461
- const managed = Boolean(installed) ||
2462
- Object.values(state.projects).some((project) => project.runtimes.some((runtime) => projectUsesUserRuntime(project, runtime)));
2463
- const lockStatus = expectsProjectLock ? 'warn' : managed ? 'pass' : 'warn';
2464
- diagnostics.push({
2465
- code: 'runtime-lock',
2466
- status: lockStatus,
2467
- message: expectsProjectLock
2468
- ? 'Project runtime lock missing'
2469
- : managed
2470
- ? 'User-scope setup does not require a project runtime lock'
2471
- : 'No managed runtime setup found for this directory',
2472
- ...(expectsProjectLock
2473
- ? { fix: `Run \`${CLI_COMMAND} setup --scope project\`.` }
2474
- : !managed
2475
- ? { fix: `Run \`${CLI_COMMAND} setup\`.` }
2476
- : {}),
2477
- });
2478
- }
2479
- else {
2480
- try {
2481
- const value = JSON.parse(readFileSync(lock, 'utf8'));
2482
- const errors = validateRuntimeLockContract(value);
2483
- if (errors.length) {
2484
- throw new RuntimeManagerError('E_RUNTIME_LOCK_INVALID', 'Project runtime lock does not match the supported contract.');
2485
- }
2486
- lockedAdapters = value.adapters;
2487
- const cliVersion = readOpenPlanrVersion();
2488
- const cliDrift = value.components?.cli !== cliVersion;
2489
- const componentDrift = cliDrift ||
2490
- (pipeline && value.components?.pipeline !== pipeline.version) ||
2491
- value.components?.skills !== readRegistry().pluginVersion;
2492
- const expectedDigest = runtimeLockManifestDigest({
2493
- protocolVersion: value.protocolVersion,
2494
- pipelineVersion: value.components?.pipeline,
2495
- adapters: value.adapters,
2496
- skillModes: value.skillModes,
2497
- });
2498
- const digestDrift = value.manifestDigest !== expectedDigest;
2499
- const registry = listRuntimeAdapters();
2500
- const adapterDrift = (value.adapters ?? []).some((locked) => {
2501
- const current = registry.find((adapter) => adapter.id === locked.runtime);
2502
- return (!current ||
2503
- current.version !== locked.version ||
2504
- current.capabilityLevel !== locked.capabilityLevel);
2505
- });
2506
- // An OpenPlanr upgrade advances the CLI version the lock recorded.
2507
- // When the CLI component trails the installed build and nothing that
2508
- // governs runtime dispatch (runtime adapters, manifest digest)
2509
- // diverged, the lock is simply behind an expected upgrade — surface it as
2510
- // an informational `warn`, not a `fail` implying the board is broken. A
2511
- // digest/adapter drift, or a component drift that does not include the CLI
2512
- // (for example a pinned obsolete skill bundle), remains a genuine `fail`.
2513
- // The warn/fail derivation lives in the single `classifyComponentDrift`
2514
- // helper so `openplanr upgrade status` reuses this exact distinction rather
2515
- // than re-deriving it; a digest/adapter drift is doctor's
2516
- // flavour of an incompatible tuple.
2517
- const { drift, genuineDrift, upgradeOnlyDrift, status } = classifyRuntimeComponentDrift({
2518
- cliDrift,
2519
- componentDrift: Boolean(componentDrift),
2520
- incompatibleDrift: digestDrift || adapterDrift,
2521
- });
2522
- diagnostics.push({
2523
- code: drift ? 'lock-drift' : 'runtime-lock',
2524
- status,
2525
- message: genuineDrift
2526
- ? `Runtime lock drift detected (components: ${componentDrift}, digest: ${digestDrift}, adapters: ${adapterDrift})`
2527
- : upgradeOnlyDrift
2528
- ? `Runtime lock component versions trail an OpenPlanr upgrade (components: ${componentDrift}); this is expected after upgrading and does not affect runtime execution`
2529
- : drift
2530
- ? `Runtime lock drift detected (components: ${componentDrift}, digest: ${digestDrift}, adapters: ${adapterDrift})`
2531
- : 'Project runtime lock matches installed component versions',
2532
- ...(drift
2533
- ? {
2534
- fix: upgradeOnlyDrift
2535
- ? `Run \`${CLI_COMMAND} runtime update all --scope project\` to refresh the lock to the upgraded component versions.`
2536
- : `Run \`${CLI_COMMAND} runtime update all --scope project\`.`,
2537
- }
2538
- : {}),
2539
- });
2540
- }
2541
- catch {
2542
- diagnostics.push({
2543
- code: 'runtime-lock-invalid',
2544
- status: 'fail',
2545
- message: 'Project runtime lock is not valid JSON or does not match the supported contract',
2546
- fix: `Run \`${CLI_COMMAND} setup --scope project\` after reviewing the existing lock.`,
2547
- });
2548
- }
2549
- }
2550
- if (installed) {
2551
- if (lockedAdapters) {
2552
- const lockedState = lockedAdapters
2553
- .map((adapter) => `${adapter.runtime}:${adapter.installScope}`)
2554
- .sort();
2555
- const installedState = installed.runtimes
2556
- .map((runtime) => `${runtime}:${installed.runtimeScopes?.[runtime] ?? inferRuntimeScope(installed.ownedFiles, runtime)}`)
2557
- .sort();
2558
- const stateDrift = JSON.stringify(lockedState) !== JSON.stringify(installedState);
2559
- diagnostics.push({
2560
- code: stateDrift ? 'lock-state-drift' : 'lock-state',
2561
- status: stateDrift ? 'fail' : 'pass',
2562
- message: stateDrift
2563
- ? 'Runtime lock adapters do not match the managed installation state'
2564
- : 'Runtime lock adapters match the managed installation state',
2565
- ...(stateDrift
2566
- ? { fix: `Run \`${CLI_COMMAND} setup --dry-run\`, then approve the repair.` }
2567
- : {}),
2568
- });
2569
- }
2570
- }
2571
- else if (lockedAdapters?.length) {
2572
- diagnostics.push({
2573
- code: 'runtime-state-missing',
2574
- status: 'warn',
2575
- message: 'The project has a runtime lock but this machine has no managed adapter state',
2576
- fix: `Run \`${CLI_COMMAND} setup\` to install the locked runtime adapters on this machine.`,
2577
- });
2578
- }
2579
- const globallyOwned = Object.values(state.userBundles ?? {}).flatMap((bundle) => bundle?.ownedFiles ?? []);
2580
- const managedFiles = [
2581
- ...(installed?.ownedFiles ?? []),
2582
- ...globallyOwned.filter((file, index, files) => files.findIndex((candidate) => candidate.target === file.target) === index),
2583
- ];
2584
- const managedFileDiagnostic = diagnoseManagedRuntimeFiles(managedFiles, ownershipHash);
2585
- if (managedFileDiagnostic)
2586
- diagnostics.push(managedFileDiagnostic);
2587
- const commandDiagnostic = diagnoseRetiredCommandSkills([...managedFiles, ...pluginSkillDocuments], options.commandRoots ?? [], managedBlockBytes);
2588
- if (commandDiagnostic)
2589
- diagnostics.push(commandDiagnostic);
2590
- const verifiedPackages = new Map();
2591
- const closureFailures = [];
2592
- for (const file of managedFiles) {
2593
- try {
2594
- if (path.basename(file.target) === 'openplanr.install.json')
2595
- inspectRuntimeLocator(path.dirname(file.target), verifiedPackages);
2596
- else if (file.runtime === 'cursor' && file.target.endsWith('.mdc'))
2597
- inspectThinRule(file.target, verifiedPackages);
2598
- }
2599
- catch (cause) {
2600
- closureFailures.push(`${file.target}: ${cause instanceof Error ? cause.message : String(cause)}`);
2601
- }
2602
- }
2603
- if (verifiedPackages.size || closureFailures.length)
2604
- diagnostics.push({
2605
- code: 'runtime-package-custody',
2606
- status: closureFailures.length ? 'fail' : 'pass',
2607
- message: closureFailures.length
2608
- ? `Pinned runtime package verification failed: ${closureFailures.join('; ')}`
2609
- : 'Pinned runtime packages match their complete content inventories',
2610
- ...(closureFailures.length
2611
- ? {
2612
- fix: `Run \`${CLI_COMMAND} doctor --fix\` to preview the repair; changed package files are backed up before anything is replaced.`,
2613
- }
2614
- : {}),
2615
- });
2616
- let codexDiscovery;
2617
- const codexBundle = state.userBundles?.codex;
2618
- if (options.codexCommandRunner ||
2619
- detectedRuntimes.some((runtime) => runtime.runtime === 'codex' && runtime.installed)) {
2620
- const configuredMode = codexBundle?.installMode ?? installed?.skillModes?.codex ?? 'direct';
2621
- const inspection = inspectCodexPluginIntegration(bundledHostRoot('openai'), configuredMode, configuredMode, options.codexCommandRunner);
2622
- codexDiscovery = inspectCodexSkillDiscovery({
2623
- hostPackageRoot: bundledHostRoot('openai'),
2624
- projectDir,
2625
- mode: configuredMode,
2626
- packageVersion: readOpenPlanrVersion(),
2627
- ownershipStatePath: statePath(),
2628
- activePlugin: inspection.installed
2629
- ? { id: inspection.pluginId, version: inspection.installedVersion }
2630
- : undefined,
2631
- });
2632
- diagnostics.push({
2633
- code: 'runtime-codex-profile',
2634
- status: 'pass',
2635
- message: `Effective Codex profile: ${codexDiscovery.effectiveHome}; ownership: ${statePath()}`,
2636
- });
2637
- const stale = codexDiscovery.skills.filter((skill) => skill.resolution === 'stale');
2638
- const ambiguous = codexDiscovery.skills.filter((skill) => skill.resolution === 'ambiguous');
2639
- const missing = codexDiscovery.skills.filter((skill) => skill.resolution === 'missing');
2640
- const unreadable = codexDiscovery.skills.filter((skill) => skill.resolution === 'unreadable');
2641
- const oldCaches = codexDiscovery.skills.filter((skill) => skill.copies.some((copy) => copy.kind === 'plugin-cache' && !copy.current && copy.enabled !== true));
2642
- if (stale.length ||
2643
- ambiguous.length ||
2644
- missing.length ||
2645
- unreadable.length ||
2646
- codexDiscovery.issues.length) {
2647
- diagnostics.push({
2648
- code: 'runtime-codex-skill-resolution',
2649
- status: 'warn',
2650
- message: `${stale.length} stale skill(s), ${missing.length} missing skill(s), ${unreadable.length} unreadable skill(s), ${ambiguous.length} multiple discovery path(s), ${oldCaches.length} skill(s) with inactive cached copies. CLI package ${codexDiscovery.packageVersion}; per-skill and Protocol versions, paths and closure hashes are in doctor --json. An already-open session may retain earlier skills.`,
2651
- fix: `Run \`${CLI_COMMAND} setup --runtime codex --dry-run\` in this profile, review the installer repair, then restart Codex. Do not delete plugin caches by hand.`,
2652
- });
2653
- }
2654
- if (oldCaches.length > 0) {
2655
- diagnostics.push({
2656
- code: 'runtime-codex-cache-history',
2657
- status: 'pass',
2658
- message: `${oldCaches.length} skill(s) have inactive cached copies. These do not imply enabled duplicates; restart Codex if an open session still shows earlier skills.`,
2659
- });
2660
- }
2661
- const directSkillFiles = codexBundle?.ownedFiles.filter((file) => pathIsWithin(file.target, codexSkillsRoot()))
2662
- .length ?? 0;
2663
- if (inspection.error) {
2664
- diagnostics.push({
2665
- code: 'runtime-codex-plugin',
2666
- status: 'warn',
2667
- message: `Codex plugin state could not be inspected: ${inspection.error}`,
2668
- fix: `Run \`${CLI_COMMAND} setup --runtime codex --dry-run\` for an exact repair preview.`,
2669
- });
2670
- }
2671
- else {
2672
- diagnostics.push({
2673
- code: 'runtime-codex-install-mode',
2674
- status: inspection.ready ? 'pass' : 'warn',
2675
- message: inspection.ready
2676
- ? `Codex ${configuredMode} discovery is current`
2677
- : inspection.operations.length > 0
2678
- ? `Codex ${configuredMode} discovery needs repair: ${inspection.operations.map((operation) => operation.description).join('; ')}`
2679
- : `Codex ${configuredMode} discovery has conflicting enabled registrations: ${inspection.duplicates.join(', ')}`,
2680
- ...(inspection.ready
2681
- ? {}
2682
- : {
2683
- fix: `Run \`${CLI_COMMAND} doctor --fix\` to preview repairs while preserving your saved discovery choice.`,
2684
- }),
2685
- });
2686
- if ((directSkillFiles > 0 && inspection.installed) || inspection.duplicates.length > 0) {
2687
- diagnostics.push({
2688
- code: 'runtime-codex-duplicate-discovery',
2689
- status: 'warn',
2690
- message: `Codex has duplicate OpenPlanr discovery ownership${inspection.duplicates.length ? `: ${inspection.duplicates.join(', ')}` : ' across direct skills and the unified plugin'}`,
2691
- fix: 'Choose direct or unified-plugin mode, preview setup, and use --replace-managed only for manifest-owned content.',
2692
- });
2693
- }
2694
- }
2695
- }
2696
- const accidentalHomeFiles = await previewHomeProjectCleanup();
2697
- if (accidentalHomeFiles.length > 0) {
2698
- diagnostics.push({
2699
- code: 'home-project-install',
2700
- status: 'warn',
2701
- message: `${accidentalHomeFiles.length} project-scoped managed file(s) were installed in the home directory`,
2702
- fix: `Run \`${CLI_COMMAND} doctor --fix\` to preview and remove only recorded OpenPlanr-owned project content.`,
2703
- });
2704
- }
2705
- const provenanceDiagnostic = diagnoseRuntimeProvenance(projectDir);
2706
- if (provenanceDiagnostic)
2707
- diagnostics.push(provenanceDiagnostic);
2708
- if (pipeline) {
2709
- // The child gets the resolved home only, so it does not repeat the OPENPLANR_HOME warning.
2710
- const { OPENPLANR_HOME: _deprecatedHome, ...inheritedEnvironment } = process.env;
2711
- const result = spawnSync(process.execPath, [
2712
- path.join(pipeline.root, 'scripts', 'doctor.mjs'),
2713
- '--json',
2714
- ...(options.pipelineRepair === 'preview' ? ['--repair-preview'] : []),
2715
- ...(options.pipelineRepair === 'apply' ? ['--fix'] : []),
2716
- ], {
2717
- cwd: projectDir,
2718
- encoding: 'utf8',
2719
- windowsHide: true,
2720
- env: { ...inheritedEnvironment, PLANR_HOME: planrHome() },
2721
- });
2722
- try {
2723
- const report = JSON.parse(result.stdout);
2724
- repairs.push(...(report.repairs ?? []));
2725
- for (const check of report.checks ?? []) {
2726
- if (check.status === 'ok')
2727
- continue;
2728
- diagnostics.push({
2729
- code: `pipeline-${check.id}`,
2730
- status: check.status,
2731
- message: check.message,
2732
- ...(check.fix ? { fix: check.fix } : {}),
2733
- });
2734
- }
2735
- }
2736
- catch {
2737
- diagnostics.push({
2738
- code: 'pipeline-doctor-unavailable',
2739
- status: 'warn',
2740
- message: 'The pipeline doctor did not return valid JSON',
2741
- fix: `Run \`${CLI_COMMAND} doctor\` for direct diagnostics.`,
2742
- });
2743
- }
2744
- }
2745
- const fail = diagnostics.some((item) => item.status === 'fail');
2746
- return { ok: !fail, diagnostics, repairs, ...(codexDiscovery ? { codexDiscovery } : {}) };
2747
- }
2748
1079
  export async function clearRuntimeStateForTests(root) {
2749
1080
  await rm(root, { recursive: true, force: true });
2750
1081
  }
1082
+ export function bundledHostRoot(host) {
1083
+ return resolveBundledHostRoot(host);
1084
+ }
1085
+ export async function runtimeDoctor(projectDir, options = {}) {
1086
+ return inspectRuntimeDoctor(projectDir, options);
1087
+ }
2751
1088
  //# sourceMappingURL=runtime-manager-service.js.map