fundus 0.0.4 → 0.2.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 (224) hide show
  1. package/README.md +49 -8
  2. package/build/client/_app/immutable/assets/0.inkPq3ni.css +1 -0
  3. package/build/client/_app/immutable/assets/0.inkPq3ni.css.br +0 -0
  4. package/build/client/_app/immutable/assets/0.inkPq3ni.css.gz +0 -0
  5. package/build/client/_app/immutable/assets/3.ChSpCG-T.css +1 -0
  6. package/build/client/_app/immutable/assets/3.ChSpCG-T.css.br +0 -0
  7. package/build/client/_app/immutable/assets/3.ChSpCG-T.css.gz +0 -0
  8. package/build/client/_app/immutable/assets/{LibraryContextMenu.CMefGNE2.css → LibraryContextMenu.BYvU4SY7.css} +1 -1
  9. package/build/client/_app/immutable/assets/LibraryContextMenu.BYvU4SY7.css.br +0 -0
  10. package/build/client/_app/immutable/assets/LibraryContextMenu.BYvU4SY7.css.gz +0 -0
  11. package/build/client/_app/immutable/assets/ManifestSelector.Il8p28r8.css +1 -0
  12. package/build/client/_app/immutable/assets/ManifestSelector.Il8p28r8.css.br +0 -0
  13. package/build/client/_app/immutable/assets/ManifestSelector.Il8p28r8.css.gz +0 -0
  14. package/build/client/_app/immutable/chunks/BAEZPv6c.js +1 -0
  15. package/build/client/_app/immutable/chunks/BAEZPv6c.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/BAEZPv6c.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/{DVEQFclp.js → ByVSyIj7.js} +1 -1
  18. package/build/client/_app/immutable/chunks/ByVSyIj7.js.br +1 -0
  19. package/build/client/_app/immutable/chunks/ByVSyIj7.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/C8W3KwDq.js +1 -0
  21. package/build/client/_app/immutable/chunks/C8W3KwDq.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/C8W3KwDq.js.gz +0 -0
  23. package/build/client/_app/immutable/chunks/CP-bnOxU.js +1 -0
  24. package/build/client/_app/immutable/chunks/CP-bnOxU.js.br +0 -0
  25. package/build/client/_app/immutable/chunks/CP-bnOxU.js.gz +0 -0
  26. package/build/client/_app/immutable/chunks/{CNRz3sSw.js → D65cSBhe.js} +2 -2
  27. package/build/client/_app/immutable/chunks/D65cSBhe.js.br +0 -0
  28. package/build/client/_app/immutable/chunks/D65cSBhe.js.gz +0 -0
  29. package/build/client/_app/immutable/chunks/Db8LRH6X.js +108 -0
  30. package/build/client/_app/immutable/chunks/Db8LRH6X.js.br +0 -0
  31. package/build/client/_app/immutable/chunks/Db8LRH6X.js.gz +0 -0
  32. package/build/client/_app/immutable/chunks/TZFh8bT7.js +1 -0
  33. package/build/client/_app/immutable/chunks/TZFh8bT7.js.br +0 -0
  34. package/build/client/_app/immutable/chunks/TZFh8bT7.js.gz +0 -0
  35. package/build/client/_app/immutable/entry/app.nM6SgHKJ.js +2 -0
  36. package/build/client/_app/immutable/entry/app.nM6SgHKJ.js.br +0 -0
  37. package/build/client/_app/immutable/entry/app.nM6SgHKJ.js.gz +0 -0
  38. package/build/client/_app/immutable/entry/start.DNXvkhtP.js +1 -0
  39. package/build/client/_app/immutable/entry/start.DNXvkhtP.js.br +0 -0
  40. package/build/client/_app/immutable/entry/start.DNXvkhtP.js.gz +0 -0
  41. package/build/client/_app/immutable/nodes/0.QeXQNCp2.js +2 -0
  42. package/build/client/_app/immutable/nodes/0.QeXQNCp2.js.br +0 -0
  43. package/build/client/_app/immutable/nodes/0.QeXQNCp2.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/1.dMklHTc2.js +1 -0
  45. package/build/client/_app/immutable/nodes/1.dMklHTc2.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/1.dMklHTc2.js.gz +0 -0
  47. package/build/client/_app/immutable/nodes/2.CW4VMcIP.js +3 -0
  48. package/build/client/_app/immutable/nodes/2.CW4VMcIP.js.br +0 -0
  49. package/build/client/_app/immutable/nodes/2.CW4VMcIP.js.gz +0 -0
  50. package/build/client/_app/immutable/nodes/3.CTiY_pbK.js +1 -0
  51. package/build/client/_app/immutable/nodes/3.CTiY_pbK.js.br +0 -0
  52. package/build/client/_app/immutable/nodes/3.CTiY_pbK.js.gz +0 -0
  53. package/build/client/_app/immutable/nodes/4.Dgxx52FJ.js +2 -0
  54. package/build/client/_app/immutable/nodes/4.Dgxx52FJ.js.br +0 -0
  55. package/build/client/_app/immutable/nodes/4.Dgxx52FJ.js.gz +0 -0
  56. package/build/client/_app/version.json +1 -1
  57. package/build/client/_app/version.json.br +0 -0
  58. package/build/client/_app/version.json.gz +0 -0
  59. package/build/client/favicon.svg +4 -0
  60. package/build/client/favicon.svg.br +0 -0
  61. package/build/client/favicon.svg.gz +0 -0
  62. package/build/handler.js +7 -7
  63. package/build/index.js +7 -7
  64. package/build/server/chunks/chunks/{context.js-BUZqFfWG.js → context.js-B77ZAVLi.js} +405 -31
  65. package/build/server/chunks/chunks/context.js-B77ZAVLi.js.map +1 -0
  66. package/build/server/chunks/chunks/{core.js-xfrkaQpS.js → core.js-EA_ysb1J.js} +130 -3
  67. package/build/server/chunks/chunks/core.js-EA_ysb1J.js.map +1 -0
  68. package/build/server/chunks/chunks/{exports.js-C0rwMMtk.js → exports.js-DvhE7Ffd.js} +2 -2
  69. package/build/server/chunks/chunks/{exports.js-C0rwMMtk.js.map → exports.js-DvhE7Ffd.js.map} +1 -1
  70. package/build/server/chunks/chunks/{http.js-DvopzwKh.js → http.js-DyoR9XRI.js} +5 -3
  71. package/build/server/chunks/chunks/http.js-DyoR9XRI.js.map +1 -0
  72. package/build/server/chunks/chunks/{internal.js-DGo2jPcg.js → internal.js-Dn37LrAW.js} +4 -4
  73. package/build/server/chunks/chunks/{internal.js-DGo2jPcg.js.map → internal.js-Dn37LrAW.js.map} +1 -1
  74. package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
  75. package/build/server/chunks/chunks/{library-store.js-ZLudCJlT.js → library-store.js-DJ-JRE-g.js} +388 -20
  76. package/build/server/chunks/chunks/library-store.js-DJ-JRE-g.js.map +1 -0
  77. package/build/server/chunks/chunks/{shared.js-B1gC9MiJ.js → shared.js-Dob0TlTu.js} +2 -2
  78. package/build/server/chunks/chunks/{shared.js-B1gC9MiJ.js.map → shared.js-Dob0TlTu.js.map} +1 -1
  79. package/build/server/chunks/chunks/{utils.js-DnEJ3UjO.js → utils.js-B4YimMxv.js} +3 -3
  80. package/build/server/chunks/chunks/{utils.js-DnEJ3UjO.js.map → utils.js-B4YimMxv.js.map} +1 -1
  81. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-Cq5gXVQT.js → _server.ts.js-H4Pq1KAE.js} +4 -5
  82. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-Cq5gXVQT.js.map → _server.ts.js-H4Pq1KAE.js.map} +1 -1
  83. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-RXbedi8q.js → _server.ts.js-BI3KHSfE.js} +5 -6
  84. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-RXbedi8q.js.map → _server.ts.js-BI3KHSfE.js.map} +1 -1
  85. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/_server.ts.js-BlNYUbXX.js +34 -0
  86. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/_server.ts.js-BlNYUbXX.js.map +1 -0
  87. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-D7Qae9vU.js → _server.ts.js-j-_N540j.js} +9 -7
  88. package/build/server/chunks/entries/endpoints/api/assets/_server.ts.js-j-_N540j.js.map +1 -0
  89. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-DtO20kgB.js → _server.ts.js-BHKobzAg.js} +5 -6
  90. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-DtO20kgB.js.map → _server.ts.js-BHKobzAg.js.map} +1 -1
  91. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BUebPbd5.js → _server.ts.js-BFKjIOCv.js} +5 -6
  92. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BUebPbd5.js.map → _server.ts.js-BFKjIOCv.js.map} +1 -1
  93. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BjT2qPMT.js → _server.ts.js-BxZeVw8y.js} +5 -6
  94. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BjT2qPMT.js.map → _server.ts.js-BxZeVw8y.js.map} +1 -1
  95. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-DYRLsoT7.js → _server.ts.js-CO36U_CT.js} +5 -6
  96. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-DYRLsoT7.js.map → _server.ts.js-CO36U_CT.js.map} +1 -1
  97. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-BbN2fGNN.js → _server.ts.js-CBlg1Bkl.js} +5 -6
  98. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-BbN2fGNN.js.map → _server.ts.js-CBlg1Bkl.js.map} +1 -1
  99. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-b_MKZ41b.js → _server.ts.js-DcGLCjIs.js} +5 -6
  100. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-b_MKZ41b.js.map → _server.ts.js-DcGLCjIs.js.map} +1 -1
  101. package/build/server/chunks/entries/endpoints/api/raw/adopt/_server.ts.js-AqhtjOiL.js +32 -0
  102. package/build/server/chunks/entries/endpoints/api/raw/adopt/_server.ts.js-AqhtjOiL.js.map +1 -0
  103. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/_server.ts.js-CSa6-DcU.js +30 -0
  104. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/_server.ts.js-CSa6-DcU.js.map +1 -0
  105. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/_server.ts.js-DnzZUoAo.js +30 -0
  106. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/_server.ts.js-DnzZUoAo.js.map +1 -0
  107. package/build/server/chunks/entries/endpoints/api/reconcile/_server.ts.js-B0KMprKq.js +33 -0
  108. package/build/server/chunks/entries/endpoints/api/reconcile/_server.ts.js-B0KMprKq.js.map +1 -0
  109. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-evEfMpvn.js → _server.ts.js-B1N9o1Ne.js} +5 -6
  110. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-evEfMpvn.js.map → _server.ts.js-B1N9o1Ne.js.map} +1 -1
  111. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-C04XE_1w.js → _server.ts.js-B6HbnRqq.js} +4 -5
  112. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-C04XE_1w.js.map → _server.ts.js-B6HbnRqq.js.map} +1 -1
  113. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-DB8TdoEv.js → _server.ts.js-7wzPyG9v.js} +5 -6
  114. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-DB8TdoEv.js.map → _server.ts.js-7wzPyG9v.js.map} +1 -1
  115. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BrpaijZO.js → _server.ts.js-BWztxYge.js} +5 -6
  116. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BrpaijZO.js.map → _server.ts.js-BWztxYge.js.map} +1 -1
  117. package/build/server/chunks/entries/{hooks.server.js-BJJwZW_I.js → hooks.server.js-CVKLksJ1.js} +4 -5
  118. package/build/server/chunks/entries/{hooks.server.js-BJJwZW_I.js.map → hooks.server.js-CVKLksJ1.js.map} +1 -1
  119. package/build/server/chunks/{handler-DnbS2t1B.js → handler-DZ2Sps4a.js} +5 -5
  120. package/build/server/chunks/{handler-DnbS2t1B.js.map → handler-DZ2Sps4a.js.map} +1 -1
  121. package/build/server/chunks/{index.js-g77q3Boj.js → index.js-CdmTWQz8.js} +5 -5
  122. package/build/server/chunks/{index.js-g77q3Boj.js.map → index.js-CdmTWQz8.js.map} +1 -1
  123. package/build/server/chunks/{manifest.js-qmEXbstS.js → manifest.js-GhfNr35x.js} +57 -22
  124. package/build/server/chunks/manifest.js-GhfNr35x.js.map +1 -0
  125. package/build/server/chunks/nodes/0.js-DGT86_i-.js +11 -0
  126. package/build/server/chunks/nodes/0.js-DGT86_i-.js.map +1 -0
  127. package/build/server/chunks/nodes/1.js-DQgYreU0.js +7 -0
  128. package/build/server/chunks/nodes/{1.js-H-qwo4GJ.js.map → 1.js-DQgYreU0.js.map} +1 -1
  129. package/build/server/chunks/nodes/2.js-DItreSrX.js +7 -0
  130. package/build/server/chunks/nodes/2.js-DItreSrX.js.map +1 -0
  131. package/build/server/chunks/nodes/3.js-bE1dJ_6v.js +7 -0
  132. package/build/server/chunks/nodes/3.js-bE1dJ_6v.js.map +1 -0
  133. package/build/server/chunks/nodes/4.js-BebbMVXy.js +7 -0
  134. package/build/server/chunks/nodes/{4.js-BKL7LSSm.js.map → 4.js-BebbMVXy.js.map} +1 -1
  135. package/dist/cli.js +32 -9
  136. package/dist/core/api-types.d.ts +39 -1
  137. package/dist/core/index.d.ts +3 -2
  138. package/dist/core/index.js +2 -1
  139. package/dist/core/ingest.d.ts +2 -0
  140. package/dist/core/ingest.js +7 -0
  141. package/dist/core/raw-path.d.ts +8 -0
  142. package/dist/core/raw-path.js +63 -0
  143. package/dist/server/build.js +11 -2
  144. package/dist/server/check.d.ts +5 -1
  145. package/dist/server/check.js +80 -28
  146. package/dist/server/cli-commands.d.ts +3 -2
  147. package/dist/server/cli-commands.js +47 -4
  148. package/dist/server/cli-init.d.ts +1 -1
  149. package/dist/server/cli-init.js +38 -5
  150. package/dist/server/http.js +5 -1
  151. package/dist/server/index.d.ts +1 -1
  152. package/dist/server/library-store.d.ts +23 -0
  153. package/dist/server/library-store.js +82 -14
  154. package/dist/server/operations.d.ts +12 -2
  155. package/dist/server/operations.js +391 -31
  156. package/dist/server/processing/processor.js +3 -0
  157. package/dist/server/raw-reconciliation.d.ts +53 -0
  158. package/dist/server/raw-reconciliation.js +338 -0
  159. package/dist/server/raw-safety.d.ts +10 -0
  160. package/dist/server/raw-safety.js +73 -0
  161. package/dist/server/start.d.ts +4 -2
  162. package/dist/server/start.js +43 -11
  163. package/package.json +1 -1
  164. package/build/client/_app/immutable/assets/0.DLhIsYA_.css +0 -1
  165. package/build/client/_app/immutable/assets/0.DLhIsYA_.css.br +0 -0
  166. package/build/client/_app/immutable/assets/0.DLhIsYA_.css.gz +0 -0
  167. package/build/client/_app/immutable/assets/3.JwgtcTEd.css +0 -1
  168. package/build/client/_app/immutable/assets/3.JwgtcTEd.css.br +0 -0
  169. package/build/client/_app/immutable/assets/3.JwgtcTEd.css.gz +0 -0
  170. package/build/client/_app/immutable/assets/LibraryContextMenu.CMefGNE2.css.br +0 -0
  171. package/build/client/_app/immutable/assets/LibraryContextMenu.CMefGNE2.css.gz +0 -0
  172. package/build/client/_app/immutable/chunks/CNRz3sSw.js.br +0 -0
  173. package/build/client/_app/immutable/chunks/CNRz3sSw.js.gz +0 -0
  174. package/build/client/_app/immutable/chunks/CO4RBJfa.js +0 -1
  175. package/build/client/_app/immutable/chunks/CO4RBJfa.js.br +0 -0
  176. package/build/client/_app/immutable/chunks/CO4RBJfa.js.gz +0 -0
  177. package/build/client/_app/immutable/chunks/DOpBpjxS.js +0 -108
  178. package/build/client/_app/immutable/chunks/DOpBpjxS.js.br +0 -0
  179. package/build/client/_app/immutable/chunks/DOpBpjxS.js.gz +0 -0
  180. package/build/client/_app/immutable/chunks/DVEQFclp.js.br +0 -1
  181. package/build/client/_app/immutable/chunks/DVEQFclp.js.gz +0 -0
  182. package/build/client/_app/immutable/chunks/DiFoWL2c.js +0 -1
  183. package/build/client/_app/immutable/chunks/DiFoWL2c.js.br +0 -0
  184. package/build/client/_app/immutable/chunks/DiFoWL2c.js.gz +0 -0
  185. package/build/client/_app/immutable/chunks/DkADHpZF.js +0 -1
  186. package/build/client/_app/immutable/chunks/DkADHpZF.js.br +0 -0
  187. package/build/client/_app/immutable/chunks/DkADHpZF.js.gz +0 -0
  188. package/build/client/_app/immutable/entry/app.CKgtYXQb.js +0 -2
  189. package/build/client/_app/immutable/entry/app.CKgtYXQb.js.br +0 -0
  190. package/build/client/_app/immutable/entry/app.CKgtYXQb.js.gz +0 -0
  191. package/build/client/_app/immutable/entry/start.CYZu5qE1.js +0 -1
  192. package/build/client/_app/immutable/entry/start.CYZu5qE1.js.br +0 -0
  193. package/build/client/_app/immutable/entry/start.CYZu5qE1.js.gz +0 -0
  194. package/build/client/_app/immutable/nodes/0.I_1SApYQ.js +0 -2
  195. package/build/client/_app/immutable/nodes/0.I_1SApYQ.js.br +0 -0
  196. package/build/client/_app/immutable/nodes/0.I_1SApYQ.js.gz +0 -0
  197. package/build/client/_app/immutable/nodes/1.v6eUTnWq.js +0 -1
  198. package/build/client/_app/immutable/nodes/1.v6eUTnWq.js.br +0 -2
  199. package/build/client/_app/immutable/nodes/1.v6eUTnWq.js.gz +0 -0
  200. package/build/client/_app/immutable/nodes/2.DoA7Dfl-.js +0 -3
  201. package/build/client/_app/immutable/nodes/2.DoA7Dfl-.js.br +0 -0
  202. package/build/client/_app/immutable/nodes/2.DoA7Dfl-.js.gz +0 -0
  203. package/build/client/_app/immutable/nodes/3.C-DO70Bs.js +0 -1
  204. package/build/client/_app/immutable/nodes/3.C-DO70Bs.js.br +0 -0
  205. package/build/client/_app/immutable/nodes/3.C-DO70Bs.js.gz +0 -0
  206. package/build/client/_app/immutable/nodes/4.CALOuSyz.js +0 -2
  207. package/build/client/_app/immutable/nodes/4.CALOuSyz.js.br +0 -0
  208. package/build/client/_app/immutable/nodes/4.CALOuSyz.js.gz +0 -0
  209. package/build/server/chunks/chunks/context.js-BUZqFfWG.js.map +0 -1
  210. package/build/server/chunks/chunks/core.js-xfrkaQpS.js.map +0 -1
  211. package/build/server/chunks/chunks/folder-path.js-ytx9ucgg.js +0 -64
  212. package/build/server/chunks/chunks/folder-path.js-ytx9ucgg.js.map +0 -1
  213. package/build/server/chunks/chunks/http.js-DvopzwKh.js.map +0 -1
  214. package/build/server/chunks/chunks/library-store.js-ZLudCJlT.js.map +0 -1
  215. package/build/server/chunks/entries/endpoints/api/assets/_server.ts.js-D7Qae9vU.js.map +0 -1
  216. package/build/server/chunks/manifest.js-qmEXbstS.js.map +0 -1
  217. package/build/server/chunks/nodes/0.js-Bk6IZ-vD.js +0 -11
  218. package/build/server/chunks/nodes/0.js-Bk6IZ-vD.js.map +0 -1
  219. package/build/server/chunks/nodes/1.js-H-qwo4GJ.js +0 -7
  220. package/build/server/chunks/nodes/2.js-DRpXVLwp.js +0 -7
  221. package/build/server/chunks/nodes/2.js-DRpXVLwp.js.map +0 -1
  222. package/build/server/chunks/nodes/3.js-CDEzNFeU.js +0 -7
  223. package/build/server/chunks/nodes/3.js-CDEzNFeU.js.map +0 -1
  224. package/build/server/chunks/nodes/4.js-BKL7LSSm.js +0 -7
