opkg 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/README.md +109 -186
  2. package/assets/openpackage_ascii_dark.png +0 -0
  3. package/assets/openpackage_ascii_light.png +0 -0
  4. package/dist/commands/add.js +34 -10
  5. package/dist/commands/add.js.map +1 -1
  6. package/dist/commands/apply.js +16 -0
  7. package/dist/commands/apply.js.map +1 -0
  8. package/dist/commands/delete.js +1 -1
  9. package/dist/commands/delete.js.map +1 -1
  10. package/dist/commands/install.js +177 -8
  11. package/dist/commands/install.js.map +1 -1
  12. package/dist/commands/list.js +2 -2
  13. package/dist/commands/list.js.map +1 -1
  14. package/dist/commands/login.js +1 -1
  15. package/dist/commands/login.js.map +1 -1
  16. package/dist/commands/logout.js +1 -1
  17. package/dist/commands/logout.js.map +1 -1
  18. package/dist/commands/new.js +125 -0
  19. package/dist/commands/new.js.map +1 -0
  20. package/dist/commands/pack.js +7 -13
  21. package/dist/commands/pack.js.map +1 -1
  22. package/dist/commands/pull.js +1 -1
  23. package/dist/commands/pull.js.map +1 -1
  24. package/dist/commands/push.js +1 -1
  25. package/dist/commands/push.js.map +1 -1
  26. package/dist/commands/remove.js +63 -0
  27. package/dist/commands/remove.js.map +1 -0
  28. package/dist/commands/save.js +11 -17
  29. package/dist/commands/save.js.map +1 -1
  30. package/dist/commands/set.js +33 -0
  31. package/dist/commands/set.js.map +1 -0
  32. package/dist/commands/show.js +16 -94
  33. package/dist/commands/show.js.map +1 -1
  34. package/dist/commands/status.js +26 -701
  35. package/dist/commands/status.js.map +1 -1
  36. package/dist/commands/uninstall.js +14 -427
  37. package/dist/commands/uninstall.js.map +1 -1
  38. package/dist/constants/index.js +72 -16
  39. package/dist/constants/index.js.map +1 -1
  40. package/dist/core/add/add-conflict-handler.js +1 -8
  41. package/dist/core/add/add-conflict-handler.js.map +1 -1
  42. package/dist/core/add/add-pipeline.js +12 -10
  43. package/dist/core/add/add-pipeline.js.map +1 -1
  44. package/dist/core/add/add-to-source-pipeline.js +123 -0
  45. package/dist/core/add/add-to-source-pipeline.js.map +1 -0
  46. package/dist/core/add/package-index-updater.js +77 -78
  47. package/dist/core/add/package-index-updater.js.map +1 -1
  48. package/dist/core/add/platform-path-transformer.js +6 -4
  49. package/dist/core/add/platform-path-transformer.js.map +1 -1
  50. package/dist/core/add/source-collector.js +2 -3
  51. package/dist/core/add/source-collector.js.map +1 -1
  52. package/dist/core/apply/apply-pipeline.js +110 -0
  53. package/dist/core/apply/apply-pipeline.js.map +1 -0
  54. package/dist/core/dependency-resolver.js +263 -21
  55. package/dist/core/dependency-resolver.js.map +1 -1
  56. package/dist/core/discovery/file-discovery.js +1 -2
  57. package/dist/core/discovery/file-discovery.js.map +1 -1
  58. package/dist/core/discovery/platform-files-discovery.js +33 -18
  59. package/dist/core/discovery/platform-files-discovery.js.map +1 -1
  60. package/dist/core/flows/flow-executor.js +974 -0
  61. package/dist/core/flows/flow-executor.js.map +1 -0
  62. package/dist/core/flows/flow-inverter.js +442 -0
  63. package/dist/core/flows/flow-inverter.js.map +1 -0
  64. package/dist/core/flows/flow-key-extractor.js +101 -0
  65. package/dist/core/flows/flow-key-extractor.js.map +1 -0
  66. package/dist/core/flows/flow-key-mapper.js +382 -0
  67. package/dist/core/flows/flow-key-mapper.js.map +1 -0
  68. package/dist/core/flows/flow-transforms.js +632 -0
  69. package/dist/core/flows/flow-transforms.js.map +1 -0
  70. package/dist/core/flows/map-pipeline/context.js +73 -0
  71. package/dist/core/flows/map-pipeline/context.js.map +1 -0
  72. package/dist/core/flows/map-pipeline/index.js +156 -0
  73. package/dist/core/flows/map-pipeline/index.js.map +1 -0
  74. package/dist/core/flows/map-pipeline/operations/copy.js +104 -0
  75. package/dist/core/flows/map-pipeline/operations/copy.js.map +1 -0
  76. package/dist/core/flows/map-pipeline/operations/pipe.js +70 -0
  77. package/dist/core/flows/map-pipeline/operations/pipe.js.map +1 -0
  78. package/dist/core/flows/map-pipeline/operations/rename.js +102 -0
  79. package/dist/core/flows/map-pipeline/operations/rename.js.map +1 -0
  80. package/dist/core/flows/map-pipeline/operations/set.js +50 -0
  81. package/dist/core/flows/map-pipeline/operations/set.js.map +1 -0
  82. package/dist/core/flows/map-pipeline/operations/switch.js +79 -0
  83. package/dist/core/flows/map-pipeline/operations/switch.js.map +1 -0
  84. package/dist/core/flows/map-pipeline/operations/transform.js +543 -0
  85. package/dist/core/flows/map-pipeline/operations/transform.js.map +1 -0
  86. package/dist/core/flows/map-pipeline/operations/unset.js +65 -0
  87. package/dist/core/flows/map-pipeline/operations/unset.js.map +1 -0
  88. package/dist/core/flows/map-pipeline/types.js +8 -0
  89. package/dist/core/flows/map-pipeline/types.js.map +1 -0
  90. package/dist/core/flows/map-pipeline/utils.js +278 -0
  91. package/dist/core/flows/map-pipeline/utils.js.map +1 -0
  92. package/dist/core/flows/platform-converter.js +328 -0
  93. package/dist/core/flows/platform-converter.js.map +1 -0
  94. package/dist/core/flows/source-resolver.js +192 -0
  95. package/dist/core/flows/source-resolver.js.map +1 -0
  96. package/dist/core/flows/toml-domain-transforms.js +23 -0
  97. package/dist/core/flows/toml-domain-transforms.js.map +1 -0
  98. package/dist/core/install/bulk-install-pipeline.js +68 -7
  99. package/dist/core/install/bulk-install-pipeline.js.map +1 -1
  100. package/dist/core/install/canonical-plan.js +3 -3
  101. package/dist/core/install/canonical-plan.js.map +1 -1
  102. package/dist/core/install/dry-run.js +3 -3
  103. package/dist/core/install/dry-run.js.map +1 -1
  104. package/dist/core/install/flow-based-installer.js +1158 -0
  105. package/dist/core/install/flow-based-installer.js.map +1 -0
  106. package/dist/core/install/flow-workspace-tracker.js +111 -0
  107. package/dist/core/install/flow-workspace-tracker.js.map +1 -0
  108. package/dist/core/install/format-detector.js +228 -0
  109. package/dist/core/install/format-detector.js.map +1 -0
  110. package/dist/core/install/git-package-loader.js +20 -0
  111. package/dist/core/install/git-package-loader.js.map +1 -0
  112. package/dist/core/install/install-errors.js +1 -1
  113. package/dist/core/install/install-errors.js.map +1 -1
  114. package/dist/core/install/install-flow.js +34 -14
  115. package/dist/core/install/install-flow.js.map +1 -1
  116. package/dist/core/install/install-pipeline.js +52 -17
  117. package/dist/core/install/install-pipeline.js.map +1 -1
  118. package/dist/core/install/install-reporting.js +26 -8
  119. package/dist/core/install/install-reporting.js.map +1 -1
  120. package/dist/core/install/local-source-resolution.js +103 -0
  121. package/dist/core/install/local-source-resolution.js.map +1 -0
  122. package/dist/core/install/marketplace-handler.js +221 -0
  123. package/dist/core/install/marketplace-handler.js.map +1 -0
  124. package/dist/core/install/path-install-pipeline.js +241 -0
  125. package/dist/core/install/path-install-pipeline.js.map +1 -0
  126. package/dist/core/install/path-package-loader.js +116 -0
  127. package/dist/core/install/path-package-loader.js.map +1 -0
  128. package/dist/core/install/plugin-detector.js +72 -0
  129. package/dist/core/install/plugin-detector.js.map +1 -0
  130. package/dist/core/install/plugin-to-universal-converter.js +218 -0
  131. package/dist/core/install/plugin-to-universal-converter.js.map +1 -0
  132. package/dist/core/install/plugin-transformer.js +191 -0
  133. package/dist/core/install/plugin-transformer.js.map +1 -0
  134. package/dist/core/install/version-selection.js +1 -1
  135. package/dist/core/install/version-selection.js.map +1 -1
  136. package/dist/core/openpackage.js +40 -22
  137. package/dist/core/openpackage.js.map +1 -1
  138. package/dist/core/pack/pack-output.js +62 -0
  139. package/dist/core/pack/pack-output.js.map +1 -0
  140. package/dist/core/pack/pack-pipeline.js +186 -0
  141. package/dist/core/pack/pack-pipeline.js.map +1 -0
  142. package/dist/core/package-context.js +45 -70
  143. package/dist/core/package-context.js.map +1 -1
  144. package/dist/core/package-creation.js +203 -0
  145. package/dist/core/package-creation.js.map +1 -0
  146. package/dist/core/package.js +20 -6
  147. package/dist/core/package.js.map +1 -1
  148. package/dist/core/platforms.js +665 -209
  149. package/dist/core/platforms.js.map +1 -1
  150. package/dist/core/push/push-context.js +1 -1
  151. package/dist/core/push/push-context.js.map +1 -1
  152. package/dist/core/push/push-upload.js +2 -2
  153. package/dist/core/push/push-upload.js.map +1 -1
  154. package/dist/core/registry.js +6 -6
  155. package/dist/core/registry.js.map +1 -1
  156. package/dist/core/remote-pull.js +2 -2
  157. package/dist/core/remote-pull.js.map +1 -1
  158. package/dist/core/remove/removal-collector.js +52 -0
  159. package/dist/core/remove/removal-collector.js.map +1 -0
  160. package/dist/core/remove/removal-confirmation.js +39 -0
  161. package/dist/core/remove/removal-confirmation.js.map +1 -0
  162. package/dist/core/remove/remove-from-source-pipeline.js +173 -0
  163. package/dist/core/remove/remove-from-source-pipeline.js.map +1 -0
  164. package/dist/core/save/constants.js +3 -3
  165. package/dist/core/save/constants.js.map +1 -1
  166. package/dist/core/save/flow-based-saver.js +270 -0
  167. package/dist/core/save/flow-based-saver.js.map +1 -0
  168. package/dist/core/save/name-resolution.js +1 -1
  169. package/dist/core/save/name-resolution.js.map +1 -1
  170. package/dist/core/save/package-yml-generator.js +4 -5
  171. package/dist/core/save/package-yml-generator.js.map +1 -1
  172. package/dist/core/save/save-candidate-builder.js +215 -0
  173. package/dist/core/save/save-candidate-builder.js.map +1 -0
  174. package/dist/core/save/save-candidate-loader.js +12 -11
  175. package/dist/core/save/save-candidate-loader.js.map +1 -1
  176. package/dist/core/save/save-conflict-analyzer.js +150 -0
  177. package/dist/core/save/save-conflict-analyzer.js.map +1 -0
  178. package/dist/core/save/save-conflict-resolution.js +28 -14
  179. package/dist/core/save/save-conflict-resolution.js.map +1 -1
  180. package/dist/core/save/save-conflict-resolver.js +31 -275
  181. package/dist/core/save/save-conflict-resolver.js.map +1 -1
  182. package/dist/core/save/save-group-builder.js +52 -0
  183. package/dist/core/save/save-group-builder.js.map +1 -0
  184. package/dist/core/save/save-interactive-resolver.js +190 -0
  185. package/dist/core/save/save-interactive-resolver.js.map +1 -0
  186. package/dist/core/save/save-pipeline.js +58 -34
  187. package/dist/core/save/save-pipeline.js.map +1 -1
  188. package/dist/core/save/save-platform-handler.js +53 -0
  189. package/dist/core/save/save-platform-handler.js.map +1 -0
  190. package/dist/core/save/save-resolution-executor.js +145 -0
  191. package/dist/core/save/save-resolution-executor.js.map +1 -0
  192. package/dist/core/save/save-result-reporter.js +167 -0
  193. package/dist/core/save/save-result-reporter.js.map +1 -0
  194. package/dist/core/save/save-to-source-pipeline.js +154 -0
  195. package/dist/core/save/save-to-source-pipeline.js.map +1 -0
  196. package/dist/core/save/save-versioning.js +4 -4
  197. package/dist/core/save/save-versioning.js.map +1 -1
  198. package/dist/core/save/save-write-coordinator.js +204 -0
  199. package/dist/core/save/save-write-coordinator.js.map +1 -0
  200. package/dist/core/save/save-yml-resolution.js +28 -216
  201. package/dist/core/save/save-yml-resolution.js.map +1 -1
  202. package/dist/core/save/workspace-rename.js +7 -8
  203. package/dist/core/save/workspace-rename.js.map +1 -1
  204. package/dist/core/set/set-output.js +72 -0
  205. package/dist/core/set/set-output.js.map +1 -0
  206. package/dist/core/set/set-pipeline.js +361 -0
  207. package/dist/core/set/set-pipeline.js.map +1 -0
  208. package/dist/core/set/set-types.js +5 -0
  209. package/dist/core/set/set-types.js.map +1 -0
  210. package/dist/core/show/package-resolver.js +257 -0
  211. package/dist/core/show/package-resolver.js.map +1 -0
  212. package/dist/core/show/scope-discovery.js +165 -0
  213. package/dist/core/show/scope-discovery.js.map +1 -0
  214. package/dist/core/show/show-output.js +168 -0
  215. package/dist/core/show/show-output.js.map +1 -0
  216. package/dist/core/show/show-pipeline.js +113 -0
  217. package/dist/core/show/show-pipeline.js.map +1 -0
  218. package/dist/core/show/show-types.js +5 -0
  219. package/dist/core/show/show-types.js.map +1 -0
  220. package/dist/core/source-resolution/dependency-graph.js +104 -0
  221. package/dist/core/source-resolution/dependency-graph.js.map +1 -0
  222. package/dist/core/source-resolution/resolve-mutable-source.js +109 -0
  223. package/dist/core/source-resolution/resolve-mutable-source.js.map +1 -0
  224. package/dist/core/source-resolution/resolve-package-source.js +29 -0
  225. package/dist/core/source-resolution/resolve-package-source.js.map +1 -0
  226. package/dist/core/source-resolution/resolve-registry-version.js +35 -0
  227. package/dist/core/source-resolution/resolve-registry-version.js.map +1 -0
  228. package/dist/core/source-resolution/types.js.map +1 -0
  229. package/dist/core/status/status-file-discovery.js +23 -12
  230. package/dist/core/status/status-file-discovery.js.map +1 -1
  231. package/dist/core/status/status-pipeline.js +134 -0
  232. package/dist/core/status/status-pipeline.js.map +1 -0
  233. package/dist/core/sync/platform-sync-summary.js +27 -0
  234. package/dist/core/sync/platform-sync-summary.js.map +1 -0
  235. package/dist/core/uninstall/flow-aware-uninstaller.js +189 -0
  236. package/dist/core/uninstall/flow-aware-uninstaller.js.map +1 -0
  237. package/dist/core/uninstall/uninstall-file-discovery.js +11 -6
  238. package/dist/core/uninstall/uninstall-file-discovery.js.map +1 -1
  239. package/dist/core/uninstall/uninstall-pipeline.js +141 -0
  240. package/dist/core/uninstall/uninstall-pipeline.js.map +1 -0
  241. package/dist/core/universal-patterns.js +64 -0
  242. package/dist/core/universal-patterns.js.map +1 -0
  243. package/dist/index.js +99 -6
  244. package/dist/index.js.map +1 -1
  245. package/dist/types/flows.js +8 -0
  246. package/dist/types/flows.js.map +1 -0
  247. package/dist/types/index.js +3 -0
  248. package/dist/types/index.js.map +1 -1
  249. package/dist/types/platform-flows.js +8 -0
  250. package/dist/types/platform-flows.js.map +1 -0
  251. package/dist/types/workspace-index.js +6 -0
  252. package/dist/types/workspace-index.js.map +1 -0
  253. package/dist/utils/custom-path-resolution.js +160 -0
  254. package/dist/utils/custom-path-resolution.js.map +1 -0
  255. package/dist/utils/dependency-coverage.js +1 -1
  256. package/dist/utils/dependency-coverage.js.map +1 -1
  257. package/dist/utils/file-processing.js +1 -1
  258. package/dist/utils/flow-index-installer.js +209 -0
  259. package/dist/utils/flow-index-installer.js.map +1 -0
  260. package/dist/utils/formatters.js +47 -1
  261. package/dist/utils/formatters.js.map +1 -1
  262. package/dist/utils/fs.js +17 -0
  263. package/dist/utils/fs.js.map +1 -1
  264. package/dist/utils/git-clone-registry.js +88 -0
  265. package/dist/utils/git-clone-registry.js.map +1 -0
  266. package/dist/utils/git-clone.js +69 -0
  267. package/dist/utils/git-clone.js.map +1 -0
  268. package/dist/utils/git-spec.js +96 -0
  269. package/dist/utils/git-spec.js.map +1 -0
  270. package/dist/utils/http-client.js +7 -0
  271. package/dist/utils/http-client.js.map +1 -1
  272. package/dist/utils/index-based-installer.js +356 -163
  273. package/dist/utils/index-based-installer.js.map +1 -1
  274. package/dist/utils/install-conflict-handler.js +2 -2
  275. package/dist/utils/install-conflict-handler.js.map +1 -1
  276. package/dist/utils/install-file-discovery.js +18 -13
  277. package/dist/utils/install-file-discovery.js.map +1 -1
  278. package/dist/utils/install-helpers.js +43 -20
  279. package/dist/utils/install-helpers.js.map +1 -1
  280. package/dist/utils/jsonc.js +23 -1
  281. package/dist/utils/jsonc.js.map +1 -1
  282. package/dist/utils/manifest-paths.js +1 -1
  283. package/dist/utils/manifest-paths.js.map +1 -1
  284. package/dist/utils/markdown-frontmatter.js +46 -0
  285. package/dist/utils/markdown-frontmatter.js.map +1 -1
  286. package/dist/utils/package-copy.js +5 -103
  287. package/dist/utils/package-copy.js.map +1 -1
  288. package/dist/utils/package-filters.js +9 -105
  289. package/dist/utils/package-filters.js.map +1 -1
  290. package/dist/utils/package-index-yml.js +27 -6
  291. package/dist/utils/package-index-yml.js.map +1 -1
  292. package/dist/utils/package-input.js +98 -0
  293. package/dist/utils/package-input.js.map +1 -0
  294. package/dist/utils/package-management.js +80 -28
  295. package/dist/utils/package-management.js.map +1 -1
  296. package/dist/utils/package-name-resolution.js +327 -0
  297. package/dist/utils/package-name-resolution.js.map +1 -0
  298. package/dist/utils/package-name.js +18 -16
  299. package/dist/utils/package-name.js.map +1 -1
  300. package/dist/utils/package-versioning.js +2 -33
  301. package/dist/utils/package-versioning.js.map +1 -1
  302. package/dist/utils/package-yml.js +19 -28
  303. package/dist/utils/package-yml.js.map +1 -1
  304. package/dist/utils/path-resolution.js +102 -0
  305. package/dist/utils/path-resolution.js.map +1 -0
  306. package/dist/utils/paths.js +6 -6
  307. package/dist/utils/paths.js.map +1 -1
  308. package/dist/utils/platform-file.js +36 -24
  309. package/dist/utils/platform-file.js.map +1 -1
  310. package/dist/utils/platform-mapper.js +222 -68
  311. package/dist/utils/platform-mapper.js.map +1 -1
  312. package/dist/utils/platform-root-files.js +44 -0
  313. package/dist/utils/platform-root-files.js.map +1 -0
  314. package/dist/utils/platform-utils.js +35 -54
  315. package/dist/utils/platform-utils.js.map +1 -1
  316. package/dist/utils/platform-yaml-merge.js +20 -140
  317. package/dist/utils/platform-yaml-merge.js.map +1 -1
  318. package/dist/utils/prompts.js +92 -7
  319. package/dist/utils/prompts.js.map +1 -1
  320. package/dist/utils/registry-entry-filter.js +50 -27
  321. package/dist/utils/registry-entry-filter.js.map +1 -1
  322. package/dist/utils/registry-paths.js +5 -4
  323. package/dist/utils/registry-paths.js.map +1 -1
  324. package/dist/utils/scope-resolution.js +156 -0
  325. package/dist/utils/scope-resolution.js.map +1 -0
  326. package/dist/utils/source-mutability.js +15 -0
  327. package/dist/utils/source-mutability.js.map +1 -0
  328. package/dist/utils/tarball.js +29 -4
  329. package/dist/utils/tarball.js.map +1 -1
  330. package/dist/utils/version-ranges.js +1 -32
  331. package/dist/utils/version-ranges.js.map +1 -1
  332. package/dist/utils/workspace-index-helpers.js +28 -0
  333. package/dist/utils/workspace-index-helpers.js.map +1 -0
  334. package/dist/utils/workspace-index-ownership.js +100 -0
  335. package/dist/utils/workspace-index-ownership.js.map +1 -0
  336. package/dist/utils/workspace-index-yml.js +173 -0
  337. package/dist/utils/workspace-index-yml.js.map +1 -0
  338. package/examples/custom-subdirs-platform.jsonc +157 -0
  339. package/package.json +7 -2
  340. package/platforms.jsonc +531 -84
  341. package/schemas/map-pipeline-v1.json +256 -0
  342. package/schemas/platforms-v1.json +400 -0
  343. package/specs/README.md +88 -0
  344. package/specs/add/README.md +166 -0
  345. package/specs/agents-claude.md +570 -0
  346. package/specs/agents-opencode.md +622 -0
  347. package/specs/apply/README.md +21 -0
  348. package/specs/apply/apply-behavior.md +58 -0
  349. package/specs/apply/apply-command.md +51 -0
  350. package/specs/apply/conflicts.md +41 -0
  351. package/specs/apply/index-effects.md +81 -0
  352. package/specs/architecture.md +107 -0
  353. package/specs/auth/README.md +17 -0
  354. package/specs/auth/auth-http-contract.md +25 -0
  355. package/specs/auth/cli/credentials.md +39 -0
  356. package/specs/auth/cli/login.md +32 -0
  357. package/specs/auth/cli/logout.md +16 -0
  358. package/specs/claude-mcp.md +1065 -0
  359. package/specs/claude-plugins-marketplace.md +363 -0
  360. package/specs/claude-plugins.md +413 -0
  361. package/specs/cli-options.md +52 -0
  362. package/specs/codex-mcp.md +114 -0
  363. package/specs/commands-overview.md +175 -0
  364. package/specs/directory-layout.md +95 -0
  365. package/specs/install/README.md +12 -4
  366. package/specs/install/git-sources.md +230 -0
  367. package/specs/install/install-behavior.md +483 -73
  368. package/specs/install/package-yml-canonical.md +67 -35
  369. package/specs/install/version-resolution.md +69 -115
  370. package/specs/new/README.md +769 -0
  371. package/specs/new/SUMMARY.md +310 -0
  372. package/specs/new/scope-behavior.md +793 -0
  373. package/specs/pack/README.md +77 -0
  374. package/specs/pack/package-name-resolution.md +330 -0
  375. package/specs/package/README.md +18 -17
  376. package/specs/package/nested-packages-and-parent-packages.md +32 -31
  377. package/specs/package/package-index-yml.md +95 -101
  378. package/specs/package/package-root-layout.md +64 -46
  379. package/specs/package/registry-payload-and-copy.md +50 -44
  380. package/specs/package/universal-content.md +33 -56
  381. package/specs/package-sources.md +248 -0
  382. package/specs/platforms/README.md +52 -0
  383. package/specs/platforms/configuration.md +571 -0
  384. package/specs/platforms/detection.md +552 -0
  385. package/specs/platforms/directory-layout.md +599 -0
  386. package/specs/platforms/examples.md +1146 -0
  387. package/specs/platforms/flow-reference.md +1240 -0
  388. package/specs/platforms/flows.md +1488 -0
  389. package/specs/platforms/map-pipeline.md +801 -0
  390. package/specs/platforms/overview.md +349 -0
  391. package/specs/platforms/specification.md +700 -0
  392. package/specs/platforms/troubleshooting.md +697 -0
  393. package/specs/platforms/universal-converter.md +520 -0
  394. package/specs/push/README.md +1 -0
  395. package/specs/push/push-behavior.md +11 -3
  396. package/specs/push/push-remote-upload.md +1 -1
  397. package/specs/push/push-scoping.md +1 -1
  398. package/specs/push/push-version-selection.md +1 -1
  399. package/specs/registry.md +111 -0
  400. package/specs/remove/README.md +257 -0
  401. package/specs/save/README.md +21 -17
  402. package/specs/save/save-conflict-resolution.md +205 -83
  403. package/specs/save/save-file-discovery.md +6 -4
  404. package/specs/save/save-frontmatter-overrides.md +11 -15
  405. package/specs/save/save-modes-inputs.md +9 -39
  406. package/specs/save/save-naming-scoping.md +4 -4
  407. package/specs/save/save-package-detection.md +13 -13
  408. package/specs/save/save-registry-sync.md +16 -106
  409. package/specs/save/save-versioning.md +80 -0
  410. package/specs/scope-management.md +92 -0
  411. package/specs/set/README.md +520 -0
  412. package/specs/set/set-behavior.md +563 -0
  413. package/specs/show/README.md +483 -0
  414. package/specs/show/show-remote.md +494 -0
  415. package/specs/status/README.md +38 -0
  416. package/specs/uninstall/README.md +231 -0
  417. package/dist/commands/duplicate.js +0 -69
  418. package/dist/commands/duplicate.js.map +0 -1
  419. package/dist/commands/init.js +0 -117
  420. package/dist/commands/init.js.map +0 -1
  421. package/dist/commands/prune.js +0 -357
  422. package/dist/commands/prune.js.map +0 -1
  423. package/dist/commands/tui.js +0 -61
  424. package/dist/commands/tui.js.map +0 -1
  425. package/dist/core/install/index.js +0 -3
  426. package/dist/core/install/index.js.map +0 -1
  427. package/dist/core/push/push-single-file.js +0 -56
  428. package/dist/core/push/push-single-file.js.map +0 -1
  429. package/dist/core/save/package-detection.js +0 -147
  430. package/dist/core/save/package-detection.js.map +0 -1
  431. package/dist/core/save/save-single-file.js +0 -124
  432. package/dist/core/save/save-single-file.js.map +0 -1
  433. package/dist/core/token-store.js +0 -73
  434. package/dist/core/token-store.js.map +0 -1
  435. package/dist/tui/app.js +0 -95
  436. package/dist/tui/app.js.map +0 -1
  437. package/dist/tui/components/package-list.js +0 -73
  438. package/dist/tui/components/package-list.js.map +0 -1
  439. package/dist/tui/controller.js +0 -365
  440. package/dist/tui/controller.js.map +0 -1
  441. package/dist/tui/index.js +0 -12
  442. package/dist/tui/index.js.map +0 -1
  443. package/dist/tui/services/file-index.js +0 -64
  444. package/dist/tui/services/file-index.js.map +0 -1
  445. package/dist/tui/services/packages.js +0 -18
  446. package/dist/tui/services/packages.js.map +0 -1
  447. package/dist/tui/services/save.js +0 -21
  448. package/dist/tui/services/save.js.map +0 -1
  449. package/dist/tui/state/app-state.js +0 -15
  450. package/dist/tui/state/app-state.js.map +0 -1
  451. package/dist/tui/state.js +0 -17
  452. package/dist/tui/state.js.map +0 -1
  453. package/dist/tui/types.js.map +0 -1
  454. package/dist/tui/views/add-file-modal.js +0 -129
  455. package/dist/tui/views/add-file-modal.js.map +0 -1
  456. package/dist/tui/views/file-preview.js +0 -44
  457. package/dist/tui/views/file-preview.js.map +0 -1
  458. package/dist/tui/views/list-packages.js +0 -73
  459. package/dist/tui/views/list-packages.js.map +0 -1
  460. package/dist/tui/views/main-menu.js +0 -29
  461. package/dist/tui/views/main-menu.js.map +0 -1
  462. package/dist/tui/views/manage-view.js +0 -81
  463. package/dist/tui/views/manage-view.js.map +0 -1
  464. package/dist/tui/views/package-hub.js +0 -120
  465. package/dist/tui/views/package-hub.js.map +0 -1
  466. package/dist/tui/views/placeholder.js +0 -24
  467. package/dist/tui/views/placeholder.js.map +0 -1
  468. package/dist/utils/bun-bootstrap.js +0 -72
  469. package/dist/utils/bun-bootstrap.js.map +0 -1
  470. package/dist/utils/entity-id.js +0 -19
  471. package/dist/utils/entity-id.js.map +0 -1
  472. package/dist/utils/package-local-files.js +0 -5
  473. package/dist/utils/package-local-files.js.map +0 -1
  474. package/dist/utils/path-matching.js +0 -74
  475. package/dist/utils/path-matching.js.map +0 -1
  476. package/dist/utils/root-file-operations.js +0 -39
  477. package/dist/utils/root-file-operations.js.map +0 -1
  478. package/dist/utils/root-file-transformer.js +0 -27
  479. package/dist/utils/root-file-transformer.js.map +0 -1
  480. package/dist/utils/yaml-frontmatter.js +0 -25
  481. package/dist/utils/yaml-frontmatter.js.map +0 -1
  482. package/specs/auth/auth-device-flow.md +0 -70
  483. package/specs/login/login-device-flow.md +0 -70
  484. package/specs/platforms.md +0 -193
  485. package/specs/save-pack-versioning.md +0 -224
  486. package/specs/save-pack.md +0 -68
  487. /package/dist/{tui → core/source-resolution}/types.js +0 -0
