gitifact 0.1.0 → 0.3.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 (304) hide show
  1. package/README.md +9 -6
  2. package/dist/THIRD_PARTY_NOTICES.txt +0 -198
  3. package/dist/browser/assets/{BottomSheet-DUajo7ZC.js → BottomSheet-DFEd7qcJ.js} +1 -1
  4. package/dist/browser/assets/{Icon-BL6mvojd.js → IconButton-DQKowV1I.js} +2 -2
  5. package/dist/browser/assets/{MenuBottomSheet-uD8HCpnn.js → MenuBottomSheet-BKiLm4Gk.js} +1 -1
  6. package/dist/browser/assets/{Section-CxDofIgF.js → Section-CXNZYeQ6.js} +1 -1
  7. package/dist/browser/assets/{Text-DRvg_LY8.js → Text-LX6541eF.js} +2 -2
  8. package/dist/browser/assets/TimestampHoverCard-CEVz4ZE2.js +1 -0
  9. package/dist/browser/assets/{Tooltip-CSXUs0wo.js → Tooltip-CH5OxdmX.js} +1 -1
  10. package/dist/browser/assets/about-Dbyd48t6.js +1 -0
  11. package/dist/browser/assets/contributors._email-BMqQlPdF.js +1 -0
  12. package/dist/browser/assets/{contributors-DG1A_fxV.js → contributors.index-Dk42HR-l.js} +1 -1
  13. package/dist/browser/assets/features._featureId-zHnvA0Go.js +1 -0
  14. package/dist/browser/assets/{requirements-CSGZCMPA.js → features.index-xgyvTm5d.js} +1 -1
  15. package/dist/browser/assets/git-D8SQ9LAI.js +1 -0
  16. package/dist/browser/assets/git-nj7fScNQ.css +1 -0
  17. package/dist/browser/assets/guides._documentId-jvtVZl4b.js +1 -0
  18. package/dist/browser/assets/guides.index-DKNn9kCJ.js +1 -0
  19. package/dist/browser/assets/{ime-DpJB0Xdq.js → ime-u5HljzMu.js} +1 -1
  20. package/dist/browser/assets/index-CiEgP5lo.css +1 -0
  21. package/dist/browser/assets/index-Cul6rpdA.js +56 -0
  22. package/dist/browser/assets/page-header-9-i3H9WR.css +1 -0
  23. package/dist/browser/assets/page-header-pnZFm0-7.js +56 -0
  24. package/dist/browser/assets/product-B_C25r7E.js +75 -0
  25. package/dist/browser/assets/product-DIl1IXZo.css +1 -0
  26. package/dist/browser/assets/product.index-IBLtgiMT.js +1 -0
  27. package/dist/browser/assets/request-state-q7VzEh1O.js +3 -0
  28. package/dist/browser/assets/requirements-BmW8BEdu.js +1 -0
  29. package/dist/browser/assets/{routes-E_7O-5Bj.js → routes-BDsb61_V.js} +1 -1
  30. package/dist/browser/assets/useClipboard-D2fN4Nhs.js +1 -0
  31. package/dist/browser/assets/{useIsomorphicLayoutEffect-C7U2IcbD.js → useIsomorphicLayoutEffect-DPqcigQ0.js} +1 -1
  32. package/dist/browser/favicon.png +0 -0
  33. package/dist/browser/favicon.svg +5 -0
  34. package/dist/browser/index.html +24 -15
  35. package/dist/docs/commit.md +36 -0
  36. package/dist/docs/design.md +42 -0
  37. package/dist/docs/product.md +21 -0
  38. package/dist/docs/spec.md +54 -0
  39. package/dist/docs/workflow.md +42 -0
  40. package/dist/main.js +902 -469
  41. package/package.json +1 -1
  42. package/dist/browser/assets/IconButton-lHbix98V.js +0 -1
  43. package/dist/browser/assets/Table-DeD03NuQ.js +0 -3
  44. package/dist/browser/assets/TimestampHoverCard-CdwdDY2W.js +0 -1
  45. package/dist/browser/assets/about-BDnPkVlL.js +0 -1
  46. package/dist/browser/assets/features-8KaaKFr1.js +0 -1
  47. package/dist/browser/assets/git-BLYFkHAP.js +0 -1
  48. package/dist/browser/assets/index-CFjKQp32.js +0 -56
  49. package/dist/browser/assets/index-DnHDaIjm.css +0 -1
  50. package/dist/browser/assets/noto-sans-kr-0-800-normal-Bw_wPEW6.woff +0 -0
  51. package/dist/browser/assets/noto-sans-kr-0-800-normal-CU2ZAuxN.woff2 +0 -0
  52. package/dist/browser/assets/noto-sans-kr-1-800-normal-Bv79PX5h.woff2 +0 -0
  53. package/dist/browser/assets/noto-sans-kr-1-800-normal-J_tKEQwZ.woff +0 -0
  54. package/dist/browser/assets/noto-sans-kr-10-800-normal-DE_9FlHK.woff2 +0 -0
  55. package/dist/browser/assets/noto-sans-kr-10-800-normal-DRiks4sP.woff +0 -0
  56. package/dist/browser/assets/noto-sans-kr-100-800-normal-BwmJ5UQj.woff2 +0 -0
  57. package/dist/browser/assets/noto-sans-kr-100-800-normal-DmRRRn-a.woff +0 -0
  58. package/dist/browser/assets/noto-sans-kr-101-800-normal-B-hehSjp.woff +0 -0
  59. package/dist/browser/assets/noto-sans-kr-101-800-normal-YdhdcxJt.woff2 +0 -0
  60. package/dist/browser/assets/noto-sans-kr-102-800-normal-C6kOYHZD.woff2 +0 -0
  61. package/dist/browser/assets/noto-sans-kr-102-800-normal-C_hmEXnv.woff +0 -0
  62. package/dist/browser/assets/noto-sans-kr-103-800-normal-CmDrDQ9V.woff2 +0 -0
  63. package/dist/browser/assets/noto-sans-kr-103-800-normal-c5SsBHZM.woff +0 -0
  64. package/dist/browser/assets/noto-sans-kr-104-800-normal-CHG9R-zy.woff +0 -0
  65. package/dist/browser/assets/noto-sans-kr-104-800-normal-DMx294uT.woff2 +0 -0
  66. package/dist/browser/assets/noto-sans-kr-105-800-normal-BmLq4tHL.woff2 +0 -0
  67. package/dist/browser/assets/noto-sans-kr-105-800-normal-fEWHmGbY.woff +0 -0
  68. package/dist/browser/assets/noto-sans-kr-106-800-normal-CjjLA4cZ.woff2 +0 -0
  69. package/dist/browser/assets/noto-sans-kr-106-800-normal-Du7jON9q.woff +0 -0
  70. package/dist/browser/assets/noto-sans-kr-107-800-normal-5ie2GORa.woff +0 -0
  71. package/dist/browser/assets/noto-sans-kr-107-800-normal-B-vw3liL.woff2 +0 -0
  72. package/dist/browser/assets/noto-sans-kr-108-800-normal-BR9Zw7IY.woff +0 -0
  73. package/dist/browser/assets/noto-sans-kr-108-800-normal-VCcXKMkT.woff2 +0 -0
  74. package/dist/browser/assets/noto-sans-kr-109-800-normal-C7tEA3KS.woff +0 -0
  75. package/dist/browser/assets/noto-sans-kr-109-800-normal-IlLk0ufL.woff2 +0 -0
  76. package/dist/browser/assets/noto-sans-kr-11-800-normal-BinZyvQR.woff2 +0 -0
  77. package/dist/browser/assets/noto-sans-kr-11-800-normal-CL9Ahwpo.woff +0 -0
  78. package/dist/browser/assets/noto-sans-kr-110-800-normal-DEZBQ-5W.woff +0 -0
  79. package/dist/browser/assets/noto-sans-kr-110-800-normal-Do0gAnv1.woff2 +0 -0
  80. package/dist/browser/assets/noto-sans-kr-111-800-normal-Cxb-EKnN.woff +0 -0
  81. package/dist/browser/assets/noto-sans-kr-111-800-normal-D1o1iTAW.woff2 +0 -0
  82. package/dist/browser/assets/noto-sans-kr-112-800-normal-DvsOBbyY.woff2 +0 -0
  83. package/dist/browser/assets/noto-sans-kr-112-800-normal-HV4h3xxa.woff +0 -0
  84. package/dist/browser/assets/noto-sans-kr-113-800-normal-DXGusidw.woff +0 -0
  85. package/dist/browser/assets/noto-sans-kr-113-800-normal-tbylWl5F.woff2 +0 -0
  86. package/dist/browser/assets/noto-sans-kr-114-800-normal-BICMpgoW.woff2 +0 -0
  87. package/dist/browser/assets/noto-sans-kr-114-800-normal-DU1U5ue1.woff +0 -0
  88. package/dist/browser/assets/noto-sans-kr-115-800-normal-C1oRdW1M.woff +0 -0
  89. package/dist/browser/assets/noto-sans-kr-115-800-normal-CLr-Zxv_.woff2 +0 -0
  90. package/dist/browser/assets/noto-sans-kr-116-800-normal-C9sKyFCZ.woff2 +0 -0
  91. package/dist/browser/assets/noto-sans-kr-116-800-normal-CtM3GUM6.woff +0 -0
  92. package/dist/browser/assets/noto-sans-kr-117-800-normal-3A2aP5IS.woff +0 -0
  93. package/dist/browser/assets/noto-sans-kr-117-800-normal-DwrcPOym.woff2 +0 -0
  94. package/dist/browser/assets/noto-sans-kr-118-800-normal-CsMV-v3_.woff2 +0 -0
  95. package/dist/browser/assets/noto-sans-kr-118-800-normal-DV16Oj1F.woff +0 -0
  96. package/dist/browser/assets/noto-sans-kr-119-800-normal-Bxd2OEQS.woff +0 -0
  97. package/dist/browser/assets/noto-sans-kr-119-800-normal-C3JA3uCT.woff2 +0 -0
  98. package/dist/browser/assets/noto-sans-kr-12-800-normal-BLqjm9D5.woff2 +0 -0
  99. package/dist/browser/assets/noto-sans-kr-12-800-normal-BdSxEe2g.woff +0 -0
  100. package/dist/browser/assets/noto-sans-kr-13-800-normal-8rZnesK8.woff2 +0 -0
  101. package/dist/browser/assets/noto-sans-kr-13-800-normal-BedB76c8.woff +0 -0
  102. package/dist/browser/assets/noto-sans-kr-14-800-normal-BtiVONz_.woff2 +0 -0
  103. package/dist/browser/assets/noto-sans-kr-14-800-normal-mA9SFjVh.woff +0 -0
  104. package/dist/browser/assets/noto-sans-kr-15-800-normal-BYbeZAE2.woff2 +0 -0
  105. package/dist/browser/assets/noto-sans-kr-15-800-normal-CXEvvUix.woff +0 -0
  106. package/dist/browser/assets/noto-sans-kr-16-800-normal-B8OWqNF3.woff2 +0 -0
  107. package/dist/browser/assets/noto-sans-kr-16-800-normal-CQuF5Ih3.woff +0 -0
  108. package/dist/browser/assets/noto-sans-kr-17-800-normal-CTUGDjHX.woff +0 -0
  109. package/dist/browser/assets/noto-sans-kr-17-800-normal-DNvAlAcZ.woff2 +0 -0
  110. package/dist/browser/assets/noto-sans-kr-18-800-normal-Petpk6Wi.woff +0 -0
  111. package/dist/browser/assets/noto-sans-kr-18-800-normal-ThFSRZa4.woff2 +0 -0
  112. package/dist/browser/assets/noto-sans-kr-19-800-normal-BMyRF7fz.woff2 +0 -0
  113. package/dist/browser/assets/noto-sans-kr-19-800-normal-BNNtnBhl.woff +0 -0
  114. package/dist/browser/assets/noto-sans-kr-2-800-normal-CQ6iDWMP.woff +0 -0
  115. package/dist/browser/assets/noto-sans-kr-2-800-normal-DrQB0PtS.woff2 +0 -0
  116. package/dist/browser/assets/noto-sans-kr-20-800-normal-CuNmAe4n.woff +0 -0
  117. package/dist/browser/assets/noto-sans-kr-20-800-normal-DdfIMDx9.woff2 +0 -0
  118. package/dist/browser/assets/noto-sans-kr-21-800-normal-C08X3_0x.woff2 +0 -0
  119. package/dist/browser/assets/noto-sans-kr-21-800-normal-Dn-tQ8Cr.woff +0 -0
  120. package/dist/browser/assets/noto-sans-kr-22-800-normal-B6q-RFF5.woff2 +0 -0
  121. package/dist/browser/assets/noto-sans-kr-22-800-normal-DZF106py.woff +0 -0
  122. package/dist/browser/assets/noto-sans-kr-23-800-normal-B0lHlsqh.woff2 +0 -0
  123. package/dist/browser/assets/noto-sans-kr-23-800-normal-DRCDT0r0.woff +0 -0
  124. package/dist/browser/assets/noto-sans-kr-24-800-normal-C1wKob6w.woff2 +0 -0
  125. package/dist/browser/assets/noto-sans-kr-24-800-normal-DG0Bfaz1.woff +0 -0
  126. package/dist/browser/assets/noto-sans-kr-25-800-normal-BuWQxFvN.woff +0 -0
  127. package/dist/browser/assets/noto-sans-kr-25-800-normal-DlN6uf6B.woff2 +0 -0
  128. package/dist/browser/assets/noto-sans-kr-26-800-normal-BQPGMN-7.woff +0 -0
  129. package/dist/browser/assets/noto-sans-kr-26-800-normal-CYgTLzoi.woff2 +0 -0
  130. package/dist/browser/assets/noto-sans-kr-27-800-normal-DsrJXERC.woff +0 -0
  131. package/dist/browser/assets/noto-sans-kr-27-800-normal-eDTiDgrj.woff2 +0 -0
  132. package/dist/browser/assets/noto-sans-kr-28-800-normal-CmRY-nZ4.woff +0 -0
  133. package/dist/browser/assets/noto-sans-kr-28-800-normal-CpLjKGyi.woff2 +0 -0
  134. package/dist/browser/assets/noto-sans-kr-29-800-normal-BFNDreIt.woff2 +0 -0
  135. package/dist/browser/assets/noto-sans-kr-29-800-normal-CDzh3Sik.woff +0 -0
  136. package/dist/browser/assets/noto-sans-kr-3-800-normal-CAuXj4Ks.woff +0 -0
  137. package/dist/browser/assets/noto-sans-kr-3-800-normal-DkylunKW.woff2 +0 -0
  138. package/dist/browser/assets/noto-sans-kr-30-800-normal-D4A2u9et.woff +0 -0
  139. package/dist/browser/assets/noto-sans-kr-30-800-normal-t7TpxFL5.woff2 +0 -0
  140. package/dist/browser/assets/noto-sans-kr-31-800-normal-D1jtl7Q7.woff2 +0 -0
  141. package/dist/browser/assets/noto-sans-kr-31-800-normal-DX9UFu0w.woff +0 -0
  142. package/dist/browser/assets/noto-sans-kr-32-800-normal-B-9BPuXB.woff2 +0 -0
  143. package/dist/browser/assets/noto-sans-kr-32-800-normal-Bm8HsYTW.woff +0 -0
  144. package/dist/browser/assets/noto-sans-kr-33-800-normal-CBJnLOB4.woff2 +0 -0
  145. package/dist/browser/assets/noto-sans-kr-33-800-normal-Lp3eLj8E.woff +0 -0
  146. package/dist/browser/assets/noto-sans-kr-34-800-normal-BTn3_cSG.woff2 +0 -0
  147. package/dist/browser/assets/noto-sans-kr-34-800-normal-C7F12ZiM.woff +0 -0
  148. package/dist/browser/assets/noto-sans-kr-35-800-normal--n-itfth.woff +0 -0
  149. package/dist/browser/assets/noto-sans-kr-35-800-normal-DMKFJwpr.woff2 +0 -0
  150. package/dist/browser/assets/noto-sans-kr-36-800-normal-DP2FYFvb.woff +0 -0
  151. package/dist/browser/assets/noto-sans-kr-36-800-normal-Udjkk8cw.woff2 +0 -0
  152. package/dist/browser/assets/noto-sans-kr-37-800-normal-BJ3T0Ds4.woff +0 -0
  153. package/dist/browser/assets/noto-sans-kr-37-800-normal-vbBvr6Mj.woff2 +0 -0
  154. package/dist/browser/assets/noto-sans-kr-38-800-normal-DHyLtjF-.woff +0 -0
  155. package/dist/browser/assets/noto-sans-kr-38-800-normal-DY5ycwVP.woff2 +0 -0
  156. package/dist/browser/assets/noto-sans-kr-39-800-normal-BYRd7hnV.woff +0 -0
  157. package/dist/browser/assets/noto-sans-kr-39-800-normal-BbTuYCr_.woff2 +0 -0
  158. package/dist/browser/assets/noto-sans-kr-4-800-normal-DaMhz_mp.woff2 +0 -0
  159. package/dist/browser/assets/noto-sans-kr-4-800-normal-DcaaVzHe.woff +0 -0
  160. package/dist/browser/assets/noto-sans-kr-40-800-normal-kb_Elzoh.woff +0 -0
  161. package/dist/browser/assets/noto-sans-kr-40-800-normal-pcG3sBn_.woff2 +0 -0
  162. package/dist/browser/assets/noto-sans-kr-41-800-normal-B5pJMNX9.woff +0 -0
  163. package/dist/browser/assets/noto-sans-kr-41-800-normal-Becq2RH9.woff2 +0 -0
  164. package/dist/browser/assets/noto-sans-kr-42-800-normal-DFBF7PpF.woff +0 -0
  165. package/dist/browser/assets/noto-sans-kr-42-800-normal-DkTTYUqF.woff2 +0 -0
  166. package/dist/browser/assets/noto-sans-kr-43-800-normal-CijJp3m4.woff2 +0 -0
  167. package/dist/browser/assets/noto-sans-kr-43-800-normal-DzNpr1oi.woff +0 -0
  168. package/dist/browser/assets/noto-sans-kr-44-800-normal-8gcTNNZz.woff2 +0 -0
  169. package/dist/browser/assets/noto-sans-kr-44-800-normal-DipWX1RS.woff +0 -0
  170. package/dist/browser/assets/noto-sans-kr-45-800-normal-CNAISROp.woff +0 -0
  171. package/dist/browser/assets/noto-sans-kr-45-800-normal-zw2L23b5.woff2 +0 -0
  172. package/dist/browser/assets/noto-sans-kr-46-800-normal-B5EFObh7.woff +0 -0
  173. package/dist/browser/assets/noto-sans-kr-46-800-normal-Be8SS2VS.woff2 +0 -0
  174. package/dist/browser/assets/noto-sans-kr-47-800-normal-CWuA3LZo.woff +0 -0
  175. package/dist/browser/assets/noto-sans-kr-47-800-normal-KHZYT9s5.woff2 +0 -0
  176. package/dist/browser/assets/noto-sans-kr-48-800-normal-DOY1zu52.woff2 +0 -0
  177. package/dist/browser/assets/noto-sans-kr-48-800-normal-X43Exj68.woff +0 -0
  178. package/dist/browser/assets/noto-sans-kr-49-800-normal-BDUKdhR6.woff +0 -0
  179. package/dist/browser/assets/noto-sans-kr-49-800-normal-CqO6w8TQ.woff2 +0 -0
  180. package/dist/browser/assets/noto-sans-kr-5-800-normal-B6D-Yisg.woff +0 -0
  181. package/dist/browser/assets/noto-sans-kr-5-800-normal-CYVng5-O.woff2 +0 -0
  182. package/dist/browser/assets/noto-sans-kr-50-800-normal-278ctRQM.woff2 +0 -0
  183. package/dist/browser/assets/noto-sans-kr-50-800-normal-D8iwvLic.woff +0 -0
  184. package/dist/browser/assets/noto-sans-kr-51-800-normal-BaGYW3mn.woff +0 -0
  185. package/dist/browser/assets/noto-sans-kr-51-800-normal-DVNzNOzs.woff2 +0 -0
  186. package/dist/browser/assets/noto-sans-kr-52-800-normal-DFoNpPkV.woff2 +0 -0
  187. package/dist/browser/assets/noto-sans-kr-52-800-normal-PYkRemOp.woff +0 -0
  188. package/dist/browser/assets/noto-sans-kr-53-800-normal-Cal8VidM.woff +0 -0
  189. package/dist/browser/assets/noto-sans-kr-53-800-normal-VTCBtYDl.woff2 +0 -0
  190. package/dist/browser/assets/noto-sans-kr-54-800-normal-D87sXgkk.woff2 +0 -0
  191. package/dist/browser/assets/noto-sans-kr-54-800-normal-IXtT1yi4.woff +0 -0
  192. package/dist/browser/assets/noto-sans-kr-55-800-normal-BHlclLP6.woff +0 -0
  193. package/dist/browser/assets/noto-sans-kr-55-800-normal-CDsENtgr.woff2 +0 -0
  194. package/dist/browser/assets/noto-sans-kr-56-800-normal-BC24-4fs.woff +0 -0
  195. package/dist/browser/assets/noto-sans-kr-56-800-normal-yP6meTAz.woff2 +0 -0
  196. package/dist/browser/assets/noto-sans-kr-57-800-normal-DHZYjKKG.woff +0 -0
  197. package/dist/browser/assets/noto-sans-kr-57-800-normal-Db3MU7be.woff2 +0 -0
  198. package/dist/browser/assets/noto-sans-kr-58-800-normal-Bgj8xXYj.woff2 +0 -0
  199. package/dist/browser/assets/noto-sans-kr-58-800-normal-DkonTaND.woff +0 -0
  200. package/dist/browser/assets/noto-sans-kr-59-800-normal-CDotMClO.woff2 +0 -0
  201. package/dist/browser/assets/noto-sans-kr-59-800-normal-F9A2zoTv.woff +0 -0
  202. package/dist/browser/assets/noto-sans-kr-6-800-normal-0EETWYnS.woff +0 -0
  203. package/dist/browser/assets/noto-sans-kr-6-800-normal-DvzBE-Ju.woff2 +0 -0
  204. package/dist/browser/assets/noto-sans-kr-60-800-normal-CFYPd-h9.woff2 +0 -0
  205. package/dist/browser/assets/noto-sans-kr-60-800-normal-CsKtzFP-.woff +0 -0
  206. package/dist/browser/assets/noto-sans-kr-61-800-normal-CcpiOzGM.woff2 +0 -0
  207. package/dist/browser/assets/noto-sans-kr-61-800-normal-Dr8kbSyF.woff +0 -0
  208. package/dist/browser/assets/noto-sans-kr-62-800-normal-CUSp51L1.woff +0 -0
  209. package/dist/browser/assets/noto-sans-kr-62-800-normal-juHSbMFI.woff2 +0 -0
  210. package/dist/browser/assets/noto-sans-kr-63-800-normal-C-Jv8bed.woff2 +0 -0
  211. package/dist/browser/assets/noto-sans-kr-63-800-normal-DyZHAD6M.woff +0 -0
  212. package/dist/browser/assets/noto-sans-kr-64-800-normal-CcBn_W6-.woff +0 -0
  213. package/dist/browser/assets/noto-sans-kr-64-800-normal-DZDmEpbV.woff2 +0 -0
  214. package/dist/browser/assets/noto-sans-kr-65-800-normal-B6c9hlnM.woff +0 -0
  215. package/dist/browser/assets/noto-sans-kr-65-800-normal-CUX4NhcL.woff2 +0 -0
  216. package/dist/browser/assets/noto-sans-kr-66-800-normal-BicMJeOH.woff2 +0 -0
  217. package/dist/browser/assets/noto-sans-kr-66-800-normal-C8Teepkx.woff +0 -0
  218. package/dist/browser/assets/noto-sans-kr-67-800-normal-BDuNl8zH.woff2 +0 -0
  219. package/dist/browser/assets/noto-sans-kr-67-800-normal-DV8QzaX3.woff +0 -0
  220. package/dist/browser/assets/noto-sans-kr-68-800-normal-B-uLhfx0.woff2 +0 -0
  221. package/dist/browser/assets/noto-sans-kr-68-800-normal-D5rgL5jh.woff +0 -0
  222. package/dist/browser/assets/noto-sans-kr-69-800-normal-Cvs4DPNE.woff2 +0 -0
  223. package/dist/browser/assets/noto-sans-kr-69-800-normal-DS_MTTAa.woff +0 -0
  224. package/dist/browser/assets/noto-sans-kr-7-800-normal-BV0xbNLH.woff2 +0 -0
  225. package/dist/browser/assets/noto-sans-kr-7-800-normal-DSoiWegj.woff +0 -0
  226. package/dist/browser/assets/noto-sans-kr-70-800-normal-2_gMfyoa.woff +0 -0
  227. package/dist/browser/assets/noto-sans-kr-70-800-normal-Ccivp_R5.woff2 +0 -0
  228. package/dist/browser/assets/noto-sans-kr-71-800-normal-BKDmUMNW.woff2 +0 -0
  229. package/dist/browser/assets/noto-sans-kr-71-800-normal-BodPXKoj.woff +0 -0
  230. package/dist/browser/assets/noto-sans-kr-72-800-normal-DWkAPAKb.woff +0 -0
  231. package/dist/browser/assets/noto-sans-kr-72-800-normal-D_-QJd8k.woff2 +0 -0
  232. package/dist/browser/assets/noto-sans-kr-73-800-normal-BWGKteHn.woff2 +0 -0
  233. package/dist/browser/assets/noto-sans-kr-73-800-normal-CoWOTZbQ.woff +0 -0
  234. package/dist/browser/assets/noto-sans-kr-74-800-normal-ClYMONqb.woff2 +0 -0
  235. package/dist/browser/assets/noto-sans-kr-74-800-normal-DO42epRW.woff +0 -0
  236. package/dist/browser/assets/noto-sans-kr-75-800-normal-DeRDdZX1.woff2 +0 -0
  237. package/dist/browser/assets/noto-sans-kr-75-800-normal-GBSDTxzy.woff +0 -0
  238. package/dist/browser/assets/noto-sans-kr-76-800-normal-De3chvya.woff2 +0 -0
  239. package/dist/browser/assets/noto-sans-kr-76-800-normal-G-QSyD_x.woff +0 -0
  240. package/dist/browser/assets/noto-sans-kr-77-800-normal-BqaEcnIO.woff +0 -0
  241. package/dist/browser/assets/noto-sans-kr-77-800-normal-D_prdc66.woff2 +0 -0
  242. package/dist/browser/assets/noto-sans-kr-78-800-normal-CBVRfgq1.woff2 +0 -0
  243. package/dist/browser/assets/noto-sans-kr-78-800-normal-CNFTj3Q6.woff +0 -0
  244. package/dist/browser/assets/noto-sans-kr-79-800-normal-CHvPT7M2.woff +0 -0
  245. package/dist/browser/assets/noto-sans-kr-79-800-normal-l-9QHPwU.woff2 +0 -0
  246. package/dist/browser/assets/noto-sans-kr-8-800-normal-BVnjuvZV.woff2 +0 -0
  247. package/dist/browser/assets/noto-sans-kr-8-800-normal-CW9POvIA.woff +0 -0
  248. package/dist/browser/assets/noto-sans-kr-80-800-normal-BmDyH9qV.woff +0 -0
  249. package/dist/browser/assets/noto-sans-kr-80-800-normal-CcFmOj-_.woff2 +0 -0
  250. package/dist/browser/assets/noto-sans-kr-81-800-normal-BebVz-4m.woff2 +0 -0
  251. package/dist/browser/assets/noto-sans-kr-81-800-normal-Chq57HAf.woff +0 -0
  252. package/dist/browser/assets/noto-sans-kr-82-800-normal-PBYyv2iB.woff +0 -0
  253. package/dist/browser/assets/noto-sans-kr-82-800-normal-T-Uuq98C.woff2 +0 -0
  254. package/dist/browser/assets/noto-sans-kr-83-800-normal-Ebp3T0Cc.woff2 +0 -0
  255. package/dist/browser/assets/noto-sans-kr-83-800-normal-YBUm2gi-.woff +0 -0
  256. package/dist/browser/assets/noto-sans-kr-84-800-normal-B5fzB2_B.woff2 +0 -0
  257. package/dist/browser/assets/noto-sans-kr-84-800-normal-CjUaLFZH.woff +0 -0
  258. package/dist/browser/assets/noto-sans-kr-85-800-normal-B3EF-yy4.woff +0 -0
  259. package/dist/browser/assets/noto-sans-kr-85-800-normal-BNXCj4iI.woff2 +0 -0
  260. package/dist/browser/assets/noto-sans-kr-86-800-normal-CJ-r59U1.woff +0 -0
  261. package/dist/browser/assets/noto-sans-kr-86-800-normal-DsjCgbm5.woff2 +0 -0
  262. package/dist/browser/assets/noto-sans-kr-87-800-normal-BP7eds1i.woff +0 -0
  263. package/dist/browser/assets/noto-sans-kr-87-800-normal-DLjlXPBa.woff2 +0 -0
  264. package/dist/browser/assets/noto-sans-kr-88-800-normal-D3d48PyQ.woff2 +0 -0
  265. package/dist/browser/assets/noto-sans-kr-88-800-normal-Yz-IPA9D.woff +0 -0
  266. package/dist/browser/assets/noto-sans-kr-89-800-normal-C3brTUST.woff +0 -0
  267. package/dist/browser/assets/noto-sans-kr-89-800-normal-DPV6dJIF.woff2 +0 -0
  268. package/dist/browser/assets/noto-sans-kr-9-800-normal-BUEQgTu0.woff2 +0 -0
  269. package/dist/browser/assets/noto-sans-kr-9-800-normal-DSOwaGDE.woff +0 -0
  270. package/dist/browser/assets/noto-sans-kr-90-800-normal-BIRlwXwP.woff +0 -0
  271. package/dist/browser/assets/noto-sans-kr-90-800-normal-Dynpszs5.woff2 +0 -0
  272. package/dist/browser/assets/noto-sans-kr-91-800-normal-CXzl4gBc.woff2 +0 -0
  273. package/dist/browser/assets/noto-sans-kr-91-800-normal-CgDxpVz2.woff +0 -0
  274. package/dist/browser/assets/noto-sans-kr-92-800-normal-CBFbbVQL.woff +0 -0
  275. package/dist/browser/assets/noto-sans-kr-92-800-normal-DlmY38cH.woff2 +0 -0
  276. package/dist/browser/assets/noto-sans-kr-93-800-normal-D7ItOUjg.woff2 +0 -0
  277. package/dist/browser/assets/noto-sans-kr-93-800-normal-D9xl1xM9.woff +0 -0
  278. package/dist/browser/assets/noto-sans-kr-94-800-normal-B4_1-zLI.woff +0 -0
  279. package/dist/browser/assets/noto-sans-kr-94-800-normal-D_inom35.woff2 +0 -0
  280. package/dist/browser/assets/noto-sans-kr-95-800-normal-5wwFPIYQ.woff2 +0 -0
  281. package/dist/browser/assets/noto-sans-kr-95-800-normal-LEtKc04Z.woff +0 -0
  282. package/dist/browser/assets/noto-sans-kr-96-800-normal-BPumKL3d.woff +0 -0
  283. package/dist/browser/assets/noto-sans-kr-96-800-normal-LcPZgb3x.woff2 +0 -0
  284. package/dist/browser/assets/noto-sans-kr-97-800-normal-B6DuSXHl.woff +0 -0
  285. package/dist/browser/assets/noto-sans-kr-97-800-normal-CM_R_M9F.woff2 +0 -0
  286. package/dist/browser/assets/noto-sans-kr-98-800-normal-BIIfv0pl.woff +0 -0
  287. package/dist/browser/assets/noto-sans-kr-98-800-normal-Db3f-61W.woff2 +0 -0
  288. package/dist/browser/assets/noto-sans-kr-99-800-normal-Bw0sYswE.woff2 +0 -0
  289. package/dist/browser/assets/noto-sans-kr-99-800-normal-iudhGOO-.woff +0 -0
  290. package/dist/browser/assets/noto-sans-kr-cyrillic-800-normal-CfofMVm8.woff +0 -0
  291. package/dist/browser/assets/noto-sans-kr-cyrillic-800-normal-DYNGV4gq.woff2 +0 -0
  292. package/dist/browser/assets/noto-sans-kr-latin-800-normal-BYRGi1Y1.woff2 +0 -0
  293. package/dist/browser/assets/noto-sans-kr-latin-800-normal-LOyLFpCE.woff +0 -0
  294. package/dist/browser/assets/noto-sans-kr-latin-ext-800-normal-BLx4oR_u.woff2 +0 -0
  295. package/dist/browser/assets/noto-sans-kr-latin-ext-800-normal-Ct5SpdHD.woff +0 -0
  296. package/dist/browser/assets/noto-sans-kr-vietnamese-800-normal-B93Gh2EH.woff2 +0 -0
  297. package/dist/browser/assets/noto-sans-kr-vietnamese-800-normal-DSdRDyh3.woff +0 -0
  298. package/dist/browser/assets/outfit-latin-800-normal-BRHLSPcU.woff +0 -0
  299. package/dist/browser/assets/page-header-BMoXdF8-.css +0 -1
  300. package/dist/browser/assets/page-header-_RNO4t-D.js +0 -56
  301. package/dist/browser/assets/product-C_SFSrKx.js +0 -75
  302. package/dist/browser/assets/product-f-WX-_YX.css +0 -1
  303. package/dist/skills/gitifact-workflow/SKILL.md +0 -157
  304. /package/dist/browser/{assets/outfit-latin-800-normal-CQna6-G7.woff2 → fonts/outfit-latin-800-normal.woff2} +0 -0
