@rafiqui987/markode 0.0.0-stage → 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 (306) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +480 -2
  3. package/dist/cli.js +2954 -0
  4. package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  5. package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  6. package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  7. package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  8. package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  9. package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  10. package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  11. package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  12. package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  13. package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  14. package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  15. package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  16. package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  17. package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  18. package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  19. package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  20. package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  21. package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  22. package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  23. package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  24. package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  25. package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  26. package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  27. package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  28. package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  29. package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  30. package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  31. package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  32. package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  33. package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  34. package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  35. package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  36. package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  37. package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  38. package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  39. package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  40. package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  41. package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  42. package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  43. package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  44. package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  45. package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  46. package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  47. package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  48. package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  49. package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  50. package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  51. package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  52. package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  53. package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  54. package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  55. package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  56. package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  57. package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  58. package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  59. package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  60. package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  61. package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  62. package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  63. package/dist/web/assets/abnfDiagram-WEYOIA3C-uyv195L6.js +1 -0
  64. package/dist/web/assets/apl-gfMMiTNf.js +1 -0
  65. package/dist/web/assets/arc-CnHtXLAe.js +1 -0
  66. package/dist/web/assets/architecture-WOLXFQ4H-tPJ2P-p7.js +1 -0
  67. package/dist/web/assets/architectureDiagram-ZHVYKT53-C-WCNTHv.js +36 -0
  68. package/dist/web/assets/array-BifhSqXX.js +1 -0
  69. package/dist/web/assets/asciiarmor-C_eisQCp.js +1 -0
  70. package/dist/web/assets/asn1-DCl_8MCW.js +1 -0
  71. package/dist/web/assets/asterisk-BEn0Cwpx.js +1 -0
  72. package/dist/web/assets/blockDiagram-RJAE7QA3-DRgAHQJ9.js +140 -0
  73. package/dist/web/assets/brainfuck-D5EjA2JK.js +1 -0
  74. package/dist/web/assets/c4Diagram-XDPPPE54-DBBsZpdC.js +38 -0
  75. package/dist/web/assets/channel-COq7V7WB.js +1 -0
  76. package/dist/web/assets/chunk-2E7WHVMD-CQSFul1o.js +88 -0
  77. package/dist/web/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  78. package/dist/web/assets/chunk-4EMNKR2T-DHeQ05G5.js +1 -0
  79. package/dist/web/assets/chunk-55XCU76K-C9qNkR8H.js +1 -0
  80. package/dist/web/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  81. package/dist/web/assets/chunk-7S6XSTFO-C_2BTQ4f.js +2 -0
  82. package/dist/web/assets/chunk-AQDTSKZB-DZLt6T8h.js +72 -0
  83. package/dist/web/assets/chunk-DSNLSHJZ-6EnWiUHw.js +129 -0
  84. package/dist/web/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  85. package/dist/web/assets/chunk-HAGFOTED-C4TrjvWC.js +62 -0
  86. package/dist/web/assets/chunk-HNE4Q36P-Bhz3xzgF.js +1 -0
  87. package/dist/web/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  88. package/dist/web/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  89. package/dist/web/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
  90. package/dist/web/assets/chunk-NQOKRVIF-fn9o0p_a.js +213 -0
  91. package/dist/web/assets/chunk-UHW7PFBP-y_Cnbpso.js +1 -0
  92. package/dist/web/assets/chunk-VQ5W62QI-BDGHstYR.js +1 -0
  93. package/dist/web/assets/chunk-WQQL4CDK-DVgns4LS.js +10 -0
  94. package/dist/web/assets/chunk-XXDRQBXY-z-xFeaXQ.js +1 -0
  95. package/dist/web/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  96. package/dist/web/assets/chunk-YZO67HFG-O3X4dDhT.js +1 -0
  97. package/dist/web/assets/chunk-ZAUX2LTX-dMpe16to.js +1 -0
  98. package/dist/web/assets/chunk-ZCHMQSOR-DmywVAUg.js +2 -0
  99. package/dist/web/assets/classDiagram-v2-4MYWR3UA-DNFxIeoH.js +217 -0
  100. package/dist/web/assets/clike-xvONpD0Z.js +1 -0
  101. package/dist/web/assets/clojure-BflJGmDX.js +1 -0
  102. package/dist/web/assets/cmake-84nbDH0I.js +1 -0
  103. package/dist/web/assets/cobol-DHLQUbN7.js +1 -0
  104. package/dist/web/assets/coffeescript-De_zI4J0.js +1 -0
  105. package/dist/web/assets/commonlisp-DgAKBe0r.js +1 -0
  106. package/dist/web/assets/cose-bilkent-JH36ORCC-C78U5xAn.js +1 -0
  107. package/dist/web/assets/crystal-Bsdphhtm.js +1 -0
  108. package/dist/web/assets/css-CnaYoQib.js +1 -0
  109. package/dist/web/assets/cynefin-EF2NZ3EQ-53hzJKH9.js +1 -0
  110. package/dist/web/assets/cynefinDiagram-WF24UDRP-D7qakSAO.js +62 -0
  111. package/dist/web/assets/cypher-p9eYesGn.js +1 -0
  112. package/dist/web/assets/cytoscape.esm-BegBNzOV.js +321 -0
  113. package/dist/web/assets/d-DWkQyNnU.js +1 -0
  114. package/dist/web/assets/dagre-NK4GIZVX-JnnDdmH0.js +4 -0
  115. package/dist/web/assets/defaultLocale-BFoDCU3G.js +1 -0
  116. package/dist/web/assets/diagram-CBK3MWTC-C9dCT7nt.js +24 -0
  117. package/dist/web/assets/diagram-I5XIIV2D-BODrA_Tz.js +24 -0
  118. package/dist/web/assets/diagram-ODIL2UCW-ByZtzdTi.js +30 -0
  119. package/dist/web/assets/diagram-PFBEJUXE-Dc3cgafk.js +3 -0
  120. package/dist/web/assets/diagram-RJA3I4VV-BnTNuzvk.js +41 -0
  121. package/dist/web/assets/diagram-TYUNFSNR-fbw92djE.js +200 -0
  122. package/dist/web/assets/diff-ChtP43wD.js +1 -0
  123. package/dist/web/assets/dist-8bzcdWlP.js +1 -0
  124. package/dist/web/assets/dist-BPKiY16E.js +2 -0
  125. package/dist/web/assets/dist-BeUBXLX5.js +1 -0
  126. package/dist/web/assets/dist-BfedOYsq.js +1 -0
  127. package/dist/web/assets/dist-BruzXrfs.js +1 -0
  128. package/dist/web/assets/dist-BwF8pAVO.js +1 -0
  129. package/dist/web/assets/dist-CAttduN3.js +1 -0
  130. package/dist/web/assets/dist-CC7LN-Hh.js +1 -0
  131. package/dist/web/assets/dist-CFUj2tCy.js +12 -0
  132. package/dist/web/assets/dist-CM1-Tp30.js +1 -0
  133. package/dist/web/assets/dist-CfeLR_Q4.js +1 -0
  134. package/dist/web/assets/dist-CrUcXP5H.js +1 -0
  135. package/dist/web/assets/dist-CubY9gnU.js +1 -0
  136. package/dist/web/assets/dist-D0R-qVeY.js +1 -0
  137. package/dist/web/assets/dist-D5EOvBuF.js +1 -0
  138. package/dist/web/assets/dist-DcGC8X1E.js +11 -0
  139. package/dist/web/assets/dist-EljmWMub.js +9 -0
  140. package/dist/web/assets/dist-ga2OvaLo.js +1 -0
  141. package/dist/web/assets/dist-tv7yScoP.js +1 -0
  142. package/dist/web/assets/dockerfile-DJkHQgkl.js +1 -0
  143. package/dist/web/assets/dtd-DTF72YNO.js +1 -0
  144. package/dist/web/assets/dylan-S7ucR4pR.js +1 -0
  145. package/dist/web/assets/ebnf-D4c0_ac3.js +1 -0
  146. package/dist/web/assets/ebnfDiagram-V4P3TTPT-DPsfv1uf.js +1 -0
  147. package/dist/web/assets/ecl-BVIeWmwY.js +1 -0
  148. package/dist/web/assets/eiffel-BEjRio4Q.js +1 -0
  149. package/dist/web/assets/elk-QONTBCTQ-DoINDZCB.js +27 -0
  150. package/dist/web/assets/elm-Cshzl8qu.js +1 -0
  151. package/dist/web/assets/erDiagram-KYQIGQAA-ChPWyTRi.js +99 -0
  152. package/dist/web/assets/erlang-BV0c4Hdn.js +1 -0
  153. package/dist/web/assets/eventmodeling-K75KTNOO-B1BhcpV6.js +1 -0
  154. package/dist/web/assets/factor-LKH5gN8L.js +1 -0
  155. package/dist/web/assets/fcl-ClOhbFR7.js +1 -0
  156. package/dist/web/assets/flowDiagram-IUGLLWBU-DZzMwlmj.js +1 -0
  157. package/dist/web/assets/forth-D6RzAl64.js +1 -0
  158. package/dist/web/assets/fortran-Bt6PBEDR.js +1 -0
  159. package/dist/web/assets/ganttDiagram-R7GPNTJY-BpChuBNI.js +292 -0
  160. package/dist/web/assets/gas-BHEdbvp9.js +1 -0
  161. package/dist/web/assets/gherkin-oBAE_ms0.js +1 -0
  162. package/dist/web/assets/gitGraph-VSP46ZUC-BhIFPres.js +1 -0
  163. package/dist/web/assets/gitGraphDiagram-PDYL7XHY-CHejLr2o.js +106 -0
  164. package/dist/web/assets/graphlib-DS17s2tU.js +1 -0
  165. package/dist/web/assets/groovy-CUpZ7dEl.js +1 -0
  166. package/dist/web/assets/haskell-BRsoo5mP.js +1 -0
  167. package/dist/web/assets/haxe-DjVOVvNP.js +1 -0
  168. package/dist/web/assets/http-CLVgA2GD.js +1 -0
  169. package/dist/web/assets/idl-BUZw3wgd.js +1 -0
  170. package/dist/web/assets/index-CQGURl2V.css +1 -0
  171. package/dist/web/assets/index-CafleYLD.js +132 -0
  172. package/dist/web/assets/info-OHQRW6UA-p_XOG9kY.js +1 -0
  173. package/dist/web/assets/infoDiagram-MVDRKAX5-BjEI8OE0.js +2 -0
  174. package/dist/web/assets/init-C-OQMol4.js +1 -0
  175. package/dist/web/assets/ishikawaDiagram-HPEHV2GE-B_7LEH0q.js +70 -0
  176. package/dist/web/assets/javascript-Bu8mWwtz.js +1 -0
  177. package/dist/web/assets/journeyDiagram-BOSHW7O7-DprR7PC1.js +139 -0
  178. package/dist/web/assets/julia-CQfgDRfP.js +1 -0
  179. package/dist/web/assets/kanban-definition-FNSA4HC5-Dgn1OKTQ.js +89 -0
  180. package/dist/web/assets/katex-B_JcIwqj.js +257 -0
  181. package/dist/web/assets/katex-DMqEsVPU.js +257 -0
  182. package/dist/web/assets/katex-Ngnvt50Q.css +1 -0
  183. package/dist/web/assets/line-CsP7ezWR.js +1 -0
  184. package/dist/web/assets/linear-DutM4-eX.js +1 -0
  185. package/dist/web/assets/livescript-BXxG0Yva.js +1 -0
  186. package/dist/web/assets/lua-DPCEkfD_.js +1 -0
  187. package/dist/web/assets/mathematica-u8YMmbU0.js +1 -0
  188. package/dist/web/assets/mbox-BEMVcqjs.js +1 -0
  189. package/dist/web/assets/mermaid-parser.core-CRHYFOhU.js +7 -0
  190. package/dist/web/assets/mermaid.core-C-ITLWC9.js +44 -0
  191. package/dist/web/assets/mindmap-definition-6OY57UG3-B3gANyDJ.js +98 -0
  192. package/dist/web/assets/mirc-C9zpzAZU.js +1 -0
  193. package/dist/web/assets/mllike-B45xgp2S.js +1 -0
  194. package/dist/web/assets/modelica-CqHI_q-D.js +1 -0
  195. package/dist/web/assets/mscgen-qgSLujhx.js +1 -0
  196. package/dist/web/assets/mumps-BSzj6Xyz.js +1 -0
  197. package/dist/web/assets/nginx-8H7HVH4w.js +1 -0
  198. package/dist/web/assets/nsis-viPvXFiq.js +1 -0
  199. package/dist/web/assets/ntriples-CzMBulkp.js +1 -0
  200. package/dist/web/assets/octave-D2Q8cUp1.js +1 -0
  201. package/dist/web/assets/ordinal-BDEzSJ7C.js +1 -0
  202. package/dist/web/assets/oz-BwTct_5T.js +1 -0
  203. package/dist/web/assets/packet-JDAUHWVQ-0H1cA20q.js +1 -0
  204. package/dist/web/assets/pascal-Dp_KdFgX.js +1 -0
  205. package/dist/web/assets/path-fybaL0A-.js +1 -0
  206. package/dist/web/assets/pegDiagram-ECIUJNBX-BUxriBaM.js +1 -0
  207. package/dist/web/assets/perl-BkSkiI9m.js +1 -0
  208. package/dist/web/assets/pie-XZMESJXO-CSOHv2zM.js +1 -0
  209. package/dist/web/assets/pieDiagram-GNMPY74T-B7B_ijMh.js +39 -0
  210. package/dist/web/assets/pig-AF4Hwhju.js +1 -0
  211. package/dist/web/assets/powershell-D8WpQPaX.js +1 -0
  212. package/dist/web/assets/properties-C357ku5U.js +1 -0
  213. package/dist/web/assets/protobuf-D1ij_kNL.js +1 -0
  214. package/dist/web/assets/pug-Dwd_FmgJ.js +1 -0
  215. package/dist/web/assets/puppet-BZyAS7I4.js +1 -0
  216. package/dist/web/assets/python-B_1Jd8HM.js +1 -0
  217. package/dist/web/assets/q-B9NhS7L_.js +1 -0
  218. package/dist/web/assets/quadrantDiagram-D57K4MC3-CuDX_hzm.js +7 -0
  219. package/dist/web/assets/r-CxDmU1I4.js +1 -0
  220. package/dist/web/assets/radar-ABXABTNO-Bhdhmyn3.js +1 -0
  221. package/dist/web/assets/railroad-I3PHUGI6-D8J0asfg.js +1 -0
  222. package/dist/web/assets/railroad-abnf-LNEFI6M7-DSkwXAS5.js +1 -0
  223. package/dist/web/assets/railroad-ebnf-DWYD2UWJ-CUA5ZluT.js +1 -0
  224. package/dist/web/assets/railroad-peg-JR7BVNR7-DOmtcJJ_.js +1 -0
  225. package/dist/web/assets/railroadDiagram-Y72NRBL7-Bfa59bDk.js +1 -0
  226. package/dist/web/assets/requirementDiagram-EQYSW5RA-TqWBSttg.js +84 -0
  227. package/dist/web/assets/rough.esm-Dy-Kn_BL.js +1 -0
  228. package/dist/web/assets/rpm-gpDK5sbt.js +1 -0
  229. package/dist/web/assets/ruby-ChfNwlCY.js +1 -0
  230. package/dist/web/assets/sankeyDiagram-SY24JHO5-DXW4vZ49.js +40 -0
  231. package/dist/web/assets/sas-C2OyD2Y6.js +1 -0
  232. package/dist/web/assets/scheme-D9qYN0V5.js +1 -0
  233. package/dist/web/assets/sequenceDiagram-BLEHNHQU-bR6akpDb.js +169 -0
  234. package/dist/web/assets/shell-DwuoZtxw.js +1 -0
  235. package/dist/web/assets/sieve-tLWDfEkZ.js +1 -0
  236. package/dist/web/assets/simple-mode-DXiLM1-G.js +1 -0
  237. package/dist/web/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  238. package/dist/web/assets/smalltalk-BLp5-jwC.js +1 -0
  239. package/dist/web/assets/solr-B78wSQJQ.js +1 -0
  240. package/dist/web/assets/sparql-Dz-mlCAC.js +1 -0
  241. package/dist/web/assets/spreadsheet-CTIncYxj.js +1 -0
  242. package/dist/web/assets/sql-DuQMFDvy.js +1 -0
  243. package/dist/web/assets/src-DWy4o1sh.js +1 -0
  244. package/dist/web/assets/stateDiagram-v2-A6IESKWZ-G-L9fY1U.js +291 -0
  245. package/dist/web/assets/stex-DNzd62Q_.js +1 -0
  246. package/dist/web/assets/stylus-BOVgKnng.js +1 -0
  247. package/dist/web/assets/swift-DKB6_1j6.js +1 -0
  248. package/dist/web/assets/swimlanes-B3ENRKFQ-Bvz0uaIY.js +1 -0
  249. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-D8Xb8aOe.js +8 -0
  250. package/dist/web/assets/tcl-BGfruO1u.js +1 -0
  251. package/dist/web/assets/textile-C4jVoHG1.js +1 -0
  252. package/dist/web/assets/tiddlywiki-VlUiK86J.js +1 -0
  253. package/dist/web/assets/tiki-DTszW0OQ.js +1 -0
  254. package/dist/web/assets/timeline-definition-JXYKGLGB-CLuowMKW.js +120 -0
  255. package/dist/web/assets/toml-BPTmHmyx.js +1 -0
  256. package/dist/web/assets/treeView-D4JQ5SDB-P9KKVCsV.js +1 -0
  257. package/dist/web/assets/treemap-SAJKECNS-CJHxQeBA.js +1 -0
  258. package/dist/web/assets/troff-C2dAgh7P.js +1 -0
  259. package/dist/web/assets/ttcn-PPSTk1ow.js +1 -0
  260. package/dist/web/assets/ttcn-cfg-2b4Gb5sE.js +1 -0
  261. package/dist/web/assets/turtle-Cx3rYX2g.js +1 -0
  262. package/dist/web/assets/usecaseDiagram-WPOLBNNX-Dp-yumhB.js +328 -0
  263. package/dist/web/assets/vb-BthdM_4N.js +1 -0
  264. package/dist/web/assets/vbscript-D9f6rSsU.js +1 -0
  265. package/dist/web/assets/velocity-CqftRsjg.js +1 -0
  266. package/dist/web/assets/vennDiagram-Q3Y3IDCW-WYfAKKah.js +34 -0
  267. package/dist/web/assets/verilog-CjI9Y6RI.js +1 -0
  268. package/dist/web/assets/vhdl-kS471soi.js +1 -0
  269. package/dist/web/assets/wardley-7MLQ67FV-16kL0OjA.js +1 -0
  270. package/dist/web/assets/wardleyDiagram-IVAIDKWA-CKpO66Ll.js +78 -0
  271. package/dist/web/assets/webidl-D5jiJvOU.js +1 -0
  272. package/dist/web/assets/xquery-BKJx4mf9.js +1 -0
  273. package/dist/web/assets/xychartDiagram-4URNHDU4-BAlKKoEI.js +7 -0
  274. package/dist/web/assets/yacas-CXXgtw0p.js +1 -0
  275. package/dist/web/assets/z80-Co3PbHdE.js +1 -0
  276. package/dist/web/index.html +17 -0
  277. package/package.json +83 -3
  278. package/templates/main.md +5 -0
  279. package/templates/method/area/docs/tasks/.gitkeep +0 -0
  280. package/templates/method/area-main.md +9 -0
  281. package/templates/method/root/docs/tasks/_TEMPLATE.md +28 -0
  282. package/templates/method/skills/markode-close-session/SKILL.md +20 -0
  283. package/templates/method/skills/markode-method/SKILL.md +68 -0
  284. package/templates/method/skills/markode-method/templates/architecture-topic.md +12 -0
  285. package/templates/method/skills/markode-method/templates/phase.md +22 -0
  286. package/templates/method/skills/markode-method/templates/project-architecture.md +14 -0
  287. package/templates/method/skills/markode-method/templates/vision.md +22 -0
  288. package/templates/method/skills/markode-new-task/SKILL.md +22 -0
  289. package/templates/method/skills/markode-status/SKILL.md +24 -0
  290. package/templates/proposals/SKILL.md +139 -0
  291. package/templates/rules/ask-before-commit.md +4 -0
  292. package/templates/rules/ask-new-dependencies.md +4 -0
  293. package/templates/rules/ask-when-unclear.md +4 -0
  294. package/templates/rules/match-conventions.md +4 -0
  295. package/templates/rules/minimal-changes.md +4 -0
  296. package/templates/rules/protect-secrets.md +4 -0
  297. package/templates/rules/tests-with-changes.md +4 -0
  298. package/templates/rules/update-docs.md +4 -0
  299. package/templates/rules/verify-before-done.md +4 -0
  300. package/templates/sdd/openspec/openspec/README.md +89 -0
  301. package/templates/sdd/openspec/openspec/changes/archive/.gitkeep +0 -0
  302. package/templates/sdd/openspec/openspec/config.yaml +12 -0
  303. package/templates/sdd/openspec/openspec/specs/.gitkeep +0 -0
  304. package/templates/sdd/speckit/.specify/memory/constitution.md +30 -0
  305. package/templates/sdd/speckit/specs/README.md +82 -0
  306. package/templates/skill/SKILL.md +109 -0