@@ -2,219 +2,658 @@
2
2
  * Platform Management Module
3
3
  * Centralized platform definitions, directory mappings, and file patterns
4
4
  * for all 13 supported AI coding platforms
5
+ *
6
+ * Now supports flow-based configurations for declarative transformations.
5
7
  */
6
- import { join, relative } from 'path';
7
- import { exists, ensureDir } from '../utils/fs.js';
8
- import { logger } from '../utils/logger.js';
9
- import { getPathLeaf } from '../utils/path-normalization.js';
10
- import { DIR_PATTERNS, FILE_PATTERNS, UNIVERSAL_SUBDIRS } from '../constants/index.js';
11
- import { mapPlatformFileToUniversal } from '../utils/platform-mapper.js';
12
- import { parseUniversalPath } from '../utils/platform-file.js';
13
- import { readJsoncFileSync } from '../utils/jsonc.js';
14
- /**
15
- * Normalize subdir config from JSONC to internal SubdirDef format
16
- */
17
- function normalizeSubdirs(subdirs) {
18
- if (!subdirs) {
19
- return {};
20
- }
21
- const normalized = {};
22
- for (const [subdirKey, subdirConfig] of Object.entries(subdirs)) {
23
- // Validate that the subdir key is a valid universal subdir
24
- if (!isValidUniversalSubdir(subdirKey)) {
25
- logger.warn(`Invalid universal subdir key in platforms.jsonc: ${subdirKey}`);
26
- continue;
27
- }
28
- // Skip if subdirConfig is undefined
29
- if (!subdirConfig) {
30
- continue;
31
- }
32
- normalized[subdirKey] = subdirConfig;
33
- }
34
- return normalized;
8
+ import { join, relative } from "path";
9
+ import { exists, ensureDir } from "../utils/fs.js";
10
+ import { logger } from "../utils/logger.js";
11
+ import { getPathLeaf } from "../utils/path-normalization.js";
12
+ import { DIR_PATTERNS, FILE_PATTERNS, } from "../constants/index.js";
13
+ import { mapPlatformFileToUniversal } from "../utils/platform-mapper.js";
14
+ import { parseUniversalPath } from "../utils/platform-file.js";
15
+ import { readJsoncFileSync, readJsoncOrJson } from "../utils/jsonc.js";
16
+ import * as os from "os";
17
+ import { matchesAnyPattern, extractSubdirectoriesFromPatterns } from "./universal-patterns.js";
18
+ /**
19
+ * Check if a config entry is a global flows config
20
+ */
21
+ function isGlobalFlowsConfig(cfg) {
22
+ return ('export' in cfg || 'import' in cfg) && !('name' in cfg) && !('rootDir' in cfg);
35
23
  }
36
24
  /**
37
- * Load platform definitions from platforms.jsonc file
25
+ * Create platform definitions from a PlatformsConfig object
26
+ * Export/import flows configuration (no legacy subdirs support)
27
+ * Assumes config is already validated.
28
+ * @param config - The merged platforms configuration
38
29
  */
39
- function loadPlatformDefinitionsFromConfig() {
40
- const raw = readJsoncFileSync('platforms.jsonc');
30
+ function createPlatformDefinitions(config) {
41
31
  const result = {};
42
- for (const [id, cfg] of Object.entries(raw)) {
32
+ for (const [id, cfg] of Object.entries(config)) {
33
+ // Skip special keys
34
+ if (id === '$schema')
35
+ continue;
36
+ // Skip global config entry
37
+ if (id === 'global')
38
+ continue;
39
+ // Type guard for platform config
40
+ if (isGlobalFlowsConfig(cfg))
41
+ continue;
42
+ // Now we know cfg is PlatformConfig
43
+ const platformConfig = cfg;
43
44
  const platformId = id;
44
45
  result[platformId] = {
45
46
  id: platformId,
46
- name: cfg.name,
47
- rootDir: cfg.rootDir,
48
- rootFile: cfg.rootFile,
49
- subdirs: normalizeSubdirs(cfg.subdirs),
50
- aliases: cfg.aliases,
51
- enabled: cfg.enabled !== false
47
+ name: platformConfig.name,
48
+ rootDir: platformConfig.rootDir,
49
+ rootFile: platformConfig.rootFile,
50
+ export: platformConfig.export || [],
51
+ import: platformConfig.import || [],
52
+ aliases: platformConfig.aliases,
53
+ enabled: platformConfig.enabled !== false,
54
+ description: platformConfig.description,
55
+ variables: platformConfig.variables,
52
56
  };
53
57
  }
54
58
  return result;
55
59
  }
56
- // Unified platform definitions loaded from platforms.jsonc
57
- export const PLATFORM_DEFINITIONS = loadPlatformDefinitionsFromConfig();
58
- const PLATFORM_IDS = Object.freeze(Object.keys(PLATFORM_DEFINITIONS));
59
- // All platforms (including disabled) for internal reference
60
- export const ALL_PLATFORMS = PLATFORM_IDS;
61
- /**
62
- * Lookup map from platform directory name to platform ID.
63
- * Used for quickly inferring platform from source directory.
64
- */
65
- export const PLATFORM_DIR_LOOKUP = (() => {
66
- const map = {};
67
- for (const def of Object.values(PLATFORM_DEFINITIONS)) {
68
- map[def.rootDir] = def.id;
69
- }
70
- return map;
71
- })();
72
- const PLATFORM_ALIAS_LOOKUP = (() => {
73
- const map = {};
74
- for (const def of Object.values(PLATFORM_DEFINITIONS)) {
60
+ const BUILT_IN_CONFIG = readJsoncFileSync("platforms.jsonc");
61
+ const builtinErrors = validatePlatformsConfig(BUILT_IN_CONFIG);
62
+ if (builtinErrors.length > 0) {
63
+ throw new Error(`Built-in platforms.jsonc validation failed:\n - ${builtinErrors.join('\n - ')}`);
64
+ }
65
+ /**
66
+ * Merge two PlatformsConfig objects, handling per-platform fields and subdirs arrays properly.
67
+ * Adds new platforms from override; merges existing with override preferences.
68
+ */
69
+ export function mergePlatformsConfig(base, override) {
70
+ const merged = { ...base };
71
+ for (const [platformId, overridePlat] of Object.entries(override)) {
72
+ // Skip special keys
73
+ if (platformId === '$schema') {
74
+ merged[platformId] = overridePlat;
75
+ continue;
76
+ }
77
+ const basePlat = base[platformId];
78
+ if (!basePlat) {
79
+ merged[platformId] = overridePlat;
80
+ continue;
81
+ }
82
+ // Handle global config separately
83
+ if (platformId === 'global') {
84
+ if (isGlobalFlowsConfig(overridePlat)) {
85
+ merged[platformId] = overridePlat; // Replace global config entirely
86
+ }
87
+ continue;
88
+ }
89
+ // Type guard for platform config
90
+ if (isGlobalFlowsConfig(overridePlat) || isGlobalFlowsConfig(basePlat)) {
91
+ merged[platformId] = overridePlat;
92
+ continue;
93
+ }
94
+ // Now we know both are PlatformConfig
95
+ const overrideCfg = overridePlat;
96
+ const baseCfg = basePlat;
97
+ merged[platformId] = {
98
+ name: overrideCfg.name ?? baseCfg.name,
99
+ rootDir: overrideCfg.rootDir ?? baseCfg.rootDir,
100
+ rootFile: overrideCfg.rootFile ?? baseCfg.rootFile,
101
+ aliases: overrideCfg.aliases ?? baseCfg.aliases, // replace array
102
+ enabled: overrideCfg.enabled ?? baseCfg.enabled,
103
+ description: overrideCfg.description ?? baseCfg.description,
104
+ variables: overrideCfg.variables ?? baseCfg.variables,
105
+ export: overrideCfg.export ?? baseCfg.export, // replace array (no merge)
106
+ import: overrideCfg.import ?? baseCfg.import, // replace array (no merge)
107
+ };
108
+ }
109
+ return merged;
110
+ }
111
+ /**
112
+ * Validate a PlatformsConfig object and return any validation errors.
113
+ * Export/import flows configuration (no legacy subdirs support).
114
+ * @param config - The config to validate
115
+ * @returns Array of error messages; empty if valid
116
+ */
117
+ export function validatePlatformsConfig(config) {
118
+ const errors = [];
119
+ for (const [platformId, platConfig] of Object.entries(config)) {
120
+ // Skip special keys
121
+ if (platformId === '$schema') {
122
+ continue;
123
+ }
124
+ // Skip global config - validate separately
125
+ if (platformId === 'global') {
126
+ if (isGlobalFlowsConfig(platConfig)) {
127
+ errors.push(...validateGlobalFlowsConfig(platConfig));
128
+ }
129
+ continue;
130
+ }
131
+ // Type guard for platform config
132
+ if (isGlobalFlowsConfig(platConfig)) {
133
+ errors.push(`Platform '${platformId}': Cannot use global flows config format for platform entry`);
134
+ continue;
135
+ }
136
+ // Now we know platConfig is PlatformConfig
137
+ const cfg = platConfig;
138
+ if (!cfg.rootDir || cfg.rootDir.trim() === '') {
139
+ errors.push(`Platform '${platformId}': Missing or empty rootDir`);
140
+ }
141
+ if (!cfg.name || cfg.name.trim() === '') {
142
+ errors.push(`Platform '${platformId}': Missing or empty name`);
143
+ }
144
+ // Validate export flows
145
+ if (cfg.export !== undefined) {
146
+ if (!Array.isArray(cfg.export)) {
147
+ errors.push(`Platform '${platformId}': export must be array or undefined`);
148
+ }
149
+ else {
150
+ errors.push(...validateFlows(cfg.export, `${platformId}.export`));
151
+ }
152
+ }
153
+ // Validate import flows
154
+ if (cfg.import !== undefined) {
155
+ if (!Array.isArray(cfg.import)) {
156
+ errors.push(`Platform '${platformId}': import must be array or undefined`);
157
+ }
158
+ else {
159
+ errors.push(...validateFlows(cfg.import, `${platformId}.import`));
160
+ }
161
+ }
162
+ // Validate that export or import or rootFile is present
163
+ const hasExport = cfg.export && cfg.export.length > 0;
164
+ const hasImport = cfg.import && cfg.import.length > 0;
165
+ if (!hasExport && !hasImport && !cfg.rootFile) {
166
+ errors.push(`Platform '${platformId}': Must define at least one of 'export', 'import', or 'rootFile'`);
167
+ }
168
+ if (cfg.aliases !== undefined && (!Array.isArray(cfg.aliases) || cfg.aliases.some((a) => typeof a !== 'string'))) {
169
+ errors.push(`Platform '${platformId}': aliases must be array of strings or undefined`);
170
+ }
171
+ if (typeof cfg.enabled !== 'boolean' && cfg.enabled !== undefined) {
172
+ errors.push(`Platform '${platformId}': enabled must be boolean or undefined`);
173
+ }
174
+ if (cfg.variables !== undefined && (typeof cfg.variables !== 'object' || Array.isArray(cfg.variables))) {
175
+ errors.push(`Platform '${platformId}': variables must be object or undefined`);
176
+ }
177
+ }
178
+ return errors;
179
+ }
180
+ /**
181
+ * Validate global flows configuration
182
+ */
183
+ function validateGlobalFlowsConfig(config) {
184
+ const errors = [];
185
+ if (config.export !== undefined) {
186
+ if (!Array.isArray(config.export)) {
187
+ errors.push(`Global config: export must be array or undefined`);
188
+ }
189
+ else {
190
+ errors.push(...validateFlows(config.export, 'global.export'));
191
+ }
192
+ }
193
+ if (config.import !== undefined) {
194
+ if (!Array.isArray(config.import)) {
195
+ errors.push(`Global config: import must be array or undefined`);
196
+ }
197
+ else {
198
+ errors.push(...validateFlows(config.import, 'global.import'));
199
+ }
200
+ }
201
+ if (config.description !== undefined && typeof config.description !== 'string') {
202
+ errors.push(`Global config: description must be string or undefined`);
203
+ }
204
+ return errors;
205
+ }
206
+ /**
207
+ * Validate an array of flows
208
+ */
209
+ function validateFlows(flows, context) {
210
+ const errors = [];
211
+ for (let i = 0; i < flows.length; i++) {
212
+ const flow = flows[i];
213
+ // Required fields
214
+ if (!flow.from) {
215
+ errors.push(`${context}, flows[${i}]: Missing 'from' field`);
216
+ }
217
+ else if (typeof flow.from !== 'string' && !Array.isArray(flow.from)) {
218
+ errors.push(`${context}, flows[${i}]: 'from' must be string or array of strings`);
219
+ }
220
+ else if (typeof flow.from === 'string' && flow.from.trim() === '') {
221
+ errors.push(`${context}, flows[${i}]: 'from' cannot be empty`);
222
+ }
223
+ else if (Array.isArray(flow.from) && (flow.from.length === 0 || flow.from.some(p => typeof p !== 'string' || p.trim() === ''))) {
224
+ errors.push(`${context}, flows[${i}]: 'from' array must contain non-empty strings`);
225
+ }
226
+ if (!flow.to) {
227
+ errors.push(`${context}, flows[${i}]: Missing 'to' field`);
228
+ }
229
+ else if (typeof flow.to !== 'string' && typeof flow.to !== 'object') {
230
+ errors.push(`${context}, flows[${i}]: 'to' must be string or object`);
231
+ }
232
+ // Validate merge strategy
233
+ if (flow.merge !== undefined) {
234
+ const validMerges = ['replace', 'shallow', 'deep', 'composite'];
235
+ if (!validMerges.includes(flow.merge)) {
236
+ errors.push(`${context}, flows[${i}]: Invalid merge strategy '${flow.merge}'. Must be one of: ${validMerges.join(', ')}`);
237
+ }
238
+ }
239
+ // Note: pipe transforms are now handled within map pipeline via $pipe operation
240
+ // Validate map pipeline (must be array)
241
+ if (flow.map !== undefined && !Array.isArray(flow.map)) {
242
+ errors.push(`${context}, flows[${i}]: 'map' must be array of operations`);
243
+ }
244
+ // Validate pick/omit
245
+ if (flow.pick !== undefined && !Array.isArray(flow.pick)) {
246
+ errors.push(`${context}, flows[${i}]: 'pick' must be array or undefined`);
247
+ }
248
+ if (flow.omit !== undefined && !Array.isArray(flow.omit)) {
249
+ errors.push(`${context}, flows[${i}]: 'omit' must be array or undefined`);
250
+ }
251
+ // Validate embed
252
+ if (flow.embed !== undefined && (typeof flow.embed !== 'string' || flow.embed.trim() === '')) {
253
+ errors.push(`${context}, flows[${i}]: 'embed' must be non-empty string or undefined`);
254
+ }
255
+ }
256
+ return errors;
257
+ }
258
+ const GLOBAL_DIR = join(os.homedir(), ".openpackage");
259
+ const stateCache = new Map();
260
+ /**
261
+ * Clear the platforms cache. Useful for testing.
262
+ * @param cwd Optional path to clear cache for. If not provided, clears all cache.
263
+ */
264
+ export function clearPlatformsCache(cwd) {
265
+ if (cwd !== undefined) {
266
+ stateCache.delete(cwd);
267
+ }
268
+ else {
269
+ stateCache.clear();
270
+ }
271
+ }
272
+ function getPlatformsState(cwd) {
273
+ const key = cwd ?? null;
274
+ if (stateCache.has(key)) {
275
+ return stateCache.get(key);
276
+ }
277
+ let config;
278
+ if (key === null) {
279
+ // Global
280
+ const globalFile = readJsoncOrJson(join(GLOBAL_DIR, "platforms.jsonc")) ??
281
+ readJsoncOrJson(join(GLOBAL_DIR, "platforms.json"));
282
+ config = globalFile
283
+ ? mergePlatformsConfig(BUILT_IN_CONFIG, globalFile)
284
+ : BUILT_IN_CONFIG;
285
+ const errors = validatePlatformsConfig(config);
286
+ if (errors.length > 0) {
287
+ throw new Error(`Global platforms config validation failed:\n - ${errors.join('\n - ')}`);
288
+ }
289
+ }
290
+ else {
291
+ // Local
292
+ const globalState = getPlatformsState(null);
293
+ const globalConfig = globalState.config;
294
+ const localDir = join(key, DIR_PATTERNS.OPENPACKAGE);
295
+ const localFile = readJsoncOrJson(join(localDir, "platforms.jsonc")) ??
296
+ readJsoncOrJson(join(localDir, "platforms.json"));
297
+ config = localFile
298
+ ? mergePlatformsConfig(globalConfig, localFile)
299
+ : globalConfig;
300
+ const errors = validatePlatformsConfig(config);
301
+ if (errors.length > 0) {
302
+ throw new Error(`Local platforms config validation failed in ${key}:\n - ${errors.join('\n - ')}`);
303
+ }
304
+ }
305
+ // Extract global flows if present
306
+ const globalConfig = config['global'];
307
+ const globalExportFlows = (globalConfig && isGlobalFlowsConfig(globalConfig))
308
+ ? globalConfig.export
309
+ : undefined;
310
+ const globalImportFlows = (globalConfig && isGlobalFlowsConfig(globalConfig))
311
+ ? globalConfig.import
312
+ : undefined;
313
+ // Create definitions and compute state
314
+ const defs = createPlatformDefinitions(config);
315
+ const dirLookup = {};
316
+ const aliasLookup = {};
317
+ const universalPatterns = new Set();
318
+ const rootFiles = [];
319
+ const allPlatforms = [];
320
+ // Collect all universal patterns from platform export flows
321
+ for (const def of Object.values(defs)) {
322
+ allPlatforms.push(def.id);
323
+ dirLookup[def.rootDir] = def.id;
75
324
  for (const alias of def.aliases ?? []) {
76
- map[alias.toLowerCase()] = def.id;
325
+ aliasLookup[alias.toLowerCase()] = def.id;
326
+ }
327
+ // Collect all 'from' patterns from export flows
328
+ if (def.export && def.export.length > 0) {
329
+ for (const flow of def.export) {
330
+ // For array patterns, add all patterns
331
+ if (Array.isArray(flow.from)) {
332
+ flow.from.forEach((p) => universalPatterns.add(p));
333
+ }
334
+ else {
335
+ universalPatterns.add(flow.from);
336
+ }
337
+ }
77
338
  }
339
+ if (def.rootFile) {
340
+ rootFiles.push(def.rootFile);
341
+ }
342
+ }
343
+ // Add patterns from global export flows
344
+ if (globalExportFlows && globalExportFlows.length > 0) {
345
+ for (const flow of globalExportFlows) {
346
+ // For array patterns, add all patterns
347
+ if (Array.isArray(flow.from)) {
348
+ flow.from.forEach((p) => universalPatterns.add(p));
349
+ }
350
+ else {
351
+ universalPatterns.add(flow.from);
352
+ }
353
+ }
354
+ }
355
+ // Derive subdirectories from patterns (backward compatibility)
356
+ const universalSubdirs = extractSubdirectoriesFromPatterns(universalPatterns);
357
+ const enabledPlatforms = allPlatforms.filter((p) => defs[p].enabled);
358
+ const state = {
359
+ config,
360
+ globalExportFlows,
361
+ globalImportFlows,
362
+ defs,
363
+ dirLookup,
364
+ aliasLookup,
365
+ universalPatterns,
366
+ universalSubdirs,
367
+ rootFiles,
368
+ allPlatforms,
369
+ enabledPlatforms
370
+ };
371
+ stateCache.set(key, state);
372
+ return state;
373
+ }
374
+ export function getPlatformDefinitions(cwd) {
375
+ return getPlatformsState(cwd).defs;
376
+ }
377
+ /**
378
+ * Get global export flows that apply to all platforms (install/apply)
379
+ */
380
+ export function getGlobalExportFlows(cwd) {
381
+ return getPlatformsState(cwd).globalExportFlows;
382
+ }
383
+ /**
384
+ * Get global import flows that apply to all platforms (save)
385
+ */
386
+ export function getGlobalImportFlows(cwd) {
387
+ return getPlatformsState(cwd).globalImportFlows;
388
+ }
389
+ /**
390
+ * @deprecated Use getGlobalExportFlows() for export flows or getGlobalImportFlows() for import flows
391
+ */
392
+ export function getGlobalFlows(cwd) {
393
+ // Return export flows for backward compatibility
394
+ return getPlatformsState(cwd).globalExportFlows;
395
+ }
396
+ /**
397
+ * Check if a platform uses flow-based configuration (export or import)
398
+ */
399
+ export function platformUsesFlows(platform, cwd) {
400
+ try {
401
+ const def = getPlatformDefinition(platform, cwd);
402
+ const hasExport = def.export !== undefined && def.export.length > 0;
403
+ const hasImport = def.import !== undefined && def.import.length > 0;
404
+ return hasExport || hasImport;
78
405
  }
79
- return map;
80
- })();
81
- const PLATFORM_ROOT_FILES = Object.freeze(Object.values(PLATFORM_DEFINITIONS)
82
- .map(def => def.rootFile)
83
- .filter((file) => typeof file === 'string'));
406
+ catch (error) {
407
+ // Platform not found - return false
408
+ return false;
409
+ }
410
+ }
411
+ /**
412
+ * Check if a platform uses legacy subdirs configuration
413
+ * @deprecated Always returns false - subdirs support removed
414
+ */
415
+ export function platformUsesSubdirs(platform, cwd) {
416
+ return false;
417
+ }
418
+ /**
419
+ * Get all universal path patterns from flow definitions.
420
+ * These patterns define which files are considered universal package content.
421
+ * This is the source of truth for determining what belongs in a package.
422
+ *
423
+ * @param cwd - Optional cwd for local config overrides
424
+ * @returns Set of glob patterns from all platform flows
425
+ *
426
+ * @example
427
+ * // Returns: Set(["rules/**\/*.md", "mcp.jsonc", "commands/*.md", ...])
428
+ * const patterns = getAllUniversalPatterns()
429
+ */
430
+ export function getAllUniversalPatterns(cwd) {
431
+ return new Set(getPlatformsState(cwd).universalPatterns);
432
+ }
433
+ /**
434
+ * Check if a file path matches any universal pattern from flows.
435
+ * This is the primary method for determining if a file is universal content.
436
+ *
437
+ * @param filePath - File path to check (normalized, no leading slash)
438
+ * @param cwd - Optional cwd for local config overrides
439
+ * @returns true if path matches any universal pattern
440
+ *
441
+ * @example
442
+ * matchesUniversalPattern("mcp.jsonc") // true (if defined in flows)
443
+ * matchesUniversalPattern("rules/typescript.md") // true
444
+ * matchesUniversalPattern("random-file.txt") // false
445
+ */
446
+ export function matchesUniversalPattern(filePath, cwd) {
447
+ const patterns = getAllUniversalPatterns(cwd);
448
+ return matchesAnyPattern(filePath, patterns);
449
+ }
450
+ /**
451
+ * Get all unique universal subdirectory names defined across all platforms.
452
+ * This is derived from universal patterns for backward compatibility.
453
+ *
454
+ * @param cwd - Optional cwd for local config overrides
455
+ * @returns Set of all universal subdir names
456
+ * @deprecated Prefer using matchesUniversalPattern for file validation
457
+ */
458
+ export function getAllUniversalSubdirs(cwd) {
459
+ return new Set(getPlatformsState(cwd).universalSubdirs);
460
+ }
461
+ /**
462
+ * Check if a string is a recognized universal subdir.
463
+ *
464
+ * @param subdirName - Name to check
465
+ * @param cwd - Optional cwd for local config overrides
466
+ * @returns true if the subdir is defined in any platform
467
+ * @deprecated Prefer using matchesUniversalPattern for file validation
468
+ */
469
+ export function isKnownUniversalSubdir(subdirName, cwd) {
470
+ return getPlatformsState(cwd).universalSubdirs.has(subdirName);
471
+ }
472
+ /**
473
+ * Get lookup map from platform directory name to platform ID.
474
+ */
475
+ export function getPlatformDirLookup(cwd) {
476
+ return getPlatformsState(cwd).dirLookup;
477
+ }
478
+ /**
479
+ * Get lookup map from platform alias to platform ID.
480
+ */
481
+ export function getPlatformAliasLookup(cwd) {
482
+ return getPlatformsState(cwd).aliasLookup;
483
+ }
484
+ /**
485
+ * Get all known platform root files.
486
+ */
487
+ export function getPlatformRootFiles(cwd) {
488
+ return getPlatformsState(cwd).rootFiles;
489
+ }
84
490
  /**
85
491
  * Get platform definition by name
492
+ * @throws Error if platform not found
86
493
  */
87
- export function getPlatformDefinition(name) {
88
- return PLATFORM_DEFINITIONS[name];
494
+ export function getPlatformDefinition(name, cwd) {
495
+ const state = getPlatformsState(cwd);
496
+ const def = state.defs[name];
497
+ if (!def) {
498
+ throw new Error(`Unknown platform: ${name}`);
499
+ }
500
+ return def;
89
501
  }
90
502
  /**
91
503
  * Get all platforms
92
504
  */
93
- export function getAllPlatforms(options) {
505
+ export function getAllPlatforms(options, cwd) {
506
+ const state = getPlatformsState(cwd);
94
507
  if (options?.includeDisabled) {
95
- return [...PLATFORM_IDS];
508
+ return state.allPlatforms;
96
509
  }
97
- return PLATFORM_IDS.filter(platform => PLATFORM_DEFINITIONS[platform].enabled);
510
+ return state.enabledPlatforms;
98
511
  }
99
- export function resolvePlatformName(input) {
512
+ export function resolvePlatformName(input, cwd) {
100
513
  if (!input) {
101
514
  return undefined;
102
515
  }
516
+ const state = getPlatformsState(cwd);
103
517
  const normalized = input.toLowerCase();
104
- if (normalized in PLATFORM_DEFINITIONS) {
518
+ if (normalized in state.defs) {
105
519
  return normalized;
106
520
  }
107
- return PLATFORM_ALIAS_LOOKUP[normalized];
521
+ return state.aliasLookup[normalized];
108
522
  }
109
- export function getAllRootFiles() {
110
- return [...PLATFORM_ROOT_FILES];
523
+ /**
524
+ * Resolve a frontmatter/platform key (id or alias, case-insensitive) to a canonical platform id.
525
+ * Returns null when the key is not a known platform or alias.
526
+ */
527
+ export function resolvePlatformKey(key, cwd) {
528
+ if (!key)
529
+ return null;
530
+ const normalized = key.toLowerCase();
531
+ const state = getPlatformsState(cwd);
532
+ if (normalized in state.defs) {
533
+ return normalized;
534
+ }
535
+ return state.aliasLookup[normalized] ?? null;
536
+ }
537
+ /**
538
+ * Internal helper to build directory paths for a single platform definition.
539
+ */
540
+ function buildDirectoryPaths(definition, cwd) {
541
+ const subdirsPaths = {};
542
+ // Build from export flows (the flows that define workspace structure)
543
+ if (definition.export && definition.export.length > 0) {
544
+ for (const flow of definition.export) {
545
+ // Extract universal subdir from 'from' pattern
546
+ // For array patterns, use the first pattern
547
+ const fromPattern = Array.isArray(flow.from) ? flow.from[0] : flow.from;
548
+ const firstComponent = fromPattern.split('/')[0];
549
+ // Skip if it's a file (contains extension) or already exists
550
+ if (firstComponent && !firstComponent.includes('.') && !subdirsPaths[firstComponent]) {
551
+ // Extract platform subdir from 'to' pattern
552
+ const toPattern = typeof flow.to === 'string' ? flow.to : Object.values(flow.to)[0];
553
+ if (typeof toPattern === 'string') {
554
+ // Get the directory part of the target path
555
+ const targetPath = toPattern.split('/').slice(0, -1).join('/');
556
+ if (targetPath) {
557
+ subdirsPaths[firstComponent] = join(cwd, targetPath);
558
+ }
559
+ }
560
+ }
561
+ }
562
+ }
563
+ return {
564
+ rootDir: join(cwd, definition.rootDir),
565
+ rootFile: definition.rootFile ? join(cwd, definition.rootFile) : undefined,
566
+ subdirs: subdirsPaths
567
+ };
111
568
  }
112
569
  /**
113
570
  * Get platform directory paths for a given working directory
114
571
  */
115
572
  export function getPlatformDirectoryPaths(cwd) {
573
+ const state = getPlatformsState(cwd);
116
574
  const paths = {};
117
- for (const platform of getAllPlatforms()) {
118
- const definition = getPlatformDefinition(platform);
119
- const rulesSubdir = definition.subdirs[UNIVERSAL_SUBDIRS.RULES];
120
- paths[platform] = {
121
- rulesDir: join(cwd, definition.rootDir, rulesSubdir?.path || '')
122
- };
123
- if (definition.rootFile) {
124
- paths[platform].rootFile = join(cwd, definition.rootFile);
125
- }
126
- const commandsSubdir = definition.subdirs[UNIVERSAL_SUBDIRS.COMMANDS];
127
- if (commandsSubdir) {
128
- paths[platform].commandsDir = join(cwd, definition.rootDir, commandsSubdir.path);
129
- }
130
- const agentsSubdir = definition.subdirs[UNIVERSAL_SUBDIRS.AGENTS];
131
- if (agentsSubdir) {
132
- paths[platform].agentsDir = join(cwd, definition.rootDir, agentsSubdir.path);
133
- }
134
- const skillsSubdir = definition.subdirs[UNIVERSAL_SUBDIRS.SKILLS];
135
- if (skillsSubdir) {
136
- paths[platform].skillsDir = join(cwd, definition.rootDir, skillsSubdir.path);
137
- }
575
+ for (const platform of state.enabledPlatforms) {
576
+ paths[platform] = buildDirectoryPaths(state.defs[platform], cwd);
138
577
  }
139
578
  return paths;
140
579
  }
141
580
  /**
142
- * Detect platforms by their root files
143
- * Note: AGENTS.md is ambiguous (maps to multiple platforms), so we return empty for it
581
+ * Get directory paths for a specific platform.
582
+ * @throws Error if platform unknown
144
583
  */
145
- export async function detectPlatformByRootFile(cwd) {
146
- const detectedPlatforms = [];
147
- // Build dynamic root file mapping from platform definitions
148
- const rootFileToPlatform = new Map();
149
- for (const platform of getAllPlatforms()) {
150
- const def = getPlatformDefinition(platform);
151
- if (def.rootFile && def.rootFile !== FILE_PATTERNS.AGENTS_MD) {
152
- rootFileToPlatform.set(def.rootFile, platform);
153
- }
154
- }
155
- // Check for existence of each root file at cwd
156
- for (const [rootFile, platform] of rootFileToPlatform.entries()) {
157
- const filePath = join(cwd, rootFile);
158
- if (await exists(filePath)) {
159
- detectedPlatforms.push(platform);
160
- }
584
+ export function getPlatformDirectoryPathsForPlatform(platform, cwd) {
585
+ const state = getPlatformsState(cwd);
586
+ const definition = state.defs[platform];
587
+ if (!definition) {
588
+ throw new Error(`Unknown platform: ${platform}`);
161
589
  }
162
- return detectedPlatforms;
590
+ return buildDirectoryPaths(definition, cwd);
163
591
  }
164
592
  /**
165
593
  * Detect all platforms present in a directory
166
- * Checks both platform directories and root files
594
+ * Checks both platform directories (.platform/) and unique root files (e.g., CLAUDE.md)
595
+ * AGENTS.md is skipped as it's universal/ambiguous.
167
596
  */
168
597
  export async function detectAllPlatforms(cwd) {
169
- // Check all platforms by directory in parallel
170
- const detectionPromises = getAllPlatforms().map(async (platform) => {
171
- const definition = getPlatformDefinition(platform);
598
+ const state = getPlatformsState(cwd);
599
+ const detectionPromises = state.enabledPlatforms.map(async (platform) => {
600
+ const definition = state.defs[platform];
172
601
  const rootDirPath = join(cwd, definition.rootDir);
173
- // Check if the rootDir exists strictly in the cwd
174
- const detected = await exists(rootDirPath);
602
+ // Detected if root dir exists OR unique root file exists (skip AGENTS.md)
603
+ const dirExists = await exists(rootDirPath);
604
+ let fileExists = false;
605
+ if (definition.rootFile &&
606
+ definition.rootFile !== FILE_PATTERNS.AGENTS_MD) {
607
+ const rootFilePath = join(cwd, definition.rootFile);
608
+ fileExists = await exists(rootFilePath);
609
+ }
610
+ const detected = dirExists || fileExists;
175
611
  return {
176
612
  name: platform,
177
- detected
613
+ detected,
178
614
  };
179
615
  });
180
- const detectionResults = await Promise.all(detectionPromises);
181
- // Also detect by root files
182
- const rootFileDetectedPlatforms = await detectPlatformByRootFile(cwd);
183
- // Merge results - mark platforms as detected if they have either directory or root file
184
- for (const platform of rootFileDetectedPlatforms) {
185
- const result = detectionResults.find(r => r.name === platform);
186
- if (result && !result.detected) {
187
- result.detected = true;
188
- }
189
- }
190
- return detectionResults;
616
+ return await Promise.all(detectionPromises);
191
617
  }
192
618
  /**
193
619
  * Get detected platforms only
194
620
  */
195
621
  export async function getDetectedPlatforms(cwd) {
196
622
  const results = await detectAllPlatforms(cwd);
197
- return results.filter(result => result.detected).map(result => result.name);
623
+ return results
624
+ .filter((result) => result.detected)
625
+ .map((result) => result.name);
198
626
  }
199
627
  /**
200
628
  * Create platform directories
201
629
  */
630
+ /**
631
+ * Create platform directories
632
+ * @deprecated Subdirs support removed - flows create directories as needed
633
+ * Only creates root directory for platforms now
634
+ */
202
635
  export async function createPlatformDirectories(cwd, platforms) {
636
+ const state = getPlatformsState(cwd);
203
637
  const created = [];
204
- const paths = getPlatformDirectoryPaths(cwd);
205
638
  for (const platform of platforms) {
206
- const platformPaths = paths[platform];
639
+ const definition = state.defs[platform];
640
+ if (!definition) {
641
+ throw new Error(`Unknown platform: ${platform}`);
642
+ }
643
+ // Only create root directory
644
+ const rootPath = join(cwd, definition.rootDir);
207
645
  try {
208
- const dirExists = await exists(platformPaths.rulesDir);
646
+ const dirExists = await exists(rootPath);
209
647
  if (!dirExists) {
210
- await ensureDir(platformPaths.rulesDir);
211
- created.push(relative(cwd, platformPaths.rulesDir));
212
- logger.debug(`Created platform directory: ${platformPaths.rulesDir}`);
648
+ await ensureDir(rootPath);
649
+ created.push(relative(cwd, rootPath));
650
+ logger.debug(`Created platform root directory: ${rootPath}`);
213
651
  }
214
652
  }
215
653
  catch (error) {
216
- logger.error(`Failed to create platform directory ${platformPaths.rulesDir}: ${error}`);
654
+ logger.error(`Failed to create platform root directory (${rootPath}): ${error}`);
217
655
  }
656
+ // Flow-based installations will create subdirectories as needed
218
657
  }
219
658
  return created;
220
659
  }
@@ -222,109 +661,125 @@ export async function createPlatformDirectories(cwd, platforms) {
222
661
  * Validate platform directory structure
223
662
  */
224
663
  export async function validatePlatformStructure(cwd, platform) {
225
- const issues = [];
226
- const definition = getPlatformDefinition(platform);
227
- const paths = getPlatformDirectoryPaths(cwd);
228
- const platformPaths = paths[platform];
229
- // Check if rules directory exists
230
- if (!(await exists(platformPaths.rulesDir))) {
231
- issues.push(`Rules directory does not exist: ${platformPaths.rulesDir}`);
664
+ const state = getPlatformsState(cwd);
665
+ const definition = state.defs[platform];
666
+ if (!definition) {
667
+ throw new Error(`Unknown platform: ${platform}`);
232
668
  }
233
- // Check root file for platforms that require it
234
- if (definition.rootFile && platformPaths.rootFile) {
235
- if (!(await exists(platformPaths.rootFile))) {
236
- issues.push(`Root file does not exist: ${platformPaths.rootFile}`);
669
+ const issues = [];
670
+ // Check root file
671
+ if (definition.rootFile) {
672
+ const rootFilePath = join(cwd, definition.rootFile);
673
+ if (!(await exists(rootFilePath))) {
674
+ issues.push(`Root file does not exist: ${rootFilePath}`);
237
675
  }
238
676
  }
677
+ // TODO: Optionally check flow-based directories exist
678
+ // (may not be necessary since flows handle missing directories gracefully)
239
679
  return {
240
680
  valid: issues.length === 0,
241
- issues
681
+ issues,
242
682
  };
243
683
  }
244
684
  /**
245
- * Get rules directory file patterns for a specific platform
685
+ * Get file extensions allowed in a specific universal subdir for a platform
686
+ * @param universalSubdir - The universal subdirectory name (e.g., 'rules', 'commands', or custom)
687
+ * @returns Allowed extensions or empty array if subdir not supported
246
688
  */
247
- export function getPlatformRulesDirFilePatterns(platform) {
248
- const definition = getPlatformDefinition(platform);
249
- return definition.subdirs[UNIVERSAL_SUBDIRS.RULES]?.exts || [];
689
+ export function getPlatformSubdirExts(platform, universalSubdir, cwd) {
690
+ const state = getPlatformsState(cwd);
691
+ const definition = state.defs[platform];
692
+ if (!definition) {
693
+ throw new Error(`Unknown platform: ${platform}`);
694
+ }
695
+ // Check export flows
696
+ if (definition.export && definition.export.length > 0) {
697
+ const extensions = new Set();
698
+ for (const flow of definition.export) {
699
+ // Check if this flow matches the universal subdir
700
+ // For array patterns, use the first pattern
701
+ const fromPattern = Array.isArray(flow.from) ? flow.from[0] : flow.from;
702
+ if (fromPattern.startsWith(`${universalSubdir}/`)) {
703
+ // Extract extension from the 'from' pattern
704
+ const extMatch = fromPattern.match(/\.[^./]+$/);
705
+ if (extMatch) {
706
+ extensions.add(extMatch[0]);
707
+ }
708
+ // Also check 'to' pattern for extension changes
709
+ const toPattern = typeof flow.to === 'string' ? flow.to : Object.values(flow.to)[0];
710
+ if (typeof toPattern === 'string') {
711
+ const toExtMatch = toPattern.match(/\.[^./]+$/);
712
+ if (toExtMatch) {
713
+ extensions.add(toExtMatch[0]);
714
+ }
715
+ }
716
+ }
717
+ }
718
+ if (extensions.size > 0) {
719
+ return Array.from(extensions);
720
+ }
721
+ }
722
+ logger.warn(`Platform ${platform} does not support universal subdir '${universalSubdir}'`);
723
+ return [];
250
724
  }
251
725
  /**
252
726
  * Get all universal subdirs that exist for a platform
253
727
  */
254
728
  export function getPlatformUniversalSubdirs(cwd, platform) {
255
- const paths = getPlatformDirectoryPaths(cwd);
256
- const platformPaths = paths[platform];
729
+ const paths = getPlatformDirectoryPathsForPlatform(platform, cwd);
257
730
  const subdirs = [];
258
- if (platformPaths.rulesDir)
259
- subdirs.push({ dir: platformPaths.rulesDir, label: UNIVERSAL_SUBDIRS.RULES, leaf: getPathLeaf(platformPaths.rulesDir) });
260
- if (platformPaths.commandsDir)
261
- subdirs.push({ dir: platformPaths.commandsDir, label: UNIVERSAL_SUBDIRS.COMMANDS, leaf: getPathLeaf(platformPaths.commandsDir) });
262
- if (platformPaths.agentsDir)
263
- subdirs.push({ dir: platformPaths.agentsDir, label: UNIVERSAL_SUBDIRS.AGENTS, leaf: getPathLeaf(platformPaths.agentsDir) });
264
- if (platformPaths.skillsDir)
265
- subdirs.push({ dir: platformPaths.skillsDir, label: UNIVERSAL_SUBDIRS.SKILLS, leaf: getPathLeaf(platformPaths.skillsDir) });
731
+ for (const [label, dir] of Object.entries(paths.subdirs)) {
732
+ subdirs.push({
733
+ dir,
734
+ label,
735
+ leaf: getPathLeaf(dir),
736
+ });
737
+ }
266
738
  return subdirs;
267
739
  }
268
740
  /**
269
741
  * Check if a normalized path represents a universal subdir
270
742
  */
271
- export function isUniversalSubdirPath(normalizedPath) {
272
- return Object.values(UNIVERSAL_SUBDIRS).some(subdir => {
273
- return (normalizedPath.startsWith(`${subdir}/`) ||
743
+ export function isUniversalSubdirPath(normalizedPath, cwd) {
744
+ const state = getPlatformsState(cwd);
745
+ for (const subdir of state.universalSubdirs) {
746
+ if (normalizedPath.startsWith(`${subdir}/`) ||
274
747
  normalizedPath === subdir ||
275
748
  normalizedPath.startsWith(`${DIR_PATTERNS.OPENPACKAGE}/${subdir}/`) ||
276
- normalizedPath === `${DIR_PATTERNS.OPENPACKAGE}/${subdir}`);
277
- });
278
- }
279
- /**
280
- * Check if a subKey is a valid universal subdir
281
- * Used for validating subdir keys before processing
282
- */
283
- export function isValidUniversalSubdir(subKey) {
284
- return Object.values(UNIVERSAL_SUBDIRS).includes(subKey);
749
+ normalizedPath === `${DIR_PATTERNS.OPENPACKAGE}/${subdir}`) {
750
+ return true;
751
+ }
752
+ }
753
+ return false;
285
754
  }
286
755
  /**
287
756
  * Check if a value is a valid platform ID.
288
757
  */
289
- export function isPlatformId(value) {
290
- return !!value && value in PLATFORM_DEFINITIONS;
758
+ export function isPlatformId(value, cwd) {
759
+ if (!value)
760
+ return false;
761
+ return value in getPlatformsState(cwd).defs;
291
762
  }
292
763
  /**
293
- * Determine whether an extension is allowed for a given subdir definition.
764
+ * @deprecated Subdirs support removed - use flows instead
765
+ * Always returns false for backward compatibility
294
766
  */
295
767
  export function isExtAllowed(subdirDef, ext) {
296
- if (!subdirDef) {
297
- return false;
298
- }
299
- if (subdirDef.exts === undefined) {
300
- return true;
301
- }
302
- if (subdirDef.exts.length === 0) {
303
- return false;
304
- }
305
- return subdirDef.exts.includes(ext);
768
+ return false;
306
769
  }
307
770
  /**
308
- * Convert a package (registry) extension to the workspace extension.
309
- * Falls back to the original extension if no transformation applies.
771
+ * @deprecated Subdirs support removed - use flows instead
772
+ * Always returns original extension for backward compatibility
310
773
  */
311
774
  export function getWorkspaceExt(subdirDef, packageExt) {
312
- if (!subdirDef.transformations || packageExt === '') {
313
- return packageExt;
314
- }
315
- const transformation = subdirDef.transformations.find(({ packageExt: candidate }) => candidate === packageExt);
316
- return transformation?.workspaceExt ?? packageExt;
775
+ return packageExt;
317
776
  }
318
777
  /**
319
- * Convert a workspace extension to the package (registry) extension.
320
- * Falls back to the original extension if no transformation applies.
778
+ * @deprecated Subdirs support removed - use flows instead
779
+ * Always returns original extension for backward compatibility
321
780
  */
322
781
  export function getPackageExt(subdirDef, workspaceExt) {
323
- if (!subdirDef.transformations || workspaceExt === '') {
324
- return workspaceExt;
325
- }
326
- const transformation = subdirDef.transformations.find(({ workspaceExt: candidate }) => candidate === workspaceExt);
327
- return transformation?.packageExt ?? workspaceExt;
782
+ return workspaceExt;
328
783
  }
329
784
  /**
330
785
  * Infer platform from workspace file information.
@@ -337,22 +792,23 @@ export function getPackageExt(subdirDef, workspaceExt) {
337
792
  * @param fullPath - Full absolute path to the file
338
793
  * @param sourceDir - Source directory name (e.g., '.cursor', 'ai')
339
794
  * @param registryPath - Registry path (e.g., 'rules/file.md')
795
+ * @param cwd - Optional cwd for local platform config overrides
340
796
  * @returns Platform ID, 'ai', or undefined if cannot be determined
341
797
  */
342
- export function inferPlatformFromWorkspaceFile(fullPath, sourceDir, registryPath) {
798
+ export function inferPlatformFromWorkspaceFile(fullPath, sourceDir, registryPath, cwd) {
343
799
  // First try to get platform from full path using existing mapper
344
- const mapping = mapPlatformFileToUniversal(fullPath);
800
+ const mapping = mapPlatformFileToUniversal(fullPath, cwd);
345
801
  if (mapping?.platform) {
346
802
  return mapping.platform;
347
803
  }
348
804
  // Look up platform from source directory
349
- const fromSource = PLATFORM_DIR_LOOKUP[sourceDir];
805
+ const fromSource = getPlatformDirLookup(cwd)[sourceDir];
350
806
  if (fromSource) {
351
807
  return fromSource;
352
808
  }
353
809
  // Fallback: check registry path for platform suffix
354
810
  const parsed = parseUniversalPath(registryPath, { allowPlatformSuffix: true });
355
- if (parsed?.platformSuffix && isPlatformId(parsed.platformSuffix)) {
811
+ if (parsed?.platformSuffix && isPlatformId(parsed.platformSuffix, cwd)) {
356
812
  return parsed.platformSuffix;
357
813
  }
358
814
  return undefined;