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
@@ -1,25 +1,79 @@
1
1
  import { dirname, join, relative, parse as parsePath, sep } from 'path';
2
2
  import { promises as fs } from 'fs';
3
- import { exists, ensureDir, listDirectories, listFiles, remove, removeEmptyDirectories, walkFiles } from './fs.js';
3
+ import { exists, ensureDir, listDirectories, listFiles, remove, removeEmptyDirectories, walkFiles, readTextFile } from './fs.js';
4
4
  import { writeIfChanged } from '../core/install/file-updater.js';
5
- import { getLocalPackagesDir } from './paths.js';
6
5
  import { packageManager } from '../core/package.js';
6
+ import { getRegistryDirectories } from '../core/directory.js';
7
7
  import { logger } from './logger.js';
8
- import { FILE_PATTERNS, UNIVERSAL_SUBDIRS } from '../constants/index.js';
8
+ import { FILE_PATTERNS, } from '../constants/index.js';
9
+ import { getPlatformRootFileNames, stripRootCopyPrefix } from './platform-root-files.js';
10
+ import { getAllUniversalSubdirs, platformUsesFlows } from '../core/platforms.js';
9
11
  import { normalizePathForProcessing } from './path-normalization.js';
12
+ import { formatPathForWorkspaceIndex } from './path-resolution.js';
10
13
  import { isAllowedRegistryPath, isRootRegistryPath, isSkippableRegistryPath, normalizeRegistryPath, extractUniversalSubdirInfo } from './registry-entry-filter.js';
11
14
  import { mapUniversalToPlatform } from './platform-mapper.js';
12
15
  import { safePrompts } from './prompts.js';
13
- import { mergePlatformYamlOverride, loadRegistryYamlOverrides } from './platform-yaml-merge.js';
16
+ import { mergeInlinePlatformOverride } from './platform-yaml-merge.js';
14
17
  import { parseUniversalPath } from './platform-file.js';
15
18
  import { getPlatformDefinition } from '../core/platforms.js';
16
- import { getPackageIndexPath, readPackageIndex, writePackageIndex, sortMapping, ensureTrailingSlash, isDirKey, pruneNestedDirectories } from './package-index-yml.js';
17
- import { createWorkspaceHash } from './version-generator.js';
19
+ import { sortMapping, ensureTrailingSlash, isDirKey, pruneNestedDirectories } from './package-index-yml.js';
20
+ import { getWorkspaceIndexPath, readWorkspaceIndex, writeWorkspaceIndex } from './workspace-index-yml.js';
21
+ import { resolvePackageContentRoot } from '../core/install/local-source-resolution.js';
22
+ import { calculateFileHash } from './hash-utils.js';
23
+ import { getTargetPath } from './workspace-index-helpers.js';
24
+ async function readPackageIndex(cwd, packageName, _location) {
25
+ const record = await readWorkspaceIndex(cwd);
26
+ const entry = record.index.packages?.[packageName];
27
+ if (!entry)
28
+ return null;
29
+ return {
30
+ path: entry.path ?? '',
31
+ packageName,
32
+ workspace: {
33
+ version: entry.version ?? '',
34
+ hash: undefined
35
+ },
36
+ files: entry.files ?? {}
37
+ };
38
+ }
39
+ async function writePackageIndex(record, cwd) {
40
+ const resolvedCwd = cwd ??
41
+ (record.path
42
+ ? dirname(dirname(record.path))
43
+ : undefined);
44
+ if (!resolvedCwd) {
45
+ logger.warn(`Unable to write workspace index for ${record.packageName}: missing cwd`);
46
+ return;
47
+ }
48
+ const wsRecord = await readWorkspaceIndex(resolvedCwd);
49
+ // Be defensive: older/invalid index files could sanitize to missing packages map.
50
+ wsRecord.index.packages = wsRecord.index.packages ?? {};
51
+ const entry = wsRecord.index.packages[record.packageName];
52
+ const rawPath = entry?.path ??
53
+ record.path ??
54
+ (record.workspace?.version
55
+ ? join(getRegistryDirectories().packages, record.packageName, record.workspace.version, sep)
56
+ : '');
57
+ if (!rawPath) {
58
+ logger.warn(`Skipping workspace index write for ${record.packageName}: source path is unknown`);
59
+ return;
60
+ }
61
+ // Prefer workspace-relative paths when the source lives under the workspace root.
62
+ // Otherwise, convert absolute paths under ~/.openpackage/ to tilde notation.
63
+ const pathToUse = formatPathForWorkspaceIndex(rawPath, resolvedCwd);
64
+ wsRecord.index.packages[record.packageName] = {
65
+ ...entry,
66
+ path: pathToUse,
67
+ version: entry?.version ?? record.workspace?.version,
68
+ files: sortMapping(record.files ?? {})
69
+ };
70
+ await writeWorkspaceIndex(wsRecord);
71
+ }
18
72
  // ============================================================================
19
73
  // Conflict Planning Functions
20
74
  // ============================================================================