package/dist/cli.js ADDED
@@ -0,0 +1,2954 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import fs20 from "fs";
5
+ import path20 from "path";
6
+
7
+ // src/core/changes.ts
8
+ import crypto from "crypto";
9
+ import fs2 from "fs";
10
+ import path2 from "path";
11
+
12
+ // src/core/config.ts
13
+ import fs from "fs";
14
+ import os from "os";
15
+ import path from "path";
16
+ import { fileURLToPath } from "url";
17
+ var LANGS = ["en", "es", "fr", "pt", "de", "it"];
18
+ var LANG_NAMES = { en: "English", es: "Espa\xF1ol", fr: "Fran\xE7ais", pt: "Portugu\xEAs", de: "Deutsch", it: "Italiano" };
19
+ var DEFAULT_SOURCE = "en";
20
+ var isLang = (v) => LANGS.includes(v);
21
+ var isLangCode = (v) => typeof v === "string" && /^[a-z]{2,3}(-[a-z0-9]{2,8})?$/i.test(v);
22
+ var langName = (code, config) => isLang(code) ? LANG_NAMES[code] : config?.langNames?.[code] ?? code;
23
+ var MAIN_DOCS = ["CLAUDE.md", "AGENTS.md"];
24
+ var otherDoc = (doc) => doc === "CLAUDE.md" ? "AGENTS.md" : "CLAUDE.md";
25
+ var POINTERS = {
26
+ // Claude Code imports the file: its content is loaded as if it were here.
27
+ "CLAUDE.md": "@AGENTS.md\n",
28
+ "AGENTS.md": "# AGENTS.md\n\nThis project's instructions for AI agents are in [CLAUDE.md](CLAUDE.md): read it and follow it.\n"
29
+ };
30
+ var isPointer = (text3, doc = "CLAUDE.md") => text3.trim() === POINTERS[doc].trim();
31
+ var readText = (abs) => {
32
+ try {
33
+ return fs.readFileSync(abs, "utf8");
34
+ } catch {
35
+ return "";
36
+ }
37
+ };
38
+ function contentDoc(dir) {
39
+ for (const doc of MAIN_DOCS) {
40
+ const abs = path.join(dir, doc);
41
+ if (!fs.existsSync(abs)) continue;
42
+ if (fs.existsSync(path.join(dir, otherDoc(doc))) && isPointer(readText(abs), doc)) continue;
43
+ return abs;
44
+ }
45
+ return null;
46
+ }
47
+ function mainDocOf(root2) {
48
+ const doc = contentDoc(root2);
49
+ return doc && path.basename(doc);
50
+ }
51
+ function missingMainDoc(dir) {
52
+ const present = MAIN_DOCS.filter((d) => fs.existsSync(path.join(dir, d)));
53
+ return present.length === 1 ? otherDoc(present[0]) : null;
54
+ }
55
+ function linkMainDoc(dir) {
56
+ const missing = missingMainDoc(dir);
57
+ if (missing) {
58
+ assertInside(path.join(dir, missing));
59
+ fs.writeFileSync(path.join(dir, missing), POINTERS[missing]);
60
+ }
61
+ return missing;
62
+ }
63
+ var linkAgentsDoc = (dir) => missingMainDoc(dir) === "CLAUDE.md" && !!linkMainDoc(dir);
64
+ var PKG_ROOT = (() => {
65
+ let dir = path.dirname(fileURLToPath(import.meta.url));
66
+ while (!fs.existsSync(path.join(dir, "package.json"))) dir = path.dirname(dir);
67
+ return dir;
68
+ })();
69
+ var TEMPLATES_DIR = path.join(PKG_ROOT, "templates");
70
+ var WEB_DIR = path.join(PKG_ROOT, "dist", "web");
71
+ var GLOBAL_DIR = process.env.MARKODE_HOME ?? path.join(os.homedir(), ".markode");
72
+ var stateDir = (root2) => path.join(root2, ".markode");
73
+ var configFile = (root2) => path.join(stateDir(root2), "config.json");
74
+ function readConfig(root2) {
75
+ try {
76
+ const raw = JSON.parse(fs.readFileSync(configFile(root2), "utf8"));
77
+ const langs = Array.isArray(raw.langs) ? raw.langs : raw.lang ? [raw.lang] : [];
78
+ const mainDoc = MAIN_DOCS.includes(raw.mainDoc) ? raw.mainDoc : null;
79
+ const workflow = ["none", "markode", "openspec", "speckit"].includes(raw.workflow) ? raw.workflow : void 0;
80
+ const proposals = raw.proposals === "general" || raw.proposals === "structured" ? raw.proposals : void 0;
81
+ const langNames = raw.langNames && typeof raw.langNames === "object" ? Object.fromEntries(Object.entries(raw.langNames).filter(([k, v]) => isLangCode(k) && typeof v === "string").map(([k, v]) => [k, v.slice(0, 40)])) : void 0;
82
+ const flags = raw.features && typeof raw.features === "object" ? Object.fromEntries(["rules", "translations", "dashboard", "skills"].filter((k) => typeof raw.features[k] === "boolean").map((k) => [k, raw.features[k]])) : void 0;
83
+ const savedRules = Array.isArray(raw.savedRules) ? raw.savedRules.filter((id) => typeof id === "string" && /^[\w-]+$/.test(id)) : void 0;
84
+ return { mainDoc, langs: langs.filter(isLangCode), langNames, workflow, proposals, skipLink: raw.skipLink === true || void 0, features: flags, savedRules, savedSourceLang: isLang(raw.savedSourceLang) ? raw.savedSourceLang : void 0, initializedAt: typeof raw.initializedAt === "string" ? raw.initializedAt : void 0 };
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ function writeConfig(root2, config) {
90
+ assertInside(configFile(root2));
91
+ fs.mkdirSync(stateDir(root2), { recursive: true });
92
+ fs.writeFileSync(configFile(root2), JSON.stringify(config, null, 2) + "\n");
93
+ }
94
+ var isInside = (abs, base) => abs === base || abs.startsWith(base + path.sep);
95
+ function realOf(abs) {
96
+ let probe = abs;
97
+ const rest = [];
98
+ for (; ; ) {
99
+ try {
100
+ fs.lstatSync(probe);
101
+ break;
102
+ } catch {
103
+ const up = path.dirname(probe);
104
+ if (up === probe) return null;
105
+ rest.unshift(path.basename(probe));
106
+ probe = up;
107
+ }
108
+ }
109
+ try {
110
+ return path.join(fs.realpathSync(probe), ...rest);
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+ var projectRoot = null;
116
+ function setProjectRoot(root2) {
117
+ projectRoot = fs.realpathSync(root2);
118
+ }
119
+ function assertInside(abs, root2 = projectRoot) {
120
+ if (!root2) return;
121
+ const base = fs.realpathSync(root2);
122
+ const real = realOf(abs);
123
+ if (!real || !isInside(real, base)) throw new Error(`Refusing to write outside the project (symlink?): ${abs}`);
124
+ }
125
+ function safeResolve(root2, rel) {
126
+ const abs = path.resolve(root2, rel);
127
+ if (!isInside(abs, root2)) throw new Error(`Path outside project: ${rel}`);
128
+ assertInside(abs, root2);
129
+ return abs;
130
+ }
131
+ var toPosix = (p3) => p3.split(path.sep).join("/");
132
+ var DEFAULT_SETTINGS = { font: "inter", size: 15, lineHeight: 1.65, padding: 28, width: "full", wrap: true, uiLang: "en" };
133
+ var settingsFile = () => path.join(GLOBAL_DIR, "settings.json");
134
+ function readSettings() {
135
+ try {
136
+ return { ...DEFAULT_SETTINGS, ...JSON.parse(fs.readFileSync(settingsFile(), "utf8")) };
137
+ } catch {
138
+ return { ...DEFAULT_SETTINGS };
139
+ }
140
+ }
141
+ function writeSettings(patch) {
142
+ const next = { ...readSettings(), ...patch };
143
+ fs.mkdirSync(GLOBAL_DIR, { recursive: true });
144
+ fs.writeFileSync(settingsFile(), JSON.stringify(next, null, 2) + "\n");
145
+ return next;
146
+ }
147
+
148
+ // src/core/changes.ts
149
+ var logDir = (root2) => path2.join(GLOBAL_DIR, "projects", crypto.createHash("sha256").update(fs2.realpathSync(root2)).digest("hex").slice(0, 16));
150
+ var logFile = (root2) => path2.join(logDir(root2), "activity.jsonl");
151
+ var cursorFile = (root2) => path2.join(logDir(root2), "activity-cursor.json");
152
+ var MAX_LINES = 1e3;
153
+ var ENTRY_HEAD = /^<!-- (comment|reply) by ([^,\n]+), (\d{4}-\d{2}-\d{2})(?:, on "((?:[^"\\\n]|\\.)*)")?:\s?/;
154
+ var HEADING = /^#{1,6}\s+(.*?)\s*#*\s*$/;
155
+ var MAX_ENTRY = 2e4;
156
+ function allEntries(text3) {
157
+ const out = [];
158
+ for (let i = 0; ; ) {
159
+ const s = text3.indexOf("<!--", i);
160
+ const e = s < 0 ? -1 : text3.indexOf("-->", s + 4);
161
+ if (e < 0) return out;
162
+ i = e + 3;
163
+ if (e - s > MAX_ENTRY) continue;
164
+ const raw = text3.slice(s, i);
165
+ const m = ENTRY_HEAD.exec(raw);
166
+ if (m) out.push({ raw, index: s, author: m[2], quote: m[4] ?? "", text: raw.slice(m[0].length, -3).trim() });
167
+ }
168
+ }
169
+ var entries = (text3) => allEntries(text3).filter((e) => e.author !== "AI" && e.text);
170
+ function stripEntries(text3) {
171
+ let out = "";
172
+ let from = 0;
173
+ for (const e of allEntries(text3)) {
174
+ const end = e.index + e.raw.length;
175
+ const lineStart = text3.lastIndexOf("\n", e.index - 1) + 1;
176
+ const nl = text3.indexOf("\n", end);
177
+ const lineEnd = nl < 0 ? text3.length : nl;
178
+ const alone = !text3.slice(Math.max(lineStart, from), e.index).trim() && !text3.slice(end, lineEnd).trim();
179
+ if (alone && lineStart >= from) {
180
+ out += text3.slice(from, lineStart);
181
+ from = nl < 0 ? text3.length : nl + 1;
182
+ } else {
183
+ out += text3.slice(from, e.index);
184
+ from = end;
185
+ }
186
+ }
187
+ return out + text3.slice(from);
188
+ }
189
+ function headings(text3) {
190
+ const out = [];
191
+ let index = 0;
192
+ for (const line of text3.split("\n")) {
193
+ const h = HEADING.exec(line);
194
+ if (h) out.push({ index, title: h[1] });
195
+ index += line.length + 1;
196
+ }
197
+ return out;
198
+ }
199
+ function sectionAt(list, index) {
200
+ let lo = 0;
201
+ let hi = list.length - 1;
202
+ let found;
203
+ while (lo <= hi) {
204
+ const mid = lo + hi >> 1;
205
+ if (list[mid].index <= index) found = list[mid].title, lo = mid + 1;
206
+ else hi = mid - 1;
207
+ }
208
+ return found;
209
+ }
210
+ var isProposal = (text3) => /^type:\s*proposal\s*$/m.test(/^---\r?\n([\s\S]*?)\r?\n---/.exec(text3)?.[1] ?? "");
211
+ function logSave(root2, rel, before, after) {
212
+ if (before === after || isProposal(after)) return;
213
+ const at = (/* @__PURE__ */ new Date()).toISOString();
214
+ const old = new Set(entries(before).map((e) => e.raw));
215
+ const sections = headings(after);
216
+ const out = entries(after).filter((e) => !old.has(e.raw)).map((e) => ({ at, path: rel, kind: "comment", section: sectionAt(sections, e.index), author: e.author, quote: e.quote || void 0, text: e.text }));
217
+ const a = stripEntries(before).split("\n");
218
+ const b = stripEntries(after).split("\n");
219
+ if (a.join("\n") !== b.join("\n")) {
220
+ let i = 0;
221
+ while (i < a.length && i < b.length && a[i] === b[i]) i++;
222
+ out.push({ at, path: rel, kind: "edit", section: sectionAt(headings(b.join("\n")), b.slice(0, i + 1).join("\n").length) });
223
+ }
224
+ if (!out.length) return;
225
+ const file = logFile(root2);
226
+ fs2.mkdirSync(logDir(root2), { recursive: true });
227
+ fs2.appendFileSync(file, out.map((e) => JSON.stringify(e)).join("\n") + "\n");
228
+ const text3 = fs2.readFileSync(file, "utf8");
229
+ const lines = text3.split("\n").filter(Boolean);
230
+ if (lines.length > MAX_LINES) {
231
+ const kept = lines.slice(-MAX_LINES / 2).join("\n") + "\n";
232
+ fs2.writeFileSync(file, kept);
233
+ const removed = Buffer.byteLength(text3) - Buffer.byteLength(kept);
234
+ const cursor = readCursor(root2);
235
+ if (cursor) writeCursor(root2, { reported: Math.max(0, cursor.reported - removed) });
236
+ }
237
+ }
238
+ function readCursor(root2) {
239
+ try {
240
+ const c = JSON.parse(fs2.readFileSync(cursorFile(root2), "utf8"));
241
+ return Number.isSafeInteger(c?.reported) && c.reported >= 0 ? { reported: c.reported } : null;
242
+ } catch {
243
+ return null;
244
+ }
245
+ }
246
+ function writeCursor(root2, c) {
247
+ fs2.mkdirSync(logDir(root2), { recursive: true });
248
+ fs2.writeFileSync(cursorFile(root2), JSON.stringify(c) + "\n");
249
+ }
250
+ var logSize = (root2) => fs2.existsSync(logFile(root2)) ? fs2.statSync(logFile(root2)).size : 0;
251
+ function startSession(root2) {
252
+ writeCursor(root2, { reported: logSize(root2) });
253
+ }
254
+ function takeChanges(root2) {
255
+ const size = logSize(root2);
256
+ const cursor = readCursor(root2);
257
+ const from = cursor && cursor.reported <= size ? cursor.reported : size;
258
+ if (from === size) {
259
+ if (!cursor) writeCursor(root2, { reported: size });
260
+ return [];
261
+ }
262
+ const fd = fs2.openSync(logFile(root2), "r");
263
+ const buf = Buffer.alloc(size - from);
264
+ fs2.readSync(fd, buf, 0, buf.length, from);
265
+ fs2.closeSync(fd);
266
+ writeCursor(root2, { reported: size });
267
+ return buf.toString("utf8").split("\n").filter(Boolean).flatMap((l) => {
268
+ try {
269
+ const e = JSON.parse(l);
270
+ const str = (v) => typeof v === "string" ? v.slice(0, 2e3) : void 0;
271
+ if (e.kind !== "edit" && e.kind !== "comment" || typeof e.path !== "string") return [];
272
+ return [{ at: str(e.at) ?? "", path: e.path.slice(0, 500), kind: e.kind, section: str(e.section), author: str(e.author), quote: str(e.quote), text: str(e.text) }];
273
+ } catch {
274
+ return [];
275
+ }
276
+ });
277
+ }
278
+ function formatChanges(list) {
279
+ if (!list.length) return "";
280
+ const edits = /* @__PURE__ */ new Map();
281
+ for (const e of list.filter((e2) => e2.kind === "edit")) {
282
+ const sections = edits.get(e.path) ?? /* @__PURE__ */ new Set();
283
+ if (e.section) sections.add(e.section);
284
+ edits.set(e.path, sections);
285
+ }
286
+ const comments = list.filter((e) => e.kind === "comment");
287
+ const lines = ['markode: the user changed docs while you worked (see "While you work" in the markode skill).'];
288
+ if (edits.size) {
289
+ lines.push("", "Edited (re-read before working on that part; nothing to do otherwise):");
290
+ for (const [p3, s] of edits) lines.push(`- ${p3}${s.size ? ` (${[...s].map((x) => `"${x}"`).join(", ")})` : ""}`);
291
+ }
292
+ if (comments.length) {
293
+ lines.push("", "New comments (about your current work: reply below it and apply it; otherwise ask the user in one line whether to take it now):");
294
+ for (const c of comments) lines.push(`- ${c.path}${c.section ? ` \u203A "${c.section}"` : ""}${c.quote ? `, on "${c.quote}"` : ""}: ${c.text} (${c.author})`);
295
+ }
296
+ return lines.join("\n");
297
+ }
298
+
299
+ // src/core/check.ts
300
+ import fs10 from "fs";
301
+ import path10 from "path";
302
+
303
+ // src/core/dashboard.ts
304
+ import fs4 from "fs";
305
+ import path4 from "path";
306
+ import YAML from "yaml";
307
+
308
+ // src/core/scan.ts
309
+ import fs3 from "fs";
310
+ import path3 from "path";
311
+ import ignore from "ignore";
312
+ var ALWAYS_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".markode", "dist", "build", ".next", ".turbo", "coverage", ".venv"]);
313
+ var IGNORE_FILE = ".markodeignore";
314
+ function loadIgnore(root2, file) {
315
+ const ig = ignore();
316
+ try {
317
+ ig.add(fs3.readFileSync(path3.join(root2, file), "utf8"));
318
+ } catch {
319
+ }
320
+ return ig;
321
+ }
322
+ function readIgnoreList(root2) {
323
+ try {
324
+ return fs3.readFileSync(path3.join(root2, IGNORE_FILE), "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
325
+ } catch {
326
+ return [];
327
+ }
328
+ }
329
+ function setIgnored(root2, rel, kind, ignored) {
330
+ const entry = kind === "dir" ? `${rel}/` : rel;
331
+ const lines = readIgnoreList(root2).filter((l) => l !== entry && l !== rel);
332
+ if (ignored) lines.push(entry);
333
+ assertInside(path3.join(root2, IGNORE_FILE));
334
+ fs3.writeFileSync(path3.join(root2, IGNORE_FILE), lines.length ? lines.join("\n") + "\n" : "");
335
+ }
336
+ function watchSkips(root2) {
337
+ const gitIgnore = loadIgnore(root2, ".gitignore");
338
+ return (rel, isDir2) => {
339
+ if (!rel || rel.startsWith("..")) return false;
340
+ if (rel.split("/").some((s) => ALWAYS_SKIP.has(s))) return true;
341
+ if (isDir2 !== false && gitIgnore.ignores(rel + "/")) return true;
342
+ return isDir2 !== true && gitIgnore.ignores(rel);
343
+ };
344
+ }
345
+ var isArchive = (rel) => {
346
+ const parts = rel.split("/");
347
+ return parts[parts.length - 1] === "applied" && parts.slice(0, -1).includes("proposals");
348
+ };
349
+ function scanTree(root2) {
350
+ const gitIgnore = loadIgnore(root2, ".gitignore");
351
+ const mdIgnore = loadIgnore(root2, IGNORE_FILE);
352
+ function walk(dirAbs, parentIgnored, parentArchived) {
353
+ let entries2;
354
+ try {
355
+ entries2 = fs3.readdirSync(dirAbs, { withFileTypes: true });
356
+ } catch {
357
+ return [];
358
+ }
359
+ const nodes = [];
360
+ for (const e of entries2) {
361
+ if (ALWAYS_SKIP.has(e.name)) continue;
362
+ const abs = path3.join(dirAbs, e.name);
363
+ const rel = toPosix(path3.relative(root2, abs));
364
+ if (e.isDirectory()) {
365
+ if (gitIgnore.ignores(rel + "/")) continue;
366
+ const archived = parentArchived || isArchive(rel);
367
+ const ignored = parentIgnored || archived || mdIgnore.ignores(rel + "/");
368
+ const children = walk(abs, ignored, archived);
369
+ if (!children.length && !isEmptyDir(abs)) continue;
370
+ const project = MAIN_DOCS.some((d) => children.some((c) => c.kind === "file" && c.name === d));
371
+ nodes.push({ name: e.name, path: rel, kind: "dir", project: project || void 0, ignored: ignored || void 0, archived: archived || void 0, children });
372
+ } else if (e.isFile() && e.name.toLowerCase().endsWith(".md")) {
373
+ if (gitIgnore.ignores(rel)) continue;
374
+ const ignored = parentIgnored || mdIgnore.ignores(rel);
375
+ nodes.push({ name: e.name, path: rel, kind: "file", ignored: ignored || void 0, archived: parentArchived || void 0 });
376
+ }
377
+ }
378
+ return nodes.sort(compareNodes);
379
+ }
380
+ return walk(root2, false, false);
381
+ }
382
+ function isEmptyDir(abs) {
383
+ try {
384
+ return fs3.readdirSync(abs).length === 0;
385
+ } catch {
386
+ return false;
387
+ }
388
+ }
389
+ var collator = new Intl.Collator(void 0, { numeric: true, sensitivity: "base" });
390
+ function compareNodes(a, b) {
391
+ return (a.kind === "dir" ? 0 : 1) - (b.kind === "dir" ? 0 : 1) || collator.compare(a.name, b.name);
392
+ }
393
+ function flattenFiles(nodes, includeIgnored = false) {
394
+ const out = [];
395
+ for (const n of nodes) {
396
+ if (n.ignored && !includeIgnored) continue;
397
+ if (n.kind === "file") out.push(n);
398
+ else out.push(...flattenFiles(n.children ?? [], includeIgnored));
399
+ }
400
+ return out;
401
+ }
402
+ function findSubprojects(nodes) {
403
+ const out = [];
404
+ for (const n of nodes) {
405
+ if (n.kind !== "dir") continue;
406
+ if (n.project) out.push(n.path);
407
+ out.push(...findSubprojects(n.children ?? []));
408
+ }
409
+ return out;
410
+ }
411
+
412
+ // src/core/dashboard.ts
413
+ function parseFrontmatter(text3) {
414
+ const m = text3.match(/^---\r?\n([\s\S]*?)\r?\n---/);
415
+ if (!m) return {};
416
+ try {
417
+ return YAML.parse(m[1], { logLevel: "error" }) ?? {};
418
+ } catch {
419
+ return {};
420
+ }
421
+ }
422
+ var asText = (v) => v instanceof Date ? v.toISOString().slice(0, 10) : v == null || typeof v === "object" ? null : String(v);
423
+ function summarize(root2) {
424
+ return flattenFiles(scanTree(root2)).flatMap((f) => {
425
+ if (f.path.split("/").some((part) => part.startsWith("."))) return [];
426
+ const text3 = fs4.readFileSync(path4.join(root2, f.path), "utf8");
427
+ const meta = parseFrontmatter(text3);
428
+ if (meta.type === "task" && f.name.startsWith("_")) return [];
429
+ const id = asText(meta.id);
430
+ const total = text3.match(/^[ \t]*[-*+] \[[ xX]\]/gm)?.length ?? 0;
431
+ const done = text3.match(/^[ \t]*[-*+] \[[xX]\]/gm)?.length ?? 0;
432
+ const heading2 = text3.match(/^#\s+(.+)$/m)?.[1];
433
+ const title = asText(meta.title) ?? (heading2 && id ? heading2.replace(new RegExp(`^${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*[\xB7:-]\\s*`), "") : heading2);
434
+ return {
435
+ path: f.path,
436
+ title: title ?? f.name.replace(/\.md$/i, ""),
437
+ type: asText(meta.type),
438
+ status: asText(meta.status),
439
+ phase: typeof meta.phase === "number" ? meta.phase : null,
440
+ updated: asText(meta.updated),
441
+ id,
442
+ area: asText(meta.area),
443
+ tasks: { done, total }
444
+ };
445
+ });
446
+ }
447
+
448
+ // src/core/method.ts
449
+ import fs9 from "fs";
450
+ import path9 from "path";
451
+ import YAML3 from "yaml";
452
+
453
+ // src/core/profile.ts
454
+ import fs5 from "fs";
455
+ import path5 from "path";
456
+ var KIND_LABEL = { app: "App", library: "Library", mobile: "Mobile", data: "Data / ML", infra: "Infrastructure" };
457
+ var KIND_HINT = {
458
+ app: "web, API, CLI, desktop",
459
+ library: "a package other code imports",
460
+ mobile: "iOS, Android, Flutter, React Native",
461
+ data: "notebooks, datasets, models",
462
+ infra: "Terraform, Pulumi, Helm, Kubernetes"
463
+ };
464
+ var EXPERIMENTS_LABEL = {
465
+ docs: "`docs/experiments.md` (one entry per run: goal, data, result)",
466
+ mlflow: "MLflow",
467
+ wandb: "Weights & Biases",
468
+ dvc: "DVC"
469
+ };
470
+ var SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", "vendor", "target", ".git", ".venv", "venv"]);
471
+ var names = (abs) => {
472
+ try {
473
+ return fs5.readdirSync(abs, { withFileTypes: true });
474
+ } catch {
475
+ return [];
476
+ }
477
+ };
478
+ function find(abs, re, depth) {
479
+ for (const e of names(abs)) {
480
+ if (re.test(e.name)) return path5.join(abs, e.name);
481
+ if (e.isDirectory() && depth > 0 && !SKIP.has(e.name) && !e.name.startsWith(".")) {
482
+ const hit = find(path5.join(abs, e.name), re, depth - 1);
483
+ if (hit) return hit;
484
+ }
485
+ }
486
+ return null;
487
+ }
488
+ var read = (abs) => {
489
+ try {
490
+ return fs5.readFileSync(abs, "utf8");
491
+ } catch {
492
+ return "";
493
+ }
494
+ };
495
+ var readJson = (abs) => {
496
+ try {
497
+ return JSON.parse(fs5.readFileSync(abs, "utf8"));
498
+ } catch {
499
+ return null;
500
+ }
501
+ };
502
+ var APP_DEPS = ["next", "nuxt", "vite", "react-scripts", "@angular/core", "@sveltejs/kit", "astro", "express", "fastify", "@nestjs/core", "koa", "hono", "electron"];
503
+ function guessKind(dir) {
504
+ const found = {};
505
+ const add = (kind, why) => (found[kind] ??= []).push(why);
506
+ const has2 = (name) => fs5.existsSync(path5.join(dir, name));
507
+ const pkg = readJson(path5.join(dir, "package.json"));
508
+ const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
509
+ if (has2("pubspec.yaml")) add("mobile", "Flutter (pubspec.yaml)");
510
+ if (deps["react-native"] || deps.expo) add("mobile", "React Native");
511
+ if (has2("android") && has2("ios")) add("mobile", "android/ and ios/");
512
+ else if (find(dir, /\.xcodeproj$/, 1)) add("mobile", "Xcode project");
513
+ if (find(dir, /\.ipynb$/, 2)) add("data", "notebooks");
514
+ if (has2("dvc.yaml") || has2("MLproject")) add("data", has2("dvc.yaml") ? "dvc.yaml" : "MLproject");
515
+ if (find(dir, /\.tf$/, 2)) add("infra", "Terraform");
516
+ if (has2("Pulumi.yaml")) add("infra", "Pulumi");
517
+ if (find(dir, /^(Chart|kustomization)\.ya?ml$/, 2)) add("infra", "Helm / Kustomize");
518
+ if (pkg && !pkg.private && !pkg.bin && (pkg.main || pkg.exports || pkg.module)) add("library", "package.json exports");
519
+ if (/^\[lib\]/m.test(read(path5.join(dir, "Cargo.toml")))) add("library", "Cargo [lib]");
520
+ if (pkg?.bin) add("app", "CLI (bin)");
521
+ const framework = APP_DEPS.find((d) => deps[d]);
522
+ if (framework) add("app", framework);
523
+ if (has2("manage.py")) add("app", "Django");
524
+ if (has2("main.go")) add("app", "main.go");
525
+ if (has2("Dockerfile")) add("app", "Dockerfile");
526
+ const kinds = Object.keys(found);
527
+ const signals = kinds.flatMap((k) => found[k]);
528
+ const specific = kinds.filter((k) => k !== "app");
529
+ if (!kinds.length) return { kind: "app", unclear: true, signals };
530
+ if (specific.length === 1) return { kind: specific[0], unclear: false, signals };
531
+ if (!specific.length) return { kind: "app", unclear: false, signals };
532
+ return { kind: specific.find((k) => k !== "library") ?? specific[0], unclear: true, signals };
533
+ }
534
+ function findContracts(root2) {
535
+ const hit = find(root2, /(\.proto|^openapi.*\.ya?ml|^openapi.*\.json|^asyncapi.*\.ya?ml)$/i, 3);
536
+ if (!hit) return null;
537
+ const dir = path5.relative(root2, path5.dirname(hit)).split(path5.sep).join("/");
538
+ return dir ? `${dir}/` : path5.basename(hit);
539
+ }
540
+ var looksLikeMicroservices = (root2, projects) => projects >= 2 && (!!findContracts(root2) || !!find(root2, /^(docker-)?compose(\.[\w-]+)?\.ya?ml$/, 0));
541
+ function detectProfile(dir, kind) {
542
+ const has2 = (name) => fs5.existsSync(path5.join(dir, name));
543
+ const pkg = readJson(path5.join(dir, "package.json"));
544
+ if (kind === "library") return { kind, published: !!pkg?.publishConfig || !!pkg?.name && !pkg.private };
545
+ if (kind === "mobile") {
546
+ const platforms = ["ios", "android", "web"].filter((p3) => has2(p3));
547
+ return { kind, platforms: platforms.length ? platforms : ["ios", "android"] };
548
+ }
549
+ if (kind === "data") {
550
+ const experiments = has2("dvc.yaml") ? "dvc" : has2("mlruns") ? "mlflow" : has2("wandb") ? "wandb" : "docs";
551
+ const data = ["data", "datasets"].find(has2);
552
+ return { kind, dataLocation: data ? `${data}/` : "", experiments };
553
+ }
554
+ if (kind === "infra") {
555
+ const envDir = ["envs", "environments", "env"].find((d) => has2(d));
556
+ const envs = envDir ? names(path5.join(dir, envDir)).filter((e) => e.isDirectory()).map((e) => e.name) : [];
557
+ return { kind, environments: envs.length ? envs.join(", ") : "dev, staging, prod" };
558
+ }
559
+ return { kind };
560
+ }
561
+ function kindGuidance(p3) {
562
+ switch (p3.kind) {
563
+ case "library":
564
+ return `Library${p3.published ? ", published" : ""}: record public API and versioning decisions in \`docs/architecture/api.md\`; phases only if the user plans milestones.${p3.published ? " One `CHANGELOG.md` entry per release." : ""}`;
565
+ case "mobile":
566
+ return `Mobile app (${(p3.platforms ?? []).join(", ") || "platforms not set"}): release and store decisions in \`docs/architecture/releases.md\`.`;
567
+ case "data":
568
+ return `Data / ML: each dataset (source, location${p3.dataLocation ? `: \`${p3.dataLocation}\`` : ""}, schema) in \`docs/architecture/data.md\`; experiments tracked in ${EXPERIMENTS_LABEL[p3.experiments ?? "docs"]}.`;
569
+ case "infra":
570
+ return `Infrastructure (environments: ${p3.environments || "dev, staging, prod"}): one runbook per operation in \`docs/runbooks/\`; changing an environment is a task.`;
571
+ default:
572
+ return null;
573
+ }
574
+ }
575
+ var contractsGuidance = (contracts) => `Microservices: contracts (APIs, events) live in \`${contracts}\`. Changing one is a shared decision (\`docs/architecture/contracts.md\`) and every consumer gets a task.`;
576
+ function existingDocs(root2) {
577
+ const docs = path5.join(root2, "docs");
578
+ const out = [];
579
+ for (const e of names(docs)) {
580
+ if (e.name.startsWith(".") || e.name === "tasks" || e.name === "proposals") continue;
581
+ if (e.isFile() && e.name.toLowerCase().endsWith(".md")) out.push(`docs/${e.name}`);
582
+ else if (e.isDirectory() && find(path5.join(docs, e.name), /\.md$/i, 2)) out.push(`docs/${e.name}/`);
583
+ }
584
+ for (const adr of ["adr", "decisions"]) if (find(path5.join(root2, adr), /\.md$/i, 1)) out.push(`${adr}/`);
585
+ return out.sort();
586
+ }
587
+
588
+ // src/core/scaffold.ts
589
+ import fs6 from "fs";
590
+ import path6 from "path";
591
+ var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
592
+ function fill(text3, vars) {
593
+ return text3.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? "");
594
+ }
595
+ function copyTree(src, dest, vars, created, root2) {
596
+ for (const e of fs6.readdirSync(src, { withFileTypes: true })) {
597
+ const from = path6.join(src, e.name);
598
+ const to = path6.join(dest, fill(e.name, vars));
599
+ assertInside(to);
600
+ if (e.isDirectory()) {
601
+ fs6.mkdirSync(to, { recursive: true });
602
+ copyTree(from, to, vars, created, root2);
603
+ } else if (!fs6.existsSync(to)) {
604
+ fs6.writeFileSync(to, fill(fs6.readFileSync(from, "utf8"), vars));
605
+ created.push(toPosix(path6.relative(root2, to)));
606
+ }
607
+ }
608
+ }
609
+ function createMainDoc(root2, name) {
610
+ const file = path6.join(root2, name);
611
+ if (fs6.existsSync(file)) return;
612
+ assertInside(file);
613
+ const text3 = fs6.readFileSync(path6.join(TEMPLATES_DIR, "main.md"), "utf8");
614
+ fs6.writeFileSync(file, fill(text3, { name: path6.basename(root2), date: today() }));
615
+ }
616
+ var vsCodeFile = (root2) => path6.join(root2, ".vscode", "settings.json");
617
+ var HIDDEN = "**/.markode";
618
+ function readVsCode(root2) {
619
+ if (!fs6.existsSync(vsCodeFile(root2))) return {};
620
+ try {
621
+ return JSON.parse(fs6.readFileSync(vsCodeFile(root2), "utf8"));
622
+ } catch {
623
+ return null;
624
+ }
625
+ }
626
+ var isHiddenInVsCode = (root2) => readVsCode(root2)?.["files.exclude"]?.[HIDDEN] === true;
627
+ function hideInVsCode(root2) {
628
+ const settings = readVsCode(root2);
629
+ if (!settings) return false;
630
+ assertInside(vsCodeFile(root2));
631
+ settings["files.exclude"] = { ...settings["files.exclude"] ?? {}, [HIDDEN]: true };
632
+ fs6.mkdirSync(path6.dirname(vsCodeFile(root2)), { recursive: true });
633
+ fs6.writeFileSync(vsCodeFile(root2), JSON.stringify(settings, null, 2) + "\n");
634
+ return true;
635
+ }
636
+ function showInVsCode(root2) {
637
+ const settings = readVsCode(root2);
638
+ if (!settings?.["files.exclude"]?.[HIDDEN]) return;
639
+ delete settings["files.exclude"][HIDDEN];
640
+ if (!Object.keys(settings["files.exclude"]).length) delete settings["files.exclude"];
641
+ assertInside(vsCodeFile(root2));
642
+ fs6.writeFileSync(vsCodeFile(root2), JSON.stringify(settings, null, 2) + "\n");
643
+ }
644
+ function missingMainDocs(root2) {
645
+ return ["", ...findSubprojects(scanTree(root2))].flatMap((dir) => {
646
+ const doc = missingMainDoc(path6.join(root2, dir));
647
+ return doc ? [{ path: path6.posix.join(dir, doc), pointsTo: doc === "CLAUDE.md" ? "AGENTS.md" : "CLAUDE.md" }] : [];
648
+ });
649
+ }
650
+ function linkMainDocs(root2) {
651
+ return missingMainDocs(root2).flatMap((m) => linkMainDoc(path6.join(root2, path6.posix.dirname(m.path))) ? [m.path] : []);
652
+ }
653
+
654
+ // src/core/sdd.ts
655
+ import { spawnSync } from "child_process";
656
+ import fs7 from "fs";
657
+ import os2 from "os";
658
+ import path7 from "path";
659
+ var SDD_AGENTS = {
660
+ claude: { label: "Claude Code", openspec: "claude", speckit: "claude" },
661
+ codex: { label: "Codex", openspec: "codex", speckit: "codex" },
662
+ copilot: { label: "GitHub Copilot", openspec: "github-copilot", speckit: "copilot" },
663
+ gemini: { label: "Gemini CLI", openspec: "gemini", speckit: "gemini" },
664
+ cursor: { label: "Cursor", openspec: "cursor", speckit: "cursor-agent" }
665
+ };
666
+ var OPENSPEC_PKG = "@fission-ai/openspec@latest";
667
+ var SPECKIT_SRC = "git+https://github.com/github/spec-kit.git";
668
+ function detectWorkflow(root2) {
669
+ if (fs7.existsSync(path7.join(root2, "openspec"))) return "openspec";
670
+ if (fs7.existsSync(path7.join(root2, ".specify"))) return "speckit";
671
+ return null;
672
+ }
673
+ var has = (cmd) => spawnSync(cmd, ["--version"], { cwd: os2.homedir(), stdio: "ignore", shell: process.platform === "win32" }).status === 0;
674
+ var hasUv = () => has("uvx");
675
+ function installCommand(workflow, agents) {
676
+ if (workflow === "openspec") {
677
+ const tools = agents.map((a) => SDD_AGENTS[a].openspec).join(",") || "none";
678
+ const runner = has("pnpm") ? ["pnpm", "dlx"] : has("npx") ? ["npx", "--yes"] : null;
679
+ return runner && [...runner, OPENSPEC_PKG, "init", "--tools", tools, "--no-animation", "--no-copilot-cloud"];
680
+ }
681
+ if (!hasUv()) return null;
682
+ return ["uvx", "--from", SPECKIT_SRC, "specify", "init", "--here", "--force", "--non-interactive", "--ignore-agent-tools", "--integration", SDD_AGENTS[agents[0] ?? "claude"].speckit];
683
+ }
684
+ function installWorkflow(root2, workflow, agents, official) {
685
+ const before = new Set(flattenFiles(scanTree(root2), true).map((f) => f.path));
686
+ const result = { official: false, created: [], ignored: [] };
687
+ const cmd = official ? installCommand(workflow, agents) : null;
688
+ if (cmd) {
689
+ const run = spawnSync(cmd[0], cmd.slice(1), { cwd: root2, encoding: "utf8", timeout: 3e5, shell: process.platform === "win32", env: { ...process.env, NO_COLOR: "1" } });
690
+ if (run.status === 0) result.official = true;
691
+ else result.error = (run.error?.message ?? `${run.stderr ?? ""}
692
+ ${run.stdout ?? ""}`).trim().split("\n").slice(-6).join("\n");
693
+ }
694
+ copyTree(path7.join(TEMPLATES_DIR, "sdd", workflow), root2, { date: today2() }, result.created, root2);
695
+ const dirs2 = /* @__PURE__ */ new Set();
696
+ for (const f of flattenFiles(scanTree(root2), true)) {
697
+ if (before.has(f.path) || !f.path.startsWith(".") || f.path.startsWith(".specify/memory/")) continue;
698
+ dirs2.add(path7.posix.dirname(f.path));
699
+ }
700
+ for (const dir of dirs2) setIgnored(root2, dir, "dir", true);
701
+ result.ignored = [...dirs2].sort();
702
+ return result;
703
+ }
704
+ var today2 = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
705
+ var WORKFLOW_TEXT = {
706
+ openspec: [
707
+ "## Workflow",
708
+ "",
709
+ "This project uses [OpenSpec](https://github.com/Fission-AI/OpenSpec) (spec-driven development; guide: `openspec/README.md`):",
710
+ "",
711
+ "- `openspec/specs/<domain>/spec.md` describes how the system behaves today.",
712
+ "- Every change starts in `openspec/changes/<change-id>/` with `proposal.md`, `tasks.md`, an optional `design.md` and delta specs, before any code.",
713
+ "- When it is done, archive it so its delta specs merge into `openspec/specs/`.",
714
+ "- Use the `/opsx:*` commands (or the `openspec` CLI) when available."
715
+ ].join("\n"),
716
+ speckit: [
717
+ "## Workflow",
718
+ "",
719
+ "This project uses [GitHub Spec Kit](https://github.com/github/spec-kit) (spec-driven development; guide: `specs/README.md`):",
720
+ "",
721
+ "- Principles every change must respect: `.specify/memory/constitution.md`.",
722
+ "- One folder per feature in `specs/NNN-feature-name/`: `spec.md` (what and why), then `plan.md` (how), then `tasks.md`.",
723
+ "- Use the `/speckit-*` commands when available."
724
+ ].join("\n")
725
+ };
726
+ function applyWorkflowToFile(file, workflow, text3) {
727
+ writeManagedBlock(file, workflow === "none" ? null : text3 ?? (workflow === "markode" ? null : WORKFLOW_TEXT[workflow]));
728
+ }
729
+ function writeManagedBlock(file, body, top = false, name = "workflow") {
730
+ const START2 = `<!-- markode:${name}:start -->`;
731
+ const END2 = `<!-- markode:${name}:end -->`;
732
+ assertInside(file);
733
+ const text3 = fs7.readFileSync(file, "utf8");
734
+ const s = text3.indexOf(START2);
735
+ const e = text3.indexOf(END2);
736
+ const existing = s >= 0 && e > s ? text3.slice(s, e + END2.length) : null;
737
+ if (body === null) {
738
+ if (existing) fs7.writeFileSync(file, text3.replace(new RegExp(`\\n*${escapeRe(existing)}\\n?`), "\n"));
739
+ return;
740
+ }
741
+ const section2 = `${START2}
742
+ ${body}
743
+ ${END2}`;
744
+ if (existing === section2) return;
745
+ if (existing) return fs7.writeFileSync(file, text3.replace(existing, () => section2));
746
+ const title = top ? /^# .*\n/m.exec(text3) : null;
747
+ if (title) return fs7.writeFileSync(file, text3.slice(0, title.index + title[0].length) + "\n" + section2 + "\n" + text3.slice(title.index + title[0].length));
748
+ fs7.writeFileSync(file, text3.replace(/\s*$/, "") + "\n\n" + section2 + "\n");
749
+ }
750
+ var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
751
+ var dirs = (abs) => {
752
+ try {
753
+ return fs7.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
754
+ } catch {
755
+ return [];
756
+ }
757
+ };
758
+ var read2 = (abs) => {
759
+ try {
760
+ return fs7.readFileSync(abs, "utf8");
761
+ } catch {
762
+ return null;
763
+ }
764
+ };
765
+ var progress = (text3) => ({ done: text3?.match(/^[ \t]*[-*+] \[[xX]\]/gm)?.length ?? 0, total: text3?.match(/^[ \t]*[-*+] \[[ xX]\]/gm)?.length ?? 0 });
766
+ var heading = (text3) => text3?.match(/^#\s+(.+)$/m)?.[1].trim() ?? null;
767
+ var countSpecs = (abs) => {
768
+ try {
769
+ return fs7.readdirSync(abs, { recursive: true }).filter((f) => String(f).endsWith("spec.md")).length;
770
+ } catch {
771
+ return 0;
772
+ }
773
+ };
774
+ function summarizeSdd(root2) {
775
+ const out = {};
776
+ const os5 = path7.join(root2, "openspec");
777
+ if (fs7.existsSync(os5)) {
778
+ const rel = (...p3) => toPosix(path7.join("openspec", ...p3));
779
+ out.openspec = {
780
+ changes: dirs(path7.join(os5, "changes")).filter((id) => id !== "archive").map((id) => {
781
+ const dir = path7.join(os5, "changes", id);
782
+ const proposal = read2(path7.join(dir, "proposal.md"));
783
+ const tasks = read2(path7.join(dir, "tasks.md"));
784
+ return {
785
+ id,
786
+ // "Proposal: Add login" → "Add login"
787
+ title: heading(proposal)?.replace(/^(change\s+)?proposal:\s*/i, "") ?? id,
788
+ path: rel("changes", id, proposal !== null ? "proposal.md" : "tasks.md"),
789
+ artifacts: { proposal: proposal !== null, design: fs7.existsSync(path7.join(dir, "design.md")), tasks: tasks !== null, specs: countSpecs(path7.join(dir, "specs")) },
790
+ tasks: progress(tasks)
791
+ };
792
+ }),
793
+ specs: dirs(path7.join(os5, "specs")).filter((id) => fs7.existsSync(path7.join(os5, "specs", id, "spec.md"))).map((id) => ({ id, path: rel("specs", id, "spec.md") })),
794
+ archived: dirs(path7.join(os5, "changes", "archive")).reverse().slice(0, 12).map((id) => {
795
+ const file = ["proposal.md", "tasks.md"].find((f) => fs7.existsSync(path7.join(os5, "changes", "archive", id, f)));
796
+ return { id, path: file ? rel("changes", "archive", id, file) : "" };
797
+ })
798
+ };
799
+ }
800
+ if (fs7.existsSync(path7.join(root2, ".specify"))) {
801
+ const constitution = read2(path7.join(root2, ".specify", "memory", "constitution.md"));
802
+ out.speckit = {
803
+ // The template is full of [PLACEHOLDERS] until /speckit-constitution fills it.
804
+ constitution: constitution === null ? null : { path: ".specify/memory/constitution.md", filled: !/\[[A-Z_0-9]+\]/.test(constitution) },
805
+ features: dirs(path7.join(root2, "specs")).map((id) => {
806
+ const dir = path7.join(root2, "specs", id);
807
+ const spec = read2(path7.join(dir, "spec.md"));
808
+ const tasks = read2(path7.join(dir, "tasks.md"));
809
+ return {
810
+ id,
811
+ // "Feature Specification: Login" → "Login"
812
+ title: heading(spec)?.replace(/^feature specification:\s*/i, "") ?? id,
813
+ path: toPosix(path7.join("specs", id, spec !== null ? "spec.md" : "plan.md")),
814
+ artifacts: { spec: spec !== null, plan: fs7.existsSync(path7.join(dir, "plan.md")), tasks: tasks !== null },
815
+ tasks: progress(tasks)
816
+ };
817
+ })
818
+ };
819
+ }
820
+ return out;
821
+ }
822
+ function sddPrefixes(summary) {
823
+ return [...summary.openspec ? ["openspec/"] : [], ...summary.speckit ? ["specs/", ".specify/"] : []];
824
+ }
825
+
826
+ // src/core/skills.ts
827
+ import fs8 from "fs";
828
+ import os3 from "os";
829
+ import path8 from "path";
830
+ import YAML2 from "yaml";
831
+ var SOURCE_DIRS = {
832
+ claude: [".claude", "skills"],
833
+ // Shared folder read by Codex, Copilot and Gemini CLI, and where `npx skills` installs.
834
+ agents: [".agents", "skills"],
835
+ codex: [".codex", "skills"],
836
+ gemini: [".gemini", "skills"],
837
+ copilot: [".copilot", "skills"],
838
+ cursor: [".cursor", "skills"]
839
+ };
840
+ var PROJECT_TARGETS = { claude: ".claude/skills", agents: ".agents/skills" };
841
+ var READS = { claude: ["claude"], agents: ["agents", "codex", "gemini", "copilot", "cursor"] };
842
+ var sourceDir = (source) => path8.join(os3.homedir(), ...SOURCE_DIRS[source]);
843
+ var display = (p3) => p3.startsWith(os3.homedir() + path8.sep) ? "~" + p3.slice(os3.homedir().length) : p3;
844
+ function skillSources() {
845
+ return Object.keys(SOURCE_DIRS).map((id) => ({ id, path: display(sourceDir(id)), exists: fs8.existsSync(sourceDir(id)) }));
846
+ }
847
+ var tagsFile = () => path8.join(GLOBAL_DIR, "skills.json");
848
+ function readTags() {
849
+ try {
850
+ return JSON.parse(fs8.readFileSync(tagsFile(), "utf8")).tags ?? {};
851
+ } catch {
852
+ return {};
853
+ }
854
+ }
855
+ function setSkillTags(name, tags) {
856
+ const all = readTags();
857
+ const clean = [...new Set(tags.map((t) => t.trim().toLowerCase()).filter(Boolean))];
858
+ if (clean.length) all[name] = clean;
859
+ else delete all[name];
860
+ fs8.mkdirSync(GLOBAL_DIR, { recursive: true });
861
+ fs8.writeFileSync(tagsFile(), JSON.stringify({ tags: all }, null, 2) + "\n");
862
+ return clean;
863
+ }
864
+ function findSkills(dir, source, managed, depth = 0) {
865
+ let entries2;
866
+ try {
867
+ entries2 = fs8.readdirSync(dir, { withFileTypes: true });
868
+ } catch {
869
+ return [];
870
+ }
871
+ return entries2.flatMap((e) => {
872
+ const abs = path8.join(dir, e.name);
873
+ let real;
874
+ try {
875
+ if (!fs8.statSync(abs).isDirectory()) return [];
876
+ real = fs8.realpathSync(abs);
877
+ } catch {
878
+ return [];
879
+ }
880
+ if (fs8.existsSync(path8.join(abs, "SKILL.md"))) return [{ dir: abs, real, source, managed }];
881
+ return depth < 2 ? findSkills(abs, source, managed || e.name === "synced" || e.name.startsWith("."), depth + 1) : [];
882
+ });
883
+ }
884
+ function frontmatter(text3) {
885
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text3);
886
+ if (!m) return null;
887
+ try {
888
+ const data = YAML2.parse(m[1], { logLevel: "error" });
889
+ return data && typeof data === "object" ? data : null;
890
+ } catch {
891
+ return null;
892
+ }
893
+ }
894
+ function listFiles(dir) {
895
+ try {
896
+ return fs8.readdirSync(dir, { recursive: true, withFileTypes: true }).map((e) => ({ e, rel: path8.relative(dir, path8.join(e.parentPath ?? e.path ?? dir, e.name)).split(path8.sep).join("/") })).filter(({ e, rel }) => e.isFile() && rel !== "SKILL.md").map(({ rel }) => rel).sort().slice(0, 100);
897
+ } catch {
898
+ return [];
899
+ }
900
+ }
901
+ function listSkills(root2) {
902
+ const byReal = /* @__PURE__ */ new Map();
903
+ for (const source of Object.keys(SOURCE_DIRS)) {
904
+ for (const f of findSkills(sourceDir(source), source, false)) byReal.set(f.real, [...byReal.get(f.real) ?? [], f]);
905
+ }
906
+ const userTags = readTags();
907
+ const used = projectTargets(root2);
908
+ const skills = [];
909
+ for (const [real, found] of byReal) {
910
+ const text3 = fs8.readFileSync(path8.join(real, "SKILL.md"), "utf8");
911
+ const fm = frontmatter(text3);
912
+ const folder = path8.basename(found[0].dir);
913
+ const name = typeof fm?.name === "string" ? fm.name : folder;
914
+ const description = typeof fm?.description === "string" ? fm.description.trim() : "";
915
+ const issues = [];
916
+ if (!fm) issues.push("frontmatter");
917
+ else {
918
+ if (typeof fm.name !== "string" || !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(fm.name) || fm.name.length > 64) issues.push("name");
919
+ else if (fm.name !== folder) issues.push("name-dir");
920
+ if (!description) issues.push("description");
921
+ else if (description.length > 1024) issues.push("description-long");
922
+ }
923
+ const installed = Object.keys(PROJECT_TARGETS).filter((t) => fs8.existsSync(path8.join(root2, PROJECT_TARGETS[t], folder, "SKILL.md")));
924
+ const declared = fm?.metadata?.tags;
925
+ const metaTags = Array.isArray(declared) ? declared.map(String) : typeof declared === "string" ? declared.split(",") : [];
926
+ skills.push({
927
+ key: real,
928
+ name,
929
+ folder,
930
+ description,
931
+ locations: found.map((f) => ({ source: f.source, path: display(f.dir) })),
932
+ managed: found.every((f) => f.managed),
933
+ files: listFiles(real),
934
+ tags: [.../* @__PURE__ */ new Set([...userTags[name] ?? [], ...metaTags.map((t) => t.trim().toLowerCase()).filter(Boolean)])],
935
+ issues,
936
+ installed,
937
+ status: Object.fromEntries(
938
+ used.map((t) => {
939
+ const global = found.some((f) => READS[t].includes(f.source));
940
+ const local = installed.includes(t);
941
+ return [t, global && local ? "both" : global ? "global" : local ? "project" : "missing"];
942
+ })
943
+ ),
944
+ differs: installed.filter((t) => {
945
+ try {
946
+ return fs8.readFileSync(path8.join(root2, PROJECT_TARGETS[t], folder, "SKILL.md"), "utf8") !== text3;
947
+ } catch {
948
+ return false;
949
+ }
950
+ })
951
+ });
952
+ }
953
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
954
+ }
955
+ function getSkill(root2, key) {
956
+ const skill = typeof key === "string" ? listSkills(root2).find((s) => s.key === key) : void 0;
957
+ if (!skill) throw Object.assign(new Error("Skill not found"), { code: "ENOENT" });
958
+ return skill;
959
+ }
960
+ function projectTargets(root2) {
961
+ const has2 = (p3) => fs8.existsSync(path8.join(root2, p3));
962
+ const out = [];
963
+ if (has2(".claude") || has2("CLAUDE.md")) out.push("claude");
964
+ if (has2(".agents") || has2("AGENTS.md") || has2(".codex") || has2(".gemini") || has2(".github/skills")) out.push("agents");
965
+ return out.length ? out : ["claude"];
966
+ }
967
+ function installSkill(root2, skill, targets, overwrite) {
968
+ const dests = targets.map((t) => path8.join(root2, PROJECT_TARGETS[t], skill.folder));
969
+ const existing = dests.filter((d) => fs8.existsSync(d));
970
+ if (existing.length && !overwrite) return { conflict: existing.map((d) => path8.relative(root2, d).split(path8.sep).join("/")) };
971
+ for (const dest of dests) {
972
+ assertInside(dest, root2);
973
+ fs8.rmSync(dest, { recursive: true, force: true });
974
+ fs8.mkdirSync(path8.dirname(dest), { recursive: true });
975
+ fs8.cpSync(skill.key, dest, { recursive: true, dereference: true });
976
+ }
977
+ return { ok: true };
978
+ }
979
+
980
+ // src/core/method.ts
981
+ var SKIP2 = /* @__PURE__ */ new Set(["docs", "node_modules", "dist", "build", "coverage", "tmp", "vendor", "target"]);
982
+ var MANIFESTS = ["package.json", "pyproject.toml", "go.mod", "Cargo.toml", "pom.xml", "build.gradle", "build.gradle.kts", "composer.json", "Gemfile", "deno.json", ".git"];
983
+ var isDir = (abs) => !!fs9.statSync(abs, { throwIfNoEntry: false })?.isDirectory();
984
+ var subdirs = (abs) => {
985
+ try {
986
+ return fs9.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SKIP2.has(e.name)).map((e) => e.name);
987
+ } catch {
988
+ return [];
989
+ }
990
+ };
991
+ var isProject = (abs) => MANIFESTS.some((m) => fs9.existsSync(path9.join(abs, m)));
992
+ function detectAreas(root2) {
993
+ const globs = [];
994
+ try {
995
+ const ws = YAML3.parse(fs9.readFileSync(path9.join(root2, "pnpm-workspace.yaml"), "utf8"), { logLevel: "error" });
996
+ if (Array.isArray(ws?.packages)) globs.push(...ws.packages);
997
+ } catch {
998
+ }
999
+ try {
1000
+ const ws = JSON.parse(fs9.readFileSync(path9.join(root2, "package.json"), "utf8")).workspaces;
1001
+ globs.push(...Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : []);
1002
+ } catch {
1003
+ }
1004
+ const found = new Set(findSubprojects(scanTree(root2)));
1005
+ for (const glob of globs) {
1006
+ const g = String(glob).replace(/^\.\//, "").replace(/\/+$/, "");
1007
+ if (g.startsWith("!")) continue;
1008
+ const base = g.replace(/\/\*\*?$/, "");
1009
+ if (base.includes("*")) continue;
1010
+ if (base === g) found.add(g);
1011
+ else for (const name of subdirs(path9.join(root2, base))) found.add(`${base}/${name}`);
1012
+ }
1013
+ if (!isProject(root2)) {
1014
+ for (const top of subdirs(root2)) {
1015
+ if (isProject(path9.join(root2, top))) found.add(top);
1016
+ else for (const name of subdirs(path9.join(root2, top))) if (isProject(path9.join(root2, top, name))) found.add(`${top}/${name}`);
1017
+ }
1018
+ }
1019
+ let unsure = false;
1020
+ if (!globs.length && isProject(root2)) {
1021
+ const tops = subdirs(root2).filter((top) => isProject(path9.join(root2, top)));
1022
+ if (tops.length >= 2 && tops.some((t) => !found.has(t))) tops.forEach((t) => found.add(t)), unsure = true;
1023
+ }
1024
+ const dirs2 = [...found].filter((d) => d && !d.split("/").some((part) => part.startsWith(".") || SKIP2.has(part)) && isDir(path9.join(root2, d)));
1025
+ return { dirs: dirs2.filter((d) => !dirs2.some((o) => o.startsWith(d + "/"))).sort(), unsure };
1026
+ }
1027
+ function areaDescription(root2, dir) {
1028
+ try {
1029
+ return String(JSON.parse(fs9.readFileSync(path9.join(root2, dir, "package.json"), "utf8")).description ?? "").trim();
1030
+ } catch {
1031
+ return "";
1032
+ }
1033
+ }
1034
+ var KNOWN_PREFIXES = {
1035
+ apps: "APP",
1036
+ app: "APP",
1037
+ services: "SVC",
1038
+ service: "SVC",
1039
+ packages: "PKG",
1040
+ libs: "LIB",
1041
+ lib: "LIB",
1042
+ infra: "INF",
1043
+ infrastructure: "INF",
1044
+ proto: "PRO",
1045
+ backend: "BE",
1046
+ frontend: "FE",
1047
+ web: "WEB",
1048
+ api: "API",
1049
+ mobile: "MOB",
1050
+ tools: "TLS",
1051
+ scripts: "SCR"
1052
+ };
1053
+ function suggestPrefix(dir, used) {
1054
+ dir = path9.posix.basename(dir);
1055
+ const base = KNOWN_PREFIXES[dir.toLowerCase()] ?? (dir.replace(/[^a-z0-9]/gi, "").slice(0, 3).toUpperCase() || "AREA");
1056
+ let prefix = base;
1057
+ for (let i = 2; used.has(prefix) || prefix === "CORE"; i++) prefix = `${base}${i}`;
1058
+ used.add(prefix);
1059
+ return prefix;
1060
+ }
1061
+ var rootPrefix = (areas) => areas.length ? "CORE" : "T";
1062
+ function scaffoldMethod(root2, mainDoc, areas) {
1063
+ const created = [];
1064
+ const name = path9.basename(root2);
1065
+ copyTree(path9.join(TEMPLATES_DIR, "method", "root"), root2, { date: today(), name, prefix: rootPrefix(areas), area: areas.length ? "core" : name }, created, root2);
1066
+ for (const a of areas) {
1067
+ const vars = { date: today(), name, prefix: a.prefix, area: a.dir, up: "../".repeat(a.dir.split("/").length) };
1068
+ const dir = path9.join(root2, a.dir);
1069
+ copyTree(path9.join(TEMPLATES_DIR, "method", "area"), dir, vars, created, root2);
1070
+ let doc = contentDoc(dir);
1071
+ if (!doc) {
1072
+ doc = path9.join(dir, mainDoc);
1073
+ assertInside(doc);
1074
+ fs9.writeFileSync(doc, fill(fs9.readFileSync(path9.join(TEMPLATES_DIR, "method", "area-main.md"), "utf8"), vars));
1075
+ created.push(path9.posix.join(a.dir, mainDoc));
1076
+ }
1077
+ if (claudeUsed(root2, mainDoc) && linkAgentsDoc(dir)) created.push(path9.posix.join(a.dir, "CLAUDE.md"));
1078
+ writeManagedBlock(doc, areaBlock(a, vars.up, mainDoc), true);
1079
+ }
1080
+ return created;
1081
+ }
1082
+ var claudeUsed = (root2, mainDoc) => mainDoc === "CLAUDE.md" || fs9.existsSync(path9.join(root2, "CLAUDE.md"));
1083
+ function areaBlock(a, up, mainDoc) {
1084
+ const guidance = a.profile && kindGuidance(a.profile);
1085
+ return [
1086
+ `- Tasks: one file per task in \`docs/tasks/\`, prefix \`${a.prefix}\`; the status is in each file's frontmatter`,
1087
+ ...guidance ? [`- ${guidance}`] : [],
1088
+ `- Decisions of this project: \`docs/ARCHITECTURE.md\` (created with the first one) \xB7 shared by several projects: \`${up}docs/architecture/\``,
1089
+ `- Overview of the repository and the other projects: \`${up}${mainDoc}\``
1090
+ ].join("\n");
1091
+ }
1092
+ function methodSection(areas, root2, ctx = {}) {
1093
+ const taskDirs = ["docs/tasks", ...areas.map((a) => `${a.dir}/docs/tasks`)].join(" ");
1094
+ const map = areas.length ? [
1095
+ "",
1096
+ "### Projects",
1097
+ "",
1098
+ "Each project has its own `CLAUDE.md`/`AGENTS.md` and `docs/` (architecture, tasks, proposals). This file and the root `docs/` hold the overview and what is shared by several projects.",
1099
+ "",
1100
+ "| Project | Kind | What it is | Docs | Task prefix |",
1101
+ "|---|---|---|---|---|",
1102
+ ...areas.map((a) => `| \`${a.dir}/\` | ${KIND_LABEL[a.profile?.kind ?? "app"]} | ${root2 && areaDescription(root2, a.dir).replace(/\|/g, "\\|") || "<!-- one line -->"} | \`${a.dir}/docs/\` | ${a.prefix} |`),
1103
+ `| \`docs/\` | | Vision, shared architecture, phases and tasks across projects | \`docs/\` | ${rootPrefix(areas)} |`,
1104
+ ...ctx.contracts ? ["", contractsGuidance(ctx.contracts)] : []
1105
+ ] : [];
1106
+ const guidance = !areas.length && ctx.profile && kindGuidance(ctx.profile);
1107
+ const about = guidance ? ["", "### This project", "", guidance] : [];
1108
+ const existing = ctx.existing?.length ? ["", "### Existing docs", "", "These are the official docs: update them instead of creating new ones in their place.", "", ...ctx.existing.map((d) => `- \`${d}\``)] : [];
1109
+ return [
1110
+ "## Workflow (markode method)",
1111
+ "",
1112
+ "Docs are created when there is something real to write (where each thing goes and templates: skill `markode-method`): vision `docs/vision.md` \xB7 architecture `docs/architecture/` \xB7 phases `docs/phases/` \xB7 tasks `docs/tasks/`.",
1113
+ ...about,
1114
+ ...map,
1115
+ ...existing,
1116
+ "",
1117
+ "### Session start",
1118
+ "",
1119
+ "1. `/markode sync`: apply proposals the user answered while no session was open, and fix what is out of date.",
1120
+ `2. Find tasks in progress: \`grep -rl "^status: in-progress" --include=*.md ${taskDirs}\`.`,
1121
+ '3. Open the one with the most recent `updated`, read its "Next step" and tell the user in one line where we left off.',
1122
+ "4. If there are none, ask the user what to work on.",
1123
+ "",
1124
+ "When the user asks what is pending or where things stand: `/markode-status`.",
1125
+ "",
1126
+ "### During the session",
1127
+ "",
1128
+ `- Read only what the task needs: its file, ${areas.length ? "the `CLAUDE.md`/`AGENTS.md` and `docs/` of its project, " : ""}and the one file in \`docs/architecture/\` that applies.`,
1129
+ '- Tick acceptance criteria as they are met and keep "Next step" current.',
1130
+ "- Before replacing a decision, propose it with the reasons; then update it in its one place.",
1131
+ "",
1132
+ "### Tasks",
1133
+ "",
1134
+ "- The user defines tasks: propose them, but create them only when asked (`/markode-new-task`).",
1135
+ `- One task = one file in ${areas.length ? "`<project>/docs/tasks/`; tasks across projects in `docs/tasks/`" : "`docs/tasks/`"}, prefix \`${rootPrefix(areas)}\`${areas.length ? " at the root" : ""}. Its status lives only in its frontmatter; each phase lists its tasks.`,
1136
+ "- At the end of a session: `/markode-close-session` (it ends with `/markode sync`).",
1137
+ "",
1138
+ "### Documentation without redundancy",
1139
+ "",
1140
+ "- Write only what the code can't tell: decisions and why, hidden conventions, the state of the work.",
1141
+ "- Each fact lives in one place; everything else links to it.",
1142
+ areas.length ? "- A decision affecting one project goes in `<project>/docs/ARCHITECTURE.md`; several projects, in `docs/architecture/`." : "- Decisions go in `docs/architecture/`, one file per topic.",
1143
+ "- Main docs hold orientation, rules and commands only: never decisions or task lists."
1144
+ ].join("\n");
1145
+ }
1146
+ function installMethodSkills(root2) {
1147
+ const src = path9.join(TEMPLATES_DIR, "method", "skills");
1148
+ for (const target of projectTargets(root2))
1149
+ for (const name of fs9.readdirSync(src)) {
1150
+ const dest = path9.join(root2, PROJECT_TARGETS[target], name);
1151
+ assertInside(dest);
1152
+ fs9.cpSync(path9.join(src, name), dest, { recursive: true });
1153
+ }
1154
+ }
1155
+ function setupMethod(root2, mainDoc, monorepo) {
1156
+ const used = /* @__PURE__ */ new Set();
1157
+ const areas = monorepo === "single" ? [] : detectAreas(root2).dirs.map((dir) => ({ dir, prefix: suggestPrefix(dir, used) }));
1158
+ for (const a of areas) a.profile = detectProfile(path9.join(root2, a.dir), guessKind(path9.join(root2, a.dir)).kind);
1159
+ const contracts = looksLikeMicroservices(root2, areas.length) ? findContracts(root2) : null;
1160
+ const profile = areas.length ? void 0 : detectProfile(root2, guessKind(root2).kind);
1161
+ const existing = existingDocs(root2);
1162
+ const created = scaffoldMethod(root2, mainDoc, areas);
1163
+ installMethodSkills(root2);
1164
+ applyWorkflowToFile(path9.join(root2, mainDoc), "markode", methodSection(areas, root2, { profile, contracts, existing }));
1165
+ return { created, areas };
1166
+ }
1167
+
1168
+ // src/core/check.ts
1169
+ var TASK_LINE = /^\s*[-*+] \[([ xX])\] .*?\[[^\]]*\]\(([^)\s]+)\)/;
1170
+ var LINK = /!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
1171
+ function checkDocs(root2) {
1172
+ const docs = summarize(root2);
1173
+ const status = new Map(docs.map((d) => [d.path, d.status ?? ""]));
1174
+ const read3 = (rel) => fs10.readFileSync(path10.join(root2, rel), "utf8");
1175
+ const phases = [];
1176
+ for (const phase of docs.filter((d) => d.type === "phase")) {
1177
+ for (const line of read3(phase.path).split("\n")) {
1178
+ const m = TASK_LINE.exec(line);
1179
+ if (!m) continue;
1180
+ const task = path10.posix.normalize(path10.posix.join(path10.posix.dirname(phase.path), m[2].split("#")[0]));
1181
+ const s = status.get(task);
1182
+ if (s === void 0) continue;
1183
+ const ticked = m[1] !== " ";
1184
+ if (ticked !== (s === "done")) phases.push({ phase: phase.path, task, ticked, status: s });
1185
+ }
1186
+ }
1187
+ const links = [];
1188
+ for (const doc of docs) {
1189
+ let fenced = false;
1190
+ read3(doc.path).split("\n").forEach((line, i) => {
1191
+ if (/^\s*(```|~~~)/.test(line)) fenced = !fenced;
1192
+ if (fenced) return;
1193
+ for (const m of line.matchAll(LINK)) {
1194
+ const target = m[1];
1195
+ if (/^[a-z][\w+.-]*:|^#|^\/|[<{]/i.test(target)) continue;
1196
+ const file = decodeURIComponent(target.split(/[#?]/)[0]);
1197
+ if (file && !fs10.existsSync(path10.join(root2, path10.dirname(doc.path), file))) links.push({ file: doc.path, line: i + 1, target });
1198
+ }
1199
+ });
1200
+ }
1201
+ return { proposals: docs.filter((d) => d.type === "proposal" && d.status === "answered").map((d) => d.path), phases, projects: projectsTable(root2), links };
1202
+ }
1203
+ function projectsTable(root2) {
1204
+ const doc = mainDocOf(root2);
1205
+ if (!doc) return null;
1206
+ const text3 = fs10.readFileSync(path10.join(root2, doc), "utf8");
1207
+ const section2 = /^### Projects\n([\s\S]*?)(?=^#{1,3} |<!-- markode:workflow:end -->)/m.exec(text3)?.[1];
1208
+ if (!section2) return null;
1209
+ const listed = [...section2.matchAll(/^\|\s*`([^`]+?)\/?`\s*\|/gm)].map((m) => m[1]).filter((d) => d !== "docs");
1210
+ const found = detectAreas(root2).dirs;
1211
+ return { missing: found.filter((d) => !listed.includes(d)), gone: listed.filter((d) => !fs10.existsSync(path10.join(root2, d))) };
1212
+ }
1213
+
1214
+ // src/core/defaults.ts
1215
+ import fs15 from "fs";
1216
+ import path15 from "path";
1217
+
1218
+ // src/core/features.ts
1219
+ import fs14 from "fs";
1220
+ import path14 from "path";
1221
+
1222
+ // src/core/hooks.ts
1223
+ import fs11 from "fs";
1224
+ import path11 from "path";
1225
+ var EVENTS = {
1226
+ SessionStart: "changes --start",
1227
+ UserPromptSubmit: "changes --hook UserPromptSubmit",
1228
+ PostToolUse: "changes --hook PostToolUse"
1229
+ };
1230
+ var LOCAL = "settings.local.json";
1231
+ var SHARED = "settings.json";
1232
+ var settingsFile2 = (root2, name = LOCAL) => path11.join(root2, ".claude", name);
1233
+ function cli() {
1234
+ const dirs2 = (process.env.PATH ?? "").split(path11.delimiter).filter(Boolean);
1235
+ if (dirs2.some((d) => ["markode", "markode.cmd"].some((f) => fs11.existsSync(path11.join(d, f))))) return "markode";
1236
+ const name = JSON.parse(fs11.readFileSync(path11.join(PKG_ROOT, "package.json"), "utf8")).name;
1237
+ return `npx -y --prefer-offline ${name}`;
1238
+ }
1239
+ var isOurs = (entry) => Array.isArray(entry?.hooks) && entry.hooks.some((h) => typeof h?.command === "string" && / changes --(start|hook)\b/.test(h.command));
1240
+ function readSettings2(root2, name = LOCAL) {
1241
+ const file = settingsFile2(root2, name);
1242
+ if (!fs11.existsSync(file)) return {};
1243
+ try {
1244
+ return JSON.parse(fs11.readFileSync(file, "utf8"));
1245
+ } catch {
1246
+ return null;
1247
+ }
1248
+ }
1249
+ function hasClaudeHooks(root2) {
1250
+ return [LOCAL, SHARED].some((name) => {
1251
+ const hooks = readSettings2(root2, name)?.hooks ?? {};
1252
+ return Object.keys(EVENTS).every((e) => (hooks[e] ?? []).some(isOurs));
1253
+ });
1254
+ }
1255
+ function writeHooks(root2, name, add) {
1256
+ const settings = readSettings2(root2, name);
1257
+ if (!settings) return false;
1258
+ if (!add && !fs11.existsSync(settingsFile2(root2, name))) return true;
1259
+ const base = cli();
1260
+ settings.hooks ??= {};
1261
+ for (const [event, args2] of Object.entries(EVENTS)) {
1262
+ const list = (settings.hooks[event] ?? []).filter((e) => !isOurs(e));
1263
+ if (add) list.push({ hooks: [{ type: "command", command: `${base} ${args2}` }] });
1264
+ if (list.length) settings.hooks[event] = list;
1265
+ else delete settings.hooks[event];
1266
+ }
1267
+ if (!Object.keys(settings.hooks).length) delete settings.hooks;
1268
+ assertInside(settingsFile2(root2, name));
1269
+ if (!Object.keys(settings).length) return fs11.rmSync(settingsFile2(root2, name), { force: true }), true;
1270
+ fs11.mkdirSync(path11.dirname(settingsFile2(root2, name)), { recursive: true });
1271
+ fs11.writeFileSync(settingsFile2(root2, name), JSON.stringify(settings, null, 2) + "\n");
1272
+ return true;
1273
+ }
1274
+ function installClaudeHooks(root2) {
1275
+ if (!writeHooks(root2, LOCAL, true)) return false;
1276
+ writeHooks(root2, SHARED, false);
1277
+ return true;
1278
+ }
1279
+ function removeClaudeHooks(root2) {
1280
+ writeHooks(root2, LOCAL, false);
1281
+ writeHooks(root2, SHARED, false);
1282
+ }
1283
+
1284
+ // src/core/langdetect.ts
1285
+ var STOPWORDS = {
1286
+ en: ["the", "and", "is", "are", "of", "to", "in", "for", "with", "this", "that", "it", "on", "be", "as", "by", "not", "or", "from", "when", "each", "use", "should", "will", "can", "all", "has", "have", "which", "only"],
1287
+ es: ["el", "la", "los", "las", "de", "del", "que", "y", "en", "un", "una", "para", "con", "por", "es", "son", "se", "como", "al", "lo", "m\xE1s", "pero", "su", "sus", "este", "esta", "cuando", "cada", "debe", "tambi\xE9n"],
1288
+ fr: ["le", "la", "les", "de", "des", "du", "et", "est", "un", "une", "pour", "dans", "avec", "que", "qui", "en", "sur", "par", "pas", "au", "aux", "ce", "cette", "sont", "ou", "chaque", "doit", "quand", "aussi", "\xEAtre"],
1289
+ pt: ["o", "os", "as", "de", "do", "da", "dos", "das", "que", "e", "em", "um", "uma", "para", "com", "por", "n\xE3o", "\xE9", "s\xE3o", "se", "como", "ao", "mais", "mas", "seu", "sua", "cada", "deve", "quando", "tamb\xE9m"],
1290
+ de: ["der", "die", "das", "und", "ist", "sind", "ein", "eine", "zu", "den", "dem", "mit", "f\xFCr", "von", "auf", "nicht", "auch", "es", "im", "bei", "wird", "werden", "oder", "wenn", "jede", "jeder", "soll", "muss", "kann", "nur"],
1291
+ it: ["il", "lo", "la", "gli", "le", "di", "del", "della", "che", "e", "\xE8", "un", "una", "per", "con", "non", "sono", "si", "come", "al", "alla", "pi\xF9", "ma", "suo", "questo", "questa", "quando", "ogni", "deve", "anche"]
1292
+ };
1293
+ var SETS = Object.fromEntries(Object.entries(STOPWORDS).map(([l, w]) => [l, new Set(w)]));
1294
+ function cut(text3, open2, close) {
1295
+ let out = "";
1296
+ let i = 0;
1297
+ for (; ; ) {
1298
+ const s = text3.indexOf(open2, i);
1299
+ const e = s < 0 ? -1 : text3.indexOf(close, s + open2.length);
1300
+ if (e < 0) return out + text3.slice(i);
1301
+ out += text3.slice(i, s) + " ";
1302
+ i = e + close.length;
1303
+ }
1304
+ }
1305
+ var SAMPLE = 2e5;
1306
+ function prose(md) {
1307
+ return cut(cut(md.slice(0, SAMPLE).replace(/^---\r?\n[\s\S]*?\r?\n---/, " "), "```", "```"), "<!--", "-->").replace(/`[^`]*`/g, " ").replace(/\]\([^)]*\)/g, " ").replace(/https?:\/\/\S+/g, " ").replace(/[|#>*_\-[\]()]/g, " ");
1308
+ }
1309
+ function detectLang(md) {
1310
+ const words = prose(md).toLowerCase().match(/[\p{L}]+/gu) ?? [];
1311
+ if (words.length < 25) return null;
1312
+ const scores = Object.keys(SETS).map((lang) => ({ lang, hits: words.reduce((n, w) => n + (SETS[lang].has(w) ? 1 : 0), 0) }));
1313
+ scores.sort((a, b) => b.hits - a.hits);
1314
+ const [best, second] = scores;
1315
+ if (best.hits < 6 || best.hits < second.hits * 1.5) return null;
1316
+ return best.lang;
1317
+ }
1318
+
1319
+ // src/core/proposals.ts
1320
+ import fs12 from "fs";
1321
+ import path12 from "path";
1322
+
1323
+ // src/core/git.ts
1324
+ import { execFileSync } from "child_process";
1325
+ import os4 from "os";
1326
+ function runGit(root2, args2) {
1327
+ return execFileSync("git", ["-C", root2, "-c", "core.fsmonitor=false", "-c", "core.quotepath=off", ...args2], {
1328
+ cwd: os4.homedir(),
1329
+ encoding: "utf8",
1330
+ stdio: ["ignore", "pipe", "ignore"],
1331
+ maxBuffer: 32 * 1024 * 1024
1332
+ });
1333
+ }
1334
+ function git(root2, args2) {
1335
+ try {
1336
+ return runGit(root2, args2);
1337
+ } catch (err) {
1338
+ return typeof err?.stdout === "string" && err.stdout ? err.stdout : null;
1339
+ }
1340
+ }
1341
+ var isGitRepo = (root2) => git(root2, ["rev-parse", "--is-inside-work-tree"])?.trim() === "true";
1342
+ function gitStatuses(root2) {
1343
+ const prefix = git(root2, ["rev-parse", "--show-prefix"])?.trim() ?? "";
1344
+ const out = git(root2, ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", "."]);
1345
+ const result = {};
1346
+ if (!out) return result;
1347
+ const parts = out.split("\0");
1348
+ for (let i = 0; i < parts.length; i++) {
1349
+ const entry = parts[i];
1350
+ if (entry.length < 4) continue;
1351
+ const code = entry.slice(0, 2);
1352
+ let file = entry.slice(3);
1353
+ if (code[0] === "R" || code[0] === "C") i++;
1354
+ if (!file.toLowerCase().endsWith(".md")) continue;
1355
+ if (prefix && file.startsWith(prefix)) file = file.slice(prefix.length);
1356
+ if (file.startsWith(".markode/")) continue;
1357
+ result[file] = code === "??" ? "U" : code.includes("D") ? "D" : code.includes("A") ? "A" : "M";
1358
+ }
1359
+ return result;
1360
+ }
1361
+ function gitDiff(root2, rel) {
1362
+ const status = gitStatuses(root2)[rel];
1363
+ const diff = ["diff", "--no-ext-diff", "--no-textconv"];
1364
+ if (status === "U") return git(root2, [...diff, "--no-index", "--", "/dev/null", rel]) ?? "";
1365
+ const hasHead = git(root2, ["rev-parse", "--verify", "HEAD"]) !== null;
1366
+ return git(root2, hasHead ? [...diff, "HEAD", "--", rel] : [...diff, "--cached", "--", rel]) ?? "";
1367
+ }
1368
+
1369
+ // src/core/proposals.ts
1370
+ var SKILL = "markode-proposals";
1371
+ var skillDirs = (root2) => Object.values(PROJECT_TARGETS).map((t) => path12.join(root2, t, SKILL));
1372
+ function proposalsState(root2) {
1373
+ return { active: skillDirs(root2).some((d) => fs12.existsSync(path12.join(d, "SKILL.md"))), layout: readConfig(root2)?.proposals ?? "general" };
1374
+ }
1375
+ var WHERE = {
1376
+ general: "`docs/proposals/NNN-short-name.md` (NNN: the next number in that folder, counting `applied/`)",
1377
+ structured: "`docs/proposals/<topic>/NNN-short-name.md`, one folder per topic (e.g. `sidebar`, `auth`; NNN: the next number in it, counting its `applied/`); in a monorepo, a proposal for a single project goes in `<project>/docs/proposals/<topic>/` (e.g. `apps/web/docs/proposals/auth/`). Reuse an existing topic folder; ask the user before creating a new one"
1378
+ };
1379
+ function section(layout) {
1380
+ return [
1381
+ "## Proposals",
1382
+ "",
1383
+ "When an answer would be long, has options or a design to validate, or the user asks for one, write a",
1384
+ "temporary proposal the user answers in markode instead of a chat answer (skill: `markode-proposals`).",
1385
+ "",
1386
+ `- Where: ${WHERE[layout]}.`,
1387
+ "- After writing one, run `markode wait <path>` in the background: it returns when the user answers in markode.",
1388
+ "- The user ticks options and comments (`<!-- comment by \u2026 -->`). When it is answered, reply to the comments,",
1389
+ " update every doc that depends on the decision (tasks, phases, architecture\u2026), then archive the proposal:",
1390
+ " `status: applied`, moved to the `applied/` folder next to it. Never read or search archived proposals",
1391
+ " unless the user asks about a past one."
1392
+ ].join("\n");
1393
+ }
1394
+ function mainDocs(root2, mainDoc) {
1395
+ const doc = mainDoc ?? mainDocOf(root2);
1396
+ return doc ? [path12.join(root2, doc)] : [];
1397
+ }
1398
+ function activateProposals(root2, layout, mainDoc) {
1399
+ const text3 = fill(fs12.readFileSync(path12.join(TEMPLATES_DIR, "proposals", "SKILL.md"), "utf8"), { where: WHERE[layout] });
1400
+ for (const target of projectTargets(root2)) {
1401
+ const file = path12.join(root2, PROJECT_TARGETS[target], SKILL, "SKILL.md");
1402
+ assertInside(file);
1403
+ fs12.mkdirSync(path12.dirname(file), { recursive: true });
1404
+ if (!fs12.existsSync(file) || fs12.readFileSync(file, "utf8") !== text3) fs12.writeFileSync(file, text3);
1405
+ }
1406
+ fs12.mkdirSync(path12.join(root2, "docs", "proposals"), { recursive: true });
1407
+ for (const file of mainDocs(root2, mainDoc)) writeManagedBlock(file, section(layout), false, "proposals");
1408
+ writeConfig(root2, { ...readConfig(root2) ?? { mainDoc, langs: [] }, proposals: layout });
1409
+ }
1410
+ function deactivateProposals(root2, mainDoc) {
1411
+ for (const dir of skillDirs(root2)) if (fs12.existsSync(dir)) assertInside(dir), fs12.rmSync(dir, { recursive: true, force: true });
1412
+ for (const file of mainDocs(root2, mainDoc)) writeManagedBlock(file, null, false, "proposals");
1413
+ }
1414
+ function authorName(root2) {
1415
+ try {
1416
+ return runGit(root2, ["config", "user.name"]).trim() || null;
1417
+ } catch {
1418
+ return null;
1419
+ }
1420
+ }
1421
+
1422
+ // src/core/rules.ts
1423
+ import fs13 from "fs";
1424
+ import path13 from "path";
1425
+ import YAML4 from "yaml";
1426
+ var rulesDir = () => path13.join(GLOBAL_DIR, "rules");
1427
+ var START = "<!-- markode:rules:start -->";
1428
+ var END = "<!-- markode:rules:end -->";
1429
+ var slugify = (s) => s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1430
+ function seedRules() {
1431
+ if (fs13.existsSync(rulesDir())) return;
1432
+ fs13.mkdirSync(rulesDir(), { recursive: true });
1433
+ const src = path13.join(TEMPLATES_DIR, "rules");
1434
+ for (const f of fs13.readdirSync(src)) fs13.copyFileSync(path13.join(src, f), path13.join(rulesDir(), f));
1435
+ }
1436
+ function parseRule(id, text3) {
1437
+ const m = text3.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
1438
+ const meta = m ? YAML4.parse(m[1], { logLevel: "error" }) ?? {} : {};
1439
+ return { id, title: meta.title ?? id, body: (m ? m[2] : text3).trim() };
1440
+ }
1441
+ function listRules() {
1442
+ seedRules();
1443
+ return fs13.readdirSync(rulesDir()).filter((f) => f.endsWith(".md")).map((f) => parseRule(f.slice(0, -3), fs13.readFileSync(path13.join(rulesDir(), f), "utf8"))).sort((a, b) => a.title.localeCompare(b.title));
1444
+ }
1445
+ function saveRule(rule) {
1446
+ seedRules();
1447
+ const id = rule.id || slugify(rule.title) || `rule-${Date.now()}`;
1448
+ if (!/^[\w-]+$/.test(id)) throw new Error(`Invalid rule id: ${id}`);
1449
+ const text3 = `---
1450
+ ${YAML4.stringify({ title: rule.title }).trim()}
1451
+ ---
1452
+ ${rule.body.trim()}
1453
+ `;
1454
+ fs13.writeFileSync(path13.join(rulesDir(), `${id}.md`), text3);
1455
+ return { id, title: rule.title, body: rule.body.trim() };
1456
+ }
1457
+ function deleteRule(id) {
1458
+ fs13.rmSync(path13.join(rulesDir(), `${path13.basename(id)}.md`), { force: true });
1459
+ }
1460
+ function appliedRuleIds(docText) {
1461
+ const section2 = extractSection(docText);
1462
+ if (!section2) return [];
1463
+ return [...section2.matchAll(/<!-- rule:([\w-]+) -->/g)].map((m) => m[1]);
1464
+ }
1465
+ function extractSection(text3) {
1466
+ const s = text3.indexOf(START);
1467
+ const e = text3.indexOf(END);
1468
+ return s >= 0 && e > s ? text3.slice(s, e + END.length) : null;
1469
+ }
1470
+ function writtenRules(docText) {
1471
+ const section2 = extractSection(docText) ?? "";
1472
+ const out = {};
1473
+ for (const m of section2.matchAll(/<!-- rule:([\w-]+) -->\n([\s\S]*?)(?=\n+<!-- (?:rule:|markode:rules:end))/g)) out[m[1]] = { id: m[1], title: m[1], body: m[2].trim() };
1474
+ return out;
1475
+ }
1476
+ function sourceLangOf(docText) {
1477
+ const m = /<!-- markode:source-lang:(\w+) -->/.exec(extractSection(docText) ?? "");
1478
+ return m && isLang(m[1]) ? m[1] : DEFAULT_SOURCE;
1479
+ }
1480
+ function projectSourceLang(root2, mainDoc) {
1481
+ const doc = mainDoc ?? mainDocOf(root2);
1482
+ if (!doc) return DEFAULT_SOURCE;
1483
+ try {
1484
+ return sourceLangOf(fs13.readFileSync(path13.join(root2, doc), "utf8"));
1485
+ } catch {
1486
+ return DEFAULT_SOURCE;
1487
+ }
1488
+ }
1489
+ function renderSection(rules, sourceLang) {
1490
+ const body = rules.map((r) => `<!-- rule:${r.id} -->
1491
+ ${r.body}`).join("\n\n");
1492
+ return [
1493
+ START,
1494
+ "## Working rules",
1495
+ "",
1496
+ "This project uses markode: follow its `markode` skill (`.claude/skills/markode/` or `.agents/skills/markode/`) when editing any `.md` file.",
1497
+ "",
1498
+ `<!-- markode:source-lang:${sourceLang} -->`,
1499
+ `- Source language of every \`.md\` file: **${LANG_NAMES[sourceLang]}** (\`${sourceLang}\`).`,
1500
+ ...body ? ["", body] : [],
1501
+ END
1502
+ ].join("\n");
1503
+ }
1504
+ function applyRules(docText, rules, sourceLang = sourceLangOf(docText)) {
1505
+ const section2 = renderSection(rules, sourceLang);
1506
+ const existing = extractSection(docText);
1507
+ if (existing) return docText.replace(existing, () => section2);
1508
+ return docText.replace(/\s*$/, "") + "\n\n" + section2 + "\n";
1509
+ }
1510
+ function applyRulesToFile(file, ids, sourceLang) {
1511
+ const text3 = fs13.existsSync(file) ? fs13.readFileSync(file, "utf8") : "";
1512
+ const all = listRules();
1513
+ const written = writtenRules(text3);
1514
+ const rules = (ids ?? appliedRuleIds(text3)).map((id) => all.find((r) => r.id === id) ?? written[id]).filter((r) => !!r);
1515
+ assertInside(file);
1516
+ fs13.writeFileSync(file, applyRules(text3, rules, sourceLang ?? sourceLangOf(text3)));
1517
+ }
1518
+
1519
+ // src/core/features.ts
1520
+ var FEATURES = ["agent", "rules", "proposals", "live", "translations", "dashboard", "skills", "vscode"];
1521
+ var REQUIRES = { rules: "agent", proposals: "agent", live: "agent", translations: "agent" };
1522
+ var FEATURE_INFO = {
1523
+ agent: { label: "AI agent", hint: "teaches your agent (Claude Code, Codex, Cursor\u2026) to work with markode docs; you can comment on any text for it" },
1524
+ rules: { label: "Working rules", hint: "rules the agent follows (ask before committing, protect secrets\u2026), chosen in the Rules tab and written in CLAUDE.md/AGENTS.md" },
1525
+ proposals: { label: "Proposals", hint: "the agent answers big questions with a doc you answer by ticking options and commenting" },
1526
+ live: { label: "Live changes", hint: "Claude Code hears about your edits and comments while it works (personal hooks in .claude/settings.local.json)" },
1527
+ translations: { label: "Translations", hint: "read and edit the docs in other languages; the agent translates them" },
1528
+ dashboard: { label: "Dashboard and tasks", hint: "a panel with the state of docs, phases and tasks; with the AI agent it also creates the task structure" },
1529
+ skills: { label: "Skills", hint: "a tab to browse, edit and add to the project the skills installed for your agents" },
1530
+ vscode: { label: "Hide in VS Code", hint: "hides markode's .markode/ folder (translations, state) from the VS Code explorer" }
1531
+ };
1532
+ var AGENT_SET = ["agent", "rules", "proposals", "live", "translations", "skills", "vscode"];
1533
+ var PROFILE_FEATURES = {
1534
+ viewer: [],
1535
+ agent: AGENT_SET,
1536
+ full: [...AGENT_SET, "dashboard"]
1537
+ };
1538
+ var PROFILE_INFO = {
1539
+ viewer: { label: "Viewer only", hint: "see and edit the .md files; nothing is written to the project" },
1540
+ agent: { label: "Viewer + AI agent", hint: "adds the agent skill, rules, proposals, comments, live changes and translations" },
1541
+ full: { label: "Complete", hint: "all of the above plus the dashboard and a docs structure for vision, phases and tasks" }
1542
+ };
1543
+ var isProfile = (v) => typeof v === "string" && v in PROFILE_FEATURES;
1544
+ var isFeature = (v) => FEATURES.includes(v);
1545
+ function withRequired(list) {
1546
+ const out = /* @__PURE__ */ new Set();
1547
+ for (const f of list) {
1548
+ const need = REQUIRES[f];
1549
+ if (need) out.add(need);
1550
+ out.add(f);
1551
+ }
1552
+ return FEATURES.filter((f) => out.has(f));
1553
+ }
1554
+ var UI_ONLY = ["rules", "translations", "dashboard", "skills"];
1555
+ var SKILL2 = "markode";
1556
+ var skillDirs2 = (root2) => Object.values(PROJECT_TARGETS).map((t) => path14.join(root2, t, SKILL2));
1557
+ var hasAgentSkill = (root2) => skillDirs2(root2).some((d) => fs14.existsSync(path14.join(d, "SKILL.md")));
1558
+ function featureState(root2) {
1559
+ const flags = readConfig(root2)?.features ?? {};
1560
+ const agent = hasAgentSkill(root2);
1561
+ return {
1562
+ agent,
1563
+ rules: agent && flags.rules !== false,
1564
+ proposals: agent && proposalsState(root2).active,
1565
+ live: agent && hasClaudeHooks(root2),
1566
+ translations: agent && flags.translations !== false,
1567
+ dashboard: flags.dashboard !== false,
1568
+ skills: flags.skills !== false,
1569
+ vscode: isHiddenInVsCode(root2)
1570
+ };
1571
+ }
1572
+ function setFlags(root2, patch) {
1573
+ const c = readConfig(root2) ?? { mainDoc: mainDocOf(root2), langs: [] };
1574
+ const { savedRules, ...flags } = patch;
1575
+ writeConfig(root2, { ...c, features: { ...c.features, ...flags }, ..."savedRules" in patch ? { savedRules } : {} });
1576
+ }
1577
+ function detectSourceLang(root2, fallback) {
1578
+ const votes = /* @__PURE__ */ new Map();
1579
+ for (const f of flattenFiles(scanTree(root2)).slice(0, 200)) {
1580
+ try {
1581
+ const lang = detectLang(fs14.readFileSync(path14.join(root2, f.path), "utf8"));
1582
+ if (lang) votes.set(lang, (votes.get(lang) ?? 0) + 1);
1583
+ } catch {
1584
+ }
1585
+ }
1586
+ return [...votes].sort((a, b) => b[1] - a[1])[0]?.[0] ?? fallback;
1587
+ }
1588
+ function ensureMainDoc(root2, preferred) {
1589
+ const existing = mainDocOf(root2);
1590
+ if (existing) return existing;
1591
+ const doc = preferred ?? (projectTargets(root2).includes("claude") ? "CLAUDE.md" : "AGENTS.md");
1592
+ createMainDoc(root2, doc);
1593
+ return doc;
1594
+ }
1595
+ function installAgentSkill(root2) {
1596
+ const src = path14.join(TEMPLATES_DIR, "skill", "SKILL.md");
1597
+ for (const target of projectTargets(root2)) {
1598
+ const dir = path14.join(root2, PROJECT_TARGETS[target], SKILL2);
1599
+ fs14.mkdirSync(dir, { recursive: true });
1600
+ const dest = path14.join(dir, "SKILL.md");
1601
+ assertInside(dest);
1602
+ const text3 = fs14.readFileSync(src, "utf8");
1603
+ if (!fs14.existsSync(dest) || fs14.readFileSync(dest, "utf8") !== text3) fs14.writeFileSync(dest, text3);
1604
+ }
1605
+ }
1606
+ function allMainDocs(root2) {
1607
+ return ["", ...findSubprojects(scanTree(root2))].map((d) => contentDoc(path14.join(root2, d))).filter((f) => !!f);
1608
+ }
1609
+ var METHOD_SKILLS = () => fs14.readdirSync(path14.join(TEMPLATES_DIR, "method", "skills"));
1610
+ function setFeature(root2, feature, on, opts = {}) {
1611
+ const state = featureState(root2);
1612
+ const changed = {};
1613
+ if (on) {
1614
+ const need = REQUIRES[feature];
1615
+ if (need && !state[need]) Object.assign(changed, setFeature(root2, need, true, opts));
1616
+ } else if (feature === "agent") {
1617
+ for (const f of FEATURES) if (REQUIRES[f] === "agent" && state[f]) Object.assign(changed, setFeature(root2, f, false, opts));
1618
+ }
1619
+ if (state[feature] === on && !UI_ONLY.includes(feature)) return changed;
1620
+ apply(root2, feature, on, opts);
1621
+ changed[feature] = on;
1622
+ return changed;
1623
+ }
1624
+ function apply(root2, feature, on, opts) {
1625
+ switch (feature) {
1626
+ case "agent": {
1627
+ if (on) {
1628
+ const lang = readConfig(root2)?.savedSourceLang ?? detectSourceLang(root2, opts.sourceLang ?? "en");
1629
+ const doc = path14.join(root2, ensureMainDoc(root2, opts.mainDoc));
1630
+ installAgentSkill(root2);
1631
+ const text3 = fs14.readFileSync(doc, "utf8");
1632
+ const declared = /<!-- markode:source-lang:\w+ -->/.test(text3);
1633
+ applyRulesToFile(doc, appliedRuleIds(text3), declared ? sourceLangOf(text3) : lang);
1634
+ } else {
1635
+ const doc = mainDocOf(root2);
1636
+ const c = readConfig(root2);
1637
+ if (doc && c) writeConfig(root2, { ...c, savedSourceLang: sourceLangOf(fs14.readFileSync(path14.join(root2, doc), "utf8")) });
1638
+ for (const d of skillDirs2(root2)) if (fs14.existsSync(d)) assertInside(d), fs14.rmSync(d, { recursive: true, force: true });
1639
+ for (const doc2 of allMainDocs(root2)) writeManagedBlock(doc2, null, false, "rules");
1640
+ }
1641
+ return;
1642
+ }
1643
+ case "rules": {
1644
+ const doc = mainDocOf(root2);
1645
+ const file = doc && path14.join(root2, doc);
1646
+ if (on) {
1647
+ const saved = readConfig(root2)?.savedRules ?? [];
1648
+ const current = file ? appliedRuleIds(fs14.readFileSync(file, "utf8")) : [];
1649
+ const ids = current.length ? current : saved.length ? saved : opts.rules ?? [];
1650
+ if (file && ids.length) applyRulesToFile(file, ids);
1651
+ setFlags(root2, { rules: true, savedRules: void 0 });
1652
+ } else {
1653
+ const ids = file ? appliedRuleIds(fs14.readFileSync(file, "utf8")) : [];
1654
+ if (file) applyRulesToFile(file, []);
1655
+ setFlags(root2, { rules: false, savedRules: ids.length ? ids : void 0 });
1656
+ }
1657
+ return;
1658
+ }
1659
+ case "proposals":
1660
+ if (on) activateProposals(root2, readConfig(root2)?.proposals ?? (findSubprojects(scanTree(root2)).length ? "structured" : "general"), mainDocOf(root2));
1661
+ else deactivateProposals(root2, mainDocOf(root2));
1662
+ return;
1663
+ case "live":
1664
+ if (on) installClaudeHooks(root2);
1665
+ else removeClaudeHooks(root2);
1666
+ return;
1667
+ case "translations":
1668
+ setFlags(root2, { translations: on });
1669
+ return;
1670
+ case "dashboard": {
1671
+ setFlags(root2, { dashboard: on });
1672
+ if (on && hasAgentSkill(root2) && !detectWorkflow(root2) && (readConfig(root2)?.workflow ?? "none") === "none") {
1673
+ const mainDoc = ensureMainDoc(root2, opts.mainDoc);
1674
+ setupMethod(root2, mainDoc, opts.monorepo ?? "per-project");
1675
+ const c = readConfig(root2);
1676
+ writeConfig(root2, { ...c, workflow: "markode" });
1677
+ }
1678
+ if (!on && readConfig(root2)?.workflow === "markode") {
1679
+ for (const name of METHOD_SKILLS())
1680
+ for (const t of Object.values(PROJECT_TARGETS)) {
1681
+ const dir = path14.join(root2, t, name);
1682
+ if (fs14.existsSync(dir)) assertInside(dir), fs14.rmSync(dir, { recursive: true, force: true });
1683
+ }
1684
+ for (const doc of allMainDocs(root2)) writeManagedBlock(doc, null, false, "workflow");
1685
+ const c = readConfig(root2);
1686
+ writeConfig(root2, { ...c, workflow: "none" });
1687
+ }
1688
+ return;
1689
+ }
1690
+ case "skills":
1691
+ setFlags(root2, { skills: on });
1692
+ return;
1693
+ case "vscode":
1694
+ if (on) hideInVsCode(root2);
1695
+ else showInVsCode(root2);
1696
+ return;
1697
+ }
1698
+ }
1699
+ function refreshInstalled(root2) {
1700
+ if (!hasAgentSkill(root2)) return;
1701
+ installAgentSkill(root2);
1702
+ const proposals = proposalsState(root2);
1703
+ if (proposals.active) activateProposals(root2, proposals.layout, mainDocOf(root2));
1704
+ if (readConfig(root2)?.workflow === "markode") installMethodSkills(root2);
1705
+ }
1706
+ function ignoreStateDir(root2) {
1707
+ const file = path14.join(stateDir(root2), ".gitignore");
1708
+ if (fs14.existsSync(file)) return;
1709
+ assertInside(file);
1710
+ fs14.mkdirSync(stateDir(root2), { recursive: true });
1711
+ fs14.writeFileSync(file, "# markode's personal state: never committed\n*\n");
1712
+ }
1713
+
1714
+ // src/core/defaults.ts
1715
+ var BASE_DEFAULTS = {
1716
+ mode: "ask",
1717
+ features: PROFILE_FEATURES.agent,
1718
+ mainDoc: "CLAUDE.md",
1719
+ monorepo: "per-project",
1720
+ sourceLang: "en",
1721
+ rules: [],
1722
+ rulesInSubprojects: false
1723
+ };
1724
+ var defaultsFile = () => path15.join(GLOBAL_DIR, "defaults.json");
1725
+ function readDefaults() {
1726
+ try {
1727
+ const raw = JSON.parse(fs15.readFileSync(defaultsFile(), "utf8"));
1728
+ const saved = { ...BASE_DEFAULTS, ...raw };
1729
+ if (!isLang(saved.sourceLang)) saved.sourceLang = BASE_DEFAULTS.sourceLang;
1730
+ if (!MAIN_DOCS.includes(saved.mainDoc)) saved.mainDoc = BASE_DEFAULTS.mainDoc;
1731
+ if (saved.monorepo !== "single") saved.monorepo = "per-project";
1732
+ saved.features = Array.isArray(raw.features) ? raw.features.filter(isFeature) : PROFILE_FEATURES.agent.filter((f) => (f !== "live" || raw.claudeHooks !== false) && (f !== "vscode" || raw.hideInVsCode !== false)).concat(raw.workflow === "markode" ? ["dashboard"] : []);
1733
+ return saved;
1734
+ } catch {
1735
+ return null;
1736
+ }
1737
+ }
1738
+ function writeDefaults(defaults) {
1739
+ fs15.mkdirSync(GLOBAL_DIR, { recursive: true });
1740
+ fs15.writeFileSync(defaultsFile(), JSON.stringify(defaults, null, 2) + "\n");
1741
+ }
1742
+
1743
+ // src/core/i18n.ts
1744
+ import crypto2 from "crypto";
1745
+ import fs16 from "fs";
1746
+ import path16 from "path";
1747
+ var stateFile = (root2) => path16.join(stateDir(root2), "state.json");
1748
+ var translationPath = (root2, lang, rel) => path16.join(stateDir(root2), "i18n", lang, rel);
1749
+ var hash = (text3) => crypto2.createHash("sha1").update(text3).digest("hex").slice(0, 12);
1750
+ var readText2 = (abs) => {
1751
+ try {
1752
+ return fs16.readFileSync(abs, "utf8");
1753
+ } catch {
1754
+ return null;
1755
+ }
1756
+ };
1757
+ function readState(root2) {
1758
+ try {
1759
+ const raw = JSON.parse(fs16.readFileSync(stateFile(root2), "utf8"));
1760
+ if (!raw.translations) return { translations: raw, keepLanguage: {} };
1761
+ return { translations: raw.translations ?? {}, keepLanguage: raw.keepLanguage ?? {} };
1762
+ } catch {
1763
+ return { translations: {}, keepLanguage: {} };
1764
+ }
1765
+ }
1766
+ function writeState(root2, state) {
1767
+ fs16.mkdirSync(stateDir(root2), { recursive: true });
1768
+ assertInside(stateFile(root2));
1769
+ fs16.writeFileSync(stateFile(root2), JSON.stringify(state, null, 2) + "\n");
1770
+ }
1771
+ function fileStatus(root2, lang, rel) {
1772
+ const source = readText2(path16.join(root2, rel));
1773
+ const translation = readText2(translationPath(root2, lang, rel));
1774
+ if (translation === null || source === null) return "missing";
1775
+ const recorded = readState(root2).translations[lang]?.[rel];
1776
+ if (!recorded) return "outdated";
1777
+ const sourceChanged = hash(source) !== recorded.source;
1778
+ const translationChanged = hash(translation) !== recorded.translation;
1779
+ if (sourceChanged && translationChanged) return "conflict";
1780
+ if (sourceChanged) return "outdated";
1781
+ if (translationChanged) return "pending-sync";
1782
+ return "synced";
1783
+ }
1784
+ function allStatuses(root2, lang) {
1785
+ return flattenFiles(scanTree(root2)).map((f) => ({
1786
+ path: f.path,
1787
+ lang,
1788
+ status: fileStatus(root2, lang, f.path),
1789
+ translation: path16.relative(root2, translationPath(root2, lang, f.path))
1790
+ }));
1791
+ }
1792
+ function foreignFiles(root2, sourceLang) {
1793
+ const { keepLanguage: keepLanguage2 } = readState(root2);
1794
+ const out = [];
1795
+ for (const f of flattenFiles(scanTree(root2))) {
1796
+ const text3 = readText2(path16.join(root2, f.path));
1797
+ const detected = text3 ? detectLang(text3) : null;
1798
+ if (detected && detected !== sourceLang && keepLanguage2[f.path] !== detected) out.push({ path: f.path, detected });
1799
+ }
1800
+ return out;
1801
+ }
1802
+ function syncReport(root2) {
1803
+ const config = readConfig(root2);
1804
+ const sourceLang = projectSourceLang(root2, config?.mainDoc);
1805
+ const langs = (config?.langs ?? []).filter((l) => l !== sourceLang);
1806
+ return {
1807
+ sourceLang,
1808
+ langs,
1809
+ /** Language names by code: translate into these (codes added by hand have no built-in name). */
1810
+ names: Object.fromEntries([sourceLang, ...langs].map((l) => [l, langName(l, config)])),
1811
+ foreign: foreignFiles(root2, sourceLang),
1812
+ files: langs.flatMap((l) => allStatuses(root2, l))
1813
+ };
1814
+ }
1815
+ function markSynced(root2, lang, rel) {
1816
+ const source = readText2(path16.join(root2, rel));
1817
+ const translation = readText2(translationPath(root2, lang, rel));
1818
+ if (source === null) throw new Error(`Source not found: ${rel}`);
1819
+ if (translation === null) throw new Error(`Translation not found: ${path16.relative(root2, translationPath(root2, lang, rel))}`);
1820
+ const state = readState(root2);
1821
+ state.translations[lang] ??= {};
1822
+ state.translations[lang][rel] = { source: hash(source), translation: hash(translation) };
1823
+ writeState(root2, state);
1824
+ }
1825
+ function keepLanguage(root2, rel) {
1826
+ const text3 = readText2(path16.join(root2, rel));
1827
+ const detected = text3 ? detectLang(text3) : null;
1828
+ if (!detected) throw new Error(`Could not detect the language of ${rel}`);
1829
+ const state = readState(root2);
1830
+ state.keepLanguage[rel] = detected;
1831
+ writeState(root2, state);
1832
+ return detected;
1833
+ }
1834
+
1835
+ // src/core/wait.ts
1836
+ import fs17 from "fs";
1837
+ import path17 from "path";
1838
+ import YAML5 from "yaml";
1839
+ var waitsDir = (root2) => path17.join(stateDir(root2), "waits");
1840
+ var pidFile = (root2, rel) => path17.join(waitsDir(root2), `${encodeURIComponent(rel)}.pid`);
1841
+ var frontmatter2 = (text3) => {
1842
+ try {
1843
+ return YAML5.parse(/^---\r?\n([\s\S]*?)\r?\n---/.exec(text3)?.[1] ?? "", { logLevel: "error" }) ?? {};
1844
+ } catch {
1845
+ return {};
1846
+ }
1847
+ };
1848
+ var isAlive = (pid) => {
1849
+ try {
1850
+ process.kill(pid, 0);
1851
+ return true;
1852
+ } catch {
1853
+ return false;
1854
+ }
1855
+ };
1856
+ function waitingOn(root2) {
1857
+ let names2 = [];
1858
+ try {
1859
+ names2 = fs17.readdirSync(waitsDir(root2)).filter((f) => f.endsWith(".pid"));
1860
+ } catch {
1861
+ }
1862
+ return names2.flatMap((f) => {
1863
+ const pid = Number(fs17.readFileSync(path17.join(waitsDir(root2), f), "utf8"));
1864
+ return isAlive(pid) ? [decodeURIComponent(f.slice(0, -4))] : [];
1865
+ });
1866
+ }
1867
+ function waitForAnswer(root2, rel) {
1868
+ const abs = path17.join(root2, rel);
1869
+ fs17.mkdirSync(waitsDir(root2), { recursive: true });
1870
+ assertInside(pidFile(root2, rel));
1871
+ fs17.writeFileSync(pidFile(root2, rel), String(process.pid));
1872
+ const cleanup = () => fs17.rmSync(pidFile(root2, rel), { force: true });
1873
+ process.once("exit", cleanup);
1874
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.once(sig, () => process.exit(130));
1875
+ return new Promise((resolve) => {
1876
+ const poll = () => {
1877
+ let text3;
1878
+ try {
1879
+ text3 = fs17.readFileSync(abs, "utf8");
1880
+ } catch {
1881
+ return resolve("gone");
1882
+ }
1883
+ const fm = frontmatter2(text3);
1884
+ if (fm.type !== "proposal") return resolve("gone");
1885
+ if (fm.status === "answered") return resolve("answered");
1886
+ setTimeout(poll, 1500);
1887
+ };
1888
+ poll();
1889
+ });
1890
+ }
1891
+ var COMMENT = /^\s*<!-- (comment|reply) by (.+?), (\d{4}-\d{2}-\d{2})(?:, on "((?:[^"\\]|\\.)*)")?:\s?([\s\S]*?)\s*-->\s*$/;
1892
+ function answerSummary(root2, rel) {
1893
+ const text3 = fs17.readFileSync(path17.join(root2, rel), "utf8");
1894
+ const fm = frontmatter2(text3);
1895
+ const body = text3.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
1896
+ const lines = body.split("\n");
1897
+ const chosen = [];
1898
+ const unanswered = [];
1899
+ const comments = [];
1900
+ const after = [];
1901
+ let heading2 = "";
1902
+ let question = "";
1903
+ let group = null;
1904
+ let inAfter = false;
1905
+ let thread = [];
1906
+ const closeGroup = () => {
1907
+ if (group && !group.ticked) unanswered.push(group.label);
1908
+ group = null;
1909
+ };
1910
+ const closeThread = () => {
1911
+ const last = thread.at(-1);
1912
+ if (last && last.author !== "AI") comments.push(`${last.author}${thread[0].quote ? ` on "${thread[0].quote}"` : ""}: ${last.text}`);
1913
+ thread = [];
1914
+ };
1915
+ for (const line of lines) {
1916
+ const c = COMMENT.exec(line);
1917
+ if (c) {
1918
+ if (c[1] === "comment") closeThread();
1919
+ thread.push({ author: c[2], text: c[5].trim(), quote: c[4] ?? "" });
1920
+ continue;
1921
+ }
1922
+ if (line.trim()) closeThread();
1923
+ const h = /^#{1,6}\s+(.*)/.exec(line);
1924
+ if (h) {
1925
+ closeGroup();
1926
+ heading2 = h[1].trim();
1927
+ question = "";
1928
+ inAfter = /^after your answer/i.test(heading2);
1929
+ continue;
1930
+ }
1931
+ if (inAfter && line.trim()) after.push(line.trim().replace(/^([-*+]|\d+\.)\s+/, ""));
1932
+ const q = /^\s*\d+\.\s+(.*\?)\s*$/.exec(line) ?? /^(\S.*[:?])\s*$/.exec(line);
1933
+ if (q && !/^\s*[-*+] /.test(line)) {
1934
+ closeGroup();
1935
+ question = q[1].trim().replace(/:$/, "");
1936
+ }
1937
+ if (/^\s*<!--\s*choose:\s*one\s*-->/.test(line)) {
1938
+ closeGroup();
1939
+ group = { label: question || heading2, ticked: false };
1940
+ continue;
1941
+ }
1942
+ const task = /^\s*[-*+] \[([ xX])\]\s*(.*)/.exec(line);
1943
+ if (task) {
1944
+ if (task[1] !== " ") {
1945
+ chosen.push(`${question || heading2}: ${task[2].trim()}`);
1946
+ if (group) group.ticked = true;
1947
+ }
1948
+ } else if (group && line.trim() && !/^\s/.test(line)) closeGroup();
1949
+ }
1950
+ closeGroup();
1951
+ closeThread();
1952
+ const section2 = (title, items) => items.length ? [`${title}:`, ...items.map((i) => `- ${i}`), ""] : [];
1953
+ const affects = Array.isArray(fm.affects) ? fm.affects.map(String) : [];
1954
+ return [
1955
+ `Proposal ${rel} was answered.`,
1956
+ "",
1957
+ ...section2("Chosen", chosen),
1958
+ ...section2("Not answered", unanswered),
1959
+ ...section2("Comments waiting for your reply", comments),
1960
+ ...section2("Affects", affects),
1961
+ ...section2("After the answer, you planned to", after),
1962
+ 'Next: follow the markode-proposals skill ("When the user answers").'
1963
+ ].join("\n");
1964
+ }
1965
+
1966
+ // src/server/index.ts
1967
+ import fs18 from "fs";
1968
+ import http from "http";
1969
+ import path18 from "path";
1970
+ import { watch } from "chokidar";
1971
+ import open from "open";
1972
+ import pc from "picocolors";
1973
+ import { WebSocketServer } from "ws";
1974
+ var IMAGE_MIME = {
1975
+ ".png": "image/png",
1976
+ ".jpg": "image/jpeg",
1977
+ ".jpeg": "image/jpeg",
1978
+ ".gif": "image/gif",
1979
+ ".webp": "image/webp",
1980
+ ".avif": "image/avif",
1981
+ ".svg": "image/svg+xml",
1982
+ ".bmp": "image/bmp",
1983
+ ".ico": "image/x-icon"
1984
+ };
1985
+ var MIME = {
1986
+ ".html": "text/html; charset=utf-8",
1987
+ ".js": "text/javascript",
1988
+ ".css": "text/css",
1989
+ ".svg": "image/svg+xml",
1990
+ ".json": "application/json",
1991
+ ".woff2": "font/woff2"
1992
+ };
1993
+ var HttpError = class extends Error {
1994
+ constructor(status, message) {
1995
+ super(message);
1996
+ this.status = status;
1997
+ }
1998
+ status;
1999
+ };
2000
+ var MAX_BODY = 20 * 1024 * 1024;
2001
+ async function readBody(req) {
2002
+ let data = "";
2003
+ for await (const chunk of req) {
2004
+ data += chunk;
2005
+ if (data.length > MAX_BODY) throw new HttpError(413, "Request too large");
2006
+ }
2007
+ if (!data) return {};
2008
+ if (!req.headers["content-type"]?.startsWith("application/json")) throw new HttpError(415, "Expected application/json");
2009
+ return JSON.parse(data);
2010
+ }
2011
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
2012
+ function isLocalRequest(req) {
2013
+ const host = req.headers.host ?? "";
2014
+ try {
2015
+ if (!LOCAL_HOSTS.has(new URL(`http://${host}`).hostname)) return false;
2016
+ const site = req.headers["sec-fetch-site"];
2017
+ if (site && site !== "same-origin" && site !== "none") return false;
2018
+ return !req.headers.origin || new URL(req.headers.origin).host === host;
2019
+ } catch {
2020
+ return false;
2021
+ }
2022
+ }
2023
+ async function startServer({ root: root2, port, open: openBrowser }) {
2024
+ const config = () => readConfig(root2) ?? { mainDoc: null, langs: [] };
2025
+ const langs = () => featureState(root2).translations ? config().langs : [];
2026
+ const sourceLang = () => projectSourceLang(root2, readConfig(root2)?.mainDoc);
2027
+ const viewLang = (value) => isLangCode(value) && value !== sourceLang() && langs().includes(value) ? value : null;
2028
+ const mdPath = (rel) => {
2029
+ if (typeof rel !== "string" || !rel.toLowerCase().endsWith(".md")) throw new HttpError(400, "Expected a .md path");
2030
+ safeResolve(root2, rel);
2031
+ return rel;
2032
+ };
2033
+ const deletable = (rel) => {
2034
+ if (typeof rel !== "string" || !rel.trim()) throw new HttpError(400, "Expected a path");
2035
+ const abs = safeResolve(root2, rel);
2036
+ if (abs === root2) throw new HttpError(400, "Refusing to delete the project root");
2037
+ if (!fs18.existsSync(abs)) throw new HttpError(404, "Not found");
2038
+ return abs;
2039
+ };
2040
+ const translationFile = (lang, rel) => {
2041
+ const file = translationPath(root2, lang, rel);
2042
+ assertInside(file, root2);
2043
+ return file;
2044
+ };
2045
+ const routes = {
2046
+ "GET /api/info": () => {
2047
+ const c = config();
2048
+ return {
2049
+ name: path18.basename(root2),
2050
+ mainDoc: c.mainDoc ?? null,
2051
+ sourceLang: sourceLang(),
2052
+ langs: langs().filter((l) => l !== sourceLang()),
2053
+ langNames: Object.fromEntries(c.langs.map((l) => [l, langName(l, c)])),
2054
+ git: isGitRepo(root2),
2055
+ proposals: proposalsState(root2),
2056
+ author: authorName(root2),
2057
+ unlinked: c.skipLink ? [] : missingMainDocs(root2),
2058
+ features: featureState(root2)
2059
+ };
2060
+ },
2061
+ /** Turn a module on or off (with the ones it needs or that need it); returns every module's state. */
2062
+ "PUT /api/features": (_, body) => {
2063
+ if (!isFeature(body.feature)) throw new HttpError(400, `Expected one of ${FEATURES.join(", ")}`);
2064
+ setFeature(root2, body.feature, !!body.on, readDefaults() ?? BASE_DEFAULTS);
2065
+ return featureState(root2);
2066
+ },
2067
+ "PUT /api/config": (_, body) => {
2068
+ const c = config();
2069
+ if (Array.isArray(body.langs)) c.langs = body.langs.filter(isLangCode);
2070
+ if (body.langNames && typeof body.langNames === "object") {
2071
+ for (const [code, name] of Object.entries(body.langNames))
2072
+ if (isLangCode(code) && !isLang(code) && typeof name === "string" && name.trim()) c.langNames = { ...c.langNames, [code]: name.trim().slice(0, 40) };
2073
+ }
2074
+ if (isLang(body.sourceLang) && body.sourceLang !== sourceLang()) {
2075
+ if (!c.mainDoc) throw new HttpError(400, "No main document configured; run `markode init`");
2076
+ applyRulesToFile(path18.join(root2, c.mainDoc), void 0, body.sourceLang);
2077
+ }
2078
+ writeConfig(root2, c);
2079
+ return { ...c, langs: c.langs.filter((l) => l !== sourceLang()) };
2080
+ },
2081
+ /** Add the CLAUDE.md / AGENTS.md a folder lacks, pointing to the other; or stop offering it. */
2082
+ "POST /api/link": (_, body) => {
2083
+ if (body.skip) return writeConfig(root2, { ...config(), skipLink: true }), { created: [] };
2084
+ return { created: linkMainDocs(root2) };
2085
+ },
2086
+ "PUT /api/proposals": (_, body) => {
2087
+ if (body.active) activateProposals(root2, body.layout === "structured" ? "structured" : "general", config().mainDoc);
2088
+ else deactivateProposals(root2, config().mainDoc);
2089
+ return proposalsState(root2);
2090
+ },
2091
+ /** Translation files behind their source, in every enabled language: work for `/markode translate`. */
2092
+ "GET /api/translations": () => ({ pending: syncReport(root2).files.filter((f) => f.status !== "synced").length + foreignFiles(root2, sourceLang()).length }),
2093
+ /** Proposals an agent is waiting on (`markode wait`): answering them wakes it up. */
2094
+ "GET /api/waiting": () => ({ paths: waitingOn(root2) }),
2095
+ "GET /api/settings": () => readSettings(),
2096
+ "PUT /api/settings": (_, body) => writeSettings(body),
2097
+ "GET /api/tree": (url2) => {
2098
+ const l = viewLang(url2.searchParams.get("lang"));
2099
+ const sync2 = l ? Object.fromEntries(allStatuses(root2, l).map((e) => [e.path, e.status])) : {};
2100
+ const foreign = featureState(root2).translations ? Object.fromEntries(foreignFiles(root2, sourceLang()).map((f) => [f.path, f.detected])) : {};
2101
+ return { tree: scanTree(root2), git: gitStatuses(root2), sync: sync2, foreign };
2102
+ },
2103
+ "POST /api/keep-language": (_, body) => ({ lang: keepLanguage(root2, mdPath(body.path)) }),
2104
+ "GET /api/file": (url2) => {
2105
+ const rel = mdPath(url2.searchParams.get("path"));
2106
+ const l = viewLang(url2.searchParams.get("lang"));
2107
+ const source = fs18.readFileSync(safeResolve(root2, rel), "utf8");
2108
+ if (l) {
2109
+ const tPath = translationFile(l, rel);
2110
+ if (fs18.existsSync(tPath)) return { path: rel, content: fs18.readFileSync(tPath, "utf8"), target: "translation", status: fileStatus(root2, l, rel) };
2111
+ return { path: rel, content: source, target: "source", status: "missing" };
2112
+ }
2113
+ return { path: rel, content: source, target: "source", status: null };
2114
+ },
2115
+ "PUT /api/file": (_, body) => {
2116
+ const rel = mdPath(body.path);
2117
+ const l = viewLang(body.lang);
2118
+ const file = body.target === "translation" && l ? translationFile(l, rel) : safeResolve(root2, rel);
2119
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
2120
+ const content = String(body.content ?? "");
2121
+ if (!l && featureState(root2).agent) logSave(root2, rel, fs18.existsSync(file) ? fs18.readFileSync(file, "utf8") : "", content);
2122
+ fs18.writeFileSync(file, content);
2123
+ return { ok: true, status: l ? fileStatus(root2, l, rel) : null };
2124
+ },
2125
+ "POST /api/file": (_, body) => {
2126
+ const rel = mdPath(body.path);
2127
+ const file = safeResolve(root2, rel);
2128
+ if (fs18.existsSync(file)) throw new HttpError(409, "File already exists");
2129
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
2130
+ const title = path18.basename(rel, ".md").replace(/^\d+[-_]/, "").replace(/[-_]/g, " ");
2131
+ fs18.writeFileSync(file, `# ${title.charAt(0).toUpperCase() + title.slice(1)}
2132
+
2133
+ `);
2134
+ return { ok: true, path: rel };
2135
+ },
2136
+ // Only a mkdir: nothing else is touched, so editors that have the project open are unaffected.
2137
+ "POST /api/dir": (_, body) => {
2138
+ if (typeof body.path !== "string" || !body.path.trim()) throw new HttpError(400, "Expected a path");
2139
+ const dir = safeResolve(root2, body.path);
2140
+ if (dir === root2) throw new HttpError(400, "Expected a path");
2141
+ if (fs18.existsSync(dir)) throw new HttpError(409, "A file or folder with that name already exists");
2142
+ fs18.mkdirSync(dir, { recursive: true });
2143
+ return { ok: true, path: toPosix(path18.relative(root2, dir)) };
2144
+ },
2145
+ "GET /api/delete-preview": (url2) => {
2146
+ const abs = deletable(url2.searchParams.get("path"));
2147
+ if (!fs18.statSync(abs).isDirectory()) return { kind: "file", files: 1, md: 1 };
2148
+ const files = fs18.readdirSync(abs, { recursive: true, withFileTypes: true }).filter((e) => e.isFile());
2149
+ return { kind: "dir", files: files.length, md: files.filter((e) => e.name.toLowerCase().endsWith(".md")).length };
2150
+ },
2151
+ "DELETE /api/path": (url2) => {
2152
+ const rel = toPosix(path18.relative(root2, deletable(url2.searchParams.get("path"))));
2153
+ fs18.rmSync(safeResolve(root2, rel), { recursive: true });
2154
+ for (const l of config().langs) fs18.rmSync(translationFile(l, rel), { recursive: true, force: true });
2155
+ return { ok: true };
2156
+ },
2157
+ "POST /api/ignore": (_, body) => {
2158
+ if (typeof body.path !== "string" || /[\r\n]/.test(body.path)) throw new HttpError(400, "Expected a path");
2159
+ safeResolve(root2, body.path);
2160
+ setIgnored(root2, body.path, body.kind === "dir" ? "dir" : "file", !!body.ignored);
2161
+ return { ok: true };
2162
+ },
2163
+ "GET /api/diff": (url2) => ({ diff: gitDiff(root2, mdPath(url2.searchParams.get("path"))) }),
2164
+ "GET /api/dashboard": () => {
2165
+ const sdd = summarizeSdd(root2);
2166
+ const shown = sddPrefixes(sdd);
2167
+ return { docs: summarize(root2).filter((d) => !shown.some((p3) => d.path.startsWith(p3))), sdd };
2168
+ },
2169
+ "GET /api/rules": () => {
2170
+ const mainDoc = readConfig(root2)?.mainDoc;
2171
+ const text3 = mainDoc && fs18.existsSync(path18.join(root2, mainDoc)) ? fs18.readFileSync(path18.join(root2, mainDoc), "utf8") : "";
2172
+ return { rules: listRules(), applied: appliedRuleIds(text3), mainDoc: mainDoc ?? null };
2173
+ },
2174
+ "PUT /api/rules": (_, body) => saveRule(body),
2175
+ "DELETE /api/rules": (url2) => {
2176
+ deleteRule(url2.searchParams.get("id") ?? "");
2177
+ return { ok: true };
2178
+ },
2179
+ // Global skills: reads and writes are limited to SKILL.md of discovered skill folders.
2180
+ "GET /api/skills": () => ({ skills: listSkills(root2), sources: skillSources(), targets: projectTargets(root2) }),
2181
+ "PUT /api/skills/tags": (_, body) => {
2182
+ if (typeof body.name !== "string" || !Array.isArray(body.tags)) throw new HttpError(400, "Expected name and tags");
2183
+ return { tags: setSkillTags(body.name, body.tags.map(String)) };
2184
+ },
2185
+ "GET /api/skills/file": (url2) => {
2186
+ const skill = getSkill(root2, url2.searchParams.get("key"));
2187
+ return { path: skill.key, content: fs18.readFileSync(path18.join(skill.key, "SKILL.md"), "utf8"), target: "source", status: null };
2188
+ },
2189
+ "PUT /api/skills/file": (_, body) => {
2190
+ const skill = getSkill(root2, body.key);
2191
+ if (skill.managed) throw new HttpError(403, "This skill is managed by the app that installed it");
2192
+ fs18.writeFileSync(path18.join(skill.key, "SKILL.md"), String(body.content ?? ""));
2193
+ return { ok: true, status: null };
2194
+ },
2195
+ "POST /api/skills/install": (_, body) => {
2196
+ const targets = (Array.isArray(body.targets) ? body.targets : []).filter((t) => typeof t === "string" && t in PROJECT_TARGETS);
2197
+ if (!targets.length) throw new HttpError(400, "Expected at least one target");
2198
+ const result = installSkill(root2, getSkill(root2, body.key), targets, !!body.overwrite);
2199
+ if (result.conflict) throw new HttpError(409, result.conflict.join(", "));
2200
+ return result;
2201
+ },
2202
+ "POST /api/rules/apply": (_, body) => {
2203
+ const mainDoc = readConfig(root2)?.mainDoc;
2204
+ if (!mainDoc) throw new HttpError(400, "No main document configured; run `markode init`");
2205
+ applyRulesToFile(path18.join(root2, mainDoc), body.ids ?? []);
2206
+ return { ok: true };
2207
+ }
2208
+ };
2209
+ const server = http.createServer(async (req, res) => {
2210
+ res.setHeader("X-Frame-Options", "DENY");
2211
+ res.setHeader("X-Content-Type-Options", "nosniff");
2212
+ if (!isLocalRequest(req)) {
2213
+ res.writeHead(403).end("Forbidden");
2214
+ return;
2215
+ }
2216
+ const url2 = new URL(req.url ?? "/", "http://localhost");
2217
+ const route = routes[`${req.method} ${url2.pathname}`];
2218
+ if (route) {
2219
+ try {
2220
+ const result = await route(url2, req.method === "GET" || req.method === "DELETE" ? {} : await readBody(req));
2221
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(result));
2222
+ } catch (err) {
2223
+ const status = err instanceof HttpError ? err.status : err.code === "ENOENT" ? 404 : 500;
2224
+ res.writeHead(status, { "content-type": "application/json" }).end(JSON.stringify({ error: err.message }));
2225
+ }
2226
+ return;
2227
+ }
2228
+ if (req.method === "GET" && url2.pathname === "/api/asset") return serveAsset(url2.searchParams.get("path") ?? "", res);
2229
+ serveStatic(url2.pathname, res);
2230
+ });
2231
+ function serveAsset(rel, res) {
2232
+ let file;
2233
+ try {
2234
+ file = safeResolve(root2, rel);
2235
+ } catch {
2236
+ res.writeHead(404).end();
2237
+ return;
2238
+ }
2239
+ const type = IMAGE_MIME[path18.extname(file).toLowerCase()];
2240
+ if (!type || !fs18.existsSync(file) || !fs18.statSync(file).isFile()) {
2241
+ res.writeHead(404).end();
2242
+ return;
2243
+ }
2244
+ res.writeHead(200, { "content-type": type, "cache-control": "no-cache", "content-security-policy": "sandbox; frame-ancestors 'none'" });
2245
+ fs18.createReadStream(file).pipe(res);
2246
+ }
2247
+ const actualPort = await listen(server, port);
2248
+ const wss = new WebSocketServer({ server, path: "/ws", verifyClient: ({ req }) => isLocalRequest(req) });
2249
+ const broadcast = (msg) => {
2250
+ const data = JSON.stringify(msg);
2251
+ for (const client of wss.clients) client.send(data);
2252
+ };
2253
+ const skips = watchSkips(root2);
2254
+ watch(root2, {
2255
+ ignoreInitial: true,
2256
+ // Unreadable folders (e.g. caches owned by another user) are skipped instead of failing.
2257
+ ignorePermissionErrors: true,
2258
+ ignored: (p3, stats) => {
2259
+ const rel = toPosix(path18.relative(root2, p3));
2260
+ if (rel === ".markode") return false;
2261
+ if (rel.startsWith(".markode/")) return !rel.startsWith(".markode/i18n") && rel !== ".markode/state.json" && rel !== ".markode/config.json";
2262
+ return skips(rel, stats ? stats.isDirectory() : void 0);
2263
+ }
2264
+ }).on("error", (err) => console.warn(pc.yellow(` file watcher: ${err.message}`))).on("all", (event, file) => {
2265
+ const abs = path18.resolve(file);
2266
+ if (!abs.endsWith(".md") && !abs.endsWith(".json") && !abs.endsWith(".markodeignore") && event !== "addDir" && event !== "unlinkDir") return;
2267
+ let rel = toPosix(path18.relative(root2, abs));
2268
+ const i18n = toPosix(path18.relative(root2, path18.join(stateDir(root2), "i18n")));
2269
+ if (rel.startsWith(i18n + "/")) rel = rel.split("/").slice(3).join("/");
2270
+ broadcast({ type: "fs", event, path: rel });
2271
+ });
2272
+ const url = `http://localhost:${actualPort}`;
2273
+ console.log(`
2274
+ ${pc.bold("markode")} ${pc.dim("\u2192")} ${pc.cyan(url)} ${pc.dim(root2)}
2275
+ `);
2276
+ if (!fs18.existsSync(WEB_DIR)) console.log(pc.yellow(" Web UI not built: run `pnpm build`, or `pnpm dev` and open http://localhost:4320\n"));
2277
+ else if (openBrowser) open(url).catch(() => {
2278
+ });
2279
+ }
2280
+ function listen(server, port) {
2281
+ return new Promise((resolve, reject) => {
2282
+ server.once("error", (err) => {
2283
+ if (err.code === "EADDRINUSE") resolve(listen(server, port + 1));
2284
+ else reject(err);
2285
+ });
2286
+ server.listen(port, "127.0.0.1", () => resolve(port));
2287
+ });
2288
+ }
2289
+ function serveStatic(pathname, res) {
2290
+ let file = path18.join(WEB_DIR, path18.normalize(pathname).replace(/^(\.\.[/\\])+/, ""));
2291
+ if (!file.startsWith(WEB_DIR) || !fs18.existsSync(file) || fs18.statSync(file).isDirectory()) file = path18.join(WEB_DIR, "index.html");
2292
+ if (!fs18.existsSync(file)) {
2293
+ res.writeHead(404).end("Web UI not built. Run `pnpm build`.");
2294
+ return;
2295
+ }
2296
+ res.writeHead(200, { "content-type": MIME[path18.extname(file)] ?? "application/octet-stream", "content-security-policy": "frame-ancestors 'none'" });
2297
+ fs18.createReadStream(file).pipe(res);
2298
+ }
2299
+
2300
+ // src/cli/init.ts
2301
+ import fs19 from "fs";
2302
+ import path19 from "path";
2303
+ import * as p from "@clack/prompts";
2304
+ import pc2 from "picocolors";
2305
+ function check(value) {
2306
+ if (p.isCancel(value)) {
2307
+ p.cancel("Cancelled.");
2308
+ process.exit(0);
2309
+ }
2310
+ return value;
2311
+ }
2312
+ async function offerClaudeLink(root2, d) {
2313
+ if (fs19.existsSync(path19.join(root2, "CLAUDE.md"))) return;
2314
+ const link2 = d ? d.mainDoc === "CLAUDE.md" : check(await p.confirm({ message: "Claude Code reads CLAUDE.md, not AGENTS.md. Add a CLAUDE.md that imports AGENTS.md?", initialValue: true }));
2315
+ if (link2 && linkAgentsDoc(root2)) p.log.success("Created CLAUDE.md (imports AGENTS.md)");
2316
+ }
2317
+ async function chooseMainDoc(root2, d) {
2318
+ const existing = MAIN_DOCS.filter((d2) => fs19.existsSync(path19.join(root2, d2)) && !isPointer(fs19.readFileSync(path19.join(root2, d2), "utf8"), d2));
2319
+ if (d) {
2320
+ if (existing.length === 1 && existing[0] === "AGENTS.md") await offerClaudeLink(root2, d);
2321
+ if (existing.length) return existing.includes(d.mainDoc) ? d.mainDoc : existing[0];
2322
+ createMainDoc(root2, d.mainDoc);
2323
+ p.log.success(`Created ${d.mainDoc}`);
2324
+ return d.mainDoc;
2325
+ }
2326
+ if (existing.length === 2) {
2327
+ return check(
2328
+ await p.select({
2329
+ message: "Both CLAUDE.md and AGENTS.md exist. Which one is the main document?",
2330
+ options: existing.map((d2) => ({ value: d2, label: d2 }))
2331
+ })
2332
+ );
2333
+ }
2334
+ if (existing.length === 1) {
2335
+ const choice2 = check(
2336
+ await p.select({
2337
+ message: `Found ${pc2.cyan(existing[0])}. Use it as the main document?`,
2338
+ options: [
2339
+ { value: existing[0], label: `Yes, use ${existing[0]}` },
2340
+ ...MAIN_DOCS.filter((d2) => d2 !== existing[0]).map((d2) => ({ value: d2, label: `No, create ${d2}` }))
2341
+ ]
2342
+ })
2343
+ );
2344
+ createMainDoc(root2, choice2);
2345
+ if (choice2 === "AGENTS.md") await offerClaudeLink(root2, d);
2346
+ return choice2;
2347
+ }
2348
+ const choice = check(
2349
+ await p.select({
2350
+ message: "No CLAUDE.md or AGENTS.md found. Which one should I create?",
2351
+ options: MAIN_DOCS.map((d2) => ({ value: d2, label: d2, hint: d2 === "CLAUDE.md" ? "Claude Code" : "other agents" }))
2352
+ })
2353
+ );
2354
+ createMainDoc(root2, choice);
2355
+ p.log.success(`Created ${choice}`);
2356
+ return choice;
2357
+ }
2358
+ var WORKFLOW_OPTIONS = [
2359
+ { value: "none", label: "None" },
2360
+ { value: "markode", label: "markode method", hint: "one file per task; vision, architecture and phases as they are needed; per project in monorepos" },
2361
+ { value: "openspec", label: "OpenSpec", hint: "specs + proposed changes, good for existing projects" },
2362
+ { value: "speckit", label: "GitHub Spec Kit", hint: "constitution \u2192 spec \u2192 plan \u2192 tasks, per feature" }
2363
+ ];
2364
+ var PROPOSALS_QUESTION = "The agent can write proposals you answer by ticking options and commenting. How should they be organized?";
2365
+ var PROPOSAL_OPTIONS = [
2366
+ { value: "structured", label: "By topic and area", hint: "docs/proposals/auth/\u2026, services/docs/proposals/\u2026" },
2367
+ { value: "general", label: "One folder", hint: "docs/proposals/" }
2368
+ ];
2369
+ var MONOREPO_QUESTION = "How should the docs of a monorepo be organized?";
2370
+ var MONOREPO_OPTIONS = [
2371
+ { value: "per-project", label: "One set per project", hint: "each project: its CLAUDE.md/AGENTS.md, architecture, tasks and proposals; the root main doc is the overview" },
2372
+ { value: "single", label: "One docs/ for everything", hint: "at the root" }
2373
+ ];
2374
+ var SOURCE_LANG_QUESTION = "Language you work in (every .md file is written in it)";
2375
+ var SOURCE_LANG_MARK = /<!-- markode:source-lang:\w+ -->/;
2376
+ var LANG_OPTIONS = LANGS.map((l) => ({ value: l, label: LANG_NAMES[l], hint: l === "en" ? "default" : void 0 }));
2377
+ var ruleOptions = (rules) => rules.map((r) => ({ value: r.id, label: r.title, hint: r.body.split("\n")[0].replace(/^- /, "") }));
2378
+ async function chooseRules(message, rules, initial) {
2379
+ const pick = check(
2380
+ await p.select({
2381
+ message,
2382
+ options: [
2383
+ { value: "none", label: "None", hint: "start from scratch; add your own later in the Rules tab" },
2384
+ { value: "pick", label: "Choose rules", hint: "common rules for AI agents, plus the ones you create" }
2385
+ ],
2386
+ initialValue: initial.length ? "pick" : "none"
2387
+ })
2388
+ );
2389
+ if (pick === "none") return [];
2390
+ return check(await p.multiselect({ message: "Which rules?", options: ruleOptions(rules), initialValues: initial, required: false }));
2391
+ }
2392
+ var agentOptions = () => Object.keys(SDD_AGENTS).map((a) => ({ value: a, label: SDD_AGENTS[a].label }));
2393
+ var WORKFLOW_LABEL = { none: "None", markode: "markode", openspec: "OpenSpec", speckit: "GitHub Spec Kit" };
2394
+ var WORKFLOW_GUIDE = { none: "", markode: ".claude/skills/markode-method/SKILL.md", openspec: "openspec/README.md", speckit: "specs/README.md" };
2395
+ async function chooseAreas(root2) {
2396
+ const { dirs: candidates, unsure } = detectAreas(root2);
2397
+ if (!candidates.length) return [];
2398
+ if (unsure) {
2399
+ const separate = check(
2400
+ await p.confirm({
2401
+ message: `${candidates.map((c) => `${c}/`).join(", ")} each have their own manifest. Are they separate projects (e.g. a front end and a back end)?`,
2402
+ initialValue: true
2403
+ })
2404
+ );
2405
+ if (!separate) return [];
2406
+ }
2407
+ p.log.info(`Monorepo detected: ${candidates.map((c) => `${c}/`).join(", ")}`);
2408
+ const layout = check(await p.select({ message: MONOREPO_QUESTION, options: MONOREPO_OPTIONS }));
2409
+ if (layout === "single") return [];
2410
+ const dirs2 = check(
2411
+ await p.multiselect({
2412
+ message: "Which projects get their own docs?",
2413
+ options: candidates.map((d) => ({ value: d, label: `${d}/` })),
2414
+ initialValues: candidates,
2415
+ required: false
2416
+ })
2417
+ );
2418
+ const used = /* @__PURE__ */ new Set();
2419
+ const areas = dirs2.map((dir) => ({ dir, prefix: suggestPrefix(dir, used) }));
2420
+ if (!areas.length) return areas;
2421
+ const ok = check(await p.confirm({ message: `Task prefixes: ${areas.map((a) => `${a.dir} \u2192 ${a.prefix}`).join(", ")}, across projects \u2192 CORE. Use them?`, initialValue: true }));
2422
+ if (ok) return areas;
2423
+ for (const a of areas) {
2424
+ a.prefix = check(
2425
+ await p.text({
2426
+ message: `Task prefix for ${a.dir}/`,
2427
+ initialValue: a.prefix,
2428
+ validate: (v) => /^[A-Z][A-Z0-9]{0,5}$/.test(v ?? "") && v !== "CORE" && !areas.some((o) => o !== a && o.prefix === v) ? void 0 : "Uppercase letters and digits, unique, not CORE"
2429
+ })
2430
+ );
2431
+ }
2432
+ return areas;
2433
+ }
2434
+ async function chooseKind(root2, dir) {
2435
+ const guess = guessKind(path19.join(root2, dir));
2436
+ if (!guess.unclear) return guess.kind;
2437
+ const where = dir ? `${dir}/` : "this project";
2438
+ const seen = guess.signals.length ? `found: ${guess.signals.join(", ")}` : "no code yet";
2439
+ return check(
2440
+ await p.select({
2441
+ message: `What kind of project is ${where}? (${seen})`,
2442
+ options: Object.keys(KIND_LABEL).map((k) => ({ value: k, label: KIND_LABEL[k], hint: KIND_HINT[k] })),
2443
+ initialValue: guess.kind
2444
+ })
2445
+ );
2446
+ }
2447
+ async function askProfile(root2, dir, kind) {
2448
+ const base = detectProfile(path19.join(root2, dir), kind);
2449
+ if (kind === "app") return base;
2450
+ const where = dir ? ` (${dir}/)` : "";
2451
+ if (kind === "library") return { kind, published: check(await p.confirm({ message: `Is the library${where} published (npm, PyPI\u2026)?`, initialValue: base.published })) };
2452
+ if (kind === "mobile") {
2453
+ const platforms = check(
2454
+ await p.multiselect({
2455
+ message: `Target platforms${where}`,
2456
+ options: ["ios", "android", "web"].map((v) => ({ value: v, label: v === "ios" ? "iOS" : v === "android" ? "Android" : "Web" })),
2457
+ initialValues: base.platforms,
2458
+ required: false
2459
+ })
2460
+ );
2461
+ return { kind, platforms };
2462
+ }
2463
+ if (kind === "data") {
2464
+ const dataLocation = check(await p.text({ message: `Where does the data live${where}? (folder, bucket, database; empty if not decided)`, initialValue: base.dataLocation, defaultValue: "" }));
2465
+ const experiments = check(
2466
+ await p.select({
2467
+ message: "How are experiments tracked?",
2468
+ options: Object.keys(EXPERIMENTS_LABEL).map((v) => ({ value: v, label: EXPERIMENTS_LABEL[v].replace(/`/g, "") })),
2469
+ initialValue: base.experiments
2470
+ })
2471
+ );
2472
+ return { kind, dataLocation: dataLocation.trim(), experiments };
2473
+ }
2474
+ const environments = check(await p.text({ message: `Environments${where}`, initialValue: base.environments, defaultValue: base.environments }));
2475
+ return { kind, environments: environments.trim() };
2476
+ }
2477
+ async function chooseContracts(root2, areas) {
2478
+ if (!looksLikeMicroservices(root2, areas.length)) return null;
2479
+ const found = findContracts(root2);
2480
+ const answer = check(
2481
+ await p.text({
2482
+ message: "Several services: where do their contracts (APIs, events) live? (e.g. proto/, an OpenAPI file; empty if none)",
2483
+ initialValue: found ?? "",
2484
+ defaultValue: ""
2485
+ })
2486
+ );
2487
+ return answer.trim() || null;
2488
+ }
2489
+ async function chooseExisting(root2) {
2490
+ const found = existingDocs(root2);
2491
+ if (!found.length) return found;
2492
+ return check(
2493
+ await p.multiselect({
2494
+ message: "These docs already exist. Which are the official ones? (the agent updates them instead of creating new ones)",
2495
+ options: found.map((f) => ({ value: f, label: f })),
2496
+ initialValues: found,
2497
+ required: false
2498
+ })
2499
+ );
2500
+ }
2501
+ async function chooseWorkflow(root2, mainDoc, previous, quiet) {
2502
+ const detected = detectWorkflow(root2);
2503
+ if (detected) {
2504
+ p.log.info(`${WORKFLOW_LABEL[detected]} found: markode shows it in the dashboard.`);
2505
+ return detected;
2506
+ }
2507
+ if (previous === "markode") return previous;
2508
+ if (quiet) {
2509
+ const { created, areas } = setupMethod(root2, mainDoc, quiet.monorepo);
2510
+ activateProposals(root2, areas.length ? "structured" : "general", mainDoc);
2511
+ if (created.length) p.log.success(`Tasks structure: ${created.length} files (${created.slice(0, 5).join(", ")}${created.length > 5 ? "\u2026" : ""})`);
2512
+ return "markode";
2513
+ }
2514
+ for (; ; ) {
2515
+ const choice = check(await p.select({ message: "Create a base structure for specs and planning?", options: WORKFLOW_OPTIONS, initialValue: previous ?? "markode" }));
2516
+ if (choice === "none") return choice;
2517
+ if (choice === "markode") {
2518
+ const existing = await chooseExisting(root2);
2519
+ const areas = await chooseAreas(root2);
2520
+ for (const a of areas) a.profile = await askProfile(root2, a.dir, await chooseKind(root2, a.dir));
2521
+ const contracts = await chooseContracts(root2, areas);
2522
+ const profile = areas.length ? void 0 : await askProfile(root2, "", await chooseKind(root2, ""));
2523
+ const created = scaffoldMethod(root2, mainDoc, areas);
2524
+ installMethodSkills(root2);
2525
+ applyWorkflowToFile(path19.join(root2, mainDoc), "markode", methodSection(areas, root2, { profile, contracts, existing }));
2526
+ const big = !areas.length && flattenFiles(scanTree(root2)).length > 40;
2527
+ const layout = areas.length ? "structured" : !big ? "general" : check(await p.select({ message: PROPOSALS_QUESTION, options: PROPOSAL_OPTIONS }));
2528
+ activateProposals(root2, layout, mainDoc);
2529
+ if (created.length) p.log.success(`Created ${created.length} files: ${created.slice(0, 8).join(", ")}${created.length > 8 ? "\u2026" : ""}`);
2530
+ p.log.info(`Docs grow when there is something to write. Skills: /markode-status, /markode-new-task, /markode-close-session.`);
2531
+ return choice;
2532
+ }
2533
+ let official = true;
2534
+ if (choice === "speckit" && !hasUv()) {
2535
+ const next = check(
2536
+ await p.select({
2537
+ message: "Spec Kit needs uv (Python) to install its agent commands, and uv was not found.",
2538
+ options: [
2539
+ { value: "back", label: "Choose another option" },
2540
+ { value: "skeleton", label: "Create the structure only", hint: "the guide explains how to install Spec Kit later" }
2541
+ ]
2542
+ })
2543
+ );
2544
+ if (next === "back") continue;
2545
+ official = false;
2546
+ }
2547
+ const initial = mainDoc === "AGENTS.md" ? "codex" : "claude";
2548
+ const agents = !official ? [initial] : choice === "openspec" ? check(await p.multiselect({ message: "Install the OpenSpec commands for which agents?", options: agentOptions(), initialValues: [initial], required: false })) : [check(await p.select({ message: "Install the Spec Kit commands for which agent?", options: agentOptions(), initialValue: initial }))];
2549
+ const spin = p.spinner();
2550
+ spin.start(official ? `Installing ${WORKFLOW_LABEL[choice]}\u2026` : "Creating the structure\u2026");
2551
+ const res = installWorkflow(root2, choice, agents, official);
2552
+ spin.stop(res.official ? `${WORKFLOW_LABEL[choice]} installed` : "Structure created");
2553
+ if (official && !res.official) p.log.warn(`Couldn't run the ${WORKFLOW_LABEL[choice]} installer:
2554
+ ${pc2.dim(res.error ?? "")}
2555
+ Created the structure only.`);
2556
+ if (!res.official) p.log.info(`To add the agent commands later, see ${pc2.cyan(WORKFLOW_GUIDE[choice])}.`);
2557
+ if (res.ignored.length) p.log.info(`Agent commands hidden in markode (${res.ignored.length} folders); the eye button in the sidebar shows them.`);
2558
+ return choice;
2559
+ }
2560
+ }
2561
+ var featureOptions = () => FEATURES.map((f) => ({ value: f, label: FEATURE_INFO[f].label, hint: FEATURE_INFO[f].hint + (REQUIRES[f] ? ` (needs ${FEATURE_INFO[REQUIRES[f]].label})` : "") }));
2562
+ async function askFeatures(message = "What should markode install in this project? (the viewer is always included)", initial = PROFILE_FEATURES.agent) {
2563
+ const same = (a, b) => a.length === b.length && a.every((f) => b.includes(f));
2564
+ const current = Object.keys(PROFILE_FEATURES).find((k) => same(PROFILE_FEATURES[k], initial)) ?? "custom";
2565
+ const choice = check(
2566
+ await p.select({
2567
+ message,
2568
+ options: [
2569
+ ...Object.keys(PROFILE_INFO).map((k) => ({ value: k, label: PROFILE_INFO[k].label + (k === "agent" ? " (recommended)" : ""), hint: PROFILE_INFO[k].hint })),
2570
+ { value: "custom", label: "Choose the modules", hint: "pick each one; any of them can be turned on or off later in Settings" }
2571
+ ],
2572
+ initialValue: current
2573
+ })
2574
+ );
2575
+ if (choice !== "custom") return PROFILE_FEATURES[choice];
2576
+ return withRequired(check(await p.multiselect({ message: "Modules (space to toggle)", options: featureOptions(), initialValues: initial, required: false })));
2577
+ }
2578
+ async function runInit(root2, plan2) {
2579
+ p.intro(pc2.bgCyan(pc2.black(" markode ")));
2580
+ const d = plan2.defaults;
2581
+ const previous = readConfig(root2);
2582
+ const features2 = withRequired(plan2.features);
2583
+ const has2 = (f) => features2.includes(f);
2584
+ const detectedLang = detectSourceLang(root2, d?.sourceLang ?? "en");
2585
+ ignoreStateDir(root2);
2586
+ let mainDoc = previous?.mainDoc ?? mainDocOf(root2);
2587
+ let workflow = previous?.workflow ?? "none";
2588
+ if (has2("agent")) {
2589
+ mainDoc = await chooseMainDoc(root2, d);
2590
+ const mainFile = path19.join(root2, mainDoc);
2591
+ if (has2("dashboard")) workflow = await chooseWorkflow(root2, mainDoc, previous?.workflow, d);
2592
+ if (workflow === "openspec" || workflow === "speckit") applyWorkflowToFile(mainFile, workflow);
2593
+ const mainText = fs19.readFileSync(mainFile, "utf8");
2594
+ const sourceLang = d ? SOURCE_LANG_MARK.test(mainText) ? sourceLangOf(mainText) : detectedLang : check(await p.select({ message: SOURCE_LANG_QUESTION, options: LANG_OPTIONS, initialValue: SOURCE_LANG_MARK.test(mainText) ? sourceLangOf(mainText) : detectedLang }));
2595
+ const rules = listRules();
2596
+ const applied = appliedRuleIds(mainText);
2597
+ const selected = !has2("rules") ? applied : d ? [.../* @__PURE__ */ new Set([...applied, ...d.rules.filter((id) => rules.some((r) => r.id === id))])] : await chooseRules("Working rules for this project", rules, applied);
2598
+ applyRulesToFile(mainFile, selected, sourceLang);
2599
+ const subprojects = findSubprojects(scanTree(root2));
2600
+ if (subprojects.length && selected.length) {
2601
+ const targets = d ? d.rulesInSubprojects ? subprojects : [] : check(
2602
+ await p.multiselect({
2603
+ message: "Monorepo detected. Apply the same rules to these projects too?",
2604
+ options: subprojects.map((s) => ({ value: s, label: s })),
2605
+ required: false
2606
+ })
2607
+ );
2608
+ for (const dir of targets) applyRulesToFile(contentDoc(path19.join(root2, dir)), selected, sourceLang);
2609
+ }
2610
+ installAgentSkill(root2);
2611
+ const proposals = proposalsState(root2);
2612
+ if (has2("proposals") || proposals.active) activateProposals(root2, proposals.active ? proposals.layout : subprojects.length ? "structured" : "general", mainDoc);
2613
+ if (has2("live") && projectTargets(root2).includes("claude") && !hasClaudeHooks(root2) && !installClaudeHooks(root2))
2614
+ p.log.warn('.claude/settings.local.json could not be read; add the markode hooks by hand (README: "Claude Code hooks").');
2615
+ }
2616
+ if (has2("vscode") && !isHiddenInVsCode(root2) && !hideInVsCode(root2)) p.log.warn('.vscode/settings.json has comments; add "**/.markode": true to files.exclude by hand.');
2617
+ const flags = { ...previous?.features };
2618
+ for (const f of ["rules", "translations", "dashboard", "skills"]) flags[f] = has2(f) || (previous?.initializedAt ? flags[f] !== false : false);
2619
+ const config = {
2620
+ ...previous,
2621
+ mainDoc,
2622
+ langs: previous?.langs ?? [],
2623
+ workflow,
2624
+ proposals: readConfig(root2)?.proposals,
2625
+ features: flags,
2626
+ initializedAt: previous?.initializedAt ?? (/* @__PURE__ */ new Date()).toISOString()
2627
+ };
2628
+ writeConfig(root2, config);
2629
+ const on = featureState(root2);
2630
+ const lines = FEATURES.filter((f) => on[f]).map((f) => `${pc2.green("\u25CF")} ${FEATURE_INFO[f].label} ${pc2.dim("\u2014 " + FEATURE_INFO[f].hint)}`);
2631
+ p.note([`${pc2.green("\u25CF")} Viewer ${pc2.dim("\u2014 see and edit every .md file of the project")}`, ...lines].join("\n"), "Installed");
2632
+ p.outro(`Ready. Turn modules on or off in ${pc2.cyan("Settings \u2192 Features")}, or with ${pc2.cyan("markode features")}.`);
2633
+ return config;
2634
+ }
2635
+ async function runSetup(quick = false, custom = false) {
2636
+ const previous = readDefaults() ?? BASE_DEFAULTS;
2637
+ if (quick) {
2638
+ const d2 = { ...previous, mode: "ask" };
2639
+ writeDefaults(d2);
2640
+ return d2;
2641
+ }
2642
+ p.intro(pc2.bgCyan(pc2.black(" markode setup ")));
2643
+ const ask = check(
2644
+ await p.select({
2645
+ message: "How should markode set up your projects?",
2646
+ options: [
2647
+ { value: "defaults", label: "Always the same way", hint: "choose the modules now; every new project gets them without questions" },
2648
+ { value: "ask", label: "Ask in each project", hint: "one question per project: which modules" }
2649
+ ],
2650
+ initialValue: previous.mode === "ask" && !readDefaults() ? "defaults" : previous.mode
2651
+ })
2652
+ );
2653
+ if (ask === "ask") {
2654
+ const d2 = { ...previous, mode: "ask" };
2655
+ writeDefaults(d2);
2656
+ p.outro(`Saved. Each project will ask which modules to install. Change it with ${pc2.cyan("markode setup")}`);
2657
+ return d2;
2658
+ }
2659
+ const features2 = await askFeatures("What should every new project get? (the viewer is always included)", previous.features);
2660
+ let d = { ...previous, mode: "defaults", features: features2 };
2661
+ if (custom) {
2662
+ const mainDoc = check(
2663
+ await p.select({
2664
+ message: "Main document to create when a project has none",
2665
+ options: MAIN_DOCS.map((m) => ({ value: m, label: m, hint: m === "CLAUDE.md" ? "Claude Code" : "other agents" })),
2666
+ initialValue: previous.mainDoc
2667
+ })
2668
+ );
2669
+ const sourceLang = check(await p.select({ message: `${SOURCE_LANG_QUESTION}, when a project's docs don't show it`, options: LANG_OPTIONS, initialValue: previous.sourceLang }));
2670
+ const rules = listRules();
2671
+ const selected = features2.includes("rules") ? await chooseRules("Working rules for your projects", rules, previous.rules.filter((id) => rules.some((r) => r.id === id))) : previous.rules;
2672
+ const rulesInSubprojects = selected.length ? check(await p.confirm({ message: "In monorepos, apply the rules to the subprojects too?", initialValue: previous.rulesInSubprojects })) : false;
2673
+ const monorepo = features2.includes("dashboard") ? check(await p.select({ message: MONOREPO_QUESTION, options: MONOREPO_OPTIONS, initialValue: previous.monorepo })) : previous.monorepo;
2674
+ d = { ...d, mainDoc, sourceLang, rules: selected, rulesInSubprojects, monorepo };
2675
+ }
2676
+ writeDefaults(d);
2677
+ p.outro(`Saved in ${pc2.cyan(defaultsFile())}. Change them with ${pc2.cyan("markode setup")}; for a single project: ${pc2.cyan("markode init --custom")}`);
2678
+ return d;
2679
+ }
2680
+
2681
+ // src/cli/translate.ts
2682
+ import * as p2 from "@clack/prompts";
2683
+ import pc3 from "picocolors";
2684
+ function check2(value) {
2685
+ if (p2.isCancel(value)) {
2686
+ p2.cancel("Cancelled.");
2687
+ process.exit(0);
2688
+ }
2689
+ return value;
2690
+ }
2691
+ var OTHER = "__other__";
2692
+ async function runTranslate(root2) {
2693
+ const config = readConfig(root2);
2694
+ if (!config) {
2695
+ console.log(`Run ${pc3.cyan("markode init")} first.`);
2696
+ process.exitCode = 1;
2697
+ return;
2698
+ }
2699
+ p2.intro(pc3.bgCyan(pc3.black(" markode translate ")));
2700
+ const source = projectSourceLang(root2, config.mainDoc);
2701
+ const custom = config.langs.filter((l) => !isLang(l));
2702
+ const picked = check2(
2703
+ await p2.multiselect({
2704
+ message: `Languages to review the docs in (the files stay in ${langName(source)})`,
2705
+ options: [
2706
+ ...LANGS.filter((l) => l !== source).map((l) => ({ value: l, label: LANG_NAMES[l] })),
2707
+ ...custom.map((l) => ({ value: l, label: `${langName(l, config)} (${l})`, hint: "interface in English" })),
2708
+ { value: OTHER, label: "Another language\u2026", hint: "type it; the interface stays in English" }
2709
+ ],
2710
+ initialValues: config.langs.filter((l) => l !== source),
2711
+ required: false
2712
+ })
2713
+ );
2714
+ const langs = picked.filter((l) => l !== OTHER);
2715
+ const names2 = { ...config.langNames ?? {} };
2716
+ if (picked.includes(OTHER)) {
2717
+ for (; ; ) {
2718
+ const name = check2(await p2.text({ message: "Language name (as the AI should understand it)", placeholder: "Japanese", validate: (v) => v?.trim() ? void 0 : "Type a language" })).trim();
2719
+ const code = check2(
2720
+ await p2.text({
2721
+ message: `Short code for ${name} (folder name of its translations)`,
2722
+ initialValue: name.slice(0, 2).toLowerCase(),
2723
+ validate: (v) => !isLangCode(v) ? "Two or three letters, e.g. ja or zh-tw" : v === source || langs.includes(v) ? "Already in the list" : void 0
2724
+ })
2725
+ ).toLowerCase();
2726
+ langs.push(code);
2727
+ names2[code] = name;
2728
+ if (!check2(await p2.confirm({ message: "Add another language?", initialValue: false }))) break;
2729
+ }
2730
+ }
2731
+ writeConfig(root2, { ...config, langs, langNames: names2 });
2732
+ if (!langs.length) {
2733
+ p2.outro("No translation languages: markode shows the docs in their source language only.");
2734
+ return;
2735
+ }
2736
+ p2.outro(
2737
+ [
2738
+ `Translations: ${langs.map((l) => langName(l, { ...config, langNames: names2 })).join(", ")}.`,
2739
+ `Ask your agent for ${pc3.cyan("/markode translate")} to translate the docs; then pick the language at the top of markode.`,
2740
+ ...langs.some((l) => !isLang(l)) ? ["Menus stay in English for languages typed by hand."] : []
2741
+ ].join("\n")
2742
+ );
2743
+ }
2744
+
2745
+ // src/cli/index.ts
2746
+ var HELP = `markode \u2014 visual editor for your project's Markdown
2747
+
2748
+ Usage
2749
+ markode set the project up if needed, then open the editor
2750
+ markode init (re)run the project setup: choose the modules to install (or use your defaults)
2751
+ markode setup choose how projects are set up: always the same modules, or asking each time
2752
+ markode features list the modules and whether they are on in this project
2753
+ markode features <module> on|off turn a module on or off (same as Settings \u2192 Features)
2754
+ markode serve open the editor without running init
2755
+ markode translate choose languages to review the docs in (or: markode --translate)
2756
+ markode check [--json] what /markode sync fixes: answered proposals, phases vs tasks, projects table, broken links
2757
+ markode wait <proposal> wait until the user answers a proposal in markode, then print the answer
2758
+ markode link [--dry-run] where there is only CLAUDE.md or only AGENTS.md, add the other one pointing to it
2759
+ markode link --skip don't offer it again in this project
2760
+ markode changes [--json] what the user edited or commented in markode since the agent was last told
2761
+ markode changes --start a new agent session: what happened before is old news
2762
+ markode changes --hook <event> the same, as Claude Code hook output (silent when nothing changed)
2763
+
2764
+ For the agent's /markode translate:
2765
+ markode translate --status [--json] translations + files not in the source language
2766
+ markode translate --mark [--lang xx] <path...>|--all record translations as in sync
2767
+ markode translate --keep <path...> keep files in their current language
2768
+
2769
+ Options
2770
+ -v, --version print the version
2771
+ -D first run: don't save your choice as the default for new projects
2772
+ --profile <viewer|agent|full> init: install this set of modules, without questions
2773
+ --yes init: no questions; your defaults, or the recommended modules (viewer + AI agent)
2774
+ --custom init: choose each module and answer every question for this project
2775
+ setup: also choose the main doc, language, rules and monorepo layout
2776
+ --port <n> server port (default 4319)
2777
+ --no-open do not open the browser
2778
+ `;
2779
+ var args = process.argv.slice(2);
2780
+ var flag = (name) => args.includes(name);
2781
+ var option = (name) => {
2782
+ const i = args.indexOf(name);
2783
+ return i >= 0 ? args[i + 1] : void 0;
2784
+ };
2785
+ var command = args[0] && !args[0].startsWith("-") ? args[0] : "start";
2786
+ var root = process.env.MARKODE_ROOT ?? process.cwd();
2787
+ async function main() {
2788
+ if (fs20.existsSync(root)) setProjectRoot(root);
2789
+ if (flag("--help") || flag("-h")) return console.log(HELP);
2790
+ if (flag("--version") || flag("-v")) return console.log(JSON.parse(fs20.readFileSync(path20.join(PKG_ROOT, "package.json"), "utf8")).version);
2791
+ const translation = flag("--status") || flag("--mark") || flag("--keep");
2792
+ if ((command === "sync" || command === "translate") && translation) return sync();
2793
+ if (command === "translate" || flag("--translate")) return runTranslate(root);
2794
+ if (command === "check") return check3();
2795
+ if (command === "wait") return wait();
2796
+ if (command === "link") return link();
2797
+ if (command === "changes") return changes();
2798
+ if (command === "features") return features();
2799
+ if (command === "setup") {
2800
+ await runSetup(false, flag("--custom"));
2801
+ return;
2802
+ }
2803
+ if (command === "init") {
2804
+ const p3 = await plan();
2805
+ if (p3) await runInit(root, p3);
2806
+ else console.log("No terminal to ask in: run `markode init --yes` or `--profile <viewer|agent|full>`.");
2807
+ return;
2808
+ }
2809
+ if (command === "start" && !readConfig(root)?.initializedAt) {
2810
+ const p3 = await plan();
2811
+ if (p3) await runInit(root, p3);
2812
+ }
2813
+ if (command === "start" || command === "serve") refreshInstalled(root);
2814
+ const unlinked = command === "start" && process.stdout.isTTY && !readConfig(root)?.skipLink ? missingMainDocs(root) : [];
2815
+ if (unlinked.length)
2816
+ console.log(`Tip: Claude Code reads CLAUDE.md and other agents read AGENTS.md; ${unlinked.map((m) => m.path).join(", ")} missing. Run \`markode link\` (or \`markode link --skip\`).`);
2817
+ if (command === "start" || command === "serve") {
2818
+ await startServer({ root, port: Number(option("--port") ?? 4319), open: !flag("--no-open") });
2819
+ return;
2820
+ }
2821
+ console.log(HELP);
2822
+ process.exitCode = 1;
2823
+ }
2824
+ async function plan() {
2825
+ const saved = readDefaults();
2826
+ const d = saved ?? BASE_DEFAULTS;
2827
+ const profile = option("--profile");
2828
+ if (flag("--custom")) return { features: await askFeatures(), defaults: null };
2829
+ if (profile) {
2830
+ if (!isProfile(profile)) throw new Error(`Unknown profile "${profile}": use viewer, agent or full`);
2831
+ return { features: PROFILE_FEATURES[profile], defaults: d };
2832
+ }
2833
+ if (saved?.mode === "defaults" || flag("--yes")) return { features: d.features, defaults: d };
2834
+ if (!process.stdin.isTTY) return null;
2835
+ const features2 = await askFeatures();
2836
+ if (!saved && !flag("-D")) {
2837
+ writeDefaults({ ...d, mode: "defaults", features: features2 });
2838
+ console.log(` Saved as your default for new projects (they won't ask again). Change it with \`markode setup\`.`);
2839
+ }
2840
+ return { features: features2, defaults: d };
2841
+ }
2842
+ function features() {
2843
+ const [id, value] = [args[1], args[2]];
2844
+ if (id) {
2845
+ if (!isFeature(id) || value !== "on" && value !== "off") {
2846
+ console.error(`Usage: markode features <${FEATURES.join("|")}> on|off`);
2847
+ process.exitCode = 1;
2848
+ return;
2849
+ }
2850
+ const changed = setFeature(root, id, value === "on", readDefaults() ?? BASE_DEFAULTS);
2851
+ for (const [f, on] of Object.entries(changed)) console.log(`${on ? "on " : "off"} ${FEATURE_INFO[f].label}`);
2852
+ if (!Object.keys(changed).length) console.log(`${FEATURE_INFO[id].label} was already ${value}.`);
2853
+ return;
2854
+ }
2855
+ const state = featureState(root);
2856
+ console.log("on Viewer \u2014 see and edit every .md file of the project (always on)");
2857
+ for (const f of FEATURES) console.log(`${state[f] ? "on " : "off"} ${FEATURE_INFO[f].label} \u2014 ${FEATURE_INFO[f].hint}${REQUIRES[f] ? ` (needs ${FEATURE_INFO[REQUIRES[f]].label})` : ""}`);
2858
+ }
2859
+ function check3() {
2860
+ const report = checkDocs(root);
2861
+ if (flag("--json")) return console.log(JSON.stringify(report, null, 2));
2862
+ const lines = [
2863
+ ...report.proposals.map((p3) => `answered proposal ${p3}`),
2864
+ ...report.phases.map((p3) => `phase vs task ${p3.phase}: ${p3.task} is ${p3.status || "without status"} but ${p3.ticked ? "ticked" : "not ticked"}`),
2865
+ ...(report.projects?.missing ?? []).map((d) => `project not listed ${d}/`),
2866
+ ...(report.projects?.gone ?? []).map((d) => `project gone ${d}/`),
2867
+ ...report.links.map((l) => `broken link ${l.file}:${l.line} \u2192 ${l.target}`)
2868
+ ];
2869
+ console.log(lines.length ? lines.join("\n") : "Docs are consistent.");
2870
+ }
2871
+ function link() {
2872
+ const config = readConfig(root);
2873
+ if (flag("--skip")) {
2874
+ writeConfig(root, { ...config ?? { mainDoc: null, langs: [] }, skipLink: true });
2875
+ return console.log("markode won't offer to link CLAUDE.md and AGENTS.md in this project again.");
2876
+ }
2877
+ if (flag("--dry-run") && config?.skipLink) return console.log("Skipped: the user chose not to link the main docs.");
2878
+ const missing = missingMainDocs(root);
2879
+ if (!missing.length) return console.log("Every main doc has its CLAUDE.md and AGENTS.md (or neither).");
2880
+ if (flag("--dry-run")) return console.log(missing.map((m) => `would create ${m.path} \u2192 ${m.pointsTo}`).join("\n"));
2881
+ for (const p3 of linkMainDocs(root)) console.log(`created ${p3}`);
2882
+ }
2883
+ function changes() {
2884
+ if (flag("--start")) return startSession(root);
2885
+ const list = takeChanges(root);
2886
+ const event = option("--hook");
2887
+ if (event) {
2888
+ const text3 = formatChanges(list);
2889
+ if (text3) console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: text3 } }));
2890
+ return;
2891
+ }
2892
+ if (flag("--json")) return console.log(JSON.stringify(list, null, 2));
2893
+ console.log(formatChanges(list) || "No edits or comments from the user since you were last told.");
2894
+ }
2895
+ async function wait() {
2896
+ const rel = args[1];
2897
+ if (!rel || !fs20.existsSync(path20.join(root, rel))) {
2898
+ console.error(`Usage: markode wait <proposal.md> (relative to ${root})`);
2899
+ process.exitCode = 1;
2900
+ return;
2901
+ }
2902
+ const result = await waitForAnswer(root, rel);
2903
+ if (result === "gone") {
2904
+ console.log(`${rel} was deleted or is no longer a proposal.`);
2905
+ process.exit(0);
2906
+ }
2907
+ console.log(answerSummary(root, rel));
2908
+ process.exit(0);
2909
+ }
2910
+ function sync() {
2911
+ const report = syncReport(root);
2912
+ const paths = (after) => args.slice(args.indexOf(after) + 1).filter((a, i, all) => !a.startsWith("--") && all[i - 1] !== "--lang");
2913
+ if (flag("--keep")) {
2914
+ for (const p3 of paths("--keep")) {
2915
+ try {
2916
+ console.log(`kept ${p3} (${keepLanguage(root, p3)})`);
2917
+ } catch (err) {
2918
+ console.error(`skipped ${p3}: ${err.message}`);
2919
+ }
2920
+ }
2921
+ return;
2922
+ }
2923
+ if (flag("--mark")) {
2924
+ const only = option("--lang");
2925
+ const langs = only && report.langs.includes(only) ? [only] : report.langs;
2926
+ const files = flag("--all") ? [...new Set(report.files.map((f) => f.path))] : paths("--mark");
2927
+ for (const p3 of files) {
2928
+ for (const l of langs) {
2929
+ try {
2930
+ markSynced(root, l, p3);
2931
+ console.log(`synced ${l} ${p3}`);
2932
+ } catch (err) {
2933
+ if (!flag("--all")) console.error(`skipped ${l} ${p3}: ${err.message}`);
2934
+ }
2935
+ }
2936
+ }
2937
+ return;
2938
+ }
2939
+ if (flag("--json")) return console.log(JSON.stringify(report, null, 2));
2940
+ console.log(`Source language: ${report.sourceLang} Translations: ${report.langs.join(", ") || "none"}`);
2941
+ if (report.foreign.length) {
2942
+ console.log(`
2943
+ Not in ${report.sourceLang}:`);
2944
+ for (const f of report.foreign) console.log(` ${f.detected} ${f.path}`);
2945
+ }
2946
+ const pending = report.files.filter((f) => f.status !== "synced");
2947
+ if (pending.length) console.log("");
2948
+ for (const e of pending) console.log(`${e.lang} ${e.status.padEnd(13)} ${e.path}`);
2949
+ if (!pending.length && !report.foreign.length) console.log("Everything in sync.");
2950
+ }
2951
+ main().catch((err) => {
2952
+ console.error(err);
2953
+ process.exit(1);
2954
+ });