@@ -1,8 +1,10 @@
1
+ import { type Library, type ValidationIssueDto } from '../core/index.js';
2
+ import { type RawClassification } from './raw-reconciliation.js';
1
3
  import type { ServerPluginRegistry } from './plugins/contract.js';
2
4
  /** One machine-readable check finding (the `--json` failure-list shape, M9). */
3
5
  export interface CheckFinding {
4
6
  /** The failure category. */
5
- kind: 'validation' | 'proxy-missing' | 'proxy-unresolvable' | 'proxy-orphaned' | 'module-missing' | 'module-out-of-sync' | 'module-stray' | 'module-regeneration-failed' | 'unreachable-asset' | 'raw-hash-drift';
7
+ kind: 'validation' | 'proxy-missing' | 'proxy-unresolvable' | 'proxy-orphaned' | 'module-missing' | 'module-out-of-sync' | 'module-stray' | 'module-regeneration-failed' | 'unreachable-asset' | 'raw-hash-drift' | 'raw-type-incompatible' | 'raw-scan-incomplete' | 'raw-unmanaged';
6
8
  message: string;
7
9
  /** The asset the finding concerns, where applicable. */
8
10
  assetId?: string;
@@ -22,3 +24,5 @@ export interface CheckReport {
22
24
  manifestCount: number;
23
25
  }
24
26
  export declare function runCheck(projectRoot: string, registry?: ServerPluginRegistry): Promise<CheckReport>;
27
+ /** Pure expansion of a raw classification into canonical hard check findings. */
28
+ export declare function rawCheckFindings(library: Library, raw: RawClassification, registry: ServerPluginRegistry, validationIssues: ValidationIssueDto[]): CheckFinding[];
@@ -2,16 +2,17 @@
2
2
  * `fundus check` — the verify-only CI gate: no writes, ever. It
3
3
  * fails when the library does not validate, when a committed proxy is stale,
4
4
  * missing, or orphaned, or when a generated manifest module differs from an
5
- * in-memory regeneration. Warnings (unreachable asset, raw-hash drift) are
5
+ * in-memory regeneration. Warnings (unreachable assets) are
6
6
  * treated as errors unless `--allow-warnings` downgrades them — errors are
7
7
  * never downgradable.
8
8
  */
9
9
  import { existsSync } from 'node:fs';
10
10
  import { readFile } from 'node:fs/promises';
11
11
  import { join } from 'node:path';
12
- import { collectAllReferences, compareCodeUnits, computeLibraryMembership, rawFileNameOfPath } from '../core/index.js';
12
+ import { collectAllReferences, compareCodeUnits, computeLibraryMembership, rawFileNameOfPath, typesForFileName } from '../core/index.js';
13
13
  import { loadConfig } from './config-loader.js';
14
- import { LibraryStore, sha256Hex } from './library-store.js';
14
+ import { LibraryStore } from './library-store.js';
15
+ import { classifyRawDirectory } from './raw-reconciliation.js';
15
16
  import { buildManifestEntries, manifestModuleFileName, renderManifestModule, strayGeneratedModuleNames } from './codegen.js';
16
17
  import { descriptorsByType, serverPluginsByType } from './plugins.js';
17
18
  import { deliveredAssetIds, expectedProxyRelativePath, isProxyFileName, readCurrentProxy } from './processing/processor.js';
@@ -32,13 +33,21 @@ export async function runCheck(projectRoot, registry = serverPluginsByType) {
32
33
  };
33
34
  // 1. Library validation (structure, parameters, raw pointers, references).
34
35
  const issues = await store.validate(library);
36
+ const validationKeys = new Set();
35
37
  for (const issue of issues) {
38
+ const key = `${issue.assetId ?? ''}|${issue.message}`;
39
+ if (validationKeys.has(key))
40
+ continue;
41
+ validationKeys.add(key);
36
42
  report.errors.push({
37
43
  kind: 'validation',
38
44
  message: issue.message,
39
45
  ...(issue.assetId !== undefined ? { assetId: issue.assetId } : {})
40
46
  });
41
47
  }
48
+ const descriptors = Object.values(registry).map((plugin) => plugin.descriptor);
49
+ const raw = await classifyRawDirectory(config.rawDir, library, (path) => typesForFileName(path, descriptors).length > 0);
50
+ report.errors.push(...rawCheckFindings(library, raw, registry, issues));
42
51
  // 2. Proxies: every delivered asset's expected proxy exists (staleness is a
43
52
  // name check), and the proxy directory holds no orphans.
44
53
  const delivered = deliveredAssetIds(library, registry);
@@ -93,15 +102,80 @@ export async function runCheck(projectRoot, registry = serverPluginsByType) {
93
102
  // 3. Generated manifest modules must be byte-identical to an in-memory
94
103
  // regeneration. Only meaningful when validation passed and every proxy
95
104
  // exists — otherwise regeneration is impossible and already reported.
96
- if (issues.length === 0 && proxiesComplete) {
105
+ if (report.errors.length === 0 && proxiesComplete) {
97
106
  await checkGeneratedModules(report, library, config, proxies, registry);
98
107
  await checkStrayModules(report, library, config);
99
108
  }
100
- // 4. Warnings: unreachable assets, raw-hash drift.
109
+ // 4. Warnings: unreachable assets only. Raw drift is always a hard CI failure.
101
110
  collectUnreachableAssets(report, library, delivered);
102
- await collectRawHashDrift(report, library, config);
111
+ report.errors.sort((left, right) => compareCodeUnits(`${left.fileName ?? ''}|${left.assetId ?? ''}|${left.kind}|${left.message}`, `${right.fileName ?? ''}|${right.assetId ?? ''}|${right.kind}|${right.message}`));
112
+ report.warnings.sort((left, right) => compareCodeUnits(`${left.assetId ?? ''}|${left.kind}|${left.message}`, `${right.assetId ?? ''}|${right.kind}|${right.message}`));
103
113
  return report;
104
114
  }
115
+ /** Pure expansion of a raw classification into canonical hard check findings. */
116
+ export function rawCheckFindings(library, raw, registry, validationIssues) {
117
+ const findings = [];
118
+ const descriptors = Object.values(registry).map((plugin) => plugin.descriptor);
119
+ const compatibleTypes = (path) => typesForFileName(path, descriptors);
120
+ for (const diagnostic of raw.diagnostics) {
121
+ findings.push({
122
+ kind: 'raw-scan-incomplete',
123
+ message: diagnostic.reason,
124
+ ...(diagnostic.path ? { fileName: diagnostic.path } : {})
125
+ });
126
+ }
127
+ const edited = new Map(raw.edited.map((entry) => [entry.assetId, entry]));
128
+ for (const assetId of raw.unchangedAssetIds) {
129
+ const record = library.assets[assetId];
130
+ if (!compatibleTypes(record.raw).includes(record.type)) {
131
+ findings.push({
132
+ kind: 'raw-type-incompatible',
133
+ message: `Raw file "${record.raw}" is not compatible with its current ${record.type} type — ` +
134
+ 'open or refocus the editor and choose a compatible type.',
135
+ assetId,
136
+ fileName: record.raw
137
+ });
138
+ }
139
+ }
140
+ for (const entry of edited.values()) {
141
+ findings.push({
142
+ kind: 'raw-hash-drift',
143
+ message: `Raw file "${entry.path}" changed outside Fundus — open or refocus the editor ` +
144
+ 'to process or dismiss the edited ingestion item.',
145
+ assetId: entry.assetId,
146
+ fileName: entry.path
147
+ });
148
+ }
149
+ const missingAssetIds = new Set(validationIssues
150
+ .filter((issue) => issue.message.includes('is missing from the raw directory'))
151
+ .map((issue) => issue.assetId));
152
+ for (const move of raw.moves) {
153
+ if (!missingAssetIds.has(move.assetId)) {
154
+ findings.push({
155
+ kind: 'validation',
156
+ message: `Raw file "${move.from}" is missing from the raw directory.`,
157
+ assetId: move.assetId
158
+ });
159
+ }
160
+ }
161
+ for (const file of [...raw.unmanaged, ...raw.moves]) {
162
+ findings.push({
163
+ kind: 'raw-unmanaged',
164
+ message: `Raw file "${file.path}" is not managed by Fundus — open or refocus the editor and ` +
165
+ 'resolve its ingestion queue item.',
166
+ fileName: file.path
167
+ });
168
+ }
169
+ for (const blocker of raw.blockers) {
170
+ findings.push({
171
+ kind: 'raw-unmanaged',
172
+ message: `Unsupported raw entry "${blocker.path}": ${blocker.reason} Rename or remove it externally.`,
173
+ fileName: blocker.path,
174
+ ...(blocker.assetId ? { assetId: blocker.assetId } : {})
175
+ });
176
+ }
177
+ return findings;
178
+ }
105
179
  async function checkGeneratedModules(report, library, config, proxies, registry) {
106
180
  const referencesByAsset = collectAllReferences(library, descriptorsByType(registry));
107
181
  const membershipByManifest = computeLibraryMembership(library, referencesByAsset);
@@ -175,25 +249,3 @@ function collectUnreachableAssets(report, library, delivered) {
175
249
  }
176
250
  }
177
251
  }
178
- /**
179
- * Raw-hash drift: a raw file replaced on disk while the library still pins the
180
- * old content hash. The proxy name derives from the *recorded* hash, so the
181
- * name check alone would keep passing while the delivered pixels are outdated.
182
- */
183
- async function collectRawHashDrift(report, library, config) {
184
- for (const [assetId, record] of Object.entries(library.assets)) {
185
- const rawPath = join(config.rawDir, record.raw);
186
- if (!existsSync(rawPath))
187
- continue; // A missing raw file is a validation error already.
188
- const currentHash = sha256Hex(new Uint8Array(await readFile(rawPath)));
189
- if (currentHash !== record.rawHash) {
190
- report.warnings.push({
191
- kind: 'raw-hash-drift',
192
- message: `Raw file "${record.raw}" changed since it was ingested (hash drift) — ` +
193
- `re-ingest or update it through the editor, then run \`fundus build\`.`,
194
- assetId,
195
- fileName: record.raw
196
- });
197
- }
198
- }
199
- }
@@ -1,9 +1,10 @@
1
1
  export declare const STATE_HELP = "fundus state \u2014 the full project state in one document\n\nUsage: fundus state [--json]\n\nThe agent's one-stop overview: every asset (type, raw file, parameters,\nexplicit + passive manifest memberships, proxy status, reference edges,\nwarnings), every manifest (members, delivered bytes), library-level\nvalidation issues, and the available preset names per asset type.\n\nFlags:\n --json print {\"ok\": true, \"state\": <FundusStateDto>} on stdout\n --help this help\n\nRead-only: nothing is processed or written.\n\nExample:\n fundus state --json | jq '.state.assets[].id'\n";
2
2
  export declare function runStateCommand(argumentList: string[]): Promise<void>;
3
- export declare const ASSET_HELP = "fundus asset \u2014 inspect and mutate assets\n\nUsage: fundus asset <subcommand> \u2026\n\nEvery mutation runs the full editor autosave chain: write library \u2192\nprocess affected assets \u2192 regenerate manifest modules. There is no\nseparate apply/sync step. All subcommands accept --json (a single JSON\ndocument on stdout) and --help.\n\nSubcommands:\n list list assets (--manifest, --type, --warnings-only)\n show one asset in detail\n ingest copy a file into the raw dir as a typed asset\n set merge parameters and/or replace manifest membership\n rename rename an asset id\n move move an asset to a folder or root\n delete delete an asset (record, raw file, proxies)\n\nRun `fundus asset <subcommand> --help` for the focused page (flags,\nsemantics, an example) \u2014 e.g. `fundus asset set --help` documents the\n--parameters merge rules.\n";
3
+ export declare const ASSET_HELP = "fundus asset \u2014 inspect and mutate assets\n\nUsage: fundus asset <subcommand> \u2026\n\nEvery mutation runs the full editor autosave chain: write library \u2192\nprocess affected assets \u2192 regenerate manifest modules. There is no\nseparate apply/sync step. All subcommands accept --json (a single JSON\ndocument on stdout) and --help.\n\nSubcommands:\n list list assets (--manifest, --type, --warnings-only)\n show one asset in detail\n ingest copy a file into the raw dir as a typed asset\n replace replace an asset's raw file while preserving its metadata\n set merge parameters and/or replace manifest membership\n rename rename an asset id\n move move an asset to a folder or root\n delete delete an asset (record, raw file, proxies)\n\nRun `fundus asset <subcommand> --help` for the focused page (flags,\nsemantics, an example) \u2014 e.g. `fundus asset set --help` documents the\n--parameters merge rules.\n";
4
4
  export declare const ASSET_LIST_HELP: string;
5
5
  export declare const ASSET_SHOW_HELP = "fundus asset show \u2014 one asset in detail\n\nUsage: fundus asset show <id> [--json]\n\nPrints the asset's raw file, content hash, parameters, explicit +\npassive manifest memberships, proxy status, reference edges (outgoing\nand incoming), and warnings.\n\nFlags:\n --json print {\"ok\": true, \"asset\": <AssetStateDto>}\n --help this help\n\nRead-only: nothing is processed or written.\n\nExample:\n fundus asset show navigationPanel --json\n";
6
6
  export declare const ASSET_INGEST_HELP: string;
7
+ export declare const ASSET_REPLACE_HELP = "fundus asset replace \u2014 replace an asset's raw file\n\nUsage: fundus asset replace <id> <file> [--json]\n\nReplaces the stored raw file and content hash while preserving the asset's\nid, type, folder, parameters, references, and manifest memberships. The new\nfile must have an extension accepted by the existing asset type. Runs the full\nautosave chain, so delivered assets are reprocessed and manifests regenerated.\n\nFlags:\n --json print {\"ok\": true, \"asset\": <AssetStateDto>, \"issues\": [\u2026]}\n --help this help\n\nExample:\n fundus asset replace navigationPanel ./exports/navigation_panel_v2.png --json\n";
7
8
  export declare const ASSET_SET_HELP = "fundus asset set \u2014 update an asset's parameters and/or memberships\n\nUsage: fundus asset set <id> [--parameters <json>] [--manifests <a,b>] [--json]\n\nAt least one of --parameters/--manifests is required. Runs the full\nautosave chain (write library \u2192 process \u2192 regenerate manifest modules).\n\nFlags:\n --parameters <json> SHALLOW MERGE at the top level onto the current\n parameters, then plugin validation:\n {\"preset\": \"webp-hq\"} sets one field,\n keeps the others\n {\"chromaKey\": {\u2026}} replaces the whole\n chromaKey object\n {\"mask\": null} clears an optional\n field\n {\"mask\": {\"$asset\": \"someImage\"}}\n sets an asset\n reference\n --manifests <a,b> REPLACES the explicit membership set (declarative,\n idempotent); --manifests \"\" clears it\n --json print {\"ok\": true, \"asset\": <AssetStateDto>, \"issues\": [\u2026]}\n --help this help\n\nThe mutation is validated first and rejected (exit 1, nothing written)\nif it would leave the library invalid \u2014 e.g. a reference to a missing\nasset, a reference cycle, or an unknown manifest name. Ingest reference\ntargets before pointing at them.\n\nExample:\n fundus asset set characterIdle \\\n --parameters '{\"mask\": {\"$asset\": \"characterIdleMask\"}}' --json\n";
8
9
  export declare const ASSET_RENAME_HELP = "fundus asset rename \u2014 rename an asset id\n\nUsage: fundus asset rename <id> <newId> [--json]\n\nRenames the asset's id (the raw file keeps its name \u2014 ids are the\nstable key) and re-hashes its proxy under the new id via the autosave\nchain. Fails if another asset references <id> \u2014 update the referrers\nfirst \u2014 or if <newId> is taken or invalid.\n\nFlags:\n --json print {\"ok\": true, \"asset\": <AssetStateDto>, \"issues\": [\u2026]}\n --help this help\n\nExample:\n fundus asset rename sidebarPanel navigationPanel --json\n";
9
10
  export declare const ASSET_DELETE_HELP = "fundus asset delete \u2014 delete an asset\n\nUsage: fundus asset delete <id> [--json]\n\nDeletes the library record, its raw file, and its proxies, then\nregenerates the affected manifest modules. Fails if another asset\nreferences it \u2014 remove the references first.\n\nFlags:\n --json print {\"ok\": true, \"deletedAssetId\": \"<id>\", \"issues\": [\u2026]}\n --help this help\n\nExample:\n fundus asset delete obsoleteBanner --json\n";
@@ -21,5 +22,5 @@ export declare const MANIFEST_PALETTE_HELP = "fundus manifest palette \u2014 lis
21
22
  export declare function runManifestCommand(argumentList: string[]): Promise<void>;
22
23
  export declare const BUILD_HELP = "fundus build \u2014 run the headless pipeline\n\nUsage: fundus build [--json]\n\nValidates the library, processes every stale or missing proxy, prunes\norphaned proxies, and regenerates all manifest modules. Deterministic\nand idempotent: a second run on an unchanged project processes nothing\nand rewrites byte-identical modules. Run it in CI, postinstall, or\nbefore the host build; commit the result.\n\nFlags:\n --json print {\"ok\": true, \"processed\": [\u2026], \"upToDate\": [\u2026],\n \"prunedProxies\": [\u2026], \"modulesWritten\": [\u2026],\n \"modulesRemoved\": [\u2026]} \u2014 asset ids for processing,\n file names for pruning/codegen\n --help this help\n\nA validation failure processes nothing and exits 1 (with --json:\n{\"error\": {\"message\": \u2026}}).\n\nExample:\n fundus build --json | jq '.processed'\n";
23
24
  export declare function runBuildCommand(argumentList: string[]): Promise<void>;
24
- export declare const CHECK_HELP = "fundus check \u2014 verify the committed state (the CI gate)\n\nUsage: fundus check [--allow-warnings] [--json]\n\nVerify-only, no writes: the library must validate, every delivered\nasset's committed proxy must exist under its expected name (staleness\nis a name check), the proxy directory must hold no orphans, and every\ngenerated manifest module must be byte-identical to an in-memory\nregeneration. Warnings (unreachable asset, raw-hash drift) fail the\ncheck too unless --allow-warnings downgrades them; errors never\ndowngrade.\n\nFlags:\n --allow-warnings exit 0 despite warnings (errors still fail)\n --json print one report document:\n {\"ok\": <pass/fail>, \"assetCount\": \u2026, \"manifestCount\": \u2026,\n \"errors\": [<finding>, \u2026], \"warnings\": [<finding>, \u2026]}\n where a finding is {\"kind\", \"message\"} plus\n \"assetId\" / \"manifest\" / \"fileName\" where applicable\n --help this help\n\nExit codes: 0 = clean (or warnings allowed), 1 = failed. A failure to\neven run the check (e.g. no fundus.config.ts) exits 1 with the usual\n{\"error\": {\"message\": \u2026}} envelope under --json.\n\nExample:\n fundus check --json | jq '.errors[].kind'\n";
25
+ export declare const CHECK_HELP = "fundus check \u2014 verify the committed state (the CI gate)\n\nUsage: fundus check [--allow-warnings] [--json]\n\nVerify-only, no writes: the library must validate, every delivered\nasset's committed proxy must exist under its expected name (staleness\nis a name check), the proxy directory must hold no orphans, and every\ngenerated manifest module must be byte-identical to an in-memory\nregeneration. Warnings (such as unreachable assets) fail the check\nunless --allow-warnings downgrades them. New, missing, edited, or\ninvalid entries in the raw directory are hard errors:\nopen or refocus the Fundus editor and resolve its ingestion queue.\nRaw-file errors never downgrade.\n\nFlags:\n --allow-warnings exit 0 despite warnings (errors still fail)\n --json print one report document:\n {\"ok\": <pass/fail>, \"assetCount\": \u2026, \"manifestCount\": \u2026,\n \"errors\": [<finding>, \u2026], \"warnings\": [<finding>, \u2026]}\n where a finding is {\"kind\", \"message\"} plus\n \"assetId\" / \"manifest\" / \"fileName\" where applicable\n --help this help\n\nExit codes: 0 = clean (or warnings allowed), 1 = failed. A failure to\neven run the check (e.g. no fundus.config.ts) exits 1 with the usual\n{\"error\": {\"message\": \u2026}} envelope under --json.\n\nExample:\n fundus check --json | jq '.errors[].kind'\n";
25
26
  export declare function runCheckCommand(argumentList: string[]): Promise<void>;
@@ -202,6 +202,7 @@ Subcommands:
202
202
  list list assets (--manifest, --type, --warnings-only)
203
203
  show one asset in detail
204
204
  ingest copy a file into the raw dir as a typed asset
205
+ replace replace an asset's raw file while preserving its metadata
205
206
  set merge parameters and/or replace manifest membership
206
207
  rename rename an asset id
207
208
  move move an asset to a folder or root
@@ -285,6 +286,22 @@ Example:
285
286
  fundus asset ingest ./exports/navigation_panel@2x.png --type slice \\
286
287
  --manifests mainCatalog --json
287
288
  `;
289
+ export const ASSET_REPLACE_HELP = `fundus asset replace — replace an asset's raw file
290
+
291
+ Usage: fundus asset replace <id> <file> [--json]
292
+
293
+ Replaces the stored raw file and content hash while preserving the asset's
294
+ id, type, folder, parameters, references, and manifest memberships. The new
295
+ file must have an extension accepted by the existing asset type. Runs the full
296
+ autosave chain, so delivered assets are reprocessed and manifests regenerated.
297
+
298
+ Flags:
299
+ --json print {"ok": true, "asset": <AssetStateDto>, "issues": […]}
300
+ --help this help
301
+
302
+ Example:
303
+ fundus asset replace navigationPanel ./exports/navigation_panel_v2.png --json
304
+ `;
288
305
  export const ASSET_SET_HELP = `fundus asset set — update an asset's parameters and/or memberships
289
306
 
290
307
  Usage: fundus asset set <id> [--parameters <json>] [--manifests <a,b>] [--json]
@@ -368,6 +385,7 @@ const ASSET_SUBCOMMAND_HELP = {
368
385
  list: ASSET_LIST_HELP,
369
386
  show: ASSET_SHOW_HELP,
370
387
  ingest: ASSET_INGEST_HELP,
388
+ replace: ASSET_REPLACE_HELP,
371
389
  set: ASSET_SET_HELP,
372
390
  rename: ASSET_RENAME_HELP,
373
391
  move: ASSET_MOVE_HELP,
@@ -490,6 +508,27 @@ async function runAssetIngest(argumentList) {
490
508
  }
491
509
  printMutationResult(`Ingested "${fileName}" as ${type} asset "${id}".`, asset, state);
492
510
  }
511
+ async function runAssetReplace(argumentList) {
512
+ const parsed = parseArguments(argumentList, COMMON_FLAGS, 'fundus asset replace');
513
+ const [id, filePath] = requirePositionals(parsed, ['<id>', '<file>'], 'fundus asset replace');
514
+ const absolutePath = resolve(process.cwd(), filePath);
515
+ let bytes;
516
+ try {
517
+ bytes = new Uint8Array(await readFile(absolutePath));
518
+ }
519
+ catch {
520
+ throw new Error(`Cannot read file "${filePath}".`);
521
+ }
522
+ const fileName = basename(absolutePath);
523
+ const operations = await createOperations();
524
+ const state = await operations.replaceRawFile(id, { fileName, bytes });
525
+ const asset = assetById(state, id);
526
+ if (parsed.flags.json) {
527
+ printJsonDocument({ ok: true, asset, issues: state.issues });
528
+ return;
529
+ }
530
+ printMutationResult(`Replaced raw file for asset "${id}" with "${fileName}".`, asset, state);
531
+ }
493
532
  async function runAssetSet(argumentList) {
494
533
  const parsed = parseArguments(argumentList, { ...COMMON_FLAGS, parameters: { takesValue: true }, manifests: { takesValue: true } }, 'fundus asset set');
495
534
  const [id] = requirePositionals(parsed, ['<id>'], 'fundus asset set');
@@ -554,7 +593,7 @@ export async function runAssetCommand(argumentList) {
554
593
  if (subcommand === undefined || subcommand === '--help') {
555
594
  console.log(ASSET_HELP);
556
595
  if (subcommand === undefined) {
557
- throw new CliUsageError('`fundus asset` needs a subcommand: list, show, ingest, set, rename, move, delete.');
596
+ throw new CliUsageError('`fundus asset` needs a subcommand: list, show, ingest, replace, set, rename, move, delete.');
558
597
  }
559
598
  return;
560
599
  }
@@ -570,6 +609,8 @@ export async function runAssetCommand(argumentList) {
570
609
  return runAssetShow(rest);
571
610
  case 'ingest':
572
611
  return runAssetIngest(rest);
612
+ case 'replace':
613
+ return runAssetReplace(rest);
573
614
  case 'set':
574
615
  return runAssetSet(rest);
575
616
  case 'rename':
@@ -1116,9 +1157,11 @@ Verify-only, no writes: the library must validate, every delivered
1116
1157
  asset's committed proxy must exist under its expected name (staleness
1117
1158
  is a name check), the proxy directory must hold no orphans, and every
1118
1159
  generated manifest module must be byte-identical to an in-memory
1119
- regeneration. Warnings (unreachable asset, raw-hash drift) fail the
1120
- check too unless --allow-warnings downgrades them; errors never
1121
- downgrade.
1160
+ regeneration. Warnings (such as unreachable assets) fail the check
1161
+ unless --allow-warnings downgrades them. New, missing, edited, or
1162
+ invalid entries in the raw directory are hard errors:
1163
+ open or refocus the Fundus editor and resolve its ingestion queue.
1164
+ Raw-file errors never downgrade.
1122
1165
 
1123
1166
  Flags:
1124
1167
  --allow-warnings exit 0 despite warnings (errors still fail)
@@ -1,3 +1,3 @@
1
1
  export declare const FUNDUS_CONFIG_TEMPLATE = "import { defineConfig } from 'fundus/config';\n\nexport default defineConfig({\n\trawDir: './assets/raw',\n\tproxyDir: './static/fundus',\n\tproxyBasePath: '/fundus/',\n\tlibrary: './assets/fundus.library.json',\n\tmanifestsDir: './src/lib/fundus'\n});\n";
2
- export declare const INIT_HELP = "fundus init \u2014 create a Fundus configuration file\n\nUsage: fundus init [--json]\n\nCreates a template fundus.config.ts in the current directory. The command\nrefuses to overwrite an existing configuration file.\n\nFlags:\n --json print {\"ok\": true, \"path\": \"fundus.config.ts\"} on stdout\n --help this help\n\nExample:\n cd my-host-app && npx fundus init\n";
2
+ export declare const INIT_HELP = "fundus init \u2014 create a Fundus configuration file\n\nUsage: fundus init [--json]\n\nCreates a template fundus.config.ts and a library with a default \"main\"\nmanifest in the current directory. The command refuses to initialize a project\nthat already has a Fundus configuration file.\n\nFlags:\n --json print {\"ok\": true, \"path\": \"fundus.config.ts\"} on stdout\n --help this help\n\nExample:\n cd my-host-app && npx fundus init\n";
3
3
  export declare function runInitCommand(argumentList: string[]): Promise<void>;
@@ -1,5 +1,6 @@
1
- import { writeFile } from 'node:fs/promises';
2
- import { resolve } from 'node:path';
1
+ import { lstat, mkdir, unlink, writeFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { createEmptyLibrary, serializeLibrary } from '../core/index.js';
3
4
  import { parseArguments, printJsonDocument, requirePositionals } from './cli-io.js';
4
5
  export const FUNDUS_CONFIG_TEMPLATE = `import { defineConfig } from 'fundus/config';
5
6
 
@@ -15,8 +16,9 @@ export const INIT_HELP = `fundus init — create a Fundus configuration file
15
16
 
16
17
  Usage: fundus init [--json]
17
18
 
18
- Creates a template fundus.config.ts in the current directory. The command
19
- refuses to overwrite an existing configuration file.
19
+ Creates a template fundus.config.ts and a library with a default "main"
20
+ manifest in the current directory. The command refuses to initialize a project
21
+ that already has a Fundus configuration file.
20
22
 
21
23
  Flags:
22
24
  --json print {"ok": true, "path": "fundus.config.ts"} on stdout
@@ -45,9 +47,40 @@ export async function runInitCommand(argumentList) {
45
47
  }
46
48
  throw new Error(`Cannot create fundus.config.ts: ${cause instanceof Error ? cause.message : String(cause)}`);
47
49
  }
50
+ const libraryPath = resolve(process.cwd(), 'assets/fundus.library.json');
51
+ const rawPath = resolve(process.cwd(), 'assets/raw');
52
+ const library = createEmptyLibrary();
53
+ library.manifests.main = {};
54
+ try {
55
+ await mkdir(dirname(libraryPath), { recursive: true });
56
+ await writeFile(libraryPath, serializeLibrary(library), { encoding: 'utf8', flag: 'wx' });
57
+ }
58
+ catch (cause) {
59
+ // The config was created by this invocation. Remove it so a failed
60
+ // library scaffold does not make the project look initialized.
61
+ await unlink(configPath).catch(() => { });
62
+ if (cause.code === 'EEXIST') {
63
+ throw new Error(`assets/fundus.library.json already exists in ${process.cwd()}.`);
64
+ }
65
+ throw new Error(`Cannot create assets/fundus.library.json: ${cause instanceof Error ? cause.message : String(cause)}`);
66
+ }
67
+ try {
68
+ await mkdir(rawPath, { recursive: true });
69
+ const rawStat = await lstat(rawPath);
70
+ if (!rawStat.isDirectory() || rawStat.isSymbolicLink()) {
71
+ throw new Error('the path exists but is not a regular directory');
72
+ }
73
+ }
74
+ catch (cause) {
75
+ // Only the two files written exclusively by this invocation are rolled
76
+ // back. Existing paths and directories are never removed recursively.
77
+ await unlink(libraryPath).catch(() => { });
78
+ await unlink(configPath).catch(() => { });
79
+ throw new Error(`Cannot create assets/raw directory: ${cause instanceof Error ? cause.message : String(cause)}`);
80
+ }
48
81
  if (parsed.flags.json) {
49
82
  printJsonDocument({ ok: true, path: 'fundus.config.ts' });
50
83
  return;
51
84
  }
52
- console.log('Created fundus.config.ts.');
85
+ console.log('Created fundus.config.ts and default "main" manifest.');
53
86
  }
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
2
+ import { lstat, readFile } from 'node:fs/promises';
3
3
  import { extname, join } from 'node:path';
4
4
  import { assertSafeRelativeFilePath } from './library-store.js';
5
5
  const CONTENT_TYPES = {
@@ -71,6 +71,10 @@ export async function serveFundusFile(config, kind, fileName, request) {
71
71
  if (!existsSync(filePath)) {
72
72
  return errorResponse(new Error(`File "${fileName}" not found.`), 404);
73
73
  }
74
+ const fileStat = await lstat(filePath);
75
+ if (!fileStat.isFile() || fileStat.isSymbolicLink()) {
76
+ return errorResponse(new Error(`File "${fileName}" is not a regular file.`), 400);
77
+ }
74
78
  const bytes = new Uint8Array(await readFile(filePath));
75
79
  const size = bytes.byteLength;
76
80
  const headers = {
@@ -1,7 +1,7 @@
1
1
  export type { ResolvedFundusConfig, ResolvedPresetPluginConfig, ResolvedVideoPluginConfig, ResolvedAudioPluginConfig } from './config-loader.js';
2
2
  export { loadConfig, resolveConfig } from './config-loader.js';
3
3
  export { LibraryStore } from './library-store.js';
4
- export type { IngestOptions } from './library-store.js';
4
+ export type { IngestOptions, ReplaceRawFileOptions } from './library-store.js';
5
5
  export type { AssetProxyInfo, ProxyOutputInfo, ProcessingReport } from './processing/processor.js';
6
6
  export { processAssets, expectedProxyFileName, expectedProxyRelativePath, deliveredAssetIds, isProxyFileName, readProxyOutputInfo } from './processing/processor.js';
7
7
  export { ffmpegBinaryPath, runToolchainBinary } from './processing/ffmpeg.js';
@@ -23,6 +23,11 @@ export interface IngestOptions {
23
23
  /** Registered destination folder; empty string means the implicit root. */
24
24
  folder?: string;
25
25
  }
26
+ export interface ReplaceRawFileOptions {
27
+ /** Original file name of the replacement (sanitized before storing). */
28
+ fileName: string;
29
+ bytes: Uint8Array;
30
+ }
26
31
  export declare class LibraryStore {
27
32
  #private;
28
33
  constructor(config: ResolvedFundusConfig, registry?: ServerPluginRegistry);
@@ -31,12 +36,30 @@ export declare class LibraryStore {
31
36
  read(): Promise<Library>;
32
37
  /** Deterministically serialize + write the library file. */
33
38
  write(library: Library): Promise<void>;
39
+ /**
40
+ * Candidate-first source transaction used for externally-owned raw files.
41
+ * The callback runs after entering the store queue and may scan the raw tree;
42
+ * Fundus writes only the library candidate and never touches external bytes.
43
+ */
44
+ transactCandidate<T>(change: (candidate: Library, current: Library) => Promise<T>): Promise<{
45
+ library: Library;
46
+ value: T;
47
+ changed: boolean;
48
+ }>;
49
+ /** Remove derived proxies after a source record was externally deleted/moved. */
50
+ deleteProxiesOf(assetId: string): Promise<void>;
34
51
  /**
35
52
  * Ingest a new asset: write the raw file into `rawDir` (under a sanitized,
36
53
  * non-colliding name), hash its content, and create the library record with
37
54
  * the owning plugin's default parameters. Returns the updated library.
38
55
  */
39
56
  ingest(options: IngestOptions): Promise<Library>;
57
+ /**
58
+ * Replace one asset's raw file while preserving its identity and authored
59
+ * metadata. The replacement stays in the current folder, receives a safe
60
+ * non-colliding name, and must be accepted by the asset's existing type.
61
+ */
62
+ replaceRawFile(id: string, options: ReplaceRawFileOptions): Promise<Library>;
40
63
  /** Replace an asset's parameters (validated by the owning plugin). */
41
64
  updateParameters(id: string, parameters: unknown): Promise<Library>;
42
65
  /** Replace an asset's manifest membership (every name must exist). */
@@ -8,7 +8,7 @@ import { existsSync } from 'node:fs';
8
8
  import { mkdir, readFile, rename, rm, unlink, writeFile } from 'node:fs/promises';
9
9
  import { dirname, extname, join } from 'node:path';
10
10
  import { createHash } from 'node:crypto';
11
- import { assetIdError, collectAllReferences, createEmptyLibrary, folderNameError, folderNameOf, folderOfRawPath, folderPathError, isFolderOrDescendant, joinFolderPath, manifestColorError, manifestEmojiError, parseLibrary, referenceErrors, normalizeFolderPath, parentFolderPath, portableFolderPathKey, rawFileNameOfPath, replaceFolderPrefix, manifestNameError, serializeLibrary } from '../core/index.js';
11
+ import { assetIdError, collectAllReferences, createEmptyLibrary, folderNameError, folderNameOf, folderOfRawPath, folderPathError, isFolderOrDescendant, joinFolderPath, manifestColorError, manifestEmojiError, parseLibrary, referenceErrors, normalizeFolderPath, parentFolderPath, portableFolderPathKey, rawFilePathError, rawFileNameOfPath, replaceFolderPrefix, manifestNameError, serializeLibrary } from '../core/index.js';
12
12
  import { manifestModuleFileName } from './codegen.js';
13
13
  import { descriptorsByType, serverPluginForType, serverPluginsByType } from './plugins.js';
14
14
  import { isProxyFileNameFor } from './processing/processor.js';
@@ -22,19 +22,9 @@ export function assertSafeFileName(fileName) {
22
22
  }
23
23
  /** Validate a persisted/served file path without allowing traversal outside its root. */
24
24
  export function assertSafeRelativeFilePath(filePath) {
25
- if (filePath.startsWith('/') || filePath.endsWith('/') || filePath.includes('\\')) {
26
- throw new Error(`Unsafe file path "${filePath}".`);
27
- }
28
- const segments = filePath.split('/');
29
- if (segments.length === 0)
30
- throw new Error(`Unsafe file path "${filePath}".`);
31
- assertSafeFileName(segments.at(-1) ?? '');
32
- const folder = segments.slice(0, -1).join('/');
33
- if (folder !== '') {
34
- const problem = folderPathError(folder);
35
- if (problem)
36
- throw new Error(problem);
37
- }
25
+ const problem = rawFilePathError(filePath);
26
+ if (problem)
27
+ throw new Error(problem);
38
28
  }
39
29
  /**
40
30
  * Turn an arbitrary uploaded file name (spaces, umlauts, parentheses, nested
@@ -97,6 +87,27 @@ export class LibraryStore {
97
87
  await writeFile(temporaryPath, serializeLibrary(library));
98
88
  await rename(temporaryPath, this.#config.library);
99
89
  }
90
+ /**
91
+ * Candidate-first source transaction used for externally-owned raw files.
92
+ * The callback runs after entering the store queue and may scan the raw tree;
93
+ * Fundus writes only the library candidate and never touches external bytes.
94
+ */
95
+ async transactCandidate(change) {
96
+ return this.#enqueue(async () => {
97
+ const current = await this.read();
98
+ const before = serializeLibrary(current);
99
+ const candidate = structuredClone(current);
100
+ const value = await change(candidate, current);
101
+ const changed = serializeLibrary(candidate) !== before;
102
+ if (changed)
103
+ await this.write(candidate);
104
+ return { library: candidate, value, changed };
105
+ });
106
+ }
107
+ /** Remove derived proxies after a source record was externally deleted/moved. */
108
+ async deleteProxiesOf(assetId) {
109
+ await this.#deleteProxiesOf(assetId);
110
+ }
100
111
  /**
101
112
  * Ingest a new asset: write the raw file into `rawDir` (under a sanitized,
102
113
  * non-colliding name), hash its content, and create the library record with
@@ -136,6 +147,63 @@ export class LibraryStore {
136
147
  await this.write(library);
137
148
  return library;
138
149
  }
150
+ /**
151
+ * Replace one asset's raw file while preserving its identity and authored
152
+ * metadata. The replacement stays in the current folder, receives a safe
153
+ * non-colliding name, and must be accepted by the asset's existing type.
154
+ */
155
+ async replaceRawFile(id, options) {
156
+ return this.#enqueue(() => this.#replaceRawFile(id, options));
157
+ }
158
+ async #replaceRawFile(id, options) {
159
+ const library = await this.read();
160
+ const record = this.#requireAsset(library, id);
161
+ const sanitizedName = sanitizeFileName(options.fileName);
162
+ const extension = extname(sanitizedName).slice(1).toLowerCase();
163
+ const plugin = serverPluginForType(record.type, this.#registry);
164
+ if (!plugin.descriptor.accepts.includes(extension)) {
165
+ throw new Error(`The ${record.type} type does not accept ".${extension}" files (accepts: ${plugin.descriptor.accepts.join(', ')}).`);
166
+ }
167
+ const previousRaw = record.raw;
168
+ const previousHash = record.rawHash;
169
+ const folder = folderOfRawPath(previousRaw);
170
+ const previousName = rawFileNameOfPath(previousRaw);
171
+ const storedName = sanitizedName === previousName ? previousName : this.#uniqueRawName(sanitizedName, folder);
172
+ const nextRaw = folder === '' ? storedName : `${folder}/${storedName}`;
173
+ const previousPath = join(this.#config.rawDir, previousRaw);
174
+ const nextPath = join(this.#config.rawDir, nextRaw);
175
+ const token = crypto.randomUUID();
176
+ const temporaryPath = join(this.#config.rawDir, folder, `.fundus-replace-${token}.tmp`);
177
+ const backupPath = `${previousPath}.fundus-replace-${token}`;
178
+ let previousStaged = false;
179
+ let replacementInstalled = false;
180
+ await mkdir(dirname(temporaryPath), { recursive: true });
181
+ try {
182
+ await writeFile(temporaryPath, options.bytes);
183
+ if (existsSync(previousPath)) {
184
+ await rename(previousPath, backupPath);
185
+ previousStaged = true;
186
+ }
187
+ await rename(temporaryPath, nextPath);
188
+ replacementInstalled = true;
189
+ record.raw = nextRaw;
190
+ record.rawHash = sha256Hex(options.bytes);
191
+ await this.write(library);
192
+ }
193
+ catch (cause) {
194
+ record.raw = previousRaw;
195
+ record.rawHash = previousHash;
196
+ if (replacementInstalled)
197
+ await unlink(nextPath).catch(() => { });
198
+ if (previousStaged)
199
+ await rename(backupPath, previousPath);
200
+ await unlink(temporaryPath).catch(() => { });
201
+ throw cause;
202
+ }
203
+ if (previousStaged)
204
+ await unlink(backupPath).catch(() => { });
205
+ return library;
206
+ }
139
207
  /** Replace an asset's parameters (validated by the owning plugin). */
140
208
  async updateParameters(id, parameters) {
141
209
  return this.#enqueue(() => this.#updateParameters(id, parameters));
@@ -1,6 +1,6 @@
1
1
  import { type FundusStateDto, type PatchAssetRequestDto, type PatchManifestRequestDto } from '../core/index.js';
2
2
  import type { ResolvedFundusConfig } from './config-loader.js';
3
- import { LibraryStore, type IngestOptions } from './library-store.js';
3
+ import { LibraryStore, type IngestOptions, type ReplaceRawFileOptions } from './library-store.js';
4
4
  import type { ServerPluginRegistry } from './plugins/contract.js';
5
5
  /**
6
6
  * The CLI's `--parameters` merge semantics (M7): a SHALLOW merge at the top
@@ -22,8 +22,16 @@ export declare class FundusOperations {
22
22
  constructor(config: ResolvedFundusConfig, registry?: ServerPluginRegistry);
23
23
  get config(): ResolvedFundusConfig;
24
24
  get store(): LibraryStore;
25
- /** Read-only state snapshot (no processing). */
25
+ /** Read-only state snapshot, serialized with mutations but never processing or writing. */
26
26
  state(): Promise<FundusStateDto>;
27
+ /** Reconcile only changes that are safe without a user decision: unique moves and deletions. */
28
+ reconcileRawState(): Promise<FundusStateDto>;
29
+ /** Adopt an already-existing raw file without copying, renaming, or deleting it. */
30
+ adoptRawFile(token: string, id: string, type: string, manifests?: readonly string[]): Promise<FundusStateDto>;
31
+ /** Reset/reseed edited bytes, optionally changing to another compatible type. */
32
+ acceptEditedRawFile(token: string, type: string): Promise<FundusStateDto>;
33
+ /** Accept edited bytes while preserving type, parameters, path and memberships. */
34
+ dismissEditedRawFile(token: string): Promise<FundusStateDto>;
27
35
  /**
28
36
  * Ingest an upload: store the raw file + record, then let the owning
29
37
  * plugin's optional `seedIngestParameters` derive better starting
@@ -37,6 +45,8 @@ export declare class FundusOperations {
37
45
  /** Apply a PATCH body: parameters and/or manifests and/or a rename — in that order. */
38
46
  patchAsset(id: string, body: PatchAssetRequestDto): Promise<FundusStateDto>;
39
47
  moveAsset(id: string, folder: string): Promise<FundusStateDto>;
48
+ /** Replace the raw bytes/name and run the normal processing + codegen tail. */
49
+ replaceRawFile(id: string, options: ReplaceRawFileOptions): Promise<FundusStateDto>;
40
50
  /** Move a selected asset set as one editor action and roll back on any failure. */
41
51
  moveAssets(assetIds: string[], folder: string): Promise<FundusStateDto>;
42
52
  createFolder(path: string): Promise<FundusStateDto>;