21
75
  export async function planConflictsForPackage(cwd, packageName, version, platforms) {
22
- const registryEntries = await loadRegistryFileEntries(packageName, version);
76
+ const registryEntries = await loadRegistryFileEntries(packageName, version, { cwd });
23
77
  const plannedFiles = createPlannedFiles(registryEntries);
24
78
  attachTargetsToPlannedFiles(cwd, plannedFiles, platforms);
25
79
  const otherIndexes = await loadOtherPackageIndexes(cwd, packageName);
@@ -106,7 +160,62 @@ async function promptConflictResolution(message) {
106
160
  const choice = response.choice;
107
161
  return choice ?? 'skip';
108
162
  }
109
- async function updateOwnerIndexAfterRename(owner, oldRelPath, newRelPath, indexByPackage) {
163
+ /**
164
+ * Prompt user for action when file content differs
165
+ */
166
+ async function promptContentDifferenceResolution(workspacePath, packagePath) {
167
+ // Format package path as relative to package root with leading slash
168
+ const formattedPackagePath = packagePath
169
+ ? (packagePath.startsWith('/') ? packagePath : `/${packagePath}`)
170
+ : undefined;
171
+ const message = formattedPackagePath
172
+ ? `Package file ${formattedPackagePath} differs from workspace file ${workspacePath}`
173
+ : `File ${workspacePath} differs from package version`;
174
+ const response = await safePrompts({
175
+ type: 'select',
176
+ name: 'choice',
177
+ message,
178
+ choices: [
179
+ {
180
+ title: 'Overwrite (use package version)',
181
+ value: 'overwrite'
182
+ },
183
+ {
184
+ title: 'Skip (keep workspace version)',
185
+ value: 'skip'
186
+ }
187
+ ]
188
+ });
189
+ const choice = response.choice;
190
+ return choice ?? 'skip';
191
+ }
192
+ /**
193
+ * Check if file content differs using hash comparison
194
+ */
195
+ async function hasContentDifference(absPath, newContent) {
196
+ try {
197
+ if (!(await exists(absPath))) {
198
+ return false; // File doesn't exist, so no content difference
199
+ }
200
+ const existingContent = await readTextFile(absPath, 'utf8');
201
+ // Quick check: if content is exactly the same, no need to hash
202
+ if (existingContent === newContent) {
203
+ return false;
204
+ }
205
+ // Hash comparison for definitive answer
206
+ const [existingHash, newHash] = await Promise.all([
207
+ calculateFileHash(existingContent),
208
+ calculateFileHash(newContent)
209
+ ]);
210
+ return existingHash !== newHash;
211
+ }
212
+ catch (error) {
213
+ logger.warn(`Failed to check content difference for ${absPath}: ${error}`);
214
+ // On error, assume content differs to be safe
215
+ return true;
216
+ }
217
+ }
218
+ async function updateOwnerIndexAfterRename(owner, oldRelPath, newRelPath, indexByPackage, cwd) {
110
219
  const normalizedOld = normalizePathForProcessing(oldRelPath);
111
220
  const normalizedNew = normalizePathForProcessing(newRelPath);
112
221
  const record = indexByPackage.get(owner.packageName);
@@ -116,11 +225,21 @@ async function updateOwnerIndexAfterRename(owner, oldRelPath, newRelPath, indexB
116
225
  const values = record.files[owner.key];
117
226
  if (!values)
118
227
  return;
119
- const idx = values.findIndex(value => normalizePathForProcessing(value) === normalizedOld);
228
+ const idx = values.findIndex(mapping => {
229
+ const target = getTargetPath(mapping);
230
+ return normalizePathForProcessing(target) === normalizedOld;
231
+ });
120
232
  if (idx === -1)
121
233
  return;
122
- values[idx] = normalizedNew;
123
- await writePackageIndex(record);
234
+ // Update the mapping (preserve keys if it was a complex mapping)
235
+ const oldMapping = values[idx];
236
+ if (typeof oldMapping === 'string') {
237
+ values[idx] = normalizedNew;
238
+ }
239
+ else {
240
+ values[idx] = { ...oldMapping, target: normalizedNew };
241
+ }
242
+ await writePackageIndex(record, cwd);
124
243
  }
125
244
  else {
126
245
  // Directory key still valid; nothing to change.
@@ -182,7 +301,7 @@ async function resolveConflictsForPlannedFiles(cwd, plannedFiles, context, other
182
301
  await ensureDir(dirname(absLocalPath));
183
302
  try {
184
303
  await fs.rename(absTarget, absLocalPath);
185
- await updateOwnerIndexAfterRename(owner, normalizedRel, localRelPath, indexByPackage);
304
+ await updateOwnerIndexAfterRename(owner, normalizedRel, localRelPath, indexByPackage, cwd);
186
305
  context.installedPathOwners.delete(normalizedRel);
187
306
  context.installedPathOwners.set(normalizePathForProcessing(localRelPath), owner);
188
307
  warnings.push(`Renamed existing ${normalizedRel} from ${owner.packageName} to ${localRelPath}.`);
@@ -205,23 +324,38 @@ async function resolveConflictsForPlannedFiles(cwd, plannedFiles, context, other
205
324
  continue;
206
325
  }
207
326
  if (!previousOwnedPaths.has(normalizedRel) && (await exists(absTarget))) {
327
+ // Check if content actually differs
328
+ const contentDiffers = await hasContentDifference(absTarget, planned.content);
329
+ if (!contentDiffers) {
330
+ // Content is the same, no conflict - just proceed
331
+ filteredTargets.push(target);
332
+ continue;
333
+ }
334
+ // Content differs - handle as a conflict
208
335
  let decision = perPathDecisions.get(normalizedRel);
209
336
  if (!decision) {
210
337
  if (options.force) {
211
- decision = 'keep-both';
338
+ // Force flag: auto-overwrite (not keep-both)
339
+ decision = 'overwrite';
340
+ warnings.push(`Overwriting ${normalizedRel} (content differs, --force flag active).`);
212
341
  }
213
342
  else if (defaultStrategy && defaultStrategy !== 'ask') {
214
343
  decision = defaultStrategy;
215
344
  if (decision === 'skip') {
216
- warnings.push(`Skipping ${normalizedRel} because it already exists (configured conflict strategy).`);
345
+ warnings.push(`Skipping ${normalizedRel} (content differs, configured conflict strategy).`);
346
+ }
347
+ else if (decision === 'overwrite') {
348
+ warnings.push(`Overwriting ${normalizedRel} (content differs, configured conflict strategy).`);
217
349
  }
218
350
  }
219
351
  else if (!interactive) {
220
- warnings.push(`Skipping ${normalizedRel} because it already exists and cannot prompt in non-interactive mode.`);
352
+ warnings.push(`Skipping ${normalizedRel} (content differs, cannot prompt in non-interactive mode).`);
221
353
  decision = 'skip';
222
354
  }
223
355
  else {
224
- decision = await promptConflictResolution(`File ${normalizedRel} already exists in your project. How would you like to proceed?`);
356
+ // Interactive mode: prompt for content-modified files
357
+ const contentDecision = await promptContentDifferenceResolution(normalizedRel, planned.registryPath);
358
+ decision = contentDecision;
225
359
  }
226
360
  }
227
361
  if (decision === 'skip') {
@@ -249,7 +383,7 @@ async function resolveConflictsForPlannedFiles(cwd, plannedFiles, context, other
249
383
  }
250
384
  // overwrite
251
385
  if (isDryRun) {
252
- warnings.push(`Would overwrite existing local file ${normalizedRel}.`);
386
+ warnings.push(`Would overwrite existing local file ${normalizedRel} (content modified).`);
253
387
  filteredTargets.push(target);
254
388
  continue;
255
389
  }
@@ -270,52 +404,25 @@ function normalizeRelativePath(cwd, absPath) {
270
404
  const normalized = normalizePathForProcessing(rel);
271
405
  return normalized.replace(/\\/g, '/');
272
406
  }
273
- async function collectPackageDirectories(cwd) {
274
- const packagesRoot = getLocalPackagesDir(cwd);
275
- if (!(await exists(packagesRoot))) {
276
- return [];
277
- }
278
- const results = [];
279
- async function recurse(currentDir, relativeBase) {
280
- const packageYmlPath = join(currentDir, FILE_PATTERNS.PACKAGE_YML);
281
- if (await exists(packageYmlPath)) {
282
- const packageName = relativeBase.replace(new RegExp(`\\${sep}`, 'g'), '/');
283
- results.push({ packageName, dir: currentDir });
284
- return;
285
- }
286
- const subdirs = await listDirectories(currentDir).catch(() => []);
287
- for (const subdir of subdirs) {
288
- const nextDir = join(currentDir, subdir);
289
- const nextRelative = relativeBase ? `${relativeBase}${sep}${subdir}` : subdir;
290
- await recurse(nextDir, nextRelative);
291
- }
292
- }
293
- const topLevelDirs = await listDirectories(packagesRoot).catch(() => []);
294
- for (const dir of topLevelDirs) {
295
- const absolute = join(packagesRoot, dir);
296
- await recurse(absolute, dir);
297
- }
298
- return results;
299
- }
300
407
  export async function loadOtherPackageIndexes(cwd, excludePackage) {
301
- const directories = await collectPackageDirectories(cwd);
408
+ const record = await readWorkspaceIndex(cwd);
409
+ const wsPath = getWorkspaceIndexPath(cwd);
410
+ const packages = record.index.packages ?? {};
302
411
  const results = [];
303
- for (const entry of directories) {
304
- if (entry.packageName === excludePackage)
305
- continue;
306
- const indexPath = join(entry.dir, FILE_PATTERNS.PACKAGE_INDEX_YML);
307
- if (!(await exists(indexPath)))
412
+ for (const [name, entry] of Object.entries(packages)) {
413
+ if (name === excludePackage)
308
414
  continue;
309
- const record = await readPackageIndex(cwd, entry.packageName);
310
- if (record) {
311
- record.path = indexPath;
312
- results.push(record);
313
- }
415
+ results.push({
416
+ path: entry?.path ?? wsPath,
417
+ packageName: name,
418
+ workspace: { version: entry?.version ?? '' },
419
+ files: entry?.files ?? {}
420
+ });
314
421
  }
315
422
  return results;
316
423
  }
317
- async function collectFilesUnderDirectory(cwd, dirRel) {
318
- const directoryRel = ensureTrailingSlash(normalizePathForProcessing(dirRel));
424
+ async function collectFilesUnderDirectory(cwd, dirRelPath) {
425
+ const directoryRel = ensureTrailingSlash(normalizePathForProcessing(dirRelPath));
319
426
  const absDir = join(cwd, directoryRel);
320
427
  if (!(await exists(absDir))) {
321
428
  return [];
@@ -341,15 +448,15 @@ async function buildExpandedIndexesContext(cwd, indexes) {
341
448
  const owner = {
342
449
  packageName: record.packageName,
343
450
  key,
344
- type: key.endsWith('/') ? 'dir' : 'file',
345
- indexPath: record.path
451
+ type: key.endsWith('/') ? 'dir' : 'file'
346
452
  };
347
453
  if (owner.type === 'dir') {
348
454
  if (!dirKeyOwners.has(key)) {
349
455
  dirKeyOwners.set(key, []);
350
456
  }
351
457
  dirKeyOwners.get(key).push(owner);
352
- for (const dirRel of values) {
458
+ for (const mapping of values) {
459
+ const dirRel = getTargetPath(mapping);
353
460
  const files = await collectFilesUnderDirectory(cwd, dirRel);
354
461
  for (const filePath of files) {
355
462
  if (!installedPathOwners.has(filePath)) {
@@ -359,7 +466,8 @@ async function buildExpandedIndexesContext(cwd, indexes) {
359
466
  }
360
467
  }
361
468
  else {
362
- for (const fileRel of values) {
469
+ for (const mapping of values) {
470
+ const fileRel = getTargetPath(mapping);
363
471
  const normalizedValue = normalizePathForProcessing(fileRel);
364
472
  if (!installedPathOwners.has(normalizedValue)) {
365
473
  installedPathOwners.set(normalizedValue, owner);
@@ -373,8 +481,15 @@ async function buildExpandedIndexesContext(cwd, indexes) {
373
481
  // ============================================================================
374
482
  // Registry File Loading Functions
375
483
  // ============================================================================
376
- async function loadRegistryFileEntries(packageName, version) {
377
- const pkg = await packageManager.loadPackage(packageName, version);
484
+ async function loadRegistryFileEntries(packageName, version, opts) {
485
+ const packageRootDir = opts?.contentRoot && (await exists(opts.contentRoot))
486
+ ? opts.contentRoot
487
+ : opts?.cwd
488
+ ? await resolvePackageContentRoot({ cwd: opts.cwd, packageName, version })
489
+ : undefined;
490
+ const pkg = await packageManager.loadPackage(packageName, version, {
491
+ packageRootDir
492
+ });
378
493
  const entries = [];
379
494
  for (const file of pkg.files) {
380
495
  const normalized = normalizeRegistryPath(file.path);
@@ -382,7 +497,7 @@ async function loadRegistryFileEntries(packageName, version) {
382
497
  if (isRootRegistryPath(normalized)) {
383
498
  continue;
384
499
  }
385
- if (!isAllowedRegistryPath(normalized)) {
500
+ if (!isAllowedRegistryPath(normalized, opts?.cwd)) {
386
501
  // Ignore any other top-level paths (e.g., README.md, some/...)
387
502
  continue;
388
503
  }
@@ -394,15 +509,15 @@ async function loadRegistryFileEntries(packageName, version) {
394
509
  }
395
510
  return entries;
396
511
  }
397
- function deriveGroupKey(registryPath) {
512
+ function deriveGroupKey(registryPath, cwd) {
398
513
  const normalized = normalizeRegistryPath(registryPath);
399
514
  const segments = normalized.split('/');
400
515
  if (segments.length <= 1) {
401
516
  return '';
402
517
  }
403
518
  const first = segments[0];
404
- const universalValues = Object.values(UNIVERSAL_SUBDIRS);
405
- if (universalValues.includes(first)) {
519
+ const universalSubdirs = getAllUniversalSubdirs(cwd);
520
+ if (universalSubdirs.has(first)) {
406
521
  if (segments.length >= 2) {
407
522
  return ensureTrailingSlash(`${segments[0]}/${segments[1]}`);
408
523
  }
@@ -421,10 +536,10 @@ function createPlannedFiles(entries) {
421
536
  targets: []
422
537
  }));
423
538
  }
424
- function groupPlannedFiles(plannedFiles) {
539
+ function groupPlannedFiles(plannedFiles, cwd) {
425
540
  const groups = new Map();
426
541
  for (const planned of plannedFiles) {
427
- const key = deriveGroupKey(planned.registryPath);
542
+ const key = deriveGroupKey(planned.registryPath, cwd);
428
543
  if (!groups.has(key)) {
429
544
  groups.set(key, []);
430
545
  }
@@ -435,7 +550,7 @@ function groupPlannedFiles(plannedFiles) {
435
550
  // ============================================================================
436
551
  // Planning Functions
437
552
  // ============================================================================
438
- function buildPlannedTargetMap(plannedFiles, yamlOverrides) {
553
+ function buildPlannedTargetMap(plannedFiles, cwd) {
439
554
  const map = new Map();
440
555
  const universalPlanned = [];
441
556
  const platformSuffixedPlanned = [];
@@ -452,16 +567,17 @@ function buildPlannedTargetMap(plannedFiles, yamlOverrides) {
452
567
  for (const { planned, parsed } of entries) {
453
568
  for (const target of planned.targets) {
454
569
  const normalizedRel = normalizePathForProcessing(target.relPath);
455
- // Compute per-target content (apply platform YAML overrides for universal files)
570
+ // Compute per-target content (apply inline platform overrides for universal files)
456
571
  let content = planned.content;
457
- if (parsed && target.platform && target.platform !== 'other') {
458
- content = mergePlatformYamlOverride(planned.content, target.platform, parsed.universalSubdir, parsed.relPath, yamlOverrides);
572
+ if (parsed && !parsed.platformSuffix && target.platform && target.platform !== 'other') {
573
+ content = mergeInlinePlatformOverride(planned.content, target.platform, cwd);
459
574
  }
460
575
  map.set(normalizedRel, {
461
576
  absPath: target.absPath,
462
577
  relPath: normalizedRel,
463
578
  content,
464
- encoding: planned.encoding
579
+ encoding: planned.encoding,
580
+ sourcePath: planned.registryPath
465
581
  });
466
582
  }
467
583
  }
@@ -480,7 +596,7 @@ function computeDiff(plannedMap, previousOwnedPaths) {
480
596
  }
481
597
  return { planned: plannedMap, deletions };
482
598
  }
483
- async function applyFileOperations(cwd, planned, deletions, options) {
599
+ async function applyFileOperations(cwd, planned, deletions, options, packageName, contentRoot) {
484
600
  const result = {
485
601
  installed: 0,
486
602
  updated: 0,
@@ -512,6 +628,53 @@ async function applyFileOperations(cwd, planned, deletions, options) {
512
628
  }
513
629
  for (const [rel, detail] of planned.entries()) {
514
630
  const absPath = detail.absPath;
631
+ // Check for content differences before writing
632
+ if (await exists(absPath)) {
633
+ const contentDiffers = await hasContentDifference(absPath, detail.content);
634
+ if (contentDiffers) {
635
+ // Content differs - need to handle conflict
636
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
637
+ let shouldWrite = false;
638
+ if (isDryRun) {
639
+ logger.warn(`Would overwrite ${rel} (content differs from package)`);
640
+ result.skipped++;
641
+ continue;
642
+ }
643
+ if (options.force) {
644
+ logger.warn(`Overwriting ${rel} (content differs, --force flag active)`);
645
+ shouldWrite = true;
646
+ }
647
+ else if (options.conflictStrategy === 'overwrite') {
648
+ logger.warn(`Overwriting ${rel} (content differs, configured conflict strategy)`);
649
+ shouldWrite = true;
650
+ }
651
+ else if (options.conflictStrategy === 'skip') {
652
+ logger.warn(`Skipping ${rel} (content differs, configured conflict strategy)`);
653
+ result.skipped++;
654
+ continue;
655
+ }
656
+ else if (!interactive) {
657
+ logger.warn(`Skipping ${rel} (content differs, cannot prompt in non-interactive mode)`);
658
+ result.skipped++;
659
+ continue;
660
+ }
661
+ else {
662
+ // Interactive mode: prompt user
663
+ // Use registry path (relative to package root)
664
+ const decision = await promptContentDifferenceResolution(rel, detail.sourcePath);
665
+ if (decision === 'skip') {
666
+ logger.info(`Skipped ${rel} (keeping workspace version)`);
667
+ result.skipped++;
668
+ continue;
669
+ }
670
+ shouldWrite = true;
671
+ }
672
+ if (!shouldWrite) {
673
+ result.skipped++;
674
+ continue;
675
+ }
676
+ }
677
+ }
515
678
  if (isDryRun) {
516
679
  result.skipped++;
517
680
  continue;
@@ -637,63 +800,42 @@ function buildIndexMappingFromPlans(plans) {
637
800
  // ============================================================================
638
801
  // Main Install Function
639
802
  // ============================================================================
640
- export async function installPackageByIndex(cwd, packageName, version, platforms, options, includePaths) {
641
- const registryEntries = await loadRegistryFileEntries(packageName, version);
642
- const normalizedIncludes = includePaths && includePaths.length > 0
643
- ? new Set(includePaths.map(p => normalizeRegistryPath(p)))
644
- : null;
645
- const plannedFiles = createPlannedFiles(registryEntries).filter(planned => {
646
- if (!normalizedIncludes)
647
- return true;
648
- return normalizedIncludes.has(normalizeRegistryPath(planned.registryPath));
649
- });
650
- if (normalizedIncludes && plannedFiles.length === 0) {
651
- const available = registryEntries.map(e => normalizeRegistryPath(e.registryPath)).sort();
652
- const message = `Requested specific file(s) not found in ${packageName}@${version}. Available registry paths: ${available.join(', ')}`;
653
- // Align with package-missing behavior: warn and skip instead of throwing
654
- logger.warn(`${message}. Skipping package install.`);
655
- return {
656
- installed: 0,
657
- updated: 0,
658
- deleted: 0,
659
- skipped: 1,
660
- files: [],
661
- installedFiles: [],
662
- updatedFiles: [],
663
- deletedFiles: []
664
- };
665
- }
666
- attachTargetsToPlannedFiles(cwd, plannedFiles, platforms);
667
- const groups = groupPlannedFiles(plannedFiles);
668
- const previousIndex = await readPackageIndex(cwd, packageName);
669
- const otherIndexes = await loadOtherPackageIndexes(cwd, packageName);
670
- const context = await buildExpandedIndexesContext(cwd, otherIndexes);
671
- const groupPlans = await decideGroupPlans(cwd, groups, previousIndex, context);
672
- const previousOwnedPaths = await expandIndexToFilePaths(cwd, previousIndex);
673
- const conflictWarnings = await resolveConflictsForPlannedFiles(cwd, plannedFiles, context, otherIndexes, previousOwnedPaths, options);
674
- for (const warning of conflictWarnings) {
675
- logger.warn(warning);
676
- }
677
- // Load platform YAML overrides once per install
678
- const yamlOverrides = await loadRegistryYamlOverrides(packageName, version);
679
- const plannedTargetMap = buildPlannedTargetMap(plannedFiles, yamlOverrides);
680
- const { planned, deletions } = computeDiff(plannedTargetMap, previousOwnedPaths);
681
- const operationResult = await applyFileOperations(cwd, planned, deletions, options);
682
- if (!options.dryRun) {
683
- const mapping = buildIndexMappingFromPlans(groupPlans);
684
- const workspaceHash = previousIndex?.workspace?.hash ?? createWorkspaceHash(cwd);
685
- const indexRecord = {
686
- path: getPackageIndexPath(cwd, packageName),
687
- packageName,
688
- workspace: {
689
- hash: workspaceHash,
690
- version
691
- },
692
- files: mapping
693
- };
694
- await writePackageIndex(indexRecord);
803
+ export async function installPackageByIndex(cwd, packageName, version, platforms, options, includePaths, contentRoot) {
804
+ // Check if any platform uses flows
805
+ const hasFlowPlatforms = platforms.some(platform => platformUsesFlows(platform, cwd));
806
+ if (hasFlowPlatforms) {
807
+ logger.debug(`Using flow-based installer for ${packageName} on platforms: ${platforms.join(', ')}`);
808
+ // Load package to get format metadata (important for plugin conversion)
809
+ let packageFormat = undefined;
810
+ if (contentRoot) {
811
+ try {
812
+ const { loadPackageFromPath } = await import('../core/install/path-package-loader.js');
813
+ const pkg = await loadPackageFromPath(contentRoot);
814
+ // Extract format metadata if available (set by plugin transformer)
815
+ if (pkg._format) {
816
+ packageFormat = pkg._format;
817
+ logger.debug(`Package format detected from _format field`, {
818
+ type: packageFormat.type,
819
+ platform: packageFormat.platform
820
+ });
821
+ }
822
+ }
823
+ catch (error) {
824
+ // If we can't load the package for format detection, that's OK
825
+ // Format will be detected from files instead
826
+ logger.debug(`Could not load package for format detection: ${error.message}`);
827
+ }
828
+ }
829
+ // Delegate to flow-based installer
830
+ const { installPackageByIndexWithFlows } = await import('./flow-index-installer.js');
831
+ return await installPackageByIndexWithFlows(cwd, packageName, version, platforms, options, includePaths, contentRoot, packageFormat // Pass format metadata
832
+ );
695
833
  }
696
- return operationResult;
834
+ // No platforms use flows - this should not happen with current config
835
+ throw new Error(`Platform(s) ${platforms.join(', ')} do not use flows. ` +
836
+ `Flow-based installation is required. Please check platforms.jsonc configuration.`);
837
+ // Legacy subdirs-based code removed - now using flow-based installation exclusively
838
+ // See flow-index-installer.ts for the new implementation
697
839
  }
698
840
  // ============================================================================
699
841
  // Target Mapping Functions
@@ -704,7 +846,8 @@ async function expandIndexToFilePaths(cwd, index) {
704
846
  return owned;
705
847
  for (const [key, values] of Object.entries(index.files)) {
706
848
  if (isDirKey(key)) {
707
- for (const dirRel of values) {
849
+ for (const mapping of values) {
850
+ const dirRel = getTargetPath(mapping);
708
851
  const files = await collectFilesUnderDirectory(cwd, dirRel);
709
852
  for (const rel of files) {
710
853
  owned.add(normalizePathForProcessing(rel));
@@ -712,7 +855,8 @@ async function expandIndexToFilePaths(cwd, index) {
712
855
  }
713
856
  }
714
857
  else {
715
- for (const value of values) {
858
+ for (const mapping of values) {
859
+ const value = getTargetPath(mapping);
716
860
  owned.add(normalizePathForProcessing(value));
717
861
  }
718
862
  }
@@ -722,7 +866,7 @@ async function expandIndexToFilePaths(cwd, index) {
722
866
  function mapRegistryPathToTargets(cwd, registryPath, platforms) {
723
867
  const normalized = normalizeRegistryPath(registryPath);
724
868
  const targets = [];
725
- const universalInfo = extractUniversalSubdirInfo(normalized);
869
+ const universalInfo = extractUniversalSubdirInfo(normalized, cwd);
726
870
  if (universalInfo) {
727
871
  // Parse the universal path to detect platform suffix and normalized relative path
728
872
  const parsed = parseUniversalPath(normalized);
@@ -731,8 +875,8 @@ function mapRegistryPathToTargets(cwd, registryPath, platforms) {
731
875
  const targetPlatform = parsed.platformSuffix;
732
876
  if (platforms.includes(targetPlatform)) {
733
877
  try {
734
- const mapped = mapUniversalToPlatform(targetPlatform, parsed.universalSubdir, parsed.relPath);
735
- const targetAbs = join(cwd, mapped.absFile);
878
+ const mapped = mapUniversalToPlatform(targetPlatform, parsed.universalSubdir, parsed.relPath, cwd);
879
+ const targetAbs = join(cwd, mapped.relFile);
736
880
  targets.push({
737
881
  absPath: targetAbs,
738
882
  relPath: normalizeRelativePath(cwd, targetAbs),
@@ -749,8 +893,8 @@ function mapRegistryPathToTargets(cwd, registryPath, platforms) {
749
893
  const rel = parsed ? parsed.relPath : universalInfo.relPath;
750
894
  for (const platform of platforms) {
751
895
  try {
752
- const mapped = mapUniversalToPlatform(platform, universalInfo.universalSubdir, rel);
753
- const targetAbs = join(cwd, mapped.absFile);
896
+ const mapped = mapUniversalToPlatform(platform, universalInfo.universalSubdir, rel, cwd);
897
+ const targetAbs = join(cwd, mapped.relFile);
754
898
  targets.push({
755
899
  absPath: targetAbs,
756
900
  relPath: normalizeRelativePath(cwd, targetAbs),
@@ -865,7 +1009,8 @@ function hadPreviousDirForPlatform(previousIndex, groupKey, platform) {
865
1009
  return false;
866
1010
  }
867
1011
  const rootDir = normalizePathForProcessing(getPlatformDefinition(platform).rootDir);
868
- for (const value of prevValues) {
1012
+ for (const mapping of prevValues) {
1013
+ const value = getTargetPath(mapping);
869
1014
  const normalizedValue = normalizePathForProcessing(value);
870
1015
  if (normalizedValue === rootDir ||
871
1016
  normalizedValue.startsWith(`${rootDir}/`)) {
@@ -924,6 +1069,62 @@ async function decideGroupPlans(cwd, groups, previousIndex, context) {
924
1069
  // ============================================================================
925
1070
  // Shared Helper for Building Index Mappings
926
1071
  // ============================================================================
1072
+ function addMappingValue(mapping, key, value) {
1073
+ if (!mapping[key]) {
1074
+ mapping[key] = [];
1075
+ }
1076
+ if (!mapping[key].includes(value)) {
1077
+ mapping[key].push(value);
1078
+ }
1079
+ }
1080
+ async function augmentIndexMappingWithRootAndCopyToRoot(cwd, mapping, packageFiles, platforms) {
1081
+ const augmented = { ...mapping };
1082
+ const rootFileNames = getPlatformRootFileNames(platforms);
1083
+ const explicitRootKeys = new Set();
1084
+ const hasAgents = packageFiles.some(file => normalizeRegistryPath(file.path) === FILE_PATTERNS.AGENTS_MD);
1085
+ for (const file of packageFiles) {
1086
+ const normalized = normalizeRegistryPath(file.path);
1087
+ const stripped = stripRootCopyPrefix(normalized);
1088
+ if (stripped !== null) {
1089
+ if (await exists(join(cwd, stripped))) {
1090
+ addMappingValue(augmented, normalized, stripped);
1091
+ }
1092
+ continue;
1093
+ }
1094
+ if (rootFileNames.has(normalized) || isRootRegistryPath(normalized)) {
1095
+ explicitRootKeys.add(normalized);
1096
+ if (await exists(join(cwd, normalized))) {
1097
+ addMappingValue(augmented, normalized, normalized);
1098
+ }
1099
+ }
1100
+ }
1101
+ // For allowed registry paths, if the file already exists at the workspace-relative path,
1102
+ // record that concrete location. This is important for root packages and for "add"/"save"
1103
+ // flows where the source path itself is the only existing workspace location before apply/install.
1104
+ for (const file of packageFiles) {
1105
+ const normalized = normalizeRegistryPath(file.path);
1106
+ if (!isAllowedRegistryPath(normalized, cwd))
1107
+ continue;
1108
+ if (isSkippableRegistryPath(normalized, cwd))
1109
+ continue;
1110
+ if (await exists(join(cwd, normalized))) {
1111
+ addMappingValue(augmented, normalized, normalized);
1112
+ }
1113
+ }
1114
+ // AGENTS.md can populate platform root files when no explicit override exists in the package.
1115
+ if (hasAgents) {
1116
+ for (const rootFile of rootFileNames) {
1117
+ if (rootFile === FILE_PATTERNS.AGENTS_MD)
1118
+ continue;
1119
+ if (explicitRootKeys.has(rootFile))
1120
+ continue;
1121
+ if (await exists(join(cwd, rootFile))) {
1122
+ addMappingValue(augmented, FILE_PATTERNS.AGENTS_MD, rootFile);
1123
+ }
1124
+ }
1125
+ }
1126
+ return sortMapping(augmented);
1127
+ }
927
1128
  /**
928
1129
  * Build index mapping for package files using the same logic flow as installPackageByIndex
929
1130
  * This function reuses the planning, grouping, and decision logic to ensure consistency
@@ -944,9 +1145,9 @@ export async function buildIndexMappingForPackageFiles(cwd, packageFiles, platfo
944
1145
  // Skip root files and skippable paths (same logic as loadRegistryFileEntries)
945
1146
  if (isRootRegistryPath(normalized))
946
1147
  return false;
947
- if (isSkippableRegistryPath(normalized))
1148
+ if (isSkippableRegistryPath(normalized, cwd))
948
1149
  return false;
949
- return isAllowedRegistryPath(normalized);
1150
+ return isAllowedRegistryPath(normalized, cwd);
950
1151
  })
951
1152
  .map(file => ({
952
1153
  registryPath: normalizeRegistryPath(file.path),
@@ -954,26 +1155,27 @@ export async function buildIndexMappingForPackageFiles(cwd, packageFiles, platfo
954
1155
  encoding: file.encoding
955
1156
  }));
956
1157
  if (registryEntries.length === 0) {
957
- return {};
1158
+ return await augmentIndexMappingWithRootAndCopyToRoot(cwd, {}, packageFiles, platforms);
958
1159
  }
959
1160
  // Reuse existing planning logic - this ensures consistency with installPackageByIndex
960
1161
  const plannedFiles = createPlannedFiles(registryEntries);
961
1162
  attachTargetsToPlannedFiles(cwd, plannedFiles, platforms);
962
- const groups = groupPlannedFiles(plannedFiles);
1163
+ const groups = groupPlannedFiles(plannedFiles, cwd);
963
1164
  const context = await buildExpandedIndexesContext(cwd, otherIndexes);
964
1165
  const groupPlans = await decideGroupPlans(cwd, groups, previousIndex, context);
965
1166
  // Build the mapping using the same logic as installPackageByIndex
966
- return buildIndexMappingFromPlans(groupPlans);
1167
+ const mapping = buildIndexMappingFromPlans(groupPlans);
1168
+ return await augmentIndexMappingWithRootAndCopyToRoot(cwd, mapping, packageFiles, platforms);
967
1169
  }
968
- function filterRegistryEntriesForPackageFiles(packageFiles) {
1170
+ function filterRegistryEntriesForPackageFiles(packageFiles, cwd) {
969
1171
  return packageFiles
970
1172
  .filter(file => {
971
1173
  const normalized = normalizeRegistryPath(file.path);
972
1174
  if (isRootRegistryPath(normalized))
973
1175
  return false;
974
- if (isSkippableRegistryPath(normalized))
1176
+ if (isSkippableRegistryPath(normalized, cwd))
975
1177
  return false;
976
- return isAllowedRegistryPath(normalized);
1178
+ return isAllowedRegistryPath(normalized, cwd);
977
1179
  })
978
1180
  .map(file => ({
979
1181
  registryPath: normalizeRegistryPath(file.path),
@@ -982,36 +1184,27 @@ function filterRegistryEntriesForPackageFiles(packageFiles) {
982
1184
  }));
983
1185
  }
984
1186
  export async function applyPlannedSyncForPackageFiles(cwd, packageName, version, packageFiles, platforms, options, location = 'nested') {
985
- const registryEntries = filterRegistryEntriesForPackageFiles(packageFiles);
1187
+ const registryEntries = filterRegistryEntriesForPackageFiles(packageFiles, cwd);
986
1188
  const plannedFiles = createPlannedFiles(registryEntries);
987
1189
  attachTargetsToPlannedFiles(cwd, plannedFiles, platforms);
988
1190
  const previousIndex = await readPackageIndex(cwd, packageName, location);
989
1191
  const otherIndexes = await loadOtherPackageIndexes(cwd, packageName);
990
1192
  const context = await buildExpandedIndexesContext(cwd, otherIndexes);
991
- const groups = groupPlannedFiles(plannedFiles);
1193
+ const groups = groupPlannedFiles(plannedFiles, cwd);
992
1194
  const groupPlans = await decideGroupPlans(cwd, groups, previousIndex, context);
993
1195
  const previousOwnedPaths = await expandIndexToFilePaths(cwd, previousIndex);
994
1196
  const conflictWarnings = await resolveConflictsForPlannedFiles(cwd, plannedFiles, context, otherIndexes, previousOwnedPaths, options);
995
1197
  for (const warning of conflictWarnings) {
996
1198
  logger.warn(warning);
997
1199
  }
998
- const plannedTargetMap = buildPlannedTargetMap(plannedFiles, packageFiles);
1200
+ const plannedTargetMap = buildPlannedTargetMap(plannedFiles, cwd);
999
1201
  const { planned, deletions } = computeDiff(plannedTargetMap, previousOwnedPaths);
1000
- const operationResult = await applyFileOperations(cwd, planned, deletions, options);
1202
+ // Try to get contentRoot from previous index for better messaging
1203
+ const contentRoot = previousIndex?.path;
1204
+ const operationResult = await applyFileOperations(cwd, planned, deletions, options, packageName, contentRoot);
1001
1205
  let mapping = {};
1002
1206
  if (!options.dryRun) {
1003
- mapping = buildIndexMappingFromPlans(groupPlans);
1004
- const workspaceHash = previousIndex?.workspace?.hash ?? createWorkspaceHash(cwd);
1005
- const indexRecord = {
1006
- path: getPackageIndexPath(cwd, packageName, location),
1007
- packageName,
1008
- workspace: {
1009
- hash: workspaceHash,
1010
- version
1011
- },
1012
- files: mapping
1013
- };
1014
- await writePackageIndex(indexRecord);
1207
+ mapping = await augmentIndexMappingWithRootAndCopyToRoot(cwd, buildIndexMappingFromPlans(groupPlans), packageFiles, platforms);
1015
1208
  }
1016
1209
  return {
1017
1210
  operation: operationResult,