package/dist/main.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
- var __export = (target2, all) => {
3
+ var __export = (target, all) => {
4
4
  for (var name in all)
5
- __defProp(target2, name, { get: all[name], enumerable: true });
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
8
  // scripts/node-require.mjs
@@ -1635,9 +1635,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
1635
1635
  * @param {string} invalidArgumentMessage
1636
1636
  * @private
1637
1637
  */
1638
- _callParseArg(target2, value, previous, invalidArgumentMessage) {
1638
+ _callParseArg(target, value, previous, invalidArgumentMessage) {
1639
1639
  try {
1640
- return target2.parseArg(value, previous);
1640
+ return target.parseArg(value, previous);
1641
1641
  } catch (err) {
1642
1642
  if (err.code === "commander.invalidArgument") {
1643
1643
  const message = `${invalidArgumentMessage} ${err.message}`;
@@ -1926,13 +1926,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1926
1926
  * @param {string} source - expected values are default/config/env/cli/implied
1927
1927
  * @return {Command} `this` command for chaining
1928
1928
  */
1929
- setOptionValueWithSource(key, value, source2) {
1929
+ setOptionValueWithSource(key, value, source) {
1930
1930
  if (this._storeOptionsAsProperties) {
1931
1931
  this[key] = value;
1932
1932
  } else {
1933
1933
  this._optionValues[key] = value;
1934
1934
  }
1935
- this._optionValueSources[key] = source2;
1935
+ this._optionValueSources[key] = source;
1936
1936
  return this;
1937
1937
  }
1938
1938
  /**
@@ -1953,13 +1953,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1953
1953
  * @return {string}
1954
1954
  */
1955
1955
  getOptionValueSourceWithGlobals(key) {
1956
- let source2;
1956
+ let source;
1957
1957
  this._getCommandAndAncestors().forEach((cmd) => {
1958
1958
  if (cmd.getOptionValueSource(key) !== void 0) {
1959
- source2 = cmd.getOptionValueSource(key);
1959
+ source = cmd.getOptionValueSource(key);
1960
1960
  }
1961
1961
  });
1962
- return source2;
1962
+ return source;
1963
1963
  }
1964
1964
  /**
1965
1965
  * Get user arguments from implied or explicit arguments.
@@ -2826,10 +2826,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2826
2826
  const optionKey = option2.attributeName();
2827
2827
  const optionValue = this.getOptionValue(optionKey);
2828
2828
  const negativeOption = this.options.find(
2829
- (target2) => target2.negate && optionKey === target2.attributeName()
2829
+ (target) => target.negate && optionKey === target.attributeName()
2830
2830
  );
2831
2831
  const positiveOption = this.options.find(
2832
- (target2) => !target2.negate && optionKey === target2.attributeName()
2832
+ (target) => !target.negate && optionKey === target.attributeName()
2833
2833
  );
2834
2834
  if (negativeOption && (negativeOption.presetArg === void 0 && optionValue === false || negativeOption.presetArg !== void 0 && optionValue === negativeOption.presetArg)) {
2835
2835
  return negativeOption;
@@ -2839,8 +2839,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
2839
2839
  const getErrorMessage = (option2) => {
2840
2840
  const bestOption = findBestOptionFromValue(option2);
2841
2841
  const optionKey = bestOption.attributeName();
2842
- const source2 = this.getOptionValueSource(optionKey);
2843
- if (source2 === "env") {
2842
+ const source = this.getOptionValueSource(optionKey);
2843
+ if (source === "env") {
2844
2844
  return `environment variable '${bestOption.envVar}'`;
2845
2845
  }
2846
2846
  return `option '${bestOption.flags}'`;
@@ -3492,12 +3492,36 @@ function parseManagedConfig(text2) {
3492
3492
  var SpecPreviewError = class extends Error {
3493
3493
  code = "INVALID_SPEC_PREVIEW";
3494
3494
  };
3495
+ var DOCUMENT_KINDS = ["product", "guide"];
3496
+ var DOCUMENT_DIRS = { product: ".gitifact/product", guide: ".gitifact/guides" };
3497
+ var documentHistoryPath = (kind) => DOCUMENT_DIRS[kind] + "/history.jsonl";
3498
+ var PRODUCT_PATH = ".gitifact/product/PRODUCT.md";
3499
+ var PRODUCT_ASSET_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,120}\.(?:png|jpe?g|gif|svg|webp)$/i;
3500
+ function isProductAssetPath(path3) {
3501
+ const match = /^\.gitifact\/product\/([^/]+)$/.exec(path3);
3502
+ return !!match && PRODUCT_ASSET_NAME.test(match[1]);
3503
+ }
3495
3504
  var token = "[a-z2-7]{10}";
3496
3505
  var STORE_DIR = ".gitifact";
3497
3506
  var LEGACY_STORE_DIR = ".tryce";
3498
3507
  var STORE_DIRS = [STORE_DIR, LEGACY_STORE_DIR];
3499
3508
  var marker = "(?:gitifact|tryce)";
3500
- var recordPathPattern = /^\.(?:gitifact|tryce)\/spec\/[^/]+\/(?:requirements\.md|design\.md|history\.jsonl)$/;
3509
+ var recordPathPattern = /^(?:\.(?:gitifact|tryce)\/spec\/[^/]+\/(?:requirements\.md|design\.md|history\.jsonl)|\.gitifact\/(?:product|guides)\/(?:(?:[^/]+\/)*[^/]+\.md|history\.jsonl))$/;
3510
+ var documentPathPattern = /^\.gitifact\/(product|guides)\/((?:[^/]+\/)*[^/]+\.md)$/;
3511
+ var documentHistoryPattern = /^\.gitifact\/(product|guides)\/history\.jsonl$/;
3512
+ var kindOfDir = (dir) => dir === "product" ? "product" : "guide";
3513
+ var nameToken = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3514
+ function validateDocumentRelativePath(relative) {
3515
+ const parts = relative.split("/");
3516
+ if (relative.length > 200 || parts.length > 8)
3517
+ fail("\uBB38\uC11C \uACBD\uB85C \uAE4A\uC774 \uB610\uB294 \uAE38\uC774 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4: " + relative);
3518
+ const file2 = parts.pop();
3519
+ if (!file2.endsWith(".md") || !nameToken.test(file2.slice(0, -3)))
3520
+ fail("\uBB38\uC11C \uD30C\uC77C \uC774\uB984\uC740 \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uACFC .md\uC5EC\uC57C \uD569\uB2C8\uB2E4: " + relative);
3521
+ for (const part of parts)
3522
+ if (!nameToken.test(part) || part.length > 80)
3523
+ fail("\uBB38\uC11C \uD3F4\uB354 \uC774\uB984\uC740 \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: " + relative);
3524
+ }
3501
3525
  var storeRelative = (path3) => path3.replace(/^\.(?:gitifact|tryce)\//, "");
3502
3526
  var reqId = new RegExp(`^R-${token}$`);
3503
3527
  var fail = (message) => {
@@ -3512,10 +3536,10 @@ function renderDesignPreview(id, design) {
3512
3536
  ${design.body}
3513
3537
  `;
3514
3538
  }
3515
- function parseDesignPreview(source2, specId) {
3516
- if (source2.includes("\0") || /\r(?!\n)/.test(source2))
3539
+ function parseDesignPreview(source, specId) {
3540
+ if (source.includes("\0") || /\r(?!\n)/.test(source))
3517
3541
  fail("\uC798\uBABB\uB41C \uC124\uACC4 \uBB38\uC790\uC785\uB2C8\uB2E4.");
3518
- const lines = normalized(source2).split("\n");
3542
+ const lines = normalized(source).split("\n");
3519
3543
  if (!new RegExp(`^<!-- ${marker}-design: ${specId} -->$`).test(lines.shift() ?? ""))
3520
3544
  fail("\uC124\uACC4\uC758 gitifact-design ID\uB294 \uAC19\uC740 \uAE30\uB2A5\uC758 S-ID\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
3521
3545
  while (lines[0] === "")
@@ -3556,27 +3580,138 @@ function parseDesignPreview(source2, specId) {
3556
3580
  fail("\uC124\uACC4 \uCF54\uB4DC \uBE14\uB85D\uC774 \uB2EB\uD788\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
3557
3581
  return { title, body, requirements: [...references] };
3558
3582
  }
3583
+ function renderDocument(doc) {
3584
+ return `<!-- gitifact-${doc.kind}: ${doc.id} -->
3585
+
3586
+ # ${doc.title}
3587
+
3588
+ ${doc.body}
3589
+ `;
3590
+ }
3591
+ function parseDocument(path3, source) {
3592
+ const match = documentPathPattern.exec(path3);
3593
+ if (!match)
3594
+ fail("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBB38\uC11C \uACBD\uB85C: " + path3);
3595
+ const kind = kindOfDir(match[1]);
3596
+ if (kind === "product") {
3597
+ if (path3 !== PRODUCT_PATH)
3598
+ fail("\uC81C\uD488 \uD3F4\uB354\uC5D0\uB294 PRODUCT.md \uD558\uB098\uB9CC \uB461\uB2C8\uB2E4: " + path3);
3599
+ } else
3600
+ validateDocumentRelativePath(match[2]);
3601
+ if (source.includes("\0") || /\r(?!\n)/.test(source))
3602
+ fail("\uC798\uBABB\uB41C \uBB38\uC11C \uBB38\uC790: " + path3);
3603
+ const lines = normalized(source).split("\n");
3604
+ const id = new RegExp(`^<!-- gitifact-${kind}: ([PG]-${token}) -->$`).exec(lines.shift() ?? "")?.[1];
3605
+ if (!id || id[0] !== (kind === "product" ? "P" : "G"))
3606
+ return fail(`\uCCAB \uC904\uC5D0 \uC720\uD6A8\uD55C gitifact-${kind} \uC8FC\uC11D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: ` + path3);
3607
+ while (lines[0] === "")
3608
+ lines.shift();
3609
+ const heading = lines.shift();
3610
+ if (!heading?.startsWith("# ") || !heading.slice(2).trim())
3611
+ fail("\uBB38\uC11C \uC81C\uBAA9\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: " + path3);
3612
+ const title = heading.slice(2).trim();
3613
+ const body = normalized(lines.join("\n"));
3614
+ if (!body)
3615
+ fail("\uBB38\uC11C \uBCF8\uBB38\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: " + path3);
3616
+ let fence;
3617
+ for (const line of body.split("\n")) {
3618
+ if (fence) {
3619
+ if (new RegExp(`^ {0,3}${fence.char}{${fence.size},}\\s*$`).test(line))
3620
+ fence = void 0;
3621
+ continue;
3622
+ }
3623
+ const open6 = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
3624
+ if (open6) {
3625
+ fence = { char: open6[1][0], size: open6[1].length };
3626
+ continue;
3627
+ }
3628
+ if (/^#\s/.test(line))
3629
+ fail("\uBB38\uC11C \uCD5C\uC0C1\uC704 \uC81C\uBAA9\uC740 \uD558\uB098\uB9CC \uC501\uB2C8\uB2E4: " + path3);
3630
+ if (/<!-- (?:gitifact|tryce)-/.test(line))
3631
+ fail("\uBB38\uC11C \uBCF8\uBB38\uC5D0\uB294 gitifact \uC8FC\uC11D\uC744 \uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + path3);
3632
+ }
3633
+ if (fence)
3634
+ fail("\uBB38\uC11C \uCF54\uB4DC \uBE14\uB85D\uC774 \uB2EB\uD788\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: " + path3);
3635
+ return { id, kind, path: path3, title, body };
3636
+ }
3637
+ function parseDocumentFiles(files) {
3638
+ const sets = DOCUMENT_KINDS.map((kind) => ({ kind, documents: [], history: parsePreviewHistory(files.get(documentHistoryPath(kind)) ?? "", "documents") }));
3639
+ for (const [path3, source] of files) {
3640
+ if (!documentPathPattern.test(path3))
3641
+ continue;
3642
+ const doc = parseDocument(path3, source);
3643
+ sets.find((s) => s.kind === doc.kind).documents.push(doc);
3644
+ }
3645
+ for (const set2 of sets)
3646
+ set2.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
3647
+ validateDocumentSets(sets);
3648
+ return sets;
3649
+ }
3650
+ function validateDocumentSets(sets) {
3651
+ const ids = /* @__PURE__ */ new Set();
3652
+ const paths2 = /* @__PURE__ */ new Set();
3653
+ const historyIds = /* @__PURE__ */ new Set();
3654
+ if (sets.map((s) => s.kind).sort().join(",") !== "guide,product")
3655
+ fail("\uBB38\uC11C \uC885\uB958 \uC9D1\uD569\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4.");
3656
+ for (const set2 of sets) {
3657
+ for (const d of set2.documents) {
3658
+ if (d.kind !== set2.kind)
3659
+ fail("\uBB38\uC11C \uC885\uB958\uAC00 \uD3F4\uB354\uC640 \uB2E4\uB985\uB2C8\uB2E4: " + d.path);
3660
+ if (ids.has(d.id))
3661
+ fail("\uC800\uC7A5\uC18C \uB0B4 \uC911\uBCF5 \uBB38\uC11C ID: " + d.id);
3662
+ ids.add(d.id);
3663
+ const lower = d.path.toLowerCase();
3664
+ if (paths2.has(lower))
3665
+ fail("\uB300\uC18C\uBB38\uC790\uB9CC \uB2E4\uB978 \uBB38\uC11C \uACBD\uB85C: " + d.path);
3666
+ paths2.add(lower);
3667
+ }
3668
+ for (const h of set2.history) {
3669
+ if (historyIds.has(h.id))
3670
+ fail("\uC800\uC7A5\uC18C \uB0B4 \uC911\uBCF5 \uBCF4\uC870 \uAE30\uB85D ID: " + h.id);
3671
+ historyIds.add(h.id);
3672
+ }
3673
+ }
3674
+ }
3675
+ var emptyDocumentSets = () => DOCUMENT_KINDS.map((kind) => ({ kind, documents: [], history: [] }));
3676
+ var asBundle = (value) => Array.isArray(value) ? { specs: value, documents: emptyDocumentSets() } : value;
3677
+ function parsePreviewBundle(files) {
3678
+ const bundle = { specs: parsePreviewFiles(files), documents: parseDocumentFiles(files) };
3679
+ validateBundle(bundle);
3680
+ return bundle;
3681
+ }
3682
+ function validateBundle(bundle) {
3683
+ validatePreviewSnapshot(bundle.specs);
3684
+ validateDocumentSets(bundle.documents);
3685
+ const ids = /* @__PURE__ */ new Set();
3686
+ for (const h of [...bundle.specs.flatMap((s) => s.history), ...bundle.documents.flatMap((d) => d.history)]) {
3687
+ if (ids.has(h.id))
3688
+ fail("\uC800\uC7A5\uC18C \uB0B4 \uC911\uBCF5 \uBCF4\uC870 \uAE30\uB85D ID: " + h.id);
3689
+ ids.add(h.id);
3690
+ }
3691
+ }
3559
3692
  function designReferenceWarnings(specs) {
3560
3693
  const known = new Set(specs.flatMap((s) => s.requirements.map((r) => r.id)));
3561
3694
  return specs.flatMap((s) => (s.design?.requirements ?? []).filter((id) => !known.has(id)).map((id) => ({ specId: s.id, requirement: id, code: "MISSING_DESIGN_REFERENCE" })));
3562
3695
  }
3563
3696
  function parsePreviewFiles(files) {
3564
3697
  const specs = [];
3565
- for (const [path3, source2] of files) {
3698
+ for (const [path3, source] of files) {
3699
+ if (documentPathPattern.test(path3) || documentHistoryPattern.test(path3))
3700
+ continue;
3566
3701
  if (path3.endsWith("/requirements.md")) {
3567
- specs.push(parseSpecPreview(path3, source2, files.get(path3.replace(/requirements\.md$/, "history.jsonl")) ?? "", files.get(path3.replace(/requirements\.md$/, "design.md"))));
3702
+ specs.push(parseSpecPreview(path3, source, files.get(path3.replace(/requirements\.md$/, "history.jsonl")) ?? "", files.get(path3.replace(/requirements\.md$/, "design.md"))));
3568
3703
  } else if (!files.has(path3.replace(/(?:history\.jsonl|design\.md)$/, "requirements.md")))
3569
3704
  fail("\uC694\uAD6C\uC0AC\uD56D \uBB38\uC11C \uC5C6\uB294 \uC124\uACC4 \uB610\uB294 \uC774\uC720 \uD30C\uC77C: " + path3);
3570
3705
  }
3571
3706
  validatePreviewSnapshot(specs);
3572
3707
  return specs;
3573
3708
  }
3574
- function parseSpecPreview(path3, source2, history = "", designSource) {
3709
+ function parseSpecPreview(path3, source, history = "", designSource) {
3575
3710
  if (!/^\.(?:gitifact|tryce)\/spec\/[^/]+\/requirements\.md$/.test(path3))
3576
3711
  fail("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBA85\uC138 \uACBD\uB85C: " + path3);
3577
- if (source2.includes("\0") || source2.includes("\r") && /\r(?!\n)/.test(source2))
3712
+ if (source.includes("\0") || source.includes("\r") && /\r(?!\n)/.test(source))
3578
3713
  fail("\uC798\uBABB\uB41C \uBA85\uC138 \uBB38\uC790: " + path3);
3579
- const lines = normalized(source2).split("\n");
3714
+ const lines = normalized(source).split("\n");
3580
3715
  const specMatch = new RegExp(`^<!-- ${marker}-spec: (S-${token}) -->$`).exec(lines[0] ?? "");
3581
3716
  if (!specMatch)
3582
3717
  fail("\uCCAB \uC904\uC5D0 \uC720\uD6A8\uD55C gitifact-spec \uC8FC\uC11D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: " + path3);
@@ -3651,10 +3786,11 @@ function parseSpecPreview(path3, source2, history = "", designSource) {
3651
3786
  }
3652
3787
  return { id: specMatch[1], path: path3, title, description: normalized(description), requirements, history: parsePreviewHistory(history), ...designSource === void 0 ? {} : { design: parseDesignPreview(designSource, specMatch[1]) } };
3653
3788
  }
3654
- function parsePreviewHistory(source2) {
3789
+ function parsePreviewHistory(source, scope = "spec") {
3655
3790
  const records = [];
3656
3791
  const ids = /* @__PURE__ */ new Set();
3657
- for (const line of source2.replace(/\r\n/g, "\n").split("\n")) {
3792
+ const shapes = scope === "spec" ? ["id,reason,requirements", "designs,id,reason,requirements"] : ["documents,id,reason,requirements"];
3793
+ for (const line of source.replace(/\r\n/g, "\n").split("\n")) {
3658
3794
  if (!line.trim())
3659
3795
  continue;
3660
3796
  let value;
@@ -3666,10 +3802,10 @@ function parsePreviewHistory(source2) {
3666
3802
  if (!value || typeof value !== "object" || Array.isArray(value))
3667
3803
  fail("\uC798\uBABB\uB41C \uBCF4\uC870 \uAE30\uB85D\uC785\uB2C8\uB2E4.");
3668
3804
  const r = value;
3669
- if (!["id,reason,requirements", "designs,id,reason,requirements"].includes(Object.keys(r).sort().join(",")) || typeof r.id !== "string" || !new RegExp(`^H-${token}$`).test(r.id) || ids.has(r.id) || typeof r.reason !== "string" || !r.reason.trim() || !Array.isArray(r.requirements) || !r.requirements.length && !Array.isArray(r.designs) || r.designs !== void 0 && (!Array.isArray(r.designs) || !r.designs.length || r.designs.some((id) => typeof id !== "string" || !/^S-[a-z2-7]{10}$/.test(id)) || new Set(r.designs).size !== r.designs.length) || r.requirements.some((id) => typeof id !== "string" || !reqId.test(id)) || new Set(r.requirements).size !== r.requirements.length)
3805
+ if (!shapes.includes(Object.keys(r).sort().join(",")) || typeof r.id !== "string" || !new RegExp(`^H-${token}$`).test(r.id) || ids.has(r.id) || typeof r.reason !== "string" || !r.reason.trim() || !Array.isArray(r.requirements) || !r.requirements.length && !Array.isArray(r.designs) && !Array.isArray(r.documents) || r.documents !== void 0 && (r.requirements.length || !Array.isArray(r.documents) || !r.documents.length || r.documents.some((id) => typeof id !== "string" || !/^[PG]-[a-z2-7]{10}$/.test(id)) || new Set(r.documents).size !== r.documents.length) || r.designs !== void 0 && (!Array.isArray(r.designs) || !r.designs.length || r.designs.some((id) => typeof id !== "string" || !/^S-[a-z2-7]{10}$/.test(id)) || new Set(r.designs).size !== r.designs.length) || r.requirements.some((id) => typeof id !== "string" || !reqId.test(id)) || new Set(r.requirements).size !== r.requirements.length)
3670
3806
  fail("\uBCF4\uC870 \uAE30\uB85D\uC758 \uD544\uB4DC\xB7ID\xB7\uB300\uC0C1\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4.");
3671
3807
  ids.add(r.id);
3672
- records.push({ id: r.id, requirements: r.requirements, ...r.designs === void 0 ? {} : { designs: r.designs }, reason: r.reason });
3808
+ records.push({ id: r.id, requirements: r.requirements, ...r.designs === void 0 ? {} : { designs: r.designs }, ...r.documents === void 0 ? {} : { documents: r.documents }, reason: r.reason });
3673
3809
  }
3674
3810
  return records;
3675
3811
  }
@@ -3743,6 +3879,50 @@ function compareSpecPreviews(before, after) {
3743
3879
  });
3744
3880
  return { specChanges, changes };
3745
3881
  }
3882
+ function compareDocumentSets(before, after) {
3883
+ validateDocumentSets(before);
3884
+ validateDocumentSets(after);
3885
+ const index = (sets) => new Map(sets.flatMap((s) => s.documents.map((d) => [d.id, { id: d.id, title: d.title, body: d.body, specId: d.kind, path: d.path }])));
3886
+ const prev = index(before);
3887
+ const next = index(after);
3888
+ const previousHistory = new Map(before.flatMap((s) => s.history.map((h) => [h.id, h])));
3889
+ const reasons = after.flatMap((s) => s.history).filter((h) => {
3890
+ const old = previousHistory.get(h.id);
3891
+ if (old && JSON.stringify(old) !== JSON.stringify(h))
3892
+ fail("\uAE30\uC874 \uBCF4\uC870 \uAE30\uB85D\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + h.id);
3893
+ return !old;
3894
+ });
3895
+ const changes = [.../* @__PURE__ */ new Set([...prev.keys(), ...next.keys()])].sort().flatMap((id) => {
3896
+ const from = prev.get(id) ?? null;
3897
+ const to = next.get(id) ?? null;
3898
+ const types = [];
3899
+ if (!from)
3900
+ types.push("created");
3901
+ else if (!to)
3902
+ types.push("deleted");
3903
+ else {
3904
+ if (from.path !== to.path)
3905
+ types.push("moved");
3906
+ if (from.title !== to.title || from.body !== to.body)
3907
+ types.push("modified");
3908
+ }
3909
+ const kind = (to ?? from).specId;
3910
+ return types.length ? [{ id, types, before: from, after: to, kind, reasons: reasons.filter((h) => (h.documents ?? []).includes(id)) }] : [];
3911
+ });
3912
+ const changed = new Set(changes.map((c) => c.id));
3913
+ for (const h of reasons)
3914
+ if ((h.documents ?? []).some((id) => !changed.has(id)))
3915
+ fail("\uC0C8 \uBCF4\uC870 \uAE30\uB85D\uC774 \uC774\uBC88 \uBB38\uC11C \uBCC0\uACBD\uACFC \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + h.id);
3916
+ return changes;
3917
+ }
3918
+ function comparePreviewBundles(before, after) {
3919
+ const a = asBundle(before);
3920
+ const b = asBundle(after);
3921
+ validateBundle(a);
3922
+ validateBundle(b);
3923
+ const specs = compareSpecPreviews(a.specs, b.specs);
3924
+ return { specChanges: specs.specChanges, changes: [...specs.changes, ...compareDocumentSets(a.documents, b.documents)] };
3925
+ }
3746
3926
 
3747
3927
  // ../../packages/core/dist/use-cases/edit-spec-preview.js
3748
3928
  var fail2 = (message) => {
@@ -3769,9 +3949,11 @@ ${r.body}
3769
3949
  function editSpecPreview(original, input2, generate) {
3770
3950
  if (!Array.isArray(input2) || !input2.length || input2.length > 100)
3771
3951
  fail2("operations\uB294 1~100\uAC1C\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
3772
- const specs = original.map((s) => ({ ...s, requirements: s.requirements.map((r) => ({ ...r })), history: s.history.map((h) => ({ ...h, requirements: [...h.requirements] })) }));
3773
- validatePreviewSnapshot(specs);
3774
- const used = new Set(specs.flatMap((s) => [s.id, ...s.requirements.map((r) => r.id)]));
3952
+ const source = asBundle(original);
3953
+ const specs = source.specs.map((s) => ({ ...s, requirements: s.requirements.map((r) => ({ ...r })), history: s.history.map((h) => ({ ...h, requirements: [...h.requirements] })) }));
3954
+ const documents = source.documents.map((set2) => ({ ...set2, documents: set2.documents.map((d) => ({ ...d })), history: set2.history.map((h) => ({ ...h })) }));
3955
+ validateBundle({ specs, documents });
3956
+ const used = /* @__PURE__ */ new Set([...specs.flatMap((s) => [s.id, ...s.requirements.map((r) => r.id)]), ...documents.flatMap((d) => d.documents.map((x) => x.id))]);
3775
3957
  const allocate = (prefix) => {
3776
3958
  for (let attempt = 0; attempt < 100; attempt++) {
3777
3959
  const id = generate(prefix);
@@ -3796,7 +3978,13 @@ function editSpecPreview(original, input2, generate) {
3796
3978
  move: ["type", "id", "feature"],
3797
3979
  "rename-spec": ["type", "id", "title"],
3798
3980
  "set-design": ["type", "feature", "title", "body"],
3799
- "delete-design": ["type", "feature"]
3981
+ "delete-design": ["type", "feature"],
3982
+ "create-doc": ["type", "path", "title", "body"],
3983
+ "update-doc": ["type", "id", "title", "body"],
3984
+ "move-doc": ["type", "id", "path"],
3985
+ "delete-doc": ["type", "id"],
3986
+ "set-product": ["type", "title", "body"],
3987
+ "delete-product": ["type"]
3800
3988
  };
3801
3989
  const type = String(op.type);
3802
3990
  const fields2 = schemas[type];
@@ -3812,10 +4000,63 @@ function editSpecPreview(original, input2, generate) {
3812
4000
  fail2("\uC798\uBABB\uB41C \uD3B8\uC9D1 \uBB38\uC790: " + key);
3813
4001
  return value;
3814
4002
  };
3815
- const target2 = () => {
4003
+ const target = () => {
3816
4004
  const path3 = previewPath(str("feature"));
3817
4005
  return specs.find((s) => s.path === path3) ?? fail2("\uB300\uC0C1 \uBA85\uC138\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: " + path3);
3818
4006
  };
4007
+ if (type === "set-product" || type === "delete-product") {
4008
+ const set2 = documents.find((s) => s.kind === "product");
4009
+ const existing = set2.documents[0];
4010
+ if (type === "delete-product") {
4011
+ if (!existing)
4012
+ fail2("\uC0AD\uC81C\uD560 \uC81C\uD488 \uBB38\uC11C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
4013
+ set2.documents = [];
4014
+ results.push({ type, id: existing.id });
4015
+ } else {
4016
+ const doc = { id: existing?.id ?? allocate("P"), kind: "product", path: PRODUCT_PATH, title: str("title"), body: str("body") };
4017
+ set2.documents = [doc];
4018
+ results.push({ type, id: doc.id });
4019
+ }
4020
+ continue;
4021
+ }
4022
+ if (type.endsWith("-doc")) {
4023
+ const all = () => documents.filter((set2) => set2.kind === "guide").flatMap((set2) => set2.documents);
4024
+ const documentPath = (kind, self) => {
4025
+ const relative = str("path");
4026
+ validateDocumentRelativePath(relative);
4027
+ const path3 = `${DOCUMENT_DIRS[kind]}/${relative}`;
4028
+ if (self && self.path === path3)
4029
+ fail2("\uC774\uBBF8 \uAC19\uC740 \uACBD\uB85C\uC785\uB2C8\uB2E4.");
4030
+ if (all().some((d) => d !== self && d.path.toLowerCase() === path3.toLowerCase()))
4031
+ fail2("\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uBB38\uC11C \uACBD\uB85C\uC785\uB2C8\uB2E4: " + path3);
4032
+ return path3;
4033
+ };
4034
+ const find = () => {
4035
+ const id = str("id");
4036
+ return all().find((d) => d.id === id) ?? fail2("\uC9C0\uCE68 \uBB38\uC11C ID\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: " + id);
4037
+ };
4038
+ if (type === "create-doc") {
4039
+ const kind = "guide";
4040
+ const doc = { id: allocate("G"), kind, path: documentPath(kind), title: str("title"), body: str("body") };
4041
+ documents.find((s) => s.kind === kind).documents.push(doc);
4042
+ results.push({ type, id: doc.id });
4043
+ } else if (type === "update-doc") {
4044
+ const doc = find();
4045
+ doc.title = str("title");
4046
+ doc.body = str("body");
4047
+ results.push({ type, id: doc.id });
4048
+ } else if (type === "move-doc") {
4049
+ const doc = find();
4050
+ doc.path = documentPath(doc.kind, doc);
4051
+ results.push({ type, id: doc.id });
4052
+ } else {
4053
+ const doc = find();
4054
+ const set2 = documents.find((s) => s.kind === doc.kind);
4055
+ set2.documents = set2.documents.filter((d) => d.id !== doc.id);
4056
+ results.push({ type, id: doc.id });
4057
+ }
4058
+ continue;
4059
+ }
3819
4060
  if (type === "create") {
3820
4061
  const path3 = previewPath(str("feature"));
3821
4062
  if (specs.some((s) => s.path.toLowerCase() === path3.toLowerCase()))
@@ -3824,12 +4065,12 @@ function editSpecPreview(original, input2, generate) {
3824
4065
  specs.push(spec2);
3825
4066
  results.push({ type, id: spec2.id });
3826
4067
  } else if (type === "add") {
3827
- const spec2 = target2();
4068
+ const spec2 = target();
3828
4069
  const req = { id: allocate("R"), title: str("title"), body: str("body") };
3829
4070
  spec2.requirements.push(req);
3830
4071
  results.push({ type, id: req.id });
3831
4072
  } else if (type === "set-design" || type === "delete-design") {
3832
- const spec2 = target2();
4073
+ const spec2 = target();
3833
4074
  if (type === "delete-design") {
3834
4075
  if (!spec2.design)
3835
4076
  fail2("\uC0AD\uC81C\uD560 \uC124\uACC4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
@@ -3846,7 +4087,7 @@ function editSpecPreview(original, input2, generate) {
3846
4087
  const source2 = specs.find((s) => s.requirements.some((r) => r.id === id)) ?? fail2("\uC694\uAD6C\uC0AC\uD56D ID\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
3847
4088
  const req = source2.requirements.find((r) => r.id === id);
3848
4089
  if (type === "move") {
3849
- const destination = target2();
4090
+ const destination = target();
3850
4091
  if (source2 === destination)
3851
4092
  fail2("\uC774\uBBF8 \uAC19\uC740 \uBA85\uC138\uC5D0 \uC18D\uD569\uB2C8\uB2E4.");
3852
4093
  source2.requirements = source2.requirements.filter((r) => r.id !== id);
@@ -3863,8 +4104,14 @@ function editSpecPreview(original, input2, generate) {
3863
4104
  if (JSON.stringify({ ...parsed, history: spec2.history }) !== JSON.stringify(spec2))
3864
4105
  fail2("\uBCF8\uBB38\uC774 \uBA85\uC138 \uAD6C\uC870\uB97C \uBCC0\uACBD\uD569\uB2C8\uB2E4.");
3865
4106
  }
3866
- validatePreviewSnapshot(specs);
3867
- return { specs, results };
4107
+ for (const set2 of documents) {
4108
+ set2.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
4109
+ for (const doc of set2.documents)
4110
+ if (JSON.stringify(parseDocument(doc.path, renderDocument(doc))) !== JSON.stringify(doc))
4111
+ fail2("\uBCF8\uBB38\uC774 \uBB38\uC11C \uAD6C\uC870\uB97C \uBCC0\uACBD\uD569\uB2C8\uB2E4: " + doc.path);
4112
+ }
4113
+ validateBundle({ specs, documents });
4114
+ return { specs, documents, results };
3868
4115
  }
3869
4116
 
3870
4117
  // ../../packages/core/dist/use-cases/prepare-spec-preview.js
@@ -3873,27 +4120,50 @@ var fail3 = (message) => {
3873
4120
  };
3874
4121
  var equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
3875
4122
  var historyPath = (s) => s.path.replace(/requirements\.md$/, "history.jsonl");
3876
- function finalSpecPreviewChanges(before, current) {
3877
- validatePreviewSnapshot(before);
3878
- validatePreviewSnapshot(current);
3879
- for (const previous of before) {
3880
- const next = current.find((s) => s.id === previous.id);
4123
+ function pendingPreviewReasons(before, current) {
4124
+ const a = asBundle(before);
4125
+ const b = asBundle(current);
4126
+ return [
4127
+ ...b.specs.flatMap((s) => s.history.slice(a.specs.find((p) => p.id === s.id)?.history.length ?? 0).map((h) => ({ specId: s.id, path: historyPath(s), ...h }))),
4128
+ ...b.documents.flatMap((d) => d.history.slice(a.documents.find((p) => p.kind === d.kind)?.history.length ?? 0).map((h) => ({ kind: d.kind, path: documentHistoryPath(d.kind), ...h })))
4129
+ ];
4130
+ }
4131
+ function finalSpecPreviewChanges(beforeValue, currentValue) {
4132
+ const before = asBundle(beforeValue);
4133
+ const current = asBundle(currentValue);
4134
+ validateBundle(before);
4135
+ validateBundle(current);
4136
+ for (const previous of before.specs) {
4137
+ const next = current.specs.find((s) => s.id === previous.id);
3881
4138
  if (previous.history.length && !next)
3882
4139
  fail3("\uAE30\uC874 \uC774\uC720\uB97C \uBCF4\uC874\uD560 \uBE48 \uBA85\uC138\uB97C \uB0A8\uACA8\uC8FC\uC138\uC694: " + previous.id);
3883
4140
  if (next && !equal(next.history.slice(0, previous.history.length), previous.history))
3884
4141
  fail3("\uCEE4\uBC0B\uB41C \uC774\uC720\uC758 \uC218\uC815\xB7\uC0AD\uC81C\xB7\uC7AC\uBC30\uCE58\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + previous.id);
3885
4142
  }
3886
- const clean = current.map((s) => ({ ...s, history: before.find((p) => p.id === s.id)?.history ?? [] }));
3887
- return compareSpecPreviews(before, clean);
4143
+ for (const previous of before.documents) {
4144
+ const next = current.documents.find((s) => s.kind === previous.kind);
4145
+ if (!equal(next.history.slice(0, previous.history.length), previous.history))
4146
+ fail3("\uCEE4\uBC0B\uB41C \uBB38\uC11C \uC774\uC720\uC758 \uC218\uC815\xB7\uC0AD\uC81C\xB7\uC7AC\uBC30\uCE58\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + previous.kind);
4147
+ }
4148
+ const clean = {
4149
+ specs: current.specs.map((s) => ({ ...s, history: before.specs.find((p) => p.id === s.id)?.history ?? [] })),
4150
+ documents: current.documents.map((d) => ({ ...d, history: before.documents.find((p) => p.kind === d.kind)?.history ?? [] }))
4151
+ };
4152
+ return comparePreviewBundles(before, clean);
3888
4153
  }
3889
- function prepareSpecPreview(before, current, baseFiles, currentFiles, input2, generate) {
3890
- const delta = finalSpecPreviewChanges(before, current);
4154
+ function prepareSpecPreview(beforeValue, currentValue, baseFiles, currentFiles, input2, generate) {
4155
+ const beforeBundle = asBundle(beforeValue);
4156
+ const currentBundle = asBundle(currentValue);
4157
+ const before = beforeBundle.specs;
4158
+ const current = currentBundle.specs;
4159
+ const delta = finalSpecPreviewChanges(beforeBundle, currentBundle);
3891
4160
  if (!Array.isArray(input2) || input2.length > 100)
3892
4161
  fail3("reasons\uB294 \uCD5C\uB300 100\uAC1C\uC758 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
3893
4162
  const writes = /* @__PURE__ */ new Map();
3894
4163
  const pending = /* @__PURE__ */ new Map();
3895
4164
  const covered = /* @__PURE__ */ new Set();
3896
- const used = new Set([...before, ...current].flatMap((s) => s.history.map((h) => h.id)));
4165
+ const used = new Set([...before.flatMap((s) => s.history), ...current.flatMap((s) => s.history), ...beforeBundle.documents.flatMap((d) => d.history), ...currentBundle.documents.flatMap((d) => d.history)].map((h) => h.id));
4166
+ const documentPending = /* @__PURE__ */ new Map();
3897
4167
  const allocate = () => {
3898
4168
  for (let attempt = 0; attempt < 100; attempt++) {
3899
4169
  const id = generate();
@@ -3910,10 +4180,32 @@ function prepareSpecPreview(before, current, baseFiles, currentFiles, input2, ge
3910
4180
  if (!value || typeof value !== "object" || Array.isArray(value))
3911
4181
  fail3("\uC798\uBABB\uB41C \uC774\uC720 \uC785\uB825\uC785\uB2C8\uB2E4.");
3912
4182
  const raw = value;
3913
- if (!["reason,requirements", "designs,reason,requirements"].includes(Object.keys(raw).sort().join(",")) || typeof raw.reason !== "string" || !raw.reason.trim() || raw.reason.includes("\0") || raw.reason.length > 16e3 || !Array.isArray(raw.requirements) || raw.designs !== void 0 && !Array.isArray(raw.designs) || !(raw.requirements.length || raw.designs?.length))
4183
+ const shape = Object.keys(raw).sort().join(",");
4184
+ if (!["reason,requirements", "designs,reason,requirements", "documents,reason,requirements"].includes(shape) || typeof raw.reason !== "string" || !raw.reason.trim() || raw.reason.includes("\0") || raw.reason.length > 16e3 || !Array.isArray(raw.requirements) || raw.designs !== void 0 && !Array.isArray(raw.designs) || raw.documents !== void 0 && !Array.isArray(raw.documents) || !(raw.requirements.length || raw.designs?.length || raw.documents?.length))
3914
4185
  fail3("\uC774\uC720\uC640 \uB300\uC0C1 \uC694\uAD6C\uC0AC\uD56D\uC744 \uD655\uC778\uD558\uC138\uC694.");
3915
4186
  const reason = raw.reason.trim();
3916
4187
  const groups = /* @__PURE__ */ new Map();
4188
+ if (raw.documents !== void 0) {
4189
+ if (raw.requirements.length)
4190
+ fail3("\uBB38\uC11C \uC774\uC720\uC5D0\uB294 \uC694\uAD6C\uC0AC\uD56D\uC744 \uD568\uAED8 \uC9C0\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
4191
+ const ids = raw.documents;
4192
+ let kind;
4193
+ for (const value2 of ids) {
4194
+ if (typeof value2 !== "string" || !/^[PG]-[a-z2-7]{10}$/.test(value2) || covered.has(value2))
4195
+ return fail3("\uBB38\uC11C \uC774\uC720 \uB300\uC0C1\uC774 \uC798\uBABB\uB410\uAC70\uB098 \uC911\uBCF5\uB429\uB2C8\uB2E4.");
4196
+ const change2 = delta.changes.find((c) => c.id === value2) ?? fail3("\uCD5C\uC885 \uBCC0\uACBD\uC774 \uC5C6\uB294 \uBB38\uC11C\uC5D0 \uC774\uC720\uB97C \uBD99\uC77C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: " + value2);
4197
+ if (kind && change2.kind !== kind)
4198
+ fail3("\uD55C \uC774\uC720\uB294 \uD55C \uC885\uB958\uC758 \uBB38\uC11C\uB9CC \uB300\uC0C1\uC73C\uB85C \uD569\uB2C8\uB2E4.");
4199
+ kind = change2.kind;
4200
+ covered.add(value2);
4201
+ }
4202
+ const documents = [...ids].sort();
4203
+ const set2 = currentBundle.documents.find((d) => d.kind === kind);
4204
+ const oldCount = beforeBundle.documents.find((d) => d.kind === kind)?.history.length ?? 0;
4205
+ const reusable = set2.history.slice(oldCount).find((h) => h.reason === reason && equal([...h.documents ?? []].sort(), documents));
4206
+ documentPending.set(kind, [...documentPending.get(kind) ?? [], { id: reusable?.id ?? allocate(), requirements: [], documents, reason }]);
4207
+ continue;
4208
+ }
3917
4209
  for (const value2 of [...raw.requirements, ...raw.designs ?? []]) {
3918
4210
  if (typeof value2 !== "string")
3919
4211
  fail3("\uC798\uBABB\uB41C \uC774\uC720 \uB300\uC0C1\uC785\uB2C8\uB2E4.");
@@ -3951,11 +4243,22 @@ function prepareSpecPreview(before, current, baseFiles, currentFiles, input2, ge
3951
4243
  if ((currentFiles.get(path3) ?? null) !== next)
3952
4244
  writes.set(path3, next);
3953
4245
  }
4246
+ for (const set2 of currentBundle.documents) {
4247
+ const path3 = documentHistoryPath(set2.kind);
4248
+ const base = baseFiles.get(path3);
4249
+ const records = (documentPending.get(set2.kind) ?? []).sort((a, b) => a.documents[0] < b.documents[0] ? -1 : 1);
4250
+ let text2 = base ?? "";
4251
+ if (records.length)
4252
+ text2 += (text2 && !text2.endsWith("\n") ? "\n" : "") + records.map((h) => JSON.stringify(h) + "\n").join("");
4253
+ const next = base !== void 0 || records.length ? text2 : null;
4254
+ if ((currentFiles.get(path3) ?? null) !== next)
4255
+ writes.set(path3, next);
4256
+ }
3954
4257
  return {
3955
4258
  writes,
3956
4259
  changes: delta.changes,
3957
4260
  specChanges: delta.specChanges,
3958
- reasons: [...pending].flatMap(([specId, records]) => records.map((record3) => ({ specId, ...record3 }))),
4261
+ reasons: [...[...pending].flatMap(([specId, records]) => records.map((record3) => ({ specId, ...record3 }))), ...[...documentPending].flatMap(([kind, records]) => records.map((record3) => ({ kind, ...record3 })))],
3959
4262
  withoutReason: delta.changes.filter((c) => !covered.has(c.id)).map((c) => c.id)
3960
4263
  };
3961
4264
  }
@@ -4908,10 +5211,10 @@ function cached(getter) {
4908
5211
  function nullish(input2) {
4909
5212
  return input2 === null || input2 === void 0;
4910
5213
  }
4911
- function cleanRegex(source2) {
4912
- const start = source2.startsWith("^") ? 1 : 0;
4913
- const end = source2.endsWith("$") ? source2.length - 1 : source2.length;
4914
- return source2.slice(start, end);
5214
+ function cleanRegex(source) {
5215
+ const start = source.startsWith("^") ? 1 : 0;
5216
+ const end = source.endsWith("$") ? source.length - 1 : source.length;
5217
+ return source.slice(start, end);
4915
5218
  }
4916
5219
  function floatSafeRemainder(val, step) {
4917
5220
  const ratio = val / step;
@@ -4947,8 +5250,8 @@ function defineLazy(object4, key, getter) {
4947
5250
  function objectClone(obj) {
4948
5251
  return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
4949
5252
  }
4950
- function assignProp(target2, prop, value) {
4951
- Object.defineProperty(target2, prop, {
5253
+ function assignProp(target, prop, value) {
5254
+ Object.defineProperty(target, prop, {
4952
5255
  value,
4953
5256
  writable: true,
4954
5257
  enumerable: true,
@@ -4962,8 +5265,8 @@ function rawShape(def) {
4962
5265
  function sourceShape(schema) {
4963
5266
  return rawShape(schema._zod.def) ?? schema._zod.def.shape;
4964
5267
  }
4965
- function deferProp(target2, key, getter) {
4966
- Object.defineProperty(target2, key, {
5268
+ function deferProp(target, key, getter) {
5269
+ Object.defineProperty(target, key, {
4967
5270
  get() {
4968
5271
  const value = getter();
4969
5272
  assignProp(this, key, value);
@@ -4973,36 +5276,36 @@ function deferProp(target2, key, getter) {
4973
5276
  configurable: true
4974
5277
  });
4975
5278
  }
4976
- function putProp(target2, key, value) {
4977
- if (key in target2)
4978
- assignProp(target2, key, value);
5279
+ function putProp(target, key, value) {
5280
+ if (key in target)
5281
+ assignProp(target, key, value);
4979
5282
  else
4980
- target2[key] = value;
5283
+ target[key] = value;
4981
5284
  }
4982
- function mirrorShape(target2, source2, keys2, wrap) {
4983
- const raw = sourceShape(source2);
5285
+ function mirrorShape(target, source, keys2, wrap) {
5286
+ const raw = sourceShape(source);
4984
5287
  for (const key of keys2) {
4985
5288
  const desc = Object.getOwnPropertyDescriptor(raw, key);
4986
5289
  if (!desc.enumerable)
4987
5290
  continue;
4988
5291
  if (desc.get) {
4989
- deferProp(target2, key, () => {
4990
- const value = source2._zod.def.shape[key];
5292
+ deferProp(target, key, () => {
5293
+ const value = source._zod.def.shape[key];
4991
5294
  return wrap ? wrap(value, key) : value;
4992
5295
  });
4993
5296
  } else
4994
- putProp(target2, key, wrap ? wrap(desc.value, key) : desc.value);
5297
+ putProp(target, key, wrap ? wrap(desc.value, key) : desc.value);
4995
5298
  }
4996
5299
  }
4997
- function mirrorProps(target2, source2) {
4998
- for (const key of Reflect.ownKeys(source2)) {
4999
- const desc = Object.getOwnPropertyDescriptor(source2, key);
5300
+ function mirrorProps(target, source) {
5301
+ for (const key of Reflect.ownKeys(source)) {
5302
+ const desc = Object.getOwnPropertyDescriptor(source, key);
5000
5303
  if (!desc.enumerable)
5001
5304
  continue;
5002
5305
  if (desc.get)
5003
- deferProp(target2, key, () => source2[key]);
5306
+ deferProp(target, key, () => source[key]);
5004
5307
  else
5005
- putProp(target2, key, desc.value);
5308
+ putProp(target, key, desc.value);
5006
5309
  }
5007
5310
  }
5008
5311
  function mergeDefs(...defs) {
@@ -5181,35 +5484,35 @@ function normalizeParams(_params) {
5181
5484
  return params;
5182
5485
  }
5183
5486
  function createTransparentProxy(getter) {
5184
- let target2;
5487
+ let target;
5185
5488
  return new Proxy({}, {
5186
5489
  get(_, prop, receiver) {
5187
- target2 ?? (target2 = getter());
5188
- return Reflect.get(target2, prop, receiver);
5490
+ target ?? (target = getter());
5491
+ return Reflect.get(target, prop, receiver);
5189
5492
  },
5190
5493
  set(_, prop, value, receiver) {
5191
- target2 ?? (target2 = getter());
5192
- return Reflect.set(target2, prop, value, receiver);
5494
+ target ?? (target = getter());
5495
+ return Reflect.set(target, prop, value, receiver);
5193
5496
  },
5194
5497
  has(_, prop) {
5195
- target2 ?? (target2 = getter());
5196
- return Reflect.has(target2, prop);
5498
+ target ?? (target = getter());
5499
+ return Reflect.has(target, prop);
5197
5500
  },
5198
5501
  deleteProperty(_, prop) {
5199
- target2 ?? (target2 = getter());
5200
- return Reflect.deleteProperty(target2, prop);
5502
+ target ?? (target = getter());
5503
+ return Reflect.deleteProperty(target, prop);
5201
5504
  },
5202
5505
  ownKeys(_) {
5203
- target2 ?? (target2 = getter());
5204
- return Reflect.ownKeys(target2);
5506
+ target ?? (target = getter());
5507
+ return Reflect.ownKeys(target);
5205
5508
  },
5206
5509
  getOwnPropertyDescriptor(_, prop) {
5207
- target2 ?? (target2 = getter());
5208
- return Reflect.getOwnPropertyDescriptor(target2, prop);
5510
+ target ?? (target = getter());
5511
+ return Reflect.getOwnPropertyDescriptor(target, prop);
5209
5512
  },
5210
5513
  defineProperty(_, prop, descriptor) {
5211
- target2 ?? (target2 = getter());
5212
- return Reflect.defineProperty(target2, prop, descriptor);
5514
+ target ?? (target = getter());
5515
+ return Reflect.defineProperty(target, prop, descriptor);
5213
5516
  }
5214
5517
  });
5215
5518
  }
@@ -5680,10 +5983,10 @@ function $constructor(name, initializer3, proto, params) {
5680
5983
  let up = own2;
5681
5984
  while (up && up !== ctorProto)
5682
5985
  up = Object.getPrototypeOf(up);
5683
- const target2 = up ?? own2;
5684
- if (!initialized.has(target2)) {
5685
- initialized.add(target2);
5686
- members(target2, protoMembers);
5986
+ const target = up ?? own2;
5987
+ if (!initialized.has(target)) {
5988
+ initialized.add(target);
5989
+ members(target, protoMembers);
5687
5990
  }
5688
5991
  }
5689
5992
  const proto2 = _.prototype;
@@ -6213,8 +6516,8 @@ var creditCard = /^\d(?:[ -]?\d){11,18}$/;
6213
6516
  var currencyCode = /^(?:AED|AFN|ALL|AMD|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|BHD|BIF|BMD|BND|BOB|BOV|BRL|BSD|BTN|BWP|BYN|BZD|CAD|CDF|CHE|CHF|CHW|CLF|CLP|CNY|COP|COU|CRC|CUP|CVE|CZK|DJF|DKK|DOP|DZD|EGP|ERN|ETB|EUR|FJD|FKP|GBP|GEL|GHS|GIP|GMD|GNF|GTQ|GYD|HKD|HNL|HTG|HUF|IDR|ILS|INR|IQD|IRR|ISK|JMD|JOD|JPY|KES|KGS|KHR|KMF|KPW|KRW|KWD|KYD|KZT|LAK|LBP|LKR|LRD|LSL|LYD|MAD|MDL|MGA|MKD|MMK|MNT|MOP|MRU|MUR|MVR|MWK|MXN|MXV|MYR|MZN|NAD|NGN|NIO|NOK|NPR|NZD|OMR|PAB|PEN|PGK|PHP|PKR|PLN|PYG|QAR|RON|RSD|RUB|RWF|SAR|SBD|SCR|SDG|SEK|SGD|SHP|SLE|SOS|SRD|SSP|STN|SVC|SYP|SZL|THB|TJS|TMT|TND|TOP|TRY|TTD|TWD|TZS|UAH|UGX|USD|USN|UYI|UYU|UYW|UZS|VED|VES|VND|VUV|WST|XAD|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XCG|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWG)$/;
6214
6517
  var iban = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
6215
6518
  var dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
6216
- function anchor(source2) {
6217
- return new RegExp(`^${source2}$`);
6519
+ function anchor(source) {
6520
+ return new RegExp(`^${source}$`);
6218
6521
  }
6219
6522
  var date = /* @__PURE__ */ anchor(dateSource);
6220
6523
  function timeSource(args) {
@@ -9037,11 +9340,11 @@ var $ZodTemplateLiteral = /* @__PURE__ */ $constructor("$ZodTemplateLiteral", (i
9037
9340
  const regexParts = [];
9038
9341
  for (const part of def.parts) {
9039
9342
  if (typeof part === "object" && part !== null) {
9040
- const source2 = partPattern(part);
9041
- if (!source2) {
9343
+ const source = partPattern(part);
9344
+ if (!source) {
9042
9345
  throw new Error(`Invalid template literal part, no pattern found: ${[...part._zod.traits].shift()}`);
9043
9346
  }
9044
- regexParts.push(cleanRegex(source2));
9347
+ regexParts.push(cleanRegex(source));
9045
9348
  } else if (part === null || primitiveTypes.has(typeof part)) {
9046
9349
  regexParts.push(escapeRegex(`${part}`));
9047
9350
  } else {
@@ -17167,9 +17470,9 @@ function withParser(schema, parser) {
17167
17470
  installCompiledUserMethods(clone2, schema, parser);
17168
17471
  return clone2;
17169
17472
  }
17170
- function installCompiledUserMethods(target2, source2, parser) {
17171
- const targetAny = target2;
17172
- const sourceAny = source2;
17473
+ function installCompiledUserMethods(target, source, parser) {
17474
+ const targetAny = target;
17475
+ const sourceAny = source;
17173
17476
  if (typeof sourceAny.safeParse === "function") {
17174
17477
  const originalSafeParse = sourceAny.safeParse;
17175
17478
  targetAny.safeParse = (data, params) => {
@@ -19763,26 +20066,26 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
19763
20066
  }
19764
20067
 
19765
20068
  // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/to-json-schema.js
19766
- function assignProps(target2, ...sources) {
19767
- for (const source2 of sources) {
19768
- for (const key of Reflect.ownKeys(source2)) {
19769
- if (Object.prototype.propertyIsEnumerable.call(source2, key)) {
19770
- assignProp(target2, key, source2[key]);
20069
+ function assignProps(target, ...sources) {
20070
+ for (const source of sources) {
20071
+ for (const key of Reflect.ownKeys(source)) {
20072
+ if (Object.prototype.propertyIsEnumerable.call(source, key)) {
20073
+ assignProp(target, key, source[key]);
19771
20074
  }
19772
20075
  }
19773
20076
  }
19774
- return target2;
20077
+ return target;
19775
20078
  }
19776
20079
  function initializeContext(params) {
19777
- let target2 = params?.target ?? "draft-2020-12";
19778
- if (target2 === "draft-4")
19779
- target2 = "draft-04";
19780
- if (target2 === "draft-7")
19781
- target2 = "draft-07";
20080
+ let target = params?.target ?? "draft-2020-12";
20081
+ if (target === "draft-4")
20082
+ target = "draft-04";
20083
+ if (target === "draft-7")
20084
+ target = "draft-07";
19782
20085
  return {
19783
20086
  processors: params.processors ?? {},
19784
20087
  metadataRegistry: params?.metadata ?? globalRegistry,
19785
- target: target2,
20088
+ target,
19786
20089
  unrepresentable: params?.unrepresentable ?? "throw",
19787
20090
  override: params?.override ?? (() => {
19788
20091
  }),
@@ -20285,8 +20588,8 @@ var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
20285
20588
  return finalize(ctx, schema);
20286
20589
  };
20287
20590
  var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
20288
- const { libraryOptions, target: target2 } = params ?? {};
20289
- const ctx = initializeContext({ ...libraryOptions ?? {}, target: target2, io, processors });
20591
+ const { libraryOptions, target } = params ?? {};
20592
+ const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
20290
20593
  processSchema(schema, ctx);
20291
20594
  extractDefs(ctx, schema);
20292
20595
  return finalize(ctx, schema);
@@ -21480,8 +21783,8 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
21480
21783
  catch(params) {
21481
21784
  return _catch2(this, params);
21482
21785
  },
21483
- pipe(target2) {
21484
- return pipe(this, target2);
21786
+ pipe(target) {
21787
+ return pipe(this, target);
21485
21788
  },
21486
21789
  readonly() {
21487
21790
  return readonly(this);
@@ -23990,27 +24293,41 @@ var browserHttpErrorV1 = external_exports.strictObject({
23990
24293
  })
23991
24294
  });
23992
24295
 
23993
- // ../../packages/contracts/dist/versions/skills-v1.js
23994
- var skillsV1 = external_exports.discriminatedUnion("ok", [
24296
+ // ../../packages/contracts/dist/versions/project-init-v4.js
24297
+ var projectInitV4 = external_exports.discriminatedUnion("ok", [
23995
24298
  external_exports.strictObject({
23996
- contract: external_exports.literal("skills"),
23997
- version: external_exports.literal(1),
24299
+ contract: external_exports.literal("project-init"),
24300
+ version: external_exports.literal(4),
23998
24301
  ok: external_exports.literal(true),
23999
- outcome: external_exports.enum(["planned", "installed", "synced", "removed", "not-installed"]),
24302
+ outcome: external_exports.enum(["planned", "created", "already-initialized"]),
24000
24303
  rootPath: external_exports.string(),
24001
- agent: external_exports.enum(["codex", "claude"]).nullable(),
24002
- source: external_exports.literal(".agents/skills/gitifact-workflow/SKILL.md"),
24003
- target: external_exports.literal(".claude/skills/gitifact-workflow/SKILL.md").nullable(),
24004
- sourcePreserved: external_exports.boolean(),
24005
- sessionState: external_exports.literal("not-observed")
24304
+ configPath: external_exports.literal(".gitifact/config.json"),
24305
+ schemaVersion: external_exports.literal(1),
24306
+ baseline: external_exports.discriminatedUnion("kind", [
24307
+ external_exports.strictObject({ kind: external_exports.literal("empty") }),
24308
+ external_exports.strictObject({ kind: external_exports.literal("commit"), objectFormat: external_exports.enum(["sha1", "sha256"]), commit: external_exports.string() })
24309
+ ]),
24310
+ agentDocs: external_exports.strictObject({ mode: external_exports.enum(["install", "remove", "skip"]), paths: external_exports.array(external_exports.string()) })
24006
24311
  }),
24007
- external_exports.strictObject({ contract: external_exports.literal("skills"), version: external_exports.literal(1), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
24312
+ external_exports.strictObject({ contract: external_exports.literal("project-init"), version: external_exports.literal(4), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
24008
24313
  ]);
24009
24314
 
24010
24315
  // ../../packages/contracts/dist/versions/browser-specs-v1.js
24011
24316
  var requirement = external_exports.strictObject({ id: external_exports.string(), title: external_exports.string(), body: external_exports.string() });
24012
- var feature = external_exports.strictObject({ id: external_exports.string(), path: external_exports.string(), title: external_exports.string(), description: external_exports.string(), requirements: external_exports.array(requirement), design: external_exports.strictObject({ title: external_exports.string(), body: external_exports.string(), requirements: external_exports.array(external_exports.string()) }).optional() });
24317
+ var contributor = external_exports.strictObject({ email: external_exports.string(), name: external_exports.string(), commits: external_exports.number().int().nonnegative(), latest: external_exports.string() });
24318
+ var feature = external_exports.strictObject({
24319
+ id: external_exports.string(),
24320
+ path: external_exports.string(),
24321
+ title: external_exports.string(),
24322
+ description: external_exports.string(),
24323
+ requirements: external_exports.array(requirement),
24324
+ design: external_exports.strictObject({ title: external_exports.string(), body: external_exports.string(), requirements: external_exports.array(external_exports.string()) }).optional(),
24325
+ // Authors of commits touching the feature folder (current or legacy store) and the latest such commit; empty until first committed.
24326
+ contributors: external_exports.array(contributor),
24327
+ updatedAt: external_exports.string().nullable()
24328
+ });
24013
24329
  var snapshot = requirement.extend({ specId: external_exports.string(), path: external_exports.string() }).nullable();
24330
+ var document = external_exports.strictObject({ id: external_exports.string(), kind: external_exports.enum(["product", "guide"]), path: external_exports.string(), title: external_exports.string(), body: external_exports.string(), updatedAt: external_exports.string().nullable() });
24014
24331
  var browserSpecsV1 = external_exports.strictObject({
24015
24332
  contract: external_exports.literal("browser-specs"),
24016
24333
  version: external_exports.literal(1),
@@ -24019,6 +24336,7 @@ var browserSpecsV1 = external_exports.strictObject({
24019
24336
  observedAt: external_exports.string(),
24020
24337
  working: external_exports.boolean(),
24021
24338
  features: external_exports.array(feature),
24339
+ documents: external_exports.array(document).default([]),
24022
24340
  events: external_exports.array(external_exports.strictObject({
24023
24341
  key: external_exports.string(),
24024
24342
  commit: external_exports.string(),
@@ -24028,13 +24346,13 @@ var browserSpecsV1 = external_exports.strictObject({
24028
24346
  committer: external_exports.string(),
24029
24347
  message: external_exports.string(),
24030
24348
  id: external_exports.string(),
24031
- kind: external_exports.enum(["requirement", "design"]).optional(),
24349
+ kind: external_exports.enum(["requirement", "design", "product", "guide"]).optional(),
24032
24350
  types: external_exports.array(external_exports.enum(["created", "modified", "moved", "deleted"])),
24033
24351
  before: snapshot,
24034
24352
  after: snapshot,
24035
24353
  reasons: external_exports.array(external_exports.string())
24036
24354
  })),
24037
- contributors: external_exports.array(external_exports.strictObject({ email: external_exports.string(), name: external_exports.string(), commits: external_exports.number().int().nonnegative(), latest: external_exports.string() })),
24355
+ contributors: external_exports.array(contributor),
24038
24356
  contributorsLimited: external_exports.boolean(),
24039
24357
  nextCursor: external_exports.number().int().nonnegative().nullable(),
24040
24358
  boundary: external_exports.boolean()
@@ -24106,6 +24424,8 @@ async function runStatus(format) {
24106
24424
  import { createServer } from "node:http";
24107
24425
  import { randomUUID as randomUUID4 } from "node:crypto";
24108
24426
  import { fileURLToPath } from "node:url";
24427
+ import { lstat as lstat3, readFile as readFile4 } from "node:fs/promises";
24428
+ import { join as join4 } from "node:path";
24109
24429
 
24110
24430
  // src/server/assets.ts
24111
24431
  import { readdir, readFile } from "node:fs/promises";
@@ -24135,6 +24455,9 @@ function contentType(path3) {
24135
24455
  if (path3.endsWith(".svg")) return "image/svg+xml";
24136
24456
  if (path3.endsWith(".woff2")) return "font/woff2";
24137
24457
  if (path3.endsWith(".png")) return "image/png";
24458
+ if (/\.jpe?g$/.test(path3)) return "image/jpeg";
24459
+ if (path3.endsWith(".gif")) return "image/gif";
24460
+ if (path3.endsWith(".webp")) return "image/webp";
24138
24461
  return "application/octet-stream";
24139
24462
  }
24140
24463
 
@@ -24195,6 +24518,7 @@ function commandScoped(key, load) {
24195
24518
  }
24196
24519
 
24197
24520
  // src/adapters/git/spec-preview-reader.ts
24521
+ var RECORD_PATHSPECS = [...STORE_DIRS.map((d) => d + "/spec/"), ...Object.values(DOCUMENT_DIRS).map((d) => d + "/")];
24198
24522
  function specPreviewReader(cwd) {
24199
24523
  for (const key of Object.keys(process.env)) if (/^GIT_(DIR|WORK_TREE|COMMON_DIR|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES|CONFIG_COUNT|CONFIG_PARAMETERS|CONFIG_KEY_\d+|CONFIG_VALUE_\d+)$/i.test(key)) {
24200
24524
  throw new SpecPreviewError("Git \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uB36E\uC5B4\uC4F0\uB294 \uD658\uACBD\uC5D0\uC11C\uB294 \uC870\uD68C\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
@@ -24218,14 +24542,17 @@ function specPreviewReader(cwd) {
24218
24542
  };
24219
24543
  return {
24220
24544
  async index() {
24221
- const output2 = decode5(await git(["ls-files", "--stage", "-z", "--", ...STORE_DIRS.map((d) => d + "/spec/")]));
24545
+ const output2 = decode5(await git(["ls-files", "--stage", "-z", "--", ...RECORD_PATHSPECS]));
24222
24546
  const files = /* @__PURE__ */ new Map();
24223
24547
  for (const row of output2.split("\0").filter(Boolean)) {
24224
24548
  const match = /^(\d+) ([a-f0-9]+) ([0-3])\t([\s\S]+)$/.exec(row);
24225
24549
  if (!match) throw new SpecPreviewError("\uC798\uBABB\uB41C index \uD56D\uBAA9\uC785\uB2C8\uB2E4.");
24226
24550
  const [, mode, oid2, stage, path3] = match;
24227
24551
  if (!["100644", "100755"].includes(mode) || stage !== "0") throw new SpecPreviewError("\uBA85\uC138 staging\uC5D0 \uCDA9\uB3CC \uB610\uB294 \uB9C1\uD06C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.");
24228
- if (!recordPathPattern.test(path3)) throw new SpecPreviewError("\uBA85\uC138 staging\uC5D0 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C\uC774 \uC788\uC2B5\uB2C8\uB2E4.");
24552
+ if (!recordPathPattern.test(path3)) {
24553
+ if (/^\.gitifact\/(?:product|guides)\//.test(path3)) continue;
24554
+ throw new SpecPreviewError("\uBA85\uC138 staging\uC5D0 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C\uC774 \uC788\uC2B5\uB2C8\uB2E4.");
24555
+ }
24229
24556
  files.set(path3, oid2);
24230
24557
  }
24231
24558
  return files;
@@ -24264,7 +24591,7 @@ function specPreviewReader(cwd) {
24264
24591
  return oid2;
24265
24592
  },
24266
24593
  async files(oid2, allowLegacyBaseline = false) {
24267
- const entries = decode5(await git(["ls-tree", "--full-tree", "-r", "-z", oid2, "--", ...STORE_DIRS.map((d) => d + "/spec/")]));
24594
+ const entries = decode5(await git(["ls-tree", "--full-tree", "-r", "-z", oid2, "--", ...RECORD_PATHSPECS]));
24268
24595
  const files = /* @__PURE__ */ new Map();
24269
24596
  let legacy;
24270
24597
  for (const row of entries.split("\0").filter(Boolean)) {
@@ -24272,7 +24599,7 @@ function specPreviewReader(cwd) {
24272
24599
  if (!match) throw new SpecPreviewError("\uC798\uBABB\uB41C Git tree\uC785\uB2C8\uB2E4.");
24273
24600
  const [, mode, type, object4, path3] = match;
24274
24601
  if (!["100644", "100755"].includes(mode) || type !== "blob") throw new SpecPreviewError("\uBA85\uC138 \uC601\uC5ED\uC5D0 \uB9C1\uD06C \uB610\uB294 \uC11C\uBE0C\uBAA8\uB4C8\uC774 \uC788\uC2B5\uB2C8\uB2E4: " + path3);
24275
- if (!/(?:requirements\.md|design\.md|history\.jsonl|tryce\.json)$/.test(path3)) continue;
24602
+ if (!recordPathPattern.test(path3) && !path3.endsWith("/tryce.json")) continue;
24276
24603
  if (path3.endsWith("/tryce.json")) {
24277
24604
  if (!allowLegacyBaseline) throw new SpecPreviewError("\uAE30\uC874 JSON \uD615\uC2DD\uC740 req \uBA85\uB839\uC73C\uB85C \uC870\uD68C\uD558\uC138\uC694. \uC774 \uBA85\uB839\uC740 \uC804\uD658\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
24278
24605
  legacy = path3.split("/")[0];
@@ -24311,6 +24638,9 @@ function specPreviewReader(cwd) {
24311
24638
  async read(oid2) {
24312
24639
  const files = await this.files(oid2);
24313
24640
  return parsePreviewFiles(files);
24641
+ },
24642
+ async readBundle(oid2) {
24643
+ return parsePreviewBundle(await this.files(oid2));
24314
24644
  }
24315
24645
  };
24316
24646
  }
@@ -24421,18 +24751,18 @@ async function snapshot2(root) {
24421
24751
  if (!parent && await info(join3(root, ".tryce"))) fail4(".tryce \uC800\uC7A5\uC18C\uC785\uB2C8\uB2E4. gitifact migrate\uB85C .gitifact\uC5D0 \uC804\uD658\uD55C \uB4A4 \uC0AC\uC6A9\uD558\uC138\uC694.");
24422
24752
  const config2 = await readConfigFile(root);
24423
24753
  if (config2 !== void 0 && !("schemaVersion" in parseManagedConfig(config2)) || await info(join3(root, "specs"))) fail4("\uAE30\uC874 \uD504\uB85C\uC81D\uD2B8 \uD615\uC2DD\uC740 \uBCC4\uB3C4 \uC804\uD658\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
24424
- async function visit2(path3, depth) {
24754
+ async function visit2(path3, depth, documents) {
24425
24755
  const stat3 = await info(join3(root, path3));
24426
24756
  if (!stat3) return;
24427
24757
  if (stat3.isSymbolicLink() || !stat3.isDirectory() && !stat3.isFile()) fail4("\uB9C1\uD06C\xB7\uD2B9\uC218 \uD30C\uC77C\uC740 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + path3);
24428
24758
  if (++count > 4e3) fail4("\uBA85\uC138 \uD30C\uC77C \uC218 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
24429
24759
  if (stat3.isDirectory()) {
24430
- if (depth > 1) fail4("\uC911\uCCA9 \uBA85\uC138 \uD3F4\uB354\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + path3);
24431
- for (const name of (await readdir2(join3(root, path3))).sort()) await visit2(path3 + "/" + name, depth + 1);
24760
+ if (depth > (documents ? 8 : 1)) fail4("\uC911\uCCA9 \uD3F4\uB354 \uAE4A\uC774 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4: " + path3);
24761
+ for (const name of (await readdir2(join3(root, path3))).sort()) await visit2(path3 + "/" + name, depth + 1, documents);
24432
24762
  } else {
24433
24763
  if (path3.endsWith("/tryce.json")) fail4("\uAE30\uC874 JSON \uD615\uC2DD\uC740 \uC804\uD658\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
24434
- if (!/\/(requirements\.md|design\.md|history\.jsonl)$/.test(path3)) return;
24435
- if (!/^\.gitifact\/spec\/[^/]+\/(requirements\.md|design\.md|history\.jsonl)$/.test(path3)) fail4("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBA85\uC138 \uACBD\uB85C\uC785\uB2C8\uB2E4.");
24764
+ if (documents ? !(path3.endsWith(".md") || depth === 1 && path3.endsWith("/history.jsonl")) : !/\/(requirements\.md|design\.md|history\.jsonl)$/.test(path3)) return;
24765
+ if (!recordPathPattern.test(path3) || path3.startsWith(".tryce/")) fail4("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBA85\uC138 \uACBD\uB85C\uC785\uB2C8\uB2E4: " + path3);
24436
24766
  if (stat3.nlink !== 1 || stat3.size > 1024 * 1024) fail4("\uD558\uB4DC \uB9C1\uD06C \uB610\uB294 1 MiB \uD55C\uB3C4\uB97C \uD655\uC778\uD558\uC138\uC694.");
24437
24767
  const raw = await readFile3(join3(root, path3));
24438
24768
  bytes += raw.length;
@@ -24440,9 +24770,10 @@ async function snapshot2(root) {
24440
24770
  files.set(path3, decode3(raw));
24441
24771
  }
24442
24772
  }
24443
- await visit2(".gitifact/spec", 0);
24444
- const specs = parsePreviewFiles(files);
24445
- return { files, specs, config: config2, stamp: digest(JSON.stringify([...files]) + (config2 ?? "")) };
24773
+ await visit2(".gitifact/spec", 0, false);
24774
+ for (const dir of Object.values(DOCUMENT_DIRS)) await visit2(dir, 0, true);
24775
+ const bundle = parsePreviewBundle(files);
24776
+ return { files, specs: bundle.specs, documents: bundle.documents, bundle, config: config2, stamp: digest(JSON.stringify([...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) + (config2 ?? "")) };
24446
24777
  }
24447
24778
  async function readWorkingPreviewState(cwd) {
24448
24779
  const { root, gitDir } = await specPreviewReader(cwd).location();
@@ -24455,21 +24786,29 @@ async function readWorkingPreviewState(cwd) {
24455
24786
  return first;
24456
24787
  }
24457
24788
  async function readWorkingPreview(cwd) {
24458
- const { stamp, specs } = await readWorkingPreviewState(cwd);
24459
- return { stamp, specs, warnings: designReferenceWarnings(specs) };
24789
+ const { stamp, specs, documents } = await readWorkingPreviewState(cwd);
24790
+ return { stamp, specs, documents, warnings: designReferenceWarnings(specs) };
24460
24791
  }
24461
24792
  async function saveWorkingPreview(cwd, input2, publish = rename) {
24462
24793
  if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) fail4("\uC800\uC7A5 \uC785\uB825\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4.");
24463
24794
  const request = input2;
24464
24795
  if (Object.keys(request).sort().join(",") !== "expected,operations" || typeof request.expected !== "string") fail4("expected\uC640 operations\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
24465
24796
  return previewTransaction(cwd, request.expected, async (before) => {
24466
- const result = editSpecPreview(before.specs, request.operations, generatePreviewId);
24797
+ const result = editSpecPreview(before.bundle, request.operations, generatePreviewId);
24467
24798
  const writes = /* @__PURE__ */ new Map();
24468
24799
  for (const spec2 of result.specs) {
24469
24800
  const old = before.specs.find((s) => s.id === spec2.id);
24470
24801
  if (!old || renderSpecPreview(old) !== renderSpecPreview(spec2)) writes.set(spec2.path, renderSpecPreview(spec2));
24471
24802
  if (JSON.stringify(old?.design) !== JSON.stringify(spec2.design)) writes.set(spec2.path.replace(/requirements\.md$/, "design.md"), spec2.design ? renderDesignPreview(spec2.id, spec2.design) : null);
24472
24803
  }
24804
+ const previousDocs = before.documents.flatMap((s) => s.documents);
24805
+ const nextDocs = result.documents.flatMap((s) => s.documents);
24806
+ for (const doc of nextDocs) {
24807
+ const old = previousDocs.find((d) => d.id === doc.id);
24808
+ if (old && old.path !== doc.path) writes.set(old.path, null);
24809
+ if (!old || renderDocument(old) !== renderDocument(doc) || old.path !== doc.path) writes.set(doc.path, renderDocument(doc));
24810
+ }
24811
+ for (const old of previousDocs) if (!nextDocs.some((d) => d.id === old.id)) writes.set(old.path, null);
24473
24812
  return { writes, data: { results: result.results } };
24474
24813
  }, publish);
24475
24814
  }
@@ -24477,9 +24816,9 @@ var readLockedPreviewState = (root) => snapshot2(root);
24477
24816
  async function previewTransaction(cwd, expected2, build, publish = rename, afterPublish) {
24478
24817
  const { root, gitDir } = await specPreviewReader(cwd).location();
24479
24818
  await failOnLegacyLock(gitDir);
24480
- const lock2 = join3(gitDir, "gitifact-spec-preview.lock");
24819
+ const lock = join3(gitDir, "gitifact-spec-preview.lock");
24481
24820
  try {
24482
- await mkdir2(lock2);
24821
+ await mkdir2(lock);
24483
24822
  } catch (e) {
24484
24823
  if (e.code === "EEXIST") fail4("\uBA85\uC138 \uC800\uC7A5 \uC7A0\uAE08 \uB610\uB294 \uBCF5\uAD6C \uC790\uB8CC\uAC00 \uC788\uC2B5\uB2C8\uB2E4.");
24485
24824
  throw e;
@@ -24504,7 +24843,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
24504
24843
  if (before.stamp !== expected2) fail4("\uC77D\uC740 \uB4A4 \uBA85\uC138\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. working\uC73C\uB85C \uB2E4\uC2DC \uC77D\uC73C\uC138\uC694.");
24505
24844
  const result = await build(before);
24506
24845
  for (const [path3, after2] of result.writes) {
24507
- if (!/^\.gitifact\/spec\/[^/]+\/(requirements\.md|design\.md|history\.jsonl)$/.test(path3) || path3.split("/").some((p) => p === ".." || p === "." || /[\\:\0]/.test(p))) fail4("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC800\uC7A5 \uACBD\uB85C\uC785\uB2C8\uB2E4.");
24846
+ if (!recordPathPattern.test(path3) || path3.startsWith(".tryce/") || path3.split("/").some((p) => p === ".." || p === "." || /[\\:\0]/.test(p))) fail4("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC800\uC7A5 \uACBD\uB85C\uC785\uB2C8\uB2E4.");
24508
24847
  if (after2 !== null && Buffer.byteLength(after2) > 1024 * 1024) fail4("\uBA85\uC138 1 MiB \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
24509
24848
  changed.push({ path: path3, before: before.files.get(path3) ?? null, after: after2 });
24510
24849
  }
@@ -24514,7 +24853,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
24514
24853
  else finalFiles.set(c.path, c.after);
24515
24854
  }
24516
24855
  if (finalFiles.size > 2e3 || [...finalFiles.values()].reduce((n, s) => n + Buffer.byteLength(s), 0) > 16 * 1024 * 1024) fail4("\uBA85\uC138 \uC804\uCCB4 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
24517
- await writeFile(join3(lock2, "recovery.json"), JSON.stringify({ root, changed }), { flag: "wx" });
24856
+ await writeFile(join3(lock, "recovery.json"), JSON.stringify({ root, changed }), { flag: "wx" });
24518
24857
  for (const c of changed) {
24519
24858
  if (c.after === null) {
24520
24859
  temporary.push("");
@@ -24540,7 +24879,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
24540
24879
  if (after.stamp !== digest(JSON.stringify([...finalFiles].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) + (before.config ?? ""))) fail4("\uC800\uC7A5 \uC911 \uBA85\uC138 \uB610\uB294 \uC124\uC815\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
24541
24880
  await result.recheck?.();
24542
24881
  await afterPublish?.(after);
24543
- return { ...result.data, stamp: after.stamp, paths: changed.map((c) => c.path), specs: after.specs, warnings: designReferenceWarnings(after.specs) };
24882
+ return { ...result.data, stamp: after.stamp, paths: changed.map((c) => c.path), specs: after.specs, documents: after.documents, warnings: designReferenceWarnings(after.specs) };
24544
24883
  } catch (error62) {
24545
24884
  if (error62 instanceof PreservedPreviewError) {
24546
24885
  keepRecovery = true;
@@ -24565,7 +24904,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
24565
24904
  keepRecovery = true;
24566
24905
  }
24567
24906
  }
24568
- if (keepRecovery) fail4("\uC790\uB3D9 \uBCF5\uAD6C\uB97C \uC644\uB8CC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uB978 \uD3B8\uC9D1\uC744 \uBCF4\uC874\uD588\uC2B5\uB2C8\uB2E4. \uBCF5\uAD6C \uC790\uB8CC: " + lock2);
24907
+ if (keepRecovery) fail4("\uC790\uB3D9 \uBCF5\uAD6C\uB97C \uC644\uB8CC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uB978 \uD3B8\uC9D1\uC744 \uBCF4\uC874\uD588\uC2B5\uB2C8\uB2E4. \uBCF5\uAD6C \uC790\uB8CC: " + lock);
24569
24908
  throw error62;
24570
24909
  } finally {
24571
24910
  for (const path3 of temporary.filter(Boolean)) await unlink2(path3).catch((e) => {
@@ -24574,10 +24913,10 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
24574
24913
  for (const path3 of [...createdDirs].reverse()) await rmdir(path3).catch(() => {
24575
24914
  });
24576
24915
  if (!keepRecovery) {
24577
- await unlink2(join3(lock2, "recovery.json")).catch((e) => {
24916
+ await unlink2(join3(lock, "recovery.json")).catch((e) => {
24578
24917
  if (e.code !== "ENOENT") throw e;
24579
24918
  });
24580
- await rmdir(lock2);
24919
+ await rmdir(lock);
24581
24920
  }
24582
24921
  }
24583
24922
  }
@@ -24598,7 +24937,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
24598
24937
  const snapshot3 = (oid2) => {
24599
24938
  let value = snapshots.get(oid2);
24600
24939
  if (!value) {
24601
- value = reader.read(oid2).catch((e) => {
24940
+ value = reader.readBundle(oid2).catch((e) => {
24602
24941
  snapshots.delete(oid2);
24603
24942
  throw e;
24604
24943
  });
@@ -24615,12 +24954,39 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
24615
24954
  const base = { head: await readHead() };
24616
24955
  if (cursor > 0 && expectedHead !== base.head) throw new SpecPreviewError("\uC774\uB825\uC774 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uC0C8\uB85C\uACE0\uCE68 \uD6C4 \uB2E4\uC2DC \uC870\uD68C\uD558\uC138\uC694.");
24617
24956
  const current = await readWorkingPreviewState(root);
24618
- const features = current.specs.map(({ history: _history, ...s }) => s);
24619
- if (!base.head) return browserSpecsV1.parse({ contract: "browser-specs", version: 1, sessionId, head: null, observedAt: (/* @__PURE__ */ new Date()).toISOString(), working: features.length > 0, features, events: [], contributors: [], contributorsLimited: false, nextCursor: null, boundary: false });
24957
+ const bare = current.specs.map(({ history: _history, ...s }) => ({ ...s, contributors: [], updatedAt: null }));
24958
+ const bareDocuments = current.documents.flatMap((set2) => set2.documents).map((d) => ({ ...d, updatedAt: null }));
24959
+ if (!base.head) return browserSpecsV1.parse({ contract: "browser-specs", version: 1, sessionId, head: null, observedAt: (/* @__PURE__ */ new Date()).toISOString(), working: bare.length > 0 || bareDocuments.length > 0, features: bare, documents: bareDocuments, events: [], contributors: [], contributorsLimited: false, nextCursor: null, boundary: false });
24620
24960
  const head = base.head;
24961
+ const features = await Promise.all(bare.map(async (feature2) => {
24962
+ const folder = feature2.path.replace(/^\.(?:gitifact|tryce)\/(spec\/[^/]+)\/requirements\.md$/, "$1");
24963
+ const lines = (await git(["log", "--format=%aN%x00%aE%x00%aI", "--max-count=2000", head, "--", `.gitifact/${folder}`, `.tryce/${folder}`])).trim().split("\n").filter(Boolean);
24964
+ const people = /* @__PURE__ */ new Map();
24965
+ for (const line of lines) {
24966
+ const [name, email3, latest] = line.split("\0");
24967
+ if (!name || !email3 || !latest) throw new SpecPreviewError("Git \uC791\uC131\uC790\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
24968
+ const person = people.get(email3);
24969
+ if (person) person.commits++;
24970
+ else people.set(email3, { name, email: email3, latest, commits: 1 });
24971
+ }
24972
+ return { ...feature2, contributors: [...people.values()].sort((a, b) => b.commits - a.commits), updatedAt: lines[0]?.split("\0")[2] ?? null };
24973
+ }));
24974
+ const documents = await Promise.all(bareDocuments.map(async (doc) => ({ ...doc, updatedAt: (await git(["log", "--format=%aI", "--max-count=1", head, "--", doc.path])).trim() || null })));
24975
+ const documentDirs = Object.values(DOCUMENT_DIRS);
24621
24976
  const [rows, dirty] = await Promise.all([
24622
- git(["log", "--first-parent", "--date-order", "--format=%H%x00%P%x00%aN%x00%aE%x00%aI%x00%cN%x00%s", "--max-count=11", "--skip=" + cursor, head, "--", ...[".gitifact", ".tryce"].flatMap((d) => [`:(glob)${d}/spec/*/requirements.md`, `:(glob)${d}/spec/*/design.md`, `:(glob)${d}/spec/*/history.jsonl`])]),
24623
- git(["status", "--porcelain=v1", "--", ".gitifact/spec"])
24977
+ git([
24978
+ "log",
24979
+ "--first-parent",
24980
+ "--date-order",
24981
+ "--format=%H%x00%P%x00%aN%x00%aE%x00%aI%x00%cN%x00%s",
24982
+ "--max-count=11",
24983
+ "--skip=" + cursor,
24984
+ head,
24985
+ "--",
24986
+ ...[".gitifact", ".tryce"].flatMap((d) => [`:(glob)${d}/spec/*/requirements.md`, `:(glob)${d}/spec/*/design.md`, `:(glob)${d}/spec/*/history.jsonl`]),
24987
+ ...documentDirs.flatMap((d) => [`:(glob)${d}/**/*.md`, `:(glob)${d}/history.jsonl`])
24988
+ ]),
24989
+ git(["status", "--porcelain=v1", "--", ".gitifact/spec", ...documentDirs])
24624
24990
  ]);
24625
24991
  {
24626
24992
  const people = /* @__PURE__ */ new Map();
@@ -24640,7 +25006,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
24640
25006
  const [commit, parents, author, email3, date5, committer, message] = row.split("\0");
24641
25007
  if (!commit || author === void 0 || email3 === void 0 || !date5 || committer === void 0 || message === void 0) throw new SpecPreviewError("Git \uC774\uB825 \uD615\uC2DD\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
24642
25008
  const after = await snapshot3(commit);
24643
- let before = [];
25009
+ let before = { specs: [], documents: [{ kind: "product", documents: [], history: [] }, { kind: "guide", documents: [], history: [] }] };
24644
25010
  const parent = parents?.split(" ")[0];
24645
25011
  if (parent) {
24646
25012
  try {
@@ -24650,7 +25016,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
24650
25016
  else throw error62;
24651
25017
  }
24652
25018
  }
24653
- return compareSpecPreviews(before, after).changes.map((c) => ({
25019
+ return comparePreviewBundles(before, after).changes.map((c) => ({
24654
25020
  key: commit + ":" + c.id,
24655
25021
  commit,
24656
25022
  author,
@@ -24675,6 +25041,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
24675
25041
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
24676
25042
  working: !!dirty.trim(),
24677
25043
  features,
25044
+ documents,
24678
25045
  events: groups.flat(),
24679
25046
  contributors: peopleCache.data,
24680
25047
  contributorsLimited: peopleCache.limited,
@@ -24756,6 +25123,21 @@ async function startBrowserServer(options) {
24756
25123
  if (api) {
24757
25124
  if (request.headers["sec-fetch-site"] === "cross-site" && !allowedOrigin) return fail8(response, 403, "FORBIDDEN", "\uC678\uBD80 \uD398\uC774\uC9C0\uC758 \uC694\uCCAD\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
24758
25125
  if (url2.search && path3 !== "/api/v1/specs") return fail8(response, 400, "BAD_REQUEST", "\uC870\uD68C \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
25126
+ if (path3.startsWith("/api/v1/product/assets/")) {
25127
+ const name = path3.slice("/api/v1/product/assets/".length);
25128
+ if (request.method !== "GET") {
25129
+ response.setHeader("Allow", "GET");
25130
+ return fail8(response, 405, "METHOD_NOT_ALLOWED", "\uD5C8\uC6A9\uD558\uC9C0 \uC54A\uB294 \uBA54\uC11C\uB4DC\uC785\uB2C8\uB2E4.");
25131
+ }
25132
+ if (!isProductAssetPath(".gitifact/product/" + name)) return fail8(response, 404, "NOT_FOUND", "\uC81C\uD488 \uC774\uBBF8\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
25133
+ const file2 = join4(initial.repository.rootPath, ".gitifact", "product", name);
25134
+ const stat3 = await lstat3(file2).catch(() => void 0);
25135
+ if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > 5 * 1024 * 1024) return fail8(response, 404, "NOT_FOUND", "\uC81C\uD488 \uC774\uBBF8\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
25136
+ const bytes = await readFile4(file2);
25137
+ response.writeHead(200, { "Content-Type": contentType(name.toLowerCase()), "Cache-Control": "no-cache", "Content-Length": bytes.length });
25138
+ response.end(bytes);
25139
+ return;
25140
+ }
24759
25141
  const method = path3 === "/api/v1/status/refresh" ? "POST" : "GET";
24760
25142
  if (!["/api/v1/session", "/api/v1/status", "/api/v1/status/refresh", "/api/v1/specs"].includes(path3)) return fail8(response, 404, "NOT_FOUND", "API\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
24761
25143
  if (request.method !== method) {
@@ -24881,12 +25263,12 @@ async function runBrowser(options) {
24881
25263
 
24882
25264
  // src/commands/spec-init.ts
24883
25265
  import { readdir as readdir3 } from "node:fs/promises";
24884
- import { join as join5 } from "node:path";
25266
+ import { join as join7 } from "node:path";
24885
25267
 
24886
25268
  // src/adapters/git/init-repository.ts
24887
25269
  import { createHash as createHash3 } from "node:crypto";
24888
- import { readFile as readFile4 } from "node:fs/promises";
24889
- import { join as join4 } from "node:path";
25270
+ import { readFile as readFile5 } from "node:fs/promises";
25271
+ import { join as join5 } from "node:path";
24890
25272
  function initRepository(cwd, inherited = process.env) {
24891
25273
  const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "1", LC_ALL: "C" };
24892
25274
  const runner = createGitRunner();
@@ -24906,10 +25288,10 @@ function initRepository(cwd, inherited = process.env) {
24906
25288
  const gitDir = locations.length === 2 ? locations[0] : decodeGitLine(await git(["rev-parse", "--absolute-git-dir"], root));
24907
25289
  const indexPath = locations.length === 2 ? locations[1] : decodeGitLine(await git(["rev-parse", "--path-format=absolute", "--git-path", "index"], root));
24908
25290
  for (const marker2 of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"]) {
24909
- if (await fileInfo(join4(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: " + marker2);
25291
+ if (await fileInfo(join5(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: " + marker2);
24910
25292
  }
24911
25293
  if (state.changes.some((change2) => change2.kind === "unmerged")) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uCDA9\uB3CC\uC744 \uBA3C\uC800 \uD574\uACB0\uD558\uC138\uC694.");
24912
- const index = await readFile4(indexPath).catch((error62) => {
25294
+ const index = await readFile5(indexPath).catch((error62) => {
24913
25295
  if (error62.code === "ENOENT") return Buffer.alloc(0);
24914
25296
  throw error62;
24915
25297
  });
@@ -24923,11 +25305,6 @@ function initRepository(cwd, inherited = process.env) {
24923
25305
  stamp: JSON.stringify({ repository: state.repository, head: state.head, index: createHash3("sha256").update(index).digest("hex") })
24924
25306
  };
24925
25307
  },
24926
- async trackedPaths(root, prefix, commit) {
24927
- const indexed = await git(["ls-files", "-z", "--", prefix], root);
24928
- const committed = commit ? await git(["ls-tree", "-r", "--name-only", "-z", commit, "--", prefix], root) : Buffer.alloc(0);
24929
- return [...new Set(Buffer.concat([indexed, committed]).toString("utf8").split("\0").filter(Boolean))];
24930
- },
24931
25308
  async checkIgnore(root, path3 = ".gitifact/config.json") {
24932
25309
  const output2 = (await git(["check-ignore", "--no-index", "-v", "-z", "--stdin"], root, [0, 1], Buffer.from(path3 + "\0"))).toString("utf8");
24933
25310
  const fields2 = output2.split("\0");
@@ -24947,87 +25324,12 @@ function initRepository(cwd, inherited = process.env) {
24947
25324
  };
24948
25325
  }
24949
25326
 
24950
- // src/commands/spec-init.ts
24951
- async function initializeSpecProject(cwd, dryRun = false, env = process.env, beforePublish) {
24952
- const repo = initRepository(cwd, env);
24953
- const first = await repo.inspect();
24954
- const root = first.state.repository.rootPath;
24955
- const result = (config3, outcome) => ({
24956
- contract: "project-init",
24957
- version: 3,
24958
- ok: true,
24959
- outcome,
24960
- rootPath: root,
24961
- configPath: ".gitifact/config.json",
24962
- schemaVersion: config3.schemaVersion,
24963
- baseline: config3.baseline,
24964
- integrations: { skills: "not-installed-by-init", hooks: "not-installed-by-init" }
24965
- });
24966
- const existing = async (text2) => {
24967
- const config3 = parseManagedConfig(text2);
24968
- if (!("schemaVersion" in config3)) throw new InitError("MIGRATION_REQUIRED", "\uAE30\uC874 \uD615\uC2DD\uC740 \uBCC4\uB3C4 \uC804\uD658\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uC124\uC815\uACFC \uAE30\uB85D\uC744 \uBCF4\uC874\uD558\uC138\uC694.");
24969
- await repo.validateBaseline(config3, root, first.state.head.commit, first.state.repository.objectFormat);
24970
- if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text2) throw new InitError("INPUT_CHANGED", "\uCD08\uAE30\uD654 \uC870\uD68C \uC911 \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
24971
- return result(config3, "already-initialized");
24972
- };
24973
- const old = await readConfigFile(root);
24974
- if (old !== void 0) return existing(old);
24975
- if (first.trackedConfig) throw new InitError("CONFIG_DELETED", "\uAE30\uC874 \uC124\uC815\uC758 \uC0AD\uC81C\uB97C \uC2E0\uADDC \uB3C4\uC785\uC73C\uB85C \uCC98\uB9AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
24976
- const checkRecords = async () => {
24977
- if (await fileInfo(join5(root, "specs"))) throw new InitError("EXISTING_RECORDS", "\uAE30\uC874 specs \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
24978
- const entries = await readdir3(join5(root, ".gitifact")).catch((e) => {
24979
- if (e.code === "ENOENT") return [];
24980
- throw e;
24981
- });
24982
- if (entries.some((name) => !/^\.init-[a-f0-9-]+\.tmp$/.test(name))) throw new InitError("EXISTING_RECORDS", "\uC124\uC815 \uC5C6\uB294 .gitifact \uC790\uB8CC\uB97C \uC790\uB3D9 \uCC44\uD0DD\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
24983
- };
24984
- const config2 = { schemaVersion: 1, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
24985
- const recheck = async () => {
24986
- await checkRecords();
24987
- await repo.checkIgnore(root);
24988
- if ((await repo.inspect()).stamp !== first.stamp) throw new InitError("INPUT_CHANGED", "HEAD \uB610\uB294 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
24989
- if (await readConfigFile(root) !== void 0) throw new InitError("CONFIG_APPEARED", "\uB2E4\uB978 \uC2E4\uD589\uC774 \uC124\uC815\uC744 \uB9CC\uB4E4\uC5C8\uC2B5\uB2C8\uB2E4.");
24990
- };
24991
- await recheck();
24992
- if (dryRun) return result(config2, "planned");
24993
- try {
24994
- await publishConfig(root, JSON.stringify(config2, null, 2) + "\n", recheck, beforePublish);
24995
- } catch (error62) {
24996
- if (error62.code === "EEXIST" || error62 instanceof InitError && error62.code === "CONFIG_APPEARED") {
24997
- const concurrent = await readConfigFile(root);
24998
- if (concurrent !== void 0) return existing(concurrent);
24999
- }
25000
- throw error62;
25001
- }
25002
- if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== JSON.stringify(config2, null, 2) + "\n") throw new InitError("INPUT_CHANGED_AFTER_WRITE", "\uC124\uC815 \uC0DD\uC131 \uD6C4 \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694.");
25003
- return result(config2, "created");
25004
- }
25005
-
25006
- // src/commands/init.ts
25007
- async function runInit(options) {
25008
- try {
25009
- const dto = await initializeSpecProject(process.cwd(), options.dryRun);
25010
- process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
25011
- \uC800\uC7A5 \uADDC\uC57D: schemaVersion 1
25012
- \uC2A4\uD0AC\xB7\uD6C5\uC740 \uC124\uCE58\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
25013
- ` : JSON.stringify(dto) + "\n");
25014
- } catch (error62) {
25015
- const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
25016
- const failure2 = { code: known ? error62.code : "INIT_FAILED", message: known ? error62.message : "\uCD08\uAE30\uD654\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uD30C\uC77C \uC811\uADFC \uAD8C\uD55C\uACFC \uC0DD\uC131\uB41C \uC124\uC815\xB7\uC784\uC2DC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694." };
25017
- process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify({ contract: "project-init", version: 1, ok: false, error: failure2 }) + "\n");
25018
- process.exitCode = 1;
25019
- }
25020
- }
25021
-
25022
- // src/commands/skills.ts
25023
- import { createHash as createHash4 } from "node:crypto";
25024
- import { readFile as readFile6 } from "node:fs/promises";
25025
-
25026
- // src/adapters/filesystem/skill-files.ts
25027
- import { open as open3, readFile as readFile5, mkdir as mkdir3, realpath as realpath2, rename as rename2, link as link2, unlink as unlink3 } from "node:fs/promises";
25327
+ // src/adapters/filesystem/managed-file.ts
25328
+ import { open as open3, readFile as readFile6, mkdir as mkdir3, realpath as realpath2, rename as rename2, link as link2, unlink as unlink3 } from "node:fs/promises";
25028
25329
  import { join as join6, dirname as dirname2 } from "node:path";
25029
25330
  import { randomUUID as randomUUID5 } from "node:crypto";
25030
- async function skillPath(root, path3, create = false) {
25331
+ var DEFAULT_LIMIT = 1024 * 1024;
25332
+ async function managedPath(root, path3, create = false) {
25031
25333
  let full = root;
25032
25334
  const parts = path3.split("/");
25033
25335
  for (let i = 0; i < parts.length; i++) {
@@ -25045,41 +25347,41 @@ async function skillPath(root, path3, create = false) {
25045
25347
  }
25046
25348
  return full;
25047
25349
  }
25048
- async function skillRead(root, path3) {
25049
- const full = await skillPath(root, path3);
25350
+ async function managedRead(root, path3, limit = DEFAULT_LIMIT) {
25351
+ const full = await managedPath(root, path3);
25050
25352
  const before = await fileInfo(full);
25051
25353
  if (!before) return null;
25052
- if (before.size > 65536) throw new InitError("SKILL_TOO_LARGE", "64 KiB\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4: " + path3);
25053
- const bytes = await readFile5(full);
25054
- await skillPath(root, path3);
25354
+ if (before.size > limit) throw new InitError("FILE_TOO_LARGE", limit / 1024 + " KiB\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4: " + path3);
25355
+ const bytes = await readFile6(full);
25356
+ await managedPath(root, path3);
25055
25357
  const after = await fileInfo(full);
25056
- if (!after || before.ino !== after.ino || before.dev !== after.dev || before.mtimeMs !== after.mtimeMs || bytes.length > 65536) {
25358
+ if (!after || before.ino !== after.ino || before.dev !== after.dev || before.mtimeMs !== after.mtimeMs || bytes.length > limit) {
25057
25359
  throw new InitError("INPUT_CHANGED", "\uC870\uD68C \uC911 \uD30C\uC77C\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
25058
25360
  }
25059
25361
  try {
25060
25362
  return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
25061
25363
  } catch {
25062
- throw new InitError("INVALID_SKILL", "UTF-8 \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: " + path3);
25364
+ throw new InitError("INVALID_UTF8", "UTF-8 \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: " + path3);
25063
25365
  }
25064
25366
  }
25065
- async function skillWrite(root, path3, previous, next, recheck) {
25066
- const full = await skillPath(root, path3, true);
25367
+ async function managedWrite(root, path3, previous, next, recheck) {
25368
+ const full = await managedPath(root, path3, true);
25067
25369
  const directory = dirname2(full);
25068
25370
  const owner = await fileInfo(directory);
25069
25371
  const canonical = await realpath2(directory);
25070
25372
  const unchanged = async () => {
25071
- await skillPath(root, path3);
25373
+ await managedPath(root, path3);
25072
25374
  const now = await fileInfo(directory);
25073
- if (now?.ino !== owner?.ino || now?.dev !== owner?.dev || await realpath2(directory) !== canonical) throw new InitError("INPUT_CHANGED", "\uC2A4\uD0AC \uACBD\uB85C\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25375
+ if (now?.ino !== owner?.ino || now?.dev !== owner?.dev || await realpath2(directory) !== canonical) throw new InitError("INPUT_CHANGED", "\uB300\uC0C1 \uACBD\uB85C\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
25074
25376
  };
25075
25377
  if (next === null) {
25076
25378
  await recheck();
25077
25379
  await unchanged();
25078
- if (await skillRead(root, path3) !== previous) throw new InitError("INPUT_CHANGED", "\uC81C\uAC70 \uB300\uC0C1\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25380
+ if (await managedRead(root, path3) !== previous) throw new InitError("INPUT_CHANGED", "\uC81C\uAC70 \uB300\uC0C1\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
25079
25381
  if (previous !== null) await unlink3(full);
25080
25382
  return;
25081
25383
  }
25082
- const temp = join6(directory, ".gitifact-skill-" + randomUUID5() + ".tmp");
25384
+ const temp = join6(directory, ".gitifact-managed-" + randomUUID5() + ".tmp");
25083
25385
  const handle = await open3(temp, "wx", 384);
25084
25386
  const tempOwner = await handle.stat();
25085
25387
  try {
@@ -25091,7 +25393,7 @@ async function skillWrite(root, path3, previous, next, recheck) {
25091
25393
  }
25092
25394
  await recheck();
25093
25395
  await unchanged();
25094
- if (await skillRead(root, path3) !== previous || await readFile5(temp, "utf8") !== next) throw new InitError("INPUT_CHANGED", "\uAC8C\uC2DC \uB300\uC0C1\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25396
+ if (await managedRead(root, path3) !== previous || await readFile6(temp, "utf8") !== next) throw new InitError("INPUT_CHANGED", "\uAC8C\uC2DC \uB300\uC0C1\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
25095
25397
  if (previous === null) await link2(temp, full);
25096
25398
  else await rename2(temp, full);
25097
25399
  } finally {
@@ -25100,163 +25402,292 @@ async function skillWrite(root, path3, previous, next, recheck) {
25100
25402
  if (now?.ino === tempOwner.ino && now.dev === tempOwner.dev) await unlink3(temp);
25101
25403
  }
25102
25404
  }
25103
- async function skillLock(root, path3, action) {
25104
- const full = await skillPath(root, path3, true);
25105
- const handle = await open3(full, "wx").catch((error62) => {
25106
- if (error62.code === "EEXIST") throw new InitError("SKILLS_BUSY", "\uC2A4\uD0AC \uC791\uC5C5 \uC7A0\uAE08\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uC2E4\uD589 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694.");
25107
- throw error62;
25108
- });
25109
- const owner = await handle.stat();
25110
- await handle.close();
25111
- try {
25112
- return await action();
25113
- } finally {
25114
- await skillPath(root, path3);
25115
- const now = await fileInfo(full);
25116
- if (now?.ino === owner.ino && now.dev === owner.dev) await unlink3(full);
25405
+
25406
+ // src/commands/docs.ts
25407
+ import { readFile as readFile7 } from "node:fs/promises";
25408
+ var docTopics = ["workflow", "spec", "design", "product", "commit"];
25409
+ var summaries = {
25410
+ workflow: "\uC2DC\uC791 \uC2DC \uD655\uC778\uD560 \uAC83, \uC694\uAD6C\uC0AC\uD56D\uC73C\uB85C \uB0A8\uAE38 \uC694\uCCAD\uC758 \uAD6C\uBD84, \uB9C8\uBB34\uB9AC \uBCF4\uACE0",
25411
+ spec: "\uC0AC\uC6A9\uC790 \uC2A4\uD1A0\uB9AC\xB7\uC218\uC6A9 \uC870\uAC74 \uD615\uC2DD, ID \uADDC\uCE59, spec save \uC785\uB825",
25412
+ design: "design.md \uBAA9\uCC28, \uCC38\uC870 \uC8FC\uC11D, \uAC1C\uC815 \uBC29\uC2DD",
25413
+ product: "PRODUCT.md\uC640 guides \uBB38\uC11C\uC758 \uC5ED\uD560\uACFC \uC800\uC7A5 \uBA85\uB839",
25414
+ commit: "spec commit \uC785\uB825, \uC774\uC720 \uAE30\uB85D, \uC2E4\uD328 \uD6C4 \uBCF5\uAD6C"
25415
+ };
25416
+ var isDocTopic = (value) => docTopics.includes(value);
25417
+ var readBundled = (topic) => readFile7(new URL("./docs/" + topic + ".md", import.meta.url), "utf8");
25418
+ function listDocTopics() {
25419
+ const width = Math.max(...docTopics.map((topic) => topic.length));
25420
+ return docTopics.map((topic) => topic.padEnd(width) + " " + summaries[topic]).join("\n") + "\n";
25421
+ }
25422
+ async function runDocs(topic, controls = {}) {
25423
+ if (topic === void 0) {
25424
+ process.stdout.write(listDocTopics());
25425
+ return;
25117
25426
  }
25427
+ if (!isDocTopic(topic)) {
25428
+ const failure2 = { contract: "docs", version: 1, ok: false, error: { code: "UNKNOWN_TOPIC", message: "\uC54C \uC218 \uC5C6\uB294 \uC8FC\uC81C\uC785\uB2C8\uB2E4: " + topic + ". \uC0AC\uC6A9 \uAC00\uB2A5: " + docTopics.join(", ") } };
25429
+ process.stderr.write(JSON.stringify(failure2) + "\n");
25430
+ process.exitCode = 1;
25431
+ return;
25432
+ }
25433
+ process.stdout.write(await (controls.readDoc ?? readBundled)(topic));
25118
25434
  }
25119
25435
 
25120
- // src/commands/skills.ts
25121
- var source = ".agents/skills/gitifact-workflow/SKILL.md";
25122
- var target = ".claude/skills/gitifact-workflow/SKILL.md";
25123
- var manifest = ".agents/gitifact-skills.local.json";
25124
- var lock = ".agents/gitifact-skills.local.lock";
25125
- var ignores = ["/" + manifest, "/" + lock, "/" + target, "/.claude/skills/gitifact-workflow/.gitifact-skill-*.tmp", "/.agents/skills/gitifact-workflow/.gitifact-skill-*.tmp", "/.agents/.gitifact-skill-*.tmp"];
25126
- var digest2 = (text2) => text2 === null ? null : createHash4("sha256").update(text2).digest("hex");
25127
- function parseState(text2) {
25128
- if (text2 === null) return null;
25129
- let value;
25130
- try {
25131
- value = JSON.parse(text2);
25132
- } catch {
25133
- throw new InitError("INVALID_SKILL_MANIFEST", "\uB85C\uCEEC \uC2A4\uD0AC \uB9E4\uB2C8\uD398\uC2A4\uD2B8\uAC00 \uC190\uC0C1\uB410\uC2B5\uB2C8\uB2E4.");
25134
- }
25135
- if (!value || Object.keys(value).sort().join() !== "accepted,agent,source,target,version" || value.version !== 1 || value.source !== source || value.target !== target || ![null, "codex", "claude"].includes(value.agent) || !Array.isArray(value.accepted) || value.accepted.length < 1 || value.accepted.length > 2 || value.accepted.some((item) => item !== null && (typeof item !== "string" || !/^[a-f0-9]{64}$/.test(item)))) {
25136
- throw new InitError("UNSUPPORTED_SKILL_MANIFEST", "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uB85C\uCEEC \uC2A4\uD0AC \uB9E4\uB2C8\uD398\uC2A4\uD2B8\uC785\uB2C8\uB2E4.");
25436
+ // src/commands/agent-block.ts
25437
+ var AGENT_START = "<!-- GITIFACT:START -->";
25438
+ var AGENT_END = "<!-- GITIFACT:END -->";
25439
+ var candidatePaths = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", ".cursorrules", ".hermes.md", "HERMES.md"];
25440
+ var agentPresetNames = ["claude", "cursor", "codex", "hermes", "all"];
25441
+ var agentPresets = {
25442
+ claude: ["CLAUDE.md", ".claude/CLAUDE.md"],
25443
+ cursor: [".cursorrules", "AGENTS.md"],
25444
+ codex: ["AGENTS.md"],
25445
+ hermes: [".hermes.md", "HERMES.md", "AGENTS.md"]
25446
+ };
25447
+ function boilerplateFor(path3) {
25448
+ const name = path3 === "AGENTS.md" ? path3 : path3.split("/").pop().replace(/^\./, "").replace(/\.md$/, "");
25449
+ return "# " + name + "\n\nProject-specific guidance for AI coding agents.\n";
25450
+ }
25451
+ function renderAgentBlock(version2) {
25452
+ const lines = [
25453
+ AGENT_START,
25454
+ "gitifact v" + version2 + " \xB7 \uC800\uC7A5 \uADDC\uC57D schemaVersion 1",
25455
+ "CLI: \uBAA8\uB4E0 \uBA85\uB839\uC740 `gitifact <cmd>`\uB85C \uC2E4\uD589\uD55C\uB2E4. \uD504\uB85C\uC81D\uD2B8 \uC9C0\uCE68\uC774 \uB2E4\uB978 \uC2E4\uD589 \uBC29\uBC95\uC744 \uC9C0\uC815\uD558\uBA74 \uADF8\uAC83\uC744 \uB530\uB978\uB2E4.",
25456
+ "",
25457
+ "\uC2DC\uC791\uD560 \uB54C:",
25458
+ "- `gitifact spec working`\uC73C\uB85C \uC81C\uD488 \uC124\uBA85\xB7\uC9C0\uCE68\xB7\uAE30\uB2A5 \uBA85\uC138\uB97C \uC77D\uACE0 git status\uC640 \uAE30\uC874 staging\uC744 \uD655\uC778\uD55C\uB2E4.",
25459
+ "- \uC774 \uBE14\uB85D\uC740 \uC694\uC57D\uC774\uB2E4. \uC0C1\uC138 \uD615\uC2DD\uC740 `gitifact docs <topic>`\uC73C\uB85C \uC77D\uACE0 \uAE30\uC5B5\uC73C\uB85C \uCC44\uC6B0\uC9C0 \uC54A\uB294\uB2E4.",
25460
+ "",
25461
+ "\uBB34\uC5C7\uC744 \uC694\uAD6C\uC0AC\uD56D\uC73C\uB85C \uB0A8\uAE30\uB294\uAC00 (\uC81C\uD488 \uB3D9\uC791\uACFC \uC720\uC9C0\uD560 \uC81C\uC57D\uB9CC):",
25462
+ "| \uC694\uCCAD | \uCC98\uB9AC |",
25463
+ "| --- | --- |",
25464
+ "| \uAC8C\uC2DC\uBB3C\uC744 \uC0AD\uC81C\uD560 \uC218 \uC788\uAC8C \uD574\uC8FC\uC138\uC694 | \uC694\uAD6C\uC0AC\uD56D\uC73C\uB85C \uC815\uB9AC\uD55C\uB2E4 |",
25465
+ "| \uC774 \uB0B4\uBD80 \uD568\uC218 \uC774\uB984\uC744 \uBC14\uAFD4\uC8FC\uC138\uC694 | \uC77C\uBC18 \uAD6C\uD604 \uBCC0\uACBD\uC774\uB2E4 |",
25466
+ "| \uC9C0\uAE08 \uD478\uC2DC\uD574\uC8FC\uC138\uC694 | \uC791\uC5C5 \uC9C0\uC2DC\uB2E4. \uB4F1\uB85D\uD558\uC9C0 \uC54A\uB294\uB2E4 |",
25467
+ "| \uC678\uBD80 \uC11C\uBE44\uC2A4 \uC5C6\uC774 \uB3D9\uC791\uD574\uC57C \uD569\uB2C8\uB2E4 | \uC81C\uD488 \uC81C\uC57D\uC73C\uB85C \uBA85\uC138\uC5D0 \uBC18\uC601\uD55C\uB2E4 |",
25468
+ "",
25469
+ "\uADDC\uCE59:",
25470
+ "- \uBA85\uC138\uB97C \uC800\uC7A5\uD558\uAE30 \uC804\uC5D0 `gitifact docs spec`\uC744 \uC77D\uB294\uB2E4. ID\uB294 CLI\uAC00 \uBC1C\uAE09\uD55C \uAC12\uB9CC \uC4F4\uB2E4.",
25471
+ "- \uC0C8 \uAE30\uB2A5\uC740 requirements.md\uC640 design.md\uB97C \uD568\uAED8 \uC815\uB9AC\uD55C\uB2E4(`gitifact docs design`). \uC694\uAD6C\uC0AC\uD56D\uB9CC \uC694\uCCAD\uBC1B\uC73C\uBA74 \uB530\uB978\uB2E4.",
25472
+ "- \uCEE4\uBC0B \uC694\uCCAD\uC744 \uBC1B\uC73C\uBA74 `gitifact docs commit`\uC744 \uC77D\uACE0 \uBA85\uC138\xB7\uC774\uC720\xB7\uCF54\uB4DC\xB7\uD14C\uC2A4\uD2B8\uB97C \uD568\uAED8 \uCEE4\uBC0B\uD55C\uB2E4.",
25473
+ "- \uC790\uB3D9 \uAE30\uB85D\uC740 \uCEE4\uBC0B \uAD8C\uD55C\uC774 \uC544\uB2C8\uB2E4. \uC0AC\uC6A9\uC790 \uC694\uCCAD\uC774\uB098 \uBA85\uC2DC\uC801 \uD504\uB85C\uC81D\uD2B8 \uC815\uCC45\uC774 \uC788\uC744 \uB54C\uB9CC \uCEE4\uBC0B\uD558\uACE0 \uD478\uC2DC\uB294 \uBCC4\uB3C4 \uC694\uCCAD\uC744 \uB530\uB978\uB2E4.",
25474
+ "- \uBD88\uBA85\uD655\uD55C \uC81C\uD488 \uB3D9\uC791\uB9CC \uC9C8\uBB38\uD558\uACE0 \uB3C5\uB9BD\uC801\uC778 \uC791\uC5C5\uC740 \uC9C4\uD589\uD55C\uB2E4. \uAE30\uC874 \uAE30\uB2A5 \uC804\uCCB4 \uB3C4\uCD9C\uC740 \uC694\uCCAD\uBC1B\uC558\uC744 \uB54C \uD55C\uB2E4.",
25475
+ "- SELF-CHECK: save\xB7commit \uC785\uB825\uC744 \uB9CC\uB4E4\uAE30 \uC804\uC5D0 \uD574\uB2F9 docs\uB97C \uB2E4\uC2DC \uC77D\uACE0 \uD615\uC2DD\uC744 \uB300\uC870\uD55C\uB2E4. \uD655\uC2E4\uD558\uC9C0 \uC54A\uC73C\uBA74 \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 `gitifact docs <topic>`\uC744 \uC2E4\uD589\uD55C\uB2E4.",
25476
+ "",
25477
+ "MORE CLI:",
25478
+ " docs <topic> " + docTopics.join(", "),
25479
+ " spec working \uD604\uC7AC \uBA85\uC138\xB7\uC81C\uD488\xB7\uC9C0\uCE68 \uC804\uCCB4\uC640 stamp",
25480
+ " spec save --file <json>: \uC694\uAD6C\uC0AC\uD56D\xB7\uC124\uACC4\xB7\uBB38\uC11C \uC800\uC7A5",
25481
+ " spec commit --file <json>: \uBCC0\uACBD \uC774\uC720 \uAE30\uB85D\uACFC \uCEE4\uBC0B\uC744 \uD55C \uBC88\uC5D0",
25482
+ " browser \uC77D\uAE30 \uC804\uC6A9 \uBE0C\uB77C\uC6B0\uC800 \uC5F4\uAE30",
25483
+ " init \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uC774 \uBE14\uB85D\uC744 \uAC31\uC2E0\uD55C\uB2E4",
25484
+ AGENT_END
25485
+ ];
25486
+ return lines.join("\n");
25487
+ }
25488
+ function findBlock(text2) {
25489
+ const start = text2.indexOf(AGENT_START);
25490
+ if (start === -1) {
25491
+ if (text2.includes(AGENT_END)) throw new InitError("AGENT_DOCS_MALFORMED", "\uC2DC\uC791 \uB9C8\uCEE4 \uC5C6\uC774 \uC885\uB8CC \uB9C8\uCEE4\uB9CC \uC788\uC2B5\uB2C8\uB2E4. \uBE14\uB85D\uC744 \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
25492
+ return null;
25137
25493
  }
25138
- return value;
25494
+ const end = text2.indexOf(AGENT_END, start + AGENT_START.length);
25495
+ if (end === -1) throw new InitError("AGENT_DOCS_MALFORMED", "\uC885\uB8CC \uB9C8\uCEE4\uAC00 \uC5C6\uB294 GITIFACT \uBE14\uB85D\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uBE14\uB85D\uC744 \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
25496
+ if (text2.indexOf(AGENT_START, start + AGENT_START.length) !== -1) throw new InitError("AGENT_DOCS_MALFORMED", "GITIFACT \uC2DC\uC791 \uB9C8\uCEE4\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4. \uC911\uBCF5 \uBE14\uB85D\uC744 \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
25497
+ return { start, end: end + AGENT_END.length };
25498
+ }
25499
+ function isWrapperFile(text2) {
25500
+ const found = findBlock(text2);
25501
+ const own2 = found ? text2.slice(0, found.start) + text2.slice(found.end) : text2;
25502
+ const lines = own2.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
25503
+ return lines.length > 0 && lines.every((line) => /^@(?:\.\/)?[^\s]+$/.test(line) && candidatePaths.includes(line.replace(/^@(?:\.\/)?/, "")));
25504
+ }
25505
+ function resolveAgentPaths(preset, existing) {
25506
+ if (preset && preset !== "all") {
25507
+ const order = agentPresets[preset];
25508
+ const found = order.find((path3) => existing.has(path3));
25509
+ return found ? { inject: [found], create: null } : { inject: [], create: order[order.length - 1] };
25510
+ }
25511
+ const inject = candidatePaths.filter((path3) => existing.has(path3) && !isWrapperFile(existing.get(path3)));
25512
+ if (inject.length > 0) return { inject, create: null };
25513
+ return { inject: [], create: "AGENTS.md" };
25514
+ }
25515
+ var eolOf = (text2) => text2?.includes("\r\n") ? "\r\n" : "\n";
25516
+ function injectBlock(text2, block, boilerplate) {
25517
+ const eol = eolOf(text2);
25518
+ const body = block.split("\n").join(eol);
25519
+ if (text2 === null) return boilerplate.split("\n").join(eol) + eol + body + eol;
25520
+ const found = findBlock(text2);
25521
+ if (found) return text2.slice(0, found.start) + body + text2.slice(found.end);
25522
+ const head = text2.replace(/\s+$/, "");
25523
+ return (head ? head + eol + eol : "") + body + eol;
25524
+ }
25525
+ function removeBlock(text2, boilerplate) {
25526
+ const found = findBlock(text2);
25527
+ if (!found) return text2;
25528
+ const eol = eolOf(text2);
25529
+ const before = text2.slice(0, found.start).replace(/\s+$/, "");
25530
+ const after = text2.slice(found.end).replace(/^\s+/, "");
25531
+ const rest = before && after ? before + eol + eol + after : before ? before + eol : after;
25532
+ const normalize = (value) => value.replace(/\r\n/g, "\n").trim();
25533
+ if (!normalize(rest) || normalize(rest) === normalize(boilerplate)) return null;
25534
+ return rest;
25139
25535
  }
25140
- async function skillsCommand(cwd, action, options = {}, env = process.env, controls = {}) {
25536
+
25537
+ // src/commands/agent-docs.ts
25538
+ var skippedAgentDocs = { mode: "skip", paths: [], writes: [] };
25539
+ async function planAgentDocs(root, options) {
25540
+ if (options.skip) return skippedAgentDocs;
25541
+ const existing = /* @__PURE__ */ new Map();
25542
+ for (const path3 of candidatePaths) {
25543
+ const text2 = await managedRead(root, path3);
25544
+ if (text2 !== null) existing.set(path3, text2);
25545
+ }
25546
+ const writes = [];
25547
+ const paths2 = [];
25548
+ if (options.remove) {
25549
+ const scope = options.agent && options.agent !== "all" ? agentPresets[options.agent] : candidatePaths;
25550
+ for (const path3 of scope) {
25551
+ const text2 = existing.get(path3);
25552
+ if (text2 === void 0 || !findBlock(text2)) continue;
25553
+ paths2.push(path3);
25554
+ writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
25555
+ }
25556
+ return { mode: "remove", paths: paths2, writes };
25557
+ }
25558
+ const block = renderAgentBlock(options.version);
25559
+ const { inject, create } = resolveAgentPaths(options.agent, existing);
25560
+ for (const path3 of inject) {
25561
+ const previous = existing.get(path3);
25562
+ const next = injectBlock(previous, block, boilerplateFor(path3));
25563
+ paths2.push(path3);
25564
+ if (next !== previous) writes.push({ path: path3, previous, next });
25565
+ }
25566
+ if (create) {
25567
+ paths2.push(create);
25568
+ writes.push({ path: create, previous: null, next: injectBlock(null, block, boilerplateFor(create)) });
25569
+ }
25570
+ for (const [path3, text2] of existing) {
25571
+ if (paths2.includes(path3) || !isWrapperFile(text2) || !findBlock(text2)) continue;
25572
+ writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
25573
+ }
25574
+ return { mode: "install", paths: paths2, writes };
25575
+ }
25576
+ async function applyAgentDocs(root, plan, recheck) {
25577
+ for (const write of plan.writes) await managedWrite(root, write.path, write.previous, write.next, recheck);
25578
+ return plan.writes.map((write) => write.path);
25579
+ }
25580
+
25581
+ // src/commands/spec-init.ts
25582
+ async function initializeSpecProject(cwd, dryRun = false, env = process.env, beforePublish, agentDocs) {
25141
25583
  const repo = initRepository(cwd, env);
25142
25584
  const first = await repo.inspect();
25143
25585
  const root = first.state.repository.rootPath;
25144
- const configText = await readConfigFile(root);
25145
- if (configText === void 0) throw new InitError("NOT_INITIALIZED", "\uBA3C\uC800 \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD558\uC138\uC694.");
25146
- const config2 = parseManagedConfig(configText);
25147
- await repo.validateBaseline(config2, root, first.state.head.commit, first.state.repository.objectFormat);
25148
- const recheck = async () => {
25149
- if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== configText) throw new InitError("INPUT_CHANGED", "Git \uB610\uB294 \uD504\uB85C\uC81D\uD2B8 \uC124\uC815\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25150
- };
25151
- const execute2 = async () => {
25152
- const original = await skillRead(root, source);
25153
- const saved = await skillRead(root, manifest);
25154
- const state = parseState(saved);
25155
- const copy = await skillRead(root, target);
25156
- if (copy !== null && !state) throw new InitError("UNMANAGED_SKILL", "\uAE30\uC874 Claude \uBCF5\uC0AC\uBCF8\uC740 \uAD00\uB9AC \uB300\uC0C1\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uBCF4\uC874\uD558\uACE0 \uCD9C\uCC98\uB97C \uD655\uC778\uD558\uC138\uC694.");
25157
- if (state && !state.accepted.includes(digest2(copy))) throw new InitError("SKILL_MODIFIED", "Claude \uBCF5\uC0AC\uBCF8\uC774 \uC218\uC815\uB418\uAC70\uB098 \uC0AD\uC81C\uB410\uC2B5\uB2C8\uB2E4. \uC6D0\uBCF8\uACFC \uBE44\uAD50\uD558\uACE0 \uBCF4\uC874\uD558\uC138\uC694.");
25158
- const tracked = await repo.trackedPaths(root, ".agents/", first.state.head.commit);
25159
- const localTracked = await repo.trackedPaths(root, ".claude/skills/gitifact-workflow/", first.state.head.commit);
25160
- if (tracked.includes(manifest) || tracked.includes(lock) || localTracked.length) throw new InitError("LOCAL_SKILL_TRACKED", "\uB85C\uCEEC \uC2A4\uD0AC \uD30C\uC77C\uC774 Git\uC5D0\uC11C \uCD94\uC801 \uC911\uC785\uB2C8\uB2E4. \uBA3C\uC800 \uBCF4\uAD00 \uBC29\uCE68\uC744 \uD655\uC778\uD558\uC138\uC694.");
25161
- if (original === null && tracked.includes(source)) throw new InitError("SKILL_DELETED", "Git\uC5D0 \uC788\uB294 \uC2A4\uD0AC \uC6D0\uBCF8\uC774 \uC0AD\uC81C\uB410\uC2B5\uB2C8\uB2E4. \uBCF5\uAD6C \uD6C4 \uC2E4\uD589\uD558\uC138\uC694.");
25162
- const agent = action === "remove" ? null : options.agent ?? state?.agent;
25163
- if (action !== "remove" && !agent) throw new InitError("AGENT_REQUIRED", "--agent codex \uB610\uB294 claude\uB97C \uC9C0\uC815\uD558\uC138\uC694.");
25164
- if (action === "sync" && original === null) throw new InitError("SKILL_NOT_INSTALLED", "\uC2A4\uD0AC \uC6D0\uBCF8\uC744 \uBA3C\uC800 \uC124\uCE58\uD558\uC138\uC694.");
25165
- let wanted = original;
25166
- if (action === "install" && wanted === null) wanted = controls.template ?? await readFile6(new URL("./skills/gitifact-workflow/SKILL.md", import.meta.url), "utf8");
25167
- if (wanted !== null && (!wanted.startsWith("---") || !/^name: gitifact-workflow\r?$/m.test(wanted) || Buffer.byteLength(wanted) > 65536)) throw new InitError("INVALID_SKILL", "gitifact-workflow \uC6D0\uBCF8 \uD615\uC2DD\uC744 \uD655\uC778\uD558\uC138\uC694.");
25168
- const nextCopy = agent === "claude" ? wanted : null;
25169
- const ignoreBefore = await skillRead(root, ".gitignore");
25170
- const lines = (ignoreBefore ?? "").split(/\r?\n/);
25171
- const missing2 = ignores.filter((line) => !lines.includes(line));
25172
- const ignoreAfter = (ignoreBefore ?? "") + (ignoreBefore && !ignoreBefore.endsWith("\n") ? "\n" : "") + missing2.join("\n") + (missing2.length ? "\n" : "");
25173
- const result = (outcome) => skillsV1.parse({
25174
- contract: "skills",
25175
- version: 1,
25176
- ok: true,
25177
- outcome,
25178
- rootPath: root,
25179
- agent: agent ?? null,
25180
- source,
25181
- target: agent === "claude" ? target : null,
25182
- sourcePreserved: original !== null,
25183
- sessionState: "not-observed"
25586
+ const docsPlan = agentDocs ? await planAgentDocs(root, agentDocs) : skippedAgentDocs;
25587
+ const result = (config3, outcome) => projectInitV4.parse({
25588
+ contract: "project-init",
25589
+ version: 4,
25590
+ ok: true,
25591
+ outcome,
25592
+ rootPath: root,
25593
+ configPath: ".gitifact/config.json",
25594
+ schemaVersion: config3.schemaVersion,
25595
+ baseline: config3.baseline,
25596
+ agentDocs: { mode: docsPlan.mode, paths: docsPlan.paths }
25597
+ });
25598
+ const existing = async (text3) => {
25599
+ const config3 = parseManagedConfig(text3);
25600
+ if (!("schemaVersion" in config3)) throw new InitError("MIGRATION_REQUIRED", "\uAE30\uC874 \uD615\uC2DD\uC740 \uBCC4\uB3C4 \uC804\uD658\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uC124\uC815\uACFC \uAE30\uB85D\uC744 \uBCF4\uC874\uD558\uC138\uC694.");
25601
+ await repo.validateBaseline(config3, root, first.state.head.commit, first.state.repository.objectFormat);
25602
+ const unchanged = async () => {
25603
+ if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text3) throw new InitError("INPUT_CHANGED", "\uCD08\uAE30\uD654 \uC870\uD68C \uC911 \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25604
+ };
25605
+ await unchanged();
25606
+ if (!dryRun) await applyAgentDocs(root, docsPlan, unchanged);
25607
+ return result(config3, "already-initialized");
25608
+ };
25609
+ const old = await readConfigFile(root);
25610
+ if (old !== void 0) return existing(old);
25611
+ if (first.trackedConfig) throw new InitError("CONFIG_DELETED", "\uAE30\uC874 \uC124\uC815\uC758 \uC0AD\uC81C\uB97C \uC2E0\uADDC \uB3C4\uC785\uC73C\uB85C \uCC98\uB9AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
25612
+ const checkRecords = async () => {
25613
+ if (await fileInfo(join7(root, "specs"))) throw new InitError("EXISTING_RECORDS", "\uAE30\uC874 specs \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
25614
+ const entries = await readdir3(join7(root, ".gitifact")).catch((e) => {
25615
+ if (e.code === "ENOENT") return [];
25616
+ throw e;
25184
25617
  });
25185
- if (action === "remove" && !state) {
25186
- await recheck();
25187
- return result("not-installed");
25188
- }
25189
- await repo.checkIgnore(root, source);
25190
- if (options.dryRun) {
25191
- await recheck();
25192
- return result("planned");
25193
- }
25194
- if (missing2.length) await skillWrite(root, ".gitignore", ignoreBefore, ignoreAfter, recheck);
25195
- for (const path3 of [manifest, lock, target]) {
25196
- let ignored = false;
25197
- try {
25198
- await repo.checkIgnore(root, path3);
25199
- } catch (error62) {
25200
- if (error62 instanceof InitError && error62.code === "CONFIG_IGNORED") ignored = true;
25201
- else throw error62;
25202
- }
25203
- if (!ignored) throw new InitError("LOCAL_SKILL_NOT_IGNORED", "\uB85C\uCEEC \uACBD\uB85C\uAC00 ignore\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + path3);
25618
+ if (entries.some((name) => !/^\.init-[a-f0-9-]+\.tmp$/.test(name))) throw new InitError("EXISTING_RECORDS", "\uC124\uC815 \uC5C6\uB294 .gitifact \uC790\uB8CC\uB97C \uC790\uB3D9 \uCC44\uD0DD\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
25619
+ };
25620
+ const config2 = { schemaVersion: 1, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
25621
+ const text2 = JSON.stringify(config2, null, 2) + "\n";
25622
+ const recheck = async () => {
25623
+ await checkRecords();
25624
+ await repo.checkIgnore(root);
25625
+ if ((await repo.inspect()).stamp !== first.stamp) throw new InitError("INPUT_CHANGED", "HEAD \uB610\uB294 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25626
+ if (await readConfigFile(root) !== void 0) throw new InitError("CONFIG_APPEARED", "\uB2E4\uB978 \uC2E4\uD589\uC774 \uC124\uC815\uC744 \uB9CC\uB4E4\uC5C8\uC2B5\uB2C8\uB2E4.");
25627
+ };
25628
+ await recheck();
25629
+ if (dryRun) return result(config2, "planned");
25630
+ try {
25631
+ await publishConfig(root, text2, recheck, beforePublish);
25632
+ } catch (error62) {
25633
+ if (error62.code === "EEXIST" || error62 instanceof InitError && error62.code === "CONFIG_APPEARED") {
25634
+ const concurrent = await readConfigFile(root);
25635
+ if (concurrent !== void 0) return existing(concurrent);
25204
25636
  }
25205
- const stateText = (accepted) => JSON.stringify({ version: 1, source, target, agent: agent ?? null, accepted }, null, 2) + "\n";
25206
- const journal = stateText([.../* @__PURE__ */ new Set([digest2(copy), digest2(nextCopy)])]);
25207
- const inputs = async () => {
25208
- await recheck();
25209
- if (await skillRead(root, source) !== original || await skillRead(root, target) !== copy) throw new InitError("INPUT_CHANGED", "\uC2A4\uD0AC \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25210
- };
25211
- await skillWrite(root, manifest, saved, journal, inputs);
25212
- await controls.afterJournal?.();
25213
- if (original === null && wanted !== null) await skillWrite(root, source, null, wanted, recheck);
25214
- if (copy !== nextCopy) await skillWrite(root, target, copy, nextCopy, recheck);
25215
- await skillWrite(root, manifest, journal, stateText([digest2(nextCopy)]), recheck);
25216
- await recheck();
25217
- if (await skillRead(root, source) !== wanted || await skillRead(root, target) !== nextCopy) throw new InitError("INPUT_CHANGED", "\uAC8C\uC2DC \uD6C4 \uC2A4\uD0AC\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC870\uD68C\uD558\uC138\uC694.");
25218
- return result(action === "remove" ? "removed" : action === "install" ? "installed" : "synced");
25637
+ throw error62;
25638
+ }
25639
+ const published = async () => {
25640
+ if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text2) throw new InitError("INPUT_CHANGED_AFTER_WRITE", "\uC124\uC815 \uC0DD\uC131 \uD6C4 \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694.");
25219
25641
  };
25220
- return options.dryRun ? execute2() : skillLock(root, lock, execute2);
25642
+ await published();
25643
+ await applyAgentDocs(root, docsPlan, published);
25644
+ return result(config2, "created");
25221
25645
  }
25222
- async function runSkills(action, options) {
25646
+
25647
+ // src/commands/init.ts
25648
+ async function runInit(options, version2) {
25223
25649
  try {
25224
- const result = await skillsCommand(process.cwd(), action, options);
25225
- console.log(options.format === "text" && result.ok ? `${result.outcome}: ${JSON.stringify(result.rootPath)}
25226
- \uC6D0\uBCF8: ${result.source}
25227
- \uB85C\uCEEC \uBCF5\uC0AC\uBCF8: ${result.target ?? "\uC5C6\uC74C"}
25228
- \uC138\uC158 \uB85C\uB529: \uD655\uC778\uD558\uC9C0 \uC54A\uC74C` : JSON.stringify(result));
25650
+ const dto = await initializeSpecProject(
25651
+ process.cwd(),
25652
+ options.dryRun,
25653
+ process.env,
25654
+ void 0,
25655
+ { version: version2, agent: options.agent, remove: options.removeAgents, skip: options.skipAgents }
25656
+ );
25657
+ if (!dto.ok) throw new Error("unreachable");
25658
+ const docs = dto.agentDocs.mode === "skip" ? "\uAC74\uB108\uB700" : dto.agentDocs.paths.join(", ") + (dto.agentDocs.mode === "remove" ? " (\uBE14\uB85D \uC81C\uAC70)" : dto.outcome === "planned" ? " (\uBE14\uB85D \uC124\uCE58 \uC608\uC815)" : " (\uBE14\uB85D \uAC31\uC2E0)");
25659
+ process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
25660
+ \uC800\uC7A5 \uADDC\uC57D: schemaVersion 1
25661
+ \uC5D0\uC774\uC804\uD2B8 \uC9C0\uCE68: ${docs}
25662
+ ` : JSON.stringify(dto) + "\n");
25229
25663
  } catch (error62) {
25230
- const failure2 = skillsV1.parse({ contract: "skills", version: 1, ok: false, error: {
25231
- code: error62 instanceof InitError ? error62.code : "SKILLS_FAILED",
25232
- message: error62 instanceof Error ? error62.message : "\uC2A4\uD0AC \uC791\uC5C5 \uC2E4\uD328"
25233
- } });
25234
- console.error(JSON.stringify(failure2));
25664
+ const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
25665
+ const failure2 = { code: known ? error62.code : "INIT_FAILED", message: known ? error62.message : "\uCD08\uAE30\uD654\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uD30C\uC77C \uC811\uADFC \uAD8C\uD55C\uACFC \uC0DD\uC131\uB41C \uC124\uC815\xB7\uC784\uC2DC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694." };
25666
+ process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(projectInitV4.parse({ contract: "project-init", version: 4, ok: false, error: failure2 })) + "\n");
25235
25667
  process.exitCode = 1;
25236
25668
  }
25237
25669
  }
25238
25670
 
25239
25671
  // src/commands/spec-preview.ts
25240
- import { join as join11 } from "node:path";
25241
- import { readFile as readFile10, stat as stat2 } from "node:fs/promises";
25672
+ import { join as join12 } from "node:path";
25673
+ import { readFile as readFile11, stat as stat2 } from "node:fs/promises";
25242
25674
 
25243
25675
  // src/adapters/filesystem/spec-preview-prepare.ts
25244
- import { lstat as lstat3 } from "node:fs/promises";
25245
- import { join as join7 } from "node:path";
25246
- import { createHash as createHash5 } from "node:crypto";
25676
+ import { lstat as lstat4 } from "node:fs/promises";
25677
+ import { join as join8 } from "node:path";
25678
+ import { createHash as createHash4 } from "node:crypto";
25247
25679
  var fail5 = (message) => {
25248
25680
  throw new SpecPreviewError(message);
25249
25681
  };
25250
25682
  var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
25251
- var expected = (base, stamp) => createHash5("sha256").update(JSON.stringify({ base, stamp })).digest("hex");
25683
+ var expected = (base, stamp) => createHash4("sha256").update(JSON.stringify({ base, stamp })).digest("hex");
25252
25684
  var previewExpected = expected;
25253
- var parseFiles = parsePreviewFiles;
25254
25685
  async function readPreviewContext(cwd) {
25255
25686
  const reader = specPreviewReader(cwd);
25256
25687
  const { gitDir } = await reader.location();
25257
25688
  const checkOperation = async () => {
25258
25689
  for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) {
25259
- const exists = await lstat3(join7(gitDir, name)).catch((e) => {
25690
+ const exists = await lstat4(join8(gitDir, name)).catch((e) => {
25260
25691
  if (e.code === "ENOENT") return void 0;
25261
25692
  throw e;
25262
25693
  });
@@ -25267,20 +25698,21 @@ async function readPreviewContext(cwd) {
25267
25698
  const base = await reader.baseline();
25268
25699
  const working = await readWorkingPreviewState(cwd);
25269
25700
  const files = base.head ? await reader.files(base.head, working.config !== void 0) : /* @__PURE__ */ new Map();
25270
- const specs = parseFiles(files);
25701
+ const bundle = parsePreviewBundle(files);
25702
+ const specs = bundle.specs;
25271
25703
  const recheck = async () => {
25272
25704
  await checkOperation();
25273
25705
  if (!same(base, await reader.baseline())) fail5("HEAD \uB610\uB294 \uBE0C\uB79C\uCE58\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. changes\uBD80\uD130 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.");
25274
25706
  };
25275
- return { reader, base, files, specs, recheck, working };
25707
+ return { reader, base, files, specs, bundle, recheck, working };
25276
25708
  }
25277
25709
  var context = readPreviewContext;
25278
25710
  async function readFinalPreviewChanges(cwd) {
25279
25711
  const c = await context(cwd);
25280
25712
  const current = await readWorkingPreviewState(cwd);
25281
- const delta = finalSpecPreviewChanges(c.specs, current.specs);
25713
+ const delta = finalSpecPreviewChanges(c.bundle, current.bundle);
25282
25714
  await c.recheck();
25283
- const pendingReasons = current.specs.flatMap((s) => s.history.slice(c.specs.find((p) => p.id === s.id)?.history.length ?? 0).map((h) => ({ specId: s.id, path: s.path.replace(/requirements\.md$/, "history.jsonl"), ...h })));
25715
+ const pendingReasons = pendingPreviewReasons(c.bundle, current.bundle);
25284
25716
  return { base: c.base, expected: expected(c.base, current.stamp), stamp: current.stamp, pendingReasons, ...delta };
25285
25717
  }
25286
25718
  async function prepareWorkingPreview(cwd, input2, publish) {
@@ -25292,7 +25724,7 @@ async function prepareWorkingPreview(cwd, input2, publish) {
25292
25724
  if (request.expected !== expected(c.base, current.stamp)) fail5("\uC900\uBE44 \uC785\uB825\uC774 \uC624\uB798\uB410\uC2B5\uB2C8\uB2E4. changes\uBD80\uD130 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.");
25293
25725
  const result = await previewTransaction(cwd, current.stamp, async (before) => {
25294
25726
  await c.recheck();
25295
- const prepared = prepareSpecPreview(c.specs, before.specs, c.files, before.files, request.reasons, () => generatePreviewId("H"));
25727
+ const prepared = prepareSpecPreview(c.bundle, before.bundle, c.files, before.files, request.reasons, () => generatePreviewId("H"));
25296
25728
  return { writes: prepared.writes, data: { changes: prepared.changes, specChanges: prepared.specChanges, reasons: prepared.reasons, withoutReason: prepared.withoutReason }, recheck: c.recheck };
25297
25729
  }, publish);
25298
25730
  return { ...result, base: c.base, expected: expected(c.base, result.stamp), verification: { base: c.base, stamp: result.stamp } };
@@ -25304,15 +25736,15 @@ async function verifyPreparedPreview(cwd, input2, staged) {
25304
25736
  const c = await context(cwd);
25305
25737
  const current = await readWorkingPreviewState(cwd);
25306
25738
  if (!same(request.base, c.base) || request.stamp !== current.stamp) fail5("\uC900\uBE44 \uB4A4 HEAD \uB610\uB294 \uBA85\uC138\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC900\uBE44\uD558\uC138\uC694.");
25307
- finalSpecPreviewChanges(c.specs, current.specs);
25308
- compareSpecPreviews(c.specs, current.specs);
25739
+ finalSpecPreviewChanges(c.bundle, current.bundle);
25740
+ comparePreviewBundles(c.bundle, current.bundle);
25309
25741
  if (staged) {
25310
25742
  const index = await c.reader.index();
25311
25743
  if (index.size !== current.files.size) fail5("staging\uC758 \uBA85\uC138 \uBC94\uC704\uAC00 \uC900\uBE44\uD55C \uC6D0\uBB38\uACFC \uB2E4\uB985\uB2C8\uB2E4.");
25312
- for (const [path3, source2] of current.files) {
25744
+ for (const [path3, source] of current.files) {
25313
25745
  const oid2 = index.get(path3);
25314
- const bytes = Buffer.from(source2);
25315
- const hash3 = createHash5(oid2?.length === 64 ? "sha256" : "sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
25746
+ const bytes = Buffer.from(source);
25747
+ const hash3 = createHash4(oid2?.length === 64 ? "sha256" : "sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
25316
25748
  if (oid2 !== hash3) fail5("staging \uC6D0\uBB38\uC774 \uC900\uBE44\uD55C \uD30C\uC77C\uACFC \uB2E4\uB985\uB2C8\uB2E4: " + path3);
25317
25749
  }
25318
25750
  if (!same([...index], [...await c.reader.index()])) fail5("\uD655\uC778 \uC911 staging\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
@@ -25324,22 +25756,22 @@ async function verifyPreparedPreview(cwd, input2, staged) {
25324
25756
 
25325
25757
  // src/commands/spec-preview-commit.ts
25326
25758
  import { randomUUID as randomUUID6 } from "node:crypto";
25327
- import { readFile as readFile8, mkdir as mkdir4, open as open4, rename as rename3, unlink as unlink4, rmdir as rmdir2, writeFile as writeFile2 } from "node:fs/promises";
25328
- import { join as join9 } from "node:path";
25759
+ import { readFile as readFile9, mkdir as mkdir4, open as open4, rename as rename3, unlink as unlink4, rmdir as rmdir2, writeFile as writeFile2 } from "node:fs/promises";
25760
+ import { join as join10 } from "node:path";
25329
25761
 
25330
25762
  // src/commands/spec-commit-files.ts
25331
- import { createHash as createHash6 } from "node:crypto";
25332
- import { lstat as lstat4, readFile as readFile7 } from "node:fs/promises";
25333
- import { join as join8 } from "node:path";
25763
+ import { createHash as createHash5 } from "node:crypto";
25764
+ import { lstat as lstat5, readFile as readFile8 } from "node:fs/promises";
25765
+ import { join as join9 } from "node:path";
25334
25766
  var fail6 = (message) => {
25335
25767
  throw new SpecPreviewError(message);
25336
25768
  };
25337
- var hash2 = (value) => createHash6("sha256").update(value).digest("hex");
25338
- var optional2 = (path3) => readFile7(path3).catch((e) => {
25769
+ var hash2 = (value) => createHash5("sha256").update(value).digest("hex");
25770
+ var optional2 = (path3) => readFile8(path3).catch((e) => {
25339
25771
  if (e.code === "ENOENT") return null;
25340
25772
  throw e;
25341
25773
  });
25342
- var info2 = (path3) => lstat4(path3).catch((e) => {
25774
+ var info2 = (path3) => lstat5(path3).catch((e) => {
25343
25775
  if (e.code === "ENOENT") return void 0;
25344
25776
  throw e;
25345
25777
  });
@@ -25351,14 +25783,14 @@ async function fingerprint(root, path3) {
25351
25783
  validPath2(path3);
25352
25784
  const parts = path3.split("/");
25353
25785
  for (let i = 1; i <= parts.length; i++) {
25354
- const target2 = join8(root, ...parts.slice(0, i));
25355
- const stat3 = await info2(target2);
25786
+ const target = join9(root, ...parts.slice(0, i));
25787
+ const stat3 = await info2(target);
25356
25788
  if (!stat3) return null;
25357
25789
  if (stat3.isSymbolicLink() || (i < parts.length ? !stat3.isDirectory() : !stat3.isFile())) fail6("\uB9C1\uD06C\xB7\uB514\uB809\uD130\uB9AC\xB7\uD2B9\uC218 \uD30C\uC77C\uC740 \uC120\uD0DD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
25358
- if (i < parts.length && await info2(join8(target2, ".git"))) fail6("\uC911\uCCA9 \uC800\uC7A5\uC18C\uB294 \uC120\uD0DD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
25790
+ if (i < parts.length && await info2(join9(target, ".git"))) fail6("\uC911\uCCA9 \uC800\uC7A5\uC18C\uB294 \uC120\uD0DD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
25359
25791
  if (i === parts.length) {
25360
25792
  if (stat3.nlink !== 1 || stat3.size > 16 * 1024 * 1024) fail6("\uD558\uB4DC \uB9C1\uD06C \uB610\uB294 \uD30C\uC77C \uD06C\uAE30 \uD55C\uB3C4\uB97C \uD655\uC778\uD558\uC138\uC694.");
25361
- return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile7(target2)]));
25793
+ return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile8(target)]));
25362
25794
  }
25363
25795
  }
25364
25796
  return null;
@@ -25378,7 +25810,7 @@ function paths(value, limit = 128) {
25378
25810
  return list;
25379
25811
  }
25380
25812
  function policyPaths(files) {
25381
- const all = /* @__PURE__ */ new Set(["AGENTS.md", "CLAUDE.md", ".agents/skills/gitifact-workflow/SKILL.md", ".gitignore", ".gitattributes", ".gitifact/config.json", ".tryce/config.json"]);
25813
+ const all = /* @__PURE__ */ new Set(["AGENTS.md", "CLAUDE.md", ".gitignore", ".gitattributes", ".gitifact/config.json", ".tryce/config.json"]);
25382
25814
  for (const file2 of files) {
25383
25815
  const parts = file2.split("/");
25384
25816
  for (let i = 1; i < parts.length; i++) for (const name of ["AGENTS.md", "CLAUDE.md", ".gitignore", ".gitattributes"]) all.add(parts.slice(0, i).join("/") + "/" + name);
@@ -25388,6 +25820,7 @@ function policyPaths(files) {
25388
25820
  async function checkLegacySelection(root, selected) {
25389
25821
  const legacyJson = /^\.(?:gitifact|tryce)\/(?:spec\/[^/]+\/tryce\.json|notes\/N-[a-f0-9-]+\.json|mode-[a-f0-9-]+\.json|config\.(?:init-1|prototype-1)\.[a-f0-9]+\.json)$/;
25390
25822
  for (const p of selected.filter((p2) => p2.startsWith(".gitifact/") && !record2(p2) && p2 !== ".gitifact/config.json")) {
25823
+ if (isProductAssetPath(p)) continue;
25391
25824
  if (!legacyJson.test(p) || await fingerprint(root, p) !== null) fail6(".gitifact\uC758 \uAD6C\uD615 \uAE30\uB85D\uC740 \uC0AD\uC81C\uB9CC \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4: " + p);
25392
25825
  }
25393
25826
  for (const p of selected.filter((p2) => p2.startsWith(".tryce/"))) {
@@ -25420,7 +25853,7 @@ async function previewCommit(cwd, action, input2) {
25420
25853
  });
25421
25854
  const indexPath = (await git(["rev-parse", "--path-format=absolute", "--git-path", "index"])).toString("utf8").trim();
25422
25855
  const staged = async (index) => (await git(["diff", "--cached", "--ita-visible-in-index", "--name-only", "--no-renames", "-z"], index)).toString("utf8").split("\0").filter(Boolean).sort();
25423
- const busy = join9(gitDir, "gitifact-spec-commit.lock");
25856
+ const busy = join10(gitDir, "gitifact-spec-commit.lock");
25424
25857
  if (await info2(busy)) fail6("\uC774\uC804 \uCEE4\uBC0B \uC791\uC5C5 \uB610\uB294 \uBCF5\uAD6C \uC790\uB8CC\uAC00 \uC788\uC2B5\uB2C8\uB2E4: " + busy);
25425
25858
  if ((await staged()).length) fail6("\uAE30\uC874 staging\uC744 \uBCF4\uC874\uD569\uB2C8\uB2E4. \uC120\uD0DD \uBC94\uC704\uB97C \uC815\uB9AC\uD55C \uD6C4 \uB2E4\uC2DC \uACC4\uD68D\uD558\uC138\uC694.");
25426
25859
  const request = object3(input2);
@@ -25462,8 +25895,8 @@ async function previewCommit(cwd, action, input2) {
25462
25895
  if (data2.index !== hash2(await optional2(indexPath) ?? Buffer.alloc(0)) || (await staged()).length) fail6("\uACC4\uD68D \uC911 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25463
25896
  return { outcome: "planned", plan: { ...data2, digest: seal(data2) }, committed: false };
25464
25897
  }
25465
- const { digest: digest3, ...data } = request;
25466
- if (request.kind !== "spec-preview-commit" || typeof digest3 !== "string" || digest3 !== seal(data)) fail6("\uCEE4\uBC0B \uACC4\uD68D\uC774 \uC190\uC0C1\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uACC4\uD68D\uD558\uC138\uC694.");
25898
+ const { digest: digest2, ...data } = request;
25899
+ if (request.kind !== "spec-preview-commit" || typeof digest2 !== "string" || digest2 !== seal(data)) fail6("\uCEE4\uBC0B \uACC4\uD68D\uC774 \uC190\uC0C1\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uACC4\uD68D\uD558\uC138\uC694.");
25467
25900
  plan = request;
25468
25901
  if (plan.root !== root || !Array.isArray(plan.files) || !Array.isArray(plan.context)) fail6("\uACC4\uD68D\uC758 \uC800\uC7A5\uC18C \uB610\uB294 \uD30C\uC77C \uBC94\uC704\uAC00 \uB2E4\uB985\uB2C8\uB2E4.");
25469
25902
  const selected = paths(plan.files.map((f) => f.path));
@@ -25479,16 +25912,16 @@ async function previewCommit(cwd, action, input2) {
25479
25912
  const before = await reader.baseline();
25480
25913
  const original = await optional2(indexPath);
25481
25914
  await mkdir4(busy);
25482
- const draftLock = join9(gitDir, "gitifact-spec-preview.lock");
25915
+ const draftLock = join10(gitDir, "gitifact-spec-preview.lock");
25483
25916
  let draftOwned = false;
25484
25917
  let indexLock;
25485
25918
  let uncertain = false;
25486
25919
  let commitStarted = false;
25487
- const temporary = join9(gitDir, "gitifact-commit-index-" + randomUUID6());
25920
+ const temporary = join10(gitDir, "gitifact-commit-index-" + randomUUID6());
25488
25921
  const checkFiles = async () => {
25489
25922
  for (const f of [...plan.files, ...plan.context]) if (f.hash !== await fingerprint(root, f.path)) fail6("\uCEE4\uBC0B \uC900\uBE44 \uC911 \uD30C\uC77C\xB7\uC815\uCC45\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + f.path);
25490
25923
  if ((await readLockedPreviewState(root)).stamp !== object3(plan.verification).stamp) fail6("\uCEE4\uBC0B \uC900\uBE44 \uC911 \uBA85\uC138 \uC9D1\uD569\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25491
- for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) if (await info2(join9(gitDir, name))) fail6("Git \uD1B5\uD569 \uC791\uC5C5\uC774 \uC2DC\uC791\uB410\uC2B5\uB2C8\uB2E4.");
25924
+ for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) if (await info2(join10(gitDir, name))) fail6("Git \uD1B5\uD569 \uC791\uC5C5\uC774 \uC2DC\uC791\uB410\uC2B5\uB2C8\uB2E4.");
25492
25925
  };
25493
25926
  try {
25494
25927
  await verifyPreparedPreview(cwd, plan.verification, false);
@@ -25496,7 +25929,7 @@ async function previewCommit(cwd, action, input2) {
25496
25929
  draftOwned = true;
25497
25930
  indexLock = await open4(indexPath + ".lock", "wx", 384);
25498
25931
  if (plan.index !== hash2(await optional2(indexPath) ?? Buffer.alloc(0)) || (await staged()).length) fail6("\uC7A0\uAE08 \uC911 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25499
- await writeFile2(join9(busy, "recovery.json"), JSON.stringify({ before, originalIndex: original?.toString("base64") ?? null, temporary, plan }));
25932
+ await writeFile2(join10(busy, "recovery.json"), JSON.stringify({ before, originalIndex: original?.toString("base64") ?? null, temporary, plan }));
25500
25933
  if (original) await writeFile2(temporary, original, { flag: "wx" });
25501
25934
  else await git(["read-tree", "--empty"], temporary);
25502
25935
  await git(["add", "--", ...selected], temporary);
@@ -25504,7 +25937,7 @@ async function previewCommit(cwd, action, input2) {
25504
25937
  if (!actual.length || actual.some((p) => !selected.includes(p))) fail6("\uBE48 \uCEE4\uBC0B \uB610\uB294 \uC120\uD0DD \uBC16 \uD30C\uC77C\uC774 \uAC10\uC9C0\uB410\uC2B5\uB2C8\uB2E4.");
25505
25938
  for (const file2 of plan.files.filter((f) => record2(f.path) && f.hash !== null)) {
25506
25939
  const stagedBytes = await git(["show", ":" + file2.path], temporary);
25507
- if (!stagedBytes.equals(await readFile8(join9(root, file2.path)))) fail6("Git \uD544\uD130\uAC00 \uBA85\uC138\xB7\uC774\uC720 \uC6D0\uBB38\uC744 \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
25940
+ if (!stagedBytes.equals(await readFile9(join10(root, file2.path)))) fail6("Git \uD544\uD130\uAC00 \uBA85\uC138\xB7\uC774\uC720 \uC6D0\uBB38\uC744 \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
25508
25941
  }
25509
25942
  await checkFiles();
25510
25943
  if (JSON.stringify(before) !== JSON.stringify(await reader.baseline())) fail6("HEAD\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
@@ -25519,7 +25952,7 @@ async function previewCommit(cwd, action, input2) {
25519
25952
  if (actualTree !== tree || after.branch !== before.branch || JSON.stringify(parents) !== JSON.stringify(before.head ? [before.head] : []) || trailers.some((t) => !message.split(/\r?\n/).includes(t))) fail6("\uCEE4\uBC0B\uC740 \uC2E4\uD589\uB410\uC9C0\uB9CC \uD6C5\xB7\uB3D9\uC2DC \uC791\uC5C5\uC774 \uACB0\uACFC\uB97C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
25520
25953
  if (hash2(await optional2(indexPath) ?? Buffer.alloc(0)) !== plan.index) fail6("\uCEE4\uBC0B \uD6C4 \uC6D0\uB798 index \uBCC0\uACBD\uC744 \uAC10\uC9C0\uD588\uC2B5\uB2C8\uB2E4.");
25521
25954
  if ((await git(["write-tree"], temporary)).toString("utf8").trim() !== tree) fail6("\uD6C5\uC774 \uCEE4\uBC0B \uD6C4 staging\uC744 \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
25522
- await indexLock.writeFile(await readFile8(temporary));
25955
+ await indexLock.writeFile(await readFile9(temporary));
25523
25956
  await indexLock.sync();
25524
25957
  await indexLock.close();
25525
25958
  await rename3(indexPath + ".lock", indexPath);
@@ -25543,7 +25976,7 @@ async function previewCommit(cwd, action, input2) {
25543
25976
  });
25544
25977
  await unlink4(temporary).catch(() => {
25545
25978
  });
25546
- await unlink4(join9(busy, "recovery.json")).catch(() => {
25979
+ await unlink4(join10(busy, "recovery.json")).catch(() => {
25547
25980
  });
25548
25981
  await rmdir2(busy);
25549
25982
  if (draftOwned) await rmdir2(draftLock);
@@ -25552,9 +25985,9 @@ async function previewCommit(cwd, action, input2) {
25552
25985
  }
25553
25986
 
25554
25987
  // src/commands/spec-commit.ts
25555
- import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
25556
- import { mkdir as mkdir5, open as open5, readFile as readFile9, rename as rename4, rmdir as rmdir3, unlink as unlink5, writeFile as writeFile3 } from "node:fs/promises";
25557
- import { join as join10 } from "node:path";
25988
+ import { createHash as createHash7, randomUUID as randomUUID7 } from "node:crypto";
25989
+ import { mkdir as mkdir5, open as open5, readFile as readFile10, rename as rename4, rmdir as rmdir3, unlink as unlink5, writeFile as writeFile3 } from "node:fs/promises";
25990
+ import { join as join11 } from "node:path";
25558
25991
  var fields = ["reasons", "paths", "message", "authorization", "requirements", "policyFiles", "expected"];
25559
25992
  var integration = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"];
25560
25993
  var same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
@@ -25593,18 +26026,18 @@ async function specCommit(cwd, input2, dryRun) {
25593
26026
  ...stdin ? { input: stdin } : {}
25594
26027
  });
25595
26028
  const staged = async (index) => (await git(["diff", "--cached", "--ita-visible-in-index", "--name-only", "--no-renames", "-z"], index)).toString("utf8").split("\0").filter(Boolean).sort();
25596
- const busy = join10(gitDir, "gitifact-spec-commit.lock");
26029
+ const busy = join11(gitDir, "gitifact-spec-commit.lock");
25597
26030
  await failOnLegacyLock(gitDir);
25598
26031
  if (await info2(busy)) fail6("\uC774\uC804 \uCEE4\uBC0B \uC791\uC5C5 \uB610\uB294 \uBCF5\uAD6C \uC790\uB8CC\uAC00 \uC788\uC2B5\uB2C8\uB2E4: " + busy);
25599
26032
  if ((await staged()).length) fail6("\uAE30\uC874 staging\uC744 \uBCF4\uC874\uD569\uB2C8\uB2E4. \uC120\uD0DD \uBC94\uC704\uB97C \uC815\uB9AC\uD55C \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
25600
26033
  const indexHash = hash2(await optional2(indexPath) ?? Buffer.alloc(0));
25601
26034
  const current = c.working;
25602
26035
  if (request.expected !== void 0 && request.expected !== previewExpected(c.base, current.stamp)) fail6("\uC785\uB825\uC774 \uC624\uB798\uB410\uC2B5\uB2C8\uB2E4. changes\uBD80\uD130 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.");
25603
- const reasons = request.reasons ?? current.specs.flatMap((s) => s.history.slice(c.specs.find((p) => p.id === s.id)?.history.length ?? 0).map((h) => ({ requirements: h.requirements, ...h.designs ? { designs: h.designs } : {}, reason: h.reason })));
25604
- const prepared = prepareSpecPreview(c.specs, current.specs, c.files, current.files, reasons, () => generatePreviewId("H"));
26036
+ const reasons = request.reasons ?? pendingPreviewReasons(c.bundle, current.bundle).map((h) => ({ requirements: h.requirements, ...h.designs ? { designs: h.designs } : {}, ...h.documents ? { documents: h.documents } : {}, reason: h.reason }));
26037
+ const prepared = prepareSpecPreview(c.bundle, current.bundle, c.files, current.files, reasons, () => generatePreviewId("H"));
25605
26038
  const blobId = (text2) => {
25606
26039
  const bytes = Buffer.from(text2);
25607
- return createHash8(objectFormat).update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
26040
+ return createHash7(objectFormat).update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
25608
26041
  };
25609
26042
  const present = [...current.files.keys()];
25610
26043
  if (present.some((p) => p.includes("\n"))) fail6("\uC904\uBC14\uAFC8\uC774 \uD3EC\uD568\uB41C \uBA85\uC138 \uACBD\uB85C\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
@@ -25642,8 +26075,12 @@ async function specCommit(cwd, input2, dryRun) {
25642
26075
  await c.recheck();
25643
26076
  return { outcome: "dry-run", committed: false, ...summary };
25644
26077
  }
25645
- const trailers = [...requirements.map((id) => "Gitifact-Req: " + id), ...prepared.changes.filter((c2) => c2.kind === "design").map((c2) => "Gitifact-Design: " + c2.id)];
25646
- const temporary = join10(gitDir, "gitifact-commit-index-" + randomUUID7());
26078
+ const trailers = [
26079
+ ...requirements.map((id) => "Gitifact-Req: " + id),
26080
+ ...prepared.changes.filter((c2) => c2.kind === "design").map((c2) => "Gitifact-Design: " + c2.id),
26081
+ ...prepared.changes.filter((c2) => c2.kind === "product" || c2.kind === "guide").map((c2) => "Gitifact-Doc: " + c2.id)
26082
+ ];
26083
+ const temporary = join11(gitDir, "gitifact-commit-index-" + randomUUID7());
25647
26084
  let owned = false;
25648
26085
  let uncertain = false;
25649
26086
  let indexLock;
@@ -25659,12 +26096,12 @@ async function specCommit(cwd, input2, dryRun) {
25659
26096
  await previewTransaction(cwd, current.stamp, async () => ({ writes, data: {}, recheck: c.recheck }), rename4, async (published) => {
25660
26097
  let commitStarted = false;
25661
26098
  try {
25662
- const lock2 = indexLock = await open5(indexPath + ".lock", "wx", 384);
26099
+ const lock = indexLock = await open5(indexPath + ".lock", "wx", 384);
25663
26100
  const original = await optional2(indexPath);
25664
26101
  if (hash2(original ?? Buffer.alloc(0)) !== indexHash || (await staged()).length) fail6("\uC2E4\uD589 \uC911 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25665
26102
  const locked = await hashes([.../* @__PURE__ */ new Set([...selected, ...contextPaths])]);
25666
26103
  for (const [path3, value] of started) if (locked.get(path3) !== value) fail6("\uC2E4\uD589 \uC911 \uD30C\uC77C\xB7\uC815\uCC45\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
25667
- await writeFile3(join10(busy, "recovery.json"), JSON.stringify({ before: c.base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected, message, requirements }));
26104
+ await writeFile3(join11(busy, "recovery.json"), JSON.stringify({ before: c.base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected, message, requirements }));
25668
26105
  if (original) await writeFile3(temporary, original, { flag: "wx" });
25669
26106
  else await git(["read-tree", "--empty"], temporary);
25670
26107
  await git(["add", "--", ...stageable], temporary);
@@ -25680,7 +26117,7 @@ async function specCommit(cwd, input2, dryRun) {
25680
26117
  const size = Number(header[2]);
25681
26118
  if (end < 0 || header[1] !== "blob" || !Number.isSafeInteger(size) || output2[end + 1 + size] !== 10) fail6("staging\uB41C \uBA85\uC138\xB7\uC774\uC720\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: " + path3);
25682
26119
  const staged2 = output2.subarray(end + 1, end + 1 + size);
25683
- const file2 = await readFile9(join10(root, path3));
26120
+ const file2 = await readFile10(join11(root, path3));
25684
26121
  if (!staged2.equals(file2) && !staged2.equals(Buffer.from(file2.toString("latin1").replace(/\r\n/g, "\n"), "latin1"))) fail6("Git \uD544\uD130\uAC00 \uBA85\uC138\xB7\uC774\uC720 \uC6D0\uBB38\uC744 \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
25685
26122
  offset = end + size + 2;
25686
26123
  }
@@ -25688,7 +26125,7 @@ async function specCommit(cwd, input2, dryRun) {
25688
26125
  const again = await hashes([...locked.keys()]);
25689
26126
  for (const [path3, value] of locked) if (again.get(path3) !== value) fail6("\uCEE4\uBC0B \uC900\uBE44 \uC911 \uD30C\uC77C\xB7\uC815\uCC45\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
25690
26127
  if ((await readLockedPreviewState(root)).stamp !== published.stamp) fail6("\uCEE4\uBC0B \uC900\uBE44 \uC911 \uBA85\uC138 \uC9D1\uD569\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25691
- for (const name of integration) if (await info2(join10(gitDir, name))) fail6("Git \uD1B5\uD569 \uC791\uC5C5\uC774 \uC2DC\uC791\uB410\uC2B5\uB2C8\uB2E4.");
26128
+ for (const name of integration) if (await info2(join11(gitDir, name))) fail6("Git \uD1B5\uD569 \uC791\uC5C5\uC774 \uC2DC\uC791\uB410\uC2B5\uB2C8\uB2E4.");
25692
26129
  if (!same2(c.base, await c.reader.baseline())) fail6("HEAD\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
25693
26130
  const tree = (await git(["write-tree"], temporary)).toString("utf8").trim();
25694
26131
  commitStarted = true;
@@ -25703,9 +26140,9 @@ async function specCommit(cwd, input2, dryRun) {
25703
26140
  if (headers[0] !== "tree " + tree || after.branch !== c.base.branch || !same2(parents, c.base.head ? [c.base.head] : []) || trailers.some((t) => !body.split(/\r?\n/).includes(t))) fail6("\uCEE4\uBC0B\uC740 \uC2E4\uD589\uB410\uC9C0\uB9CC \uD6C5\xB7\uB3D9\uC2DC \uC791\uC5C5\uC774 \uACB0\uACFC\uB97C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
25704
26141
  if (hash2(await optional2(indexPath) ?? Buffer.alloc(0)) !== indexHash) fail6("\uCEE4\uBC0B \uD6C4 \uC6D0\uB798 index \uBCC0\uACBD\uC744 \uAC10\uC9C0\uD588\uC2B5\uB2C8\uB2E4.");
25705
26142
  if ((await git(["write-tree"], temporary)).toString("utf8").trim() !== tree) fail6("\uD6C5\uC774 \uCEE4\uBC0B \uD6C4 staging\uC744 \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
25706
- await lock2.writeFile(await readFile9(temporary));
25707
- await lock2.sync();
25708
- await lock2.close();
26143
+ await lock.writeFile(await readFile10(temporary));
26144
+ await lock.sync();
26145
+ await lock.close();
25709
26146
  await rename4(indexPath + ".lock", indexPath);
25710
26147
  indexLock = void 0;
25711
26148
  committed = { commit: after.head, paths: actual };
@@ -25730,7 +26167,7 @@ async function specCommit(cwd, input2, dryRun) {
25730
26167
  await unlink5(temporary).catch(() => {
25731
26168
  });
25732
26169
  if (owned) {
25733
- await unlink5(join10(busy, "recovery.json")).catch(() => {
26170
+ await unlink5(join11(busy, "recovery.json")).catch(() => {
25734
26171
  });
25735
26172
  await rmdir3(busy);
25736
26173
  }
@@ -25747,7 +26184,7 @@ async function execute(action, options) {
25747
26184
  try {
25748
26185
  const guard = specPreviewReader(process.cwd());
25749
26186
  const { root, gitDir, objectFormat } = await guard.location();
25750
- for (const marker2 of operations) if (await fileInfo(join11(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: " + marker2);
26187
+ for (const marker2 of operations) if (await fileInfo(join12(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: " + marker2);
25751
26188
  if (await guard.hasUnmerged()) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uCDA9\uB3CC\uC744 \uBA3C\uC800 \uD574\uACB0\uD558\uC138\uC694.");
25752
26189
  const raw = await readConfigFile(root);
25753
26190
  if (raw === void 0) throw new SpecPreviewError("\uBA3C\uC800 gitifact init\uC73C\uB85C \uCD08\uAE30\uD654\uD558\uC138\uC694.");
@@ -25759,9 +26196,9 @@ async function execute(action, options) {
25759
26196
  if (action === "working") result = await readWorkingPreview(process.cwd());
25760
26197
  else if (action === "changes") result = await readFinalPreviewChanges(process.cwd());
25761
26198
  else {
25762
- const source2 = await stat2(options.file);
25763
- if (!source2.isFile() || source2.size > 1024 * 1024) throw new SpecPreviewError("\uC785\uB825\uC740 1 MiB \uC774\uD558\uC758 JSON \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
25764
- const bytes = await readFile10(options.file);
26199
+ const source = await stat2(options.file);
26200
+ if (!source.isFile() || source.size > 1024 * 1024) throw new SpecPreviewError("\uC785\uB825\uC740 1 MiB \uC774\uD558\uC758 JSON \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
26201
+ const bytes = await readFile11(options.file);
25765
26202
  if (bytes.length > 1024 * 1024) throw new SpecPreviewError("\uC785\uB825 \uD06C\uAE30\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
25766
26203
  let input2;
25767
26204
  try {
@@ -25777,12 +26214,12 @@ async function execute(action, options) {
25777
26214
  const reader = specPreviewReader(process.cwd());
25778
26215
  if (action === "read") {
25779
26216
  const commit = await reader.resolve(options.ref ?? "HEAD");
25780
- const specs = await reader.read(commit);
25781
- process.stdout.write(JSON.stringify({ ...envelope, ok: true, commit, specs }) + "\n");
26217
+ const bundle = await reader.readBundle(commit);
26218
+ process.stdout.write(JSON.stringify({ ...envelope, ok: true, commit, specs: bundle.specs, documents: bundle.documents }) + "\n");
25782
26219
  } else {
25783
26220
  const [from, to] = await Promise.all([reader.resolve(options.from), reader.resolve(options.to)]);
25784
- const [before, after] = await Promise.all([reader.read(from), reader.read(to)]);
25785
- process.stdout.write(JSON.stringify({ ...envelope, ok: true, from, to, ...compareSpecPreviews(before, after) }) + "\n");
26221
+ const [before, after] = await Promise.all([reader.readBundle(from), reader.readBundle(to)]);
26222
+ process.stdout.write(JSON.stringify({ ...envelope, ok: true, from, to, ...comparePreviewBundles(before, after) }) + "\n");
25786
26223
  }
25787
26224
  } catch (error62) {
25788
26225
  const known = error62 instanceof SpecPreviewError || error62 instanceof RepositoryReadError || error62 instanceof InitError;
@@ -25792,11 +26229,11 @@ async function execute(action, options) {
25792
26229
  }
25793
26230
 
25794
26231
  // src/commands/migrate.ts
25795
- import { lstat as lstat6, readdir as readdir4, readFile as readFile11, rename as rename5, writeFile as writeFile4, unlink as unlink6 } from "node:fs/promises";
25796
- import { join as join12 } from "node:path";
26232
+ import { lstat as lstat7, readdir as readdir4, readFile as readFile12, rename as rename5, writeFile as writeFile4, unlink as unlink6 } from "node:fs/promises";
26233
+ import { join as join13 } from "node:path";
25797
26234
  import { randomBytes as randomBytes2 } from "node:crypto";
25798
26235
  var operations2 = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"];
25799
- var info3 = (path3) => lstat6(path3).catch((e) => {
26236
+ var info3 = (path3) => lstat7(path3).catch((e) => {
25800
26237
  if (e.code === "ENOENT") return void 0;
25801
26238
  throw e;
25802
26239
  });
@@ -25807,18 +26244,18 @@ var fail7 = (code, message) => {
25807
26244
  async function migrateStore(cwd, dryRun = false) {
25808
26245
  const reader = specPreviewReader(cwd);
25809
26246
  const { root, gitDir } = await reader.location();
25810
- for (const marker3 of operations2) if (await info3(join12(gitDir, marker3))) fail7("GIT_OPERATION_IN_PROGRESS", "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: " + marker3);
26247
+ for (const marker3 of operations2) if (await info3(join13(gitDir, marker3))) fail7("GIT_OPERATION_IN_PROGRESS", "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: " + marker3);
25811
26248
  if (await reader.hasUnmerged()) fail7("GIT_OPERATION_IN_PROGRESS", "Git \uCDA9\uB3CC\uC744 \uBA3C\uC800 \uD574\uACB0\uD558\uC138\uC694.");
25812
- for (const name of [...LEGACY_LOCKS, "gitifact-spec-preview.lock", "gitifact-spec-commit.lock"]) if (await info3(join12(gitDir, name))) fail7("LOCKED", "\uC7A0\uAE08 \uB610\uB294 \uBCF5\uAD6C \uC790\uB8CC\uAC00 \uC788\uC2B5\uB2C8\uB2E4: " + join12(gitDir, name));
25813
- const from = join12(root, ".tryce");
25814
- const to = join12(root, ".gitifact");
26249
+ for (const name of [...LEGACY_LOCKS, "gitifact-spec-preview.lock", "gitifact-spec-commit.lock"]) if (await info3(join13(gitDir, name))) fail7("LOCKED", "\uC7A0\uAE08 \uB610\uB294 \uBCF5\uAD6C \uC790\uB8CC\uAC00 \uC788\uC2B5\uB2C8\uB2E4: " + join13(gitDir, name));
26250
+ const from = join13(root, ".tryce");
26251
+ const to = join13(root, ".gitifact");
25815
26252
  const fromInfo = await info3(from);
25816
26253
  if (await info3(to)) fail7("ALREADY_MIGRATED", ".gitifact\uAC00 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4. .tryce\uAC00 \uB0A8\uC544 \uC788\uC73C\uBA74 \uB0B4\uC6A9\uC744 \uBE44\uAD50\uD55C \uB4A4 \uC9C1\uC811 \uC815\uB9AC\uD558\uC138\uC694.");
25817
26254
  if (!fromInfo) return fail7("NOTHING_TO_MIGRATE", ".tryce \uC800\uC7A5\uC18C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
25818
26255
  if (!fromInfo.isDirectory() || fromInfo.isSymbolicLink()) fail7("PATH_CONFLICT", ".tryce\uB294 \uB9C1\uD06C\uAC00 \uC544\uB2CC \uC77C\uBC18 \uB514\uB809\uD130\uB9AC\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
25819
- const configInfo = await info3(join12(from, "config.json"));
26256
+ const configInfo = await info3(join13(from, "config.json"));
25820
26257
  if (!configInfo?.isFile() || configInfo.isSymbolicLink()) fail7("NOT_INITIALIZED", ".tryce/config.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
25821
- const config2 = parseManagedConfig(decode4(await readFile11(join12(from, "config.json"))));
26258
+ const config2 = parseManagedConfig(decode4(await readFile12(join13(from, "config.json"))));
25822
26259
  if (!("schemaVersion" in config2)) fail7("UNSUPPORTED_FORMAT", "\uAD6C\uD615 JSON \uD615\uC2DD\uC740 \uC804\uD658\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uAE30\uB85D\uC744 \uBCF4\uC874\uD558\uACE0 \uBCC4\uB3C4 \uC808\uCC28\uB97C \uD655\uC778\uD558\uC138\uC694.");
25823
26260
  const top = (await readdir4(from)).sort();
25824
26261
  if (top.some((name) => name !== "config.json" && name !== "spec")) fail7("UNSUPPORTED_FORMAT", ".tryce\uC5D0 config.json\xB7spec \uC678 \uD56D\uBAA9\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
@@ -25827,20 +26264,20 @@ async function migrateStore(cwd, dryRun = false) {
25827
26264
  const legacyRecords = [];
25828
26265
  const marker2 = /^<!-- tryce-(spec|req|design|ref): /;
25829
26266
  if (top.includes("spec")) {
25830
- for (const feature2 of (await readdir4(join12(from, "spec"))).sort()) {
25831
- const dir = join12(from, "spec", feature2);
26267
+ for (const feature2 of (await readdir4(join13(from, "spec"))).sort()) {
26268
+ const dir = join13(from, "spec", feature2);
25832
26269
  const stat3 = await info3(dir);
25833
26270
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) fail7("UNSUPPORTED_FORMAT", "\uBA85\uC138 \uD3F4\uB354\uAC00 \uC544\uB2D9\uB2C8\uB2E4: " + feature2);
25834
26271
  for (const name of (await readdir4(dir)).sort()) {
25835
26272
  if (!["requirements.md", "design.md", "history.jsonl", "tryce.json"].includes(name)) fail7("UNSUPPORTED_FORMAT", "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBA85\uC138 \uD30C\uC77C\uC785\uB2C8\uB2E4: .tryce/spec/" + feature2 + "/" + name);
25836
- const fileStat = await info3(join12(dir, name));
26273
+ const fileStat = await info3(join13(dir, name));
25837
26274
  if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.nlink !== 1 || fileStat.size > 1024 * 1024) fail7("UNSUPPORTED_FORMAT", "\uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2C8\uAC70\uB098 1 MiB\uB97C \uB118\uC2B5\uB2C8\uB2E4: " + name);
25838
26275
  const newPath = `.gitifact/spec/${feature2}/${name}`;
25839
26276
  if (name === "tryce.json") {
25840
26277
  legacyRecords.push(newPath);
25841
26278
  continue;
25842
26279
  }
25843
- const text2 = decode4(await readFile11(join12(dir, name)));
26280
+ const text2 = decode4(await readFile12(join13(dir, name)));
25844
26281
  if (name === "history.jsonl") {
25845
26282
  files.set(newPath, text2);
25846
26283
  continue;
@@ -25878,15 +26315,15 @@ async function migrateStore(cwd, dryRun = false) {
25878
26315
  next: [
25879
26316
  "\uBCC0\uACBD\uC744 \uD655\uC778\uD55C \uB4A4 gitifact spec commit\uC73C\uB85C .tryce \uC0AD\uC81C\uC640 .gitifact \uCD94\uAC00\uB97C \uD568\uAED8 \uCEE4\uBC0B\uD558\uC138\uC694.",
25880
26317
  ...legacyRecords.length ? ["\uAD6C\uD615 JSON \uBA85\uC138(tryce.json)\uB294 \uC77D\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uB0B4\uC6A9\uC744 Markdown \uBA85\uC138\uB85C \uC62E\uAE34 \uB4A4 \uC0AD\uC81C\uB97C spec commit\uC5D0 \uC120\uD0DD\uD558\uC138\uC694."] : [],
25881
- ".agents/skills/tryce-workflow\uC640 .claude \uBCF5\uC0AC\uBCF8\uC774 \uC788\uC73C\uBA74 \uC815\uB9AC\uD558\uACE0 gitifact skills install\uB85C \uB2E4\uC2DC \uC124\uCE58\uD558\uC138\uC694.",
26318
+ ".agents/skills/tryce-workflow\uC640 .claude \uBCF5\uC0AC\uBCF8\uC774 \uC788\uC73C\uBA74 \uC815\uB9AC\uD558\uACE0 gitifact init\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 AGENTS.md \uB4F1\uC758 GITIFACT \uBE14\uB85D\uC744 \uAC31\uC2E0\uD558\uC138\uC694.",
25882
26319
  ".gitignore\xB7AGENTS.md\xB7CLAUDE.md\uC758 .tryce\xB7tryce-workflow \uC5B8\uAE09\uC744 \uC0C8 \uC774\uB984\uC73C\uB85C \uB9DE\uCD94\uC138\uC694."
25883
26320
  ]
25884
26321
  });
25885
26322
  if (dryRun) return result("planned");
25886
26323
  await rename5(from, to);
25887
26324
  for (const r of rewrites) {
25888
- const full = join12(root, r.path);
25889
- const temp = join12(root, ".gitifact", "spec", ".gitifact-migrate-" + randomBytes2(8).toString("hex"));
26325
+ const full = join13(root, r.path);
26326
+ const temp = join13(root, ".gitifact", "spec", ".gitifact-migrate-" + randomBytes2(8).toString("hex"));
25890
26327
  await writeFile4(temp, r.content, { flag: "wx" });
25891
26328
  try {
25892
26329
  await rename5(temp, full);
@@ -25909,20 +26346,16 @@ async function runMigrate(options) {
25909
26346
  }
25910
26347
 
25911
26348
  // src/main.ts
25912
- var program2 = new Command().name("gitifact").description("\uD504\uB85C\uC81D\uD2B8\uC758 \uC694\uAD6C\uC0AC\uD56D\uACFC \uACB0\uC815 \uC774\uB825\uC744 Git\uC5D0 \uB0A8\uAE30\uB294 \uB3C4\uAD6C").version("0.1.0").allowExcessArguments(false).addHelpText("after", "\n\uC804\uCCB4 \uC774\uB825 \uAC80\uC0AC\uC640 GUI \uC694\uAD6C\uC0AC\uD56D \uD3B8\uC9D1\uC740 \uC544\uC9C1 \uC81C\uACF5\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.").action(() => program2.outputHelp());
26349
+ var program2 = new Command().name("gitifact").description("\uD504\uB85C\uC81D\uD2B8\uC758 \uC694\uAD6C\uC0AC\uD56D\uACFC \uACB0\uC815 \uC774\uB825\uC744 Git\uC5D0 \uB0A8\uAE30\uB294 \uB3C4\uAD6C").version("0.3.0").allowExcessArguments(false).addHelpText("after", "\n\uC804\uCCB4 \uC774\uB825 \uAC80\uC0AC\uC640 GUI \uC694\uAD6C\uC0AC\uD56D \uD3B8\uC9D1\uC740 \uC544\uC9C1 \uC81C\uACF5\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.").action(() => program2.outputHelp());
25913
26350
  program2.command("status").description("\uD604\uC7AC checkout\uC758 Git \uC0C1\uD0DC \uC870\uD68C (gitifact \uAC80\uC0AC \uBBF8\uC2E4\uD589)").allowExcessArguments(false).addOption(new Option("--format <format>", "\uCD9C\uB825 \uD615\uC2DD").choices(["json", "text"]).default("json")).action(async (options) => {
25914
26351
  await runStatus(options.format);
25915
26352
  });
25916
26353
  program2.command("browser").description("\uD604\uC7AC checkout\uC758 Git \uC0C1\uD0DC\uB97C \uB85C\uCEEC \uBE0C\uB77C\uC6B0\uC800\uB85C \uC870\uD68C").allowExcessArguments(false).option("--port <port>", "\uC218\uC2E0 \uD3EC\uD2B8 (0\uC740 \uC790\uB3D9 \uC120\uD0DD)", parsePort, 0).option("--dev", "Vite \uAC1C\uBC1C \uC11C\uBC84 Origin \uD5C8\uC6A9").action(runBrowser);
25917
- program2.command("init").description("\uD504\uB85C\uC81D\uD2B8 \uC124\uC815\uACFC \uB3C4\uC785 \uAE30\uC900\uC120 \uC0DD\uC131 (\uC2A4\uD0AC\xB7\uD6C5 \uC81C\uC678)").allowExcessArguments(false).option("--dry-run", "\uD30C\uC77C\uC744 \uB9CC\uB4E4\uC9C0 \uC54A\uACE0 \uCD08\uAE30\uD654 \uACC4\uD68D \uD655\uC778").addOption(new Option("--format <format>", "\uCD9C\uB825 \uD615\uC2DD").choices(["json", "text"]).default("json")).action(runInit);
26354
+ program2.command("init").description("\uD504\uB85C\uC81D\uD2B8 \uC124\uC815\xB7\uAE30\uC900\uC120 \uC0DD\uC131\uACFC \uC5D0\uC774\uC804\uD2B8 \uC9C0\uCE68 \uD30C\uC77C\uC758 GITIFACT \uBE14\uB85D \uC124\uCE58 (\uC7AC\uC2E4\uD589 \uC2DC \uBE14\uB85D \uAC31\uC2E0)").allowExcessArguments(false).option("--dry-run", "\uD30C\uC77C\uC744 \uB9CC\uB4E4\uC9C0 \uC54A\uACE0 \uCD08\uAE30\uD654 \uACC4\uD68D \uD655\uC778").addOption(new Option("--agent <agent>", "\uC9C0\uCE68 \uD30C\uC77C \uB300\uC0C1 (\uAE30\uBCF8: \uAE30\uC874 \uD30C\uC77C \uC804\uBD80, \uC5C6\uC73C\uBA74 AGENTS.md)").choices([...agentPresetNames])).addOption(new Option("--remove-agents", "GITIFACT \uBE14\uB85D\uC744 \uC81C\uAC70").conflicts("skipAgents")).option("--skip-agents", "\uC9C0\uCE68 \uD30C\uC77C\uC744 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC74C").addOption(new Option("--format <format>", "\uCD9C\uB825 \uD615\uC2DD").choices(["json", "text"]).default("json")).action((options) => runInit(options, "0.3.0"));
26355
+ program2.command("docs").description("\uC5D0\uC774\uC804\uD2B8\uC6A9 \uC791\uC5C5 \uC9C0\uCE68 \uCD9C\uB825 (\uC8FC\uC81C \uC5C6\uC774 \uC2E4\uD589\uD558\uBA74 \uBAA9\uB85D)").argument("[topic]", "workflow, spec, design, product, commit \uC911 \uD558\uB098").allowExcessArguments(false).action(async (topic) => {
26356
+ await runDocs(topic);
26357
+ });
25918
26358
  program2.command("migrate").description(".tryce \uC800\uC7A5\uC18C\uB97C .gitifact\uB85C \uC804\uD658 (\uACBD\uB85C\uC640 \uB9C8\uCEE4\uB9CC \uBC14\uAFB8\uACE0 ID\xB7\uC774\uC720\xB7\uC774\uB825\uC740 \uBCF4\uC874)").allowExcessArguments(false).option("--dry-run", "\uD30C\uC77C\uC744 \uBC14\uAFB8\uC9C0 \uC54A\uACE0 \uC804\uD658 \uACC4\uD68D \uD655\uC778").action(runMigrate);
25919
- var outputOption = () => new Option("--format <format>", "\uCD9C\uB825 \uD615\uC2DD").choices(["json", "text"]).default("json");
25920
- var skills = program2.command("skills").description("\uD504\uB85C\uC81D\uD2B8 \uC2A4\uD0AC \uC6D0\uBCF8 \uC124\uCE58\uC640 \uB85C\uCEEC \uBCF5\uC0AC\uBCF8 \uAD00\uB9AC");
25921
- for (const action of ["install", "sync", "remove"]) {
25922
- const command = skills.command(action).allowExcessArguments(false).option("--dry-run", "\uD30C\uC77C\uC744 \uC4F0\uC9C0 \uC54A\uACE0 \uACC4\uD68D \uD655\uC778").addOption(outputOption());
25923
- if (action !== "remove") command.addOption(new Option("--agent <agent>", "\uC0AC\uC6A9\uD560 \uC5D0\uC774\uC804\uD2B8").choices(["codex", "claude"]));
25924
- command.action((options) => runSkills(action, options));
25925
- }
25926
26359
  var spec = program2.command("spec").description("Markdown \uBA85\uC138 \uC791\uC131\xB7\uC870\uD68C\xB7Git \uAE30\uB85D");
25927
26360
  var replaced = "Deprecated: 0.6.0\uC5D0\uC11C \uC81C\uAC70 \uC608\uC815. spec commit\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.";
25928
26361
  spec.command("commit").description("\uBCC0\uACBD \uC774\uC720 \uAE30\uB85D\uACFC \uAD00\uB828 \uD30C\uC77C \uCEE4\uBC0B\uC744 \uD55C \uBC88\uC5D0 \uC2E4\uD589").allowExcessArguments(false).requiredOption("--file <path>", "reasons\xB7paths\xB7message\xB7authorization\uC744 \uB2F4\uC740 JSON \uD30C\uC77C").option("--dry-run", "\uD30C\uC77C\uC744 \uC4F0\uAC70\uB098 \uCEE4\uBC0B\uD558\uC9C0 \uC54A\uACE0 \uACB0\uACFC\uB9CC \uD655\uC778").action((o) => runSpecPreview("commit", o));