@rafiqui987/markode 0.0.0-stage → 0.2.7

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 +349 -2
  3. package/dist/cli.js +2304 -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-DpbJC7Xp.js +1 -0
  64. package/dist/web/assets/apl-gfMMiTNf.js +1 -0
  65. package/dist/web/assets/arc-DC4i8MWg.js +1 -0
  66. package/dist/web/assets/architecture-WOLXFQ4H-eBViYpWx.js +1 -0
  67. package/dist/web/assets/architectureDiagram-ZHVYKT53-BUdoz7It.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-g9DAd_oM.js +140 -0
  73. package/dist/web/assets/brainfuck-D5EjA2JK.js +1 -0
  74. package/dist/web/assets/c4Diagram-XDPPPE54-DRNtmahu.js +38 -0
  75. package/dist/web/assets/channel-B4Lj3Yy_.js +1 -0
  76. package/dist/web/assets/chunk-2E7WHVMD-pLVU_fk3.js +88 -0
  77. package/dist/web/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  78. package/dist/web/assets/chunk-4EMNKR2T-CILPvkQp.js +1 -0
  79. package/dist/web/assets/chunk-55XCU76K-D3MGCCrv.js +1 -0
  80. package/dist/web/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  81. package/dist/web/assets/chunk-7S6XSTFO-CN1GsbIT.js +2 -0
  82. package/dist/web/assets/chunk-AQDTSKZB-CUWKsFsd.js +72 -0
  83. package/dist/web/assets/chunk-DSNLSHJZ-CwfiRg01.js +129 -0
  84. package/dist/web/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  85. package/dist/web/assets/chunk-HAGFOTED-Do-8YoBu.js +62 -0
  86. package/dist/web/assets/chunk-HNE4Q36P-1-JjBgqP.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-BEHeLMtz.js +213 -0
  91. package/dist/web/assets/chunk-UHW7PFBP-CC1DKtp5.js +1 -0
  92. package/dist/web/assets/chunk-VQ5W62QI-CWY0eXsE.js +1 -0
  93. package/dist/web/assets/chunk-WQQL4CDK-BJl_ufDd.js +10 -0
  94. package/dist/web/assets/chunk-XXDRQBXY-DKVbKHOf.js +1 -0
  95. package/dist/web/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  96. package/dist/web/assets/chunk-YZO67HFG-dhkHuKWt.js +1 -0
  97. package/dist/web/assets/chunk-ZAUX2LTX-CA6m357j.js +1 -0
  98. package/dist/web/assets/chunk-ZCHMQSOR-DUBbqrP7.js +2 -0
  99. package/dist/web/assets/classDiagram-v2-4MYWR3UA-PXy4ulRK.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-BDYEjy3u.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-BOMUQs4F.js +1 -0
  110. package/dist/web/assets/cynefinDiagram-WF24UDRP-cX0NpQ7L.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-BAP6Gj-L.js +4 -0
  115. package/dist/web/assets/defaultLocale-BFoDCU3G.js +1 -0
  116. package/dist/web/assets/diagram-CBK3MWTC-B5AONjPA.js +24 -0
  117. package/dist/web/assets/diagram-I5XIIV2D-BkwhoH5d.js +24 -0
  118. package/dist/web/assets/diagram-ODIL2UCW-DUFps_5l.js +30 -0
  119. package/dist/web/assets/diagram-PFBEJUXE-Dj94WcA4.js +3 -0
  120. package/dist/web/assets/diagram-RJA3I4VV-BRhr049b.js +41 -0
  121. package/dist/web/assets/diagram-TYUNFSNR-C9EtKuKD.js +200 -0
  122. package/dist/web/assets/diff-ChtP43wD.js +1 -0
  123. package/dist/web/assets/dist-6_cnylgs.js +1 -0
  124. package/dist/web/assets/dist-B-4tAY5T.js +12 -0
  125. package/dist/web/assets/dist-B91b1nu8.js +1 -0
  126. package/dist/web/assets/dist-BOIkyUDE.js +1 -0
  127. package/dist/web/assets/dist-BhvCwF6v.js +1 -0
  128. package/dist/web/assets/dist-C6oRdq_x.js +1 -0
  129. package/dist/web/assets/dist-C7gsSbg8.js +11 -0
  130. package/dist/web/assets/dist-CMFTG-Cz.js +9 -0
  131. package/dist/web/assets/dist-CRKS1wXG.js +1 -0
  132. package/dist/web/assets/dist-CUajnf5G.js +1 -0
  133. package/dist/web/assets/dist-CXAToOJL.js +1 -0
  134. package/dist/web/assets/dist-CxtEXJtW.js +1 -0
  135. package/dist/web/assets/dist-D5_2d6Ki.js +1 -0
  136. package/dist/web/assets/dist-DMEslBFn.js +2 -0
  137. package/dist/web/assets/dist-DTXSqpYP.js +1 -0
  138. package/dist/web/assets/dist-DfKlgEia.js +1 -0
  139. package/dist/web/assets/dist-SlIdOLRu.js +1 -0
  140. package/dist/web/assets/dist-hiB1k3yb.js +1 -0
  141. package/dist/web/assets/dist-uYDRxuA0.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-hUPVHW_L.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-CK3_-qCI.js +27 -0
  150. package/dist/web/assets/elm-Cshzl8qu.js +1 -0
  151. package/dist/web/assets/erDiagram-KYQIGQAA-BnzshuRd.js +99 -0
  152. package/dist/web/assets/erlang-BV0c4Hdn.js +1 -0
  153. package/dist/web/assets/eventmodeling-K75KTNOO-YpRkYpNJ.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-DA73lQLy.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-Cof-TrXj.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-ClyrQrUV.js +1 -0
  163. package/dist/web/assets/gitGraphDiagram-PDYL7XHY-DcHBmgeW.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-CFg9w5Ih.js +63 -0
  171. package/dist/web/assets/index-CT9Wzb6W.css +1 -0
  172. package/dist/web/assets/info-OHQRW6UA-DJuVnR49.js +1 -0
  173. package/dist/web/assets/infoDiagram-MVDRKAX5-SPivATVi.js +2 -0
  174. package/dist/web/assets/init-C-OQMol4.js +1 -0
  175. package/dist/web/assets/ishikawaDiagram-HPEHV2GE-luEQCi0p.js +70 -0
  176. package/dist/web/assets/javascript-Bu8mWwtz.js +1 -0
  177. package/dist/web/assets/journeyDiagram-BOSHW7O7-CDWNQ0FV.js +139 -0
  178. package/dist/web/assets/julia-CQfgDRfP.js +1 -0
  179. package/dist/web/assets/kanban-definition-FNSA4HC5-kCg2Ntni.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-Cs-2UOsh.js +1 -0
  184. package/dist/web/assets/linear-Dn78ogyi.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-XUq3kRg7.js +7 -0
  190. package/dist/web/assets/mermaid.core-KP0iScKW.js +44 -0
  191. package/dist/web/assets/mindmap-definition-6OY57UG3-DYfhBOEV.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-m_mxaPh2.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-C57XlI0z.js +1 -0
  207. package/dist/web/assets/perl-BkSkiI9m.js +1 -0
  208. package/dist/web/assets/pie-XZMESJXO-COU-cPpy.js +1 -0
  209. package/dist/web/assets/pieDiagram-GNMPY74T-Cgv_MwBq.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-DLP-IJBg.js +7 -0
  219. package/dist/web/assets/r-CxDmU1I4.js +1 -0
  220. package/dist/web/assets/radar-ABXABTNO-DVnhW8Tu.js +1 -0
  221. package/dist/web/assets/railroad-I3PHUGI6-CbNbR4f3.js +1 -0
  222. package/dist/web/assets/railroad-abnf-LNEFI6M7-QapD3MRY.js +1 -0
  223. package/dist/web/assets/railroad-ebnf-DWYD2UWJ-CSlvjrRs.js +1 -0
  224. package/dist/web/assets/railroad-peg-JR7BVNR7-CabNa_kN.js +1 -0
  225. package/dist/web/assets/railroadDiagram-Y72NRBL7-DnlOXd0I.js +1 -0
  226. package/dist/web/assets/requirementDiagram-EQYSW5RA-Dq1MJEdy.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--LVb7hhV.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-Cg8W2Kc8.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-w1h_Rr-8.js +1 -0
  244. package/dist/web/assets/stateDiagram-v2-A6IESKWZ-ck5PGLTp.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--RxIRIY6.js +1 -0
  249. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-CeoYxgix.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-Cgtckb20.js +120 -0
  255. package/dist/web/assets/toml-BPTmHmyx.js +1 -0
  256. package/dist/web/assets/treeView-D4JQ5SDB-Byi5cU2l.js +1 -0
  257. package/dist/web/assets/treemap-SAJKECNS-DJ7_3_F8.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-DkTE13SV.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-C5G521q2.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-PChC3W6T.js +1 -0
  270. package/dist/web/assets/wardleyDiagram-IVAIDKWA-B9sJfj7x.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-CmuAuTkp.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 +81 -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 +131 -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 +91 -0
package/dist/cli.js ADDED
@@ -0,0 +1,2304 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import fs17 from "fs";
5
+ import path17 from "path";
6
+
7
+ // src/core/check.ts
8
+ import fs8 from "fs";
9
+ import path8 from "path";
10
+
11
+ // src/core/config.ts
12
+ import fs from "fs";
13
+ import os from "os";
14
+ import path from "path";
15
+ import { fileURLToPath } from "url";
16
+ var LANGS = ["en", "es", "fr", "pt", "de", "it"];
17
+ var LANG_NAMES = { en: "English", es: "Espa\xF1ol", fr: "Fran\xE7ais", pt: "Portugu\xEAs", de: "Deutsch", it: "Italiano" };
18
+ var DEFAULT_SOURCE = "en";
19
+ var isLang = (v) => LANGS.includes(v);
20
+ var isLangCode = (v) => typeof v === "string" && /^[a-z]{2,3}(-[a-z0-9]{2,8})?$/i.test(v);
21
+ var langName = (code, config) => isLang(code) ? LANG_NAMES[code] : config?.langNames?.[code] ?? code;
22
+ var MAIN_DOCS = ["CLAUDE.md", "AGENTS.md"];
23
+ var otherDoc = (doc) => doc === "CLAUDE.md" ? "AGENTS.md" : "CLAUDE.md";
24
+ var POINTERS = {
25
+ // Claude Code imports the file: its content is loaded as if it were here.
26
+ "CLAUDE.md": "@AGENTS.md\n",
27
+ "AGENTS.md": "# AGENTS.md\n\nThis project's instructions for AI agents are in [CLAUDE.md](CLAUDE.md): read it and follow it.\n"
28
+ };
29
+ var isPointer = (text3, doc = "CLAUDE.md") => text3.trim() === POINTERS[doc].trim();
30
+ var readText = (abs) => {
31
+ try {
32
+ return fs.readFileSync(abs, "utf8");
33
+ } catch {
34
+ return "";
35
+ }
36
+ };
37
+ function contentDoc(dir) {
38
+ for (const doc of MAIN_DOCS) {
39
+ const abs = path.join(dir, doc);
40
+ if (!fs.existsSync(abs)) continue;
41
+ if (fs.existsSync(path.join(dir, otherDoc(doc))) && isPointer(readText(abs), doc)) continue;
42
+ return abs;
43
+ }
44
+ return null;
45
+ }
46
+ function mainDocOf(root2) {
47
+ const doc = contentDoc(root2);
48
+ return doc && path.basename(doc);
49
+ }
50
+ function missingMainDoc(dir) {
51
+ const present = MAIN_DOCS.filter((d) => fs.existsSync(path.join(dir, d)));
52
+ return present.length === 1 ? otherDoc(present[0]) : null;
53
+ }
54
+ function linkMainDoc(dir) {
55
+ const missing = missingMainDoc(dir);
56
+ if (missing) fs.writeFileSync(path.join(dir, missing), POINTERS[missing]);
57
+ return missing;
58
+ }
59
+ var linkAgentsDoc = (dir) => missingMainDoc(dir) === "CLAUDE.md" && !!linkMainDoc(dir);
60
+ var PKG_ROOT = (() => {
61
+ let dir = path.dirname(fileURLToPath(import.meta.url));
62
+ while (!fs.existsSync(path.join(dir, "package.json"))) dir = path.dirname(dir);
63
+ return dir;
64
+ })();
65
+ var TEMPLATES_DIR = path.join(PKG_ROOT, "templates");
66
+ var WEB_DIR = path.join(PKG_ROOT, "dist", "web");
67
+ var GLOBAL_DIR = process.env.MARKODE_HOME ?? path.join(os.homedir(), ".markode");
68
+ var stateDir = (root2) => path.join(root2, ".markode");
69
+ var configFile = (root2) => path.join(stateDir(root2), "config.json");
70
+ function readConfig(root2) {
71
+ try {
72
+ const raw = JSON.parse(fs.readFileSync(configFile(root2), "utf8"));
73
+ const langs = Array.isArray(raw.langs) ? raw.langs : raw.lang ? [raw.lang] : [];
74
+ return { mainDoc: raw.mainDoc ?? null, langs: langs.filter(isLangCode), langNames: raw.langNames, workflow: raw.workflow, proposals: raw.proposals, skipLink: raw.skipLink === true || void 0, initializedAt: raw.initializedAt };
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ function writeConfig(root2, config) {
80
+ fs.mkdirSync(stateDir(root2), { recursive: true });
81
+ fs.writeFileSync(configFile(root2), JSON.stringify(config, null, 2) + "\n");
82
+ }
83
+ function safeResolve(root2, rel) {
84
+ const abs = path.resolve(root2, rel);
85
+ if (abs !== root2 && !abs.startsWith(root2 + path.sep)) throw new Error(`Path outside project: ${rel}`);
86
+ return abs;
87
+ }
88
+ var toPosix = (p3) => p3.split(path.sep).join("/");
89
+ var DEFAULT_SETTINGS = { font: "inter", size: 15, lineHeight: 1.65, padding: 28, width: "full", wrap: true, uiLang: "en" };
90
+ var settingsFile = () => path.join(GLOBAL_DIR, "settings.json");
91
+ function readSettings() {
92
+ try {
93
+ return { ...DEFAULT_SETTINGS, ...JSON.parse(fs.readFileSync(settingsFile(), "utf8")) };
94
+ } catch {
95
+ return { ...DEFAULT_SETTINGS };
96
+ }
97
+ }
98
+ function writeSettings(patch) {
99
+ const next = { ...readSettings(), ...patch };
100
+ fs.mkdirSync(GLOBAL_DIR, { recursive: true });
101
+ fs.writeFileSync(settingsFile(), JSON.stringify(next, null, 2) + "\n");
102
+ return next;
103
+ }
104
+
105
+ // src/core/dashboard.ts
106
+ import fs3 from "fs";
107
+ import path3 from "path";
108
+ import YAML from "yaml";
109
+
110
+ // src/core/scan.ts
111
+ import fs2 from "fs";
112
+ import path2 from "path";
113
+ import ignore from "ignore";
114
+ var ALWAYS_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".markode", "dist", "build", ".next", ".turbo", "coverage", ".venv"]);
115
+ var IGNORE_FILE = ".markodeignore";
116
+ function loadIgnore(root2, file) {
117
+ const ig = ignore();
118
+ try {
119
+ ig.add(fs2.readFileSync(path2.join(root2, file), "utf8"));
120
+ } catch {
121
+ }
122
+ return ig;
123
+ }
124
+ function readIgnoreList(root2) {
125
+ try {
126
+ return fs2.readFileSync(path2.join(root2, IGNORE_FILE), "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
127
+ } catch {
128
+ return [];
129
+ }
130
+ }
131
+ function setIgnored(root2, rel, kind, ignored) {
132
+ const entry = kind === "dir" ? `${rel}/` : rel;
133
+ const lines = readIgnoreList(root2).filter((l) => l !== entry && l !== rel);
134
+ if (ignored) lines.push(entry);
135
+ fs2.writeFileSync(path2.join(root2, IGNORE_FILE), lines.length ? lines.join("\n") + "\n" : "");
136
+ }
137
+ function watchSkips(root2) {
138
+ const gitIgnore = loadIgnore(root2, ".gitignore");
139
+ return (rel, isDir2) => {
140
+ if (!rel || rel.startsWith("..")) return false;
141
+ if (rel.split("/").some((s) => ALWAYS_SKIP.has(s))) return true;
142
+ if (isDir2 !== false && gitIgnore.ignores(rel + "/")) return true;
143
+ return isDir2 !== true && gitIgnore.ignores(rel);
144
+ };
145
+ }
146
+ function scanTree(root2) {
147
+ const gitIgnore = loadIgnore(root2, ".gitignore");
148
+ const mdIgnore = loadIgnore(root2, IGNORE_FILE);
149
+ function walk(dirAbs, parentIgnored) {
150
+ let entries;
151
+ try {
152
+ entries = fs2.readdirSync(dirAbs, { withFileTypes: true });
153
+ } catch {
154
+ return [];
155
+ }
156
+ const nodes = [];
157
+ for (const e of entries) {
158
+ if (ALWAYS_SKIP.has(e.name)) continue;
159
+ const abs = path2.join(dirAbs, e.name);
160
+ const rel = toPosix(path2.relative(root2, abs));
161
+ if (e.isDirectory()) {
162
+ if (gitIgnore.ignores(rel + "/")) continue;
163
+ const ignored = parentIgnored || mdIgnore.ignores(rel + "/");
164
+ const children = walk(abs, ignored);
165
+ if (!children.length && !isEmptyDir(abs)) continue;
166
+ const project = MAIN_DOCS.some((d) => children.some((c) => c.kind === "file" && c.name === d));
167
+ nodes.push({ name: e.name, path: rel, kind: "dir", project: project || void 0, ignored: ignored || void 0, children });
168
+ } else if (e.isFile() && e.name.toLowerCase().endsWith(".md")) {
169
+ if (gitIgnore.ignores(rel)) continue;
170
+ const ignored = parentIgnored || mdIgnore.ignores(rel);
171
+ nodes.push({ name: e.name, path: rel, kind: "file", ignored: ignored || void 0 });
172
+ }
173
+ }
174
+ return nodes.sort(compareNodes);
175
+ }
176
+ return walk(root2, false);
177
+ }
178
+ function isEmptyDir(abs) {
179
+ try {
180
+ return fs2.readdirSync(abs).length === 0;
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+ var collator = new Intl.Collator(void 0, { numeric: true, sensitivity: "base" });
186
+ function compareNodes(a, b) {
187
+ return (a.kind === "dir" ? 0 : 1) - (b.kind === "dir" ? 0 : 1) || collator.compare(a.name, b.name);
188
+ }
189
+ function flattenFiles(nodes, includeIgnored = false) {
190
+ const out = [];
191
+ for (const n of nodes) {
192
+ if (n.ignored && !includeIgnored) continue;
193
+ if (n.kind === "file") out.push(n);
194
+ else out.push(...flattenFiles(n.children ?? [], includeIgnored));
195
+ }
196
+ return out;
197
+ }
198
+ function findSubprojects(nodes) {
199
+ const out = [];
200
+ for (const n of nodes) {
201
+ if (n.kind !== "dir") continue;
202
+ if (n.project) out.push(n.path);
203
+ out.push(...findSubprojects(n.children ?? []));
204
+ }
205
+ return out;
206
+ }
207
+
208
+ // src/core/dashboard.ts
209
+ function parseFrontmatter(text3) {
210
+ const m = text3.match(/^---\r?\n([\s\S]*?)\r?\n---/);
211
+ if (!m) return {};
212
+ try {
213
+ return YAML.parse(m[1], { logLevel: "error" }) ?? {};
214
+ } catch {
215
+ return {};
216
+ }
217
+ }
218
+ var asText = (v) => v instanceof Date ? v.toISOString().slice(0, 10) : v == null || typeof v === "object" ? null : String(v);
219
+ function summarize(root2) {
220
+ return flattenFiles(scanTree(root2)).flatMap((f) => {
221
+ if (f.path.split("/").some((part) => part.startsWith("."))) return [];
222
+ const text3 = fs3.readFileSync(path3.join(root2, f.path), "utf8");
223
+ const meta = parseFrontmatter(text3);
224
+ if (meta.type === "task" && f.name.startsWith("_")) return [];
225
+ const id = asText(meta.id);
226
+ const total = text3.match(/^\s*[-*+] \[[ xX]\]/gm)?.length ?? 0;
227
+ const done = text3.match(/^\s*[-*+] \[[xX]\]/gm)?.length ?? 0;
228
+ const heading2 = text3.match(/^#\s+(.+)$/m)?.[1];
229
+ const title = asText(meta.title) ?? (heading2 && id ? heading2.replace(new RegExp(`^${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*[\xB7:-]\\s*`), "") : heading2);
230
+ return {
231
+ path: f.path,
232
+ title: title ?? f.name.replace(/\.md$/i, ""),
233
+ type: asText(meta.type),
234
+ status: asText(meta.status),
235
+ phase: typeof meta.phase === "number" ? meta.phase : null,
236
+ updated: asText(meta.updated),
237
+ id,
238
+ area: asText(meta.area),
239
+ tasks: { done, total }
240
+ };
241
+ });
242
+ }
243
+
244
+ // src/core/method.ts
245
+ import fs7 from "fs";
246
+ import path7 from "path";
247
+ import YAML2 from "yaml";
248
+
249
+ // src/core/profile.ts
250
+ import fs4 from "fs";
251
+ import path4 from "path";
252
+ var KIND_LABEL = { app: "App", library: "Library", mobile: "Mobile", data: "Data / ML", infra: "Infrastructure" };
253
+ var KIND_HINT = {
254
+ app: "web, API, CLI, desktop",
255
+ library: "a package other code imports",
256
+ mobile: "iOS, Android, Flutter, React Native",
257
+ data: "notebooks, datasets, models",
258
+ infra: "Terraform, Pulumi, Helm, Kubernetes"
259
+ };
260
+ var EXPERIMENTS_LABEL = {
261
+ docs: "`docs/experiments.md` (one entry per run: goal, data, result)",
262
+ mlflow: "MLflow",
263
+ wandb: "Weights & Biases",
264
+ dvc: "DVC"
265
+ };
266
+ var SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", "vendor", "target", ".git", ".venv", "venv"]);
267
+ var names = (abs) => {
268
+ try {
269
+ return fs4.readdirSync(abs, { withFileTypes: true });
270
+ } catch {
271
+ return [];
272
+ }
273
+ };
274
+ function find(abs, re, depth) {
275
+ for (const e of names(abs)) {
276
+ if (re.test(e.name)) return path4.join(abs, e.name);
277
+ if (e.isDirectory() && depth > 0 && !SKIP.has(e.name) && !e.name.startsWith(".")) {
278
+ const hit = find(path4.join(abs, e.name), re, depth - 1);
279
+ if (hit) return hit;
280
+ }
281
+ }
282
+ return null;
283
+ }
284
+ var read = (abs) => {
285
+ try {
286
+ return fs4.readFileSync(abs, "utf8");
287
+ } catch {
288
+ return "";
289
+ }
290
+ };
291
+ var readJson = (abs) => {
292
+ try {
293
+ return JSON.parse(fs4.readFileSync(abs, "utf8"));
294
+ } catch {
295
+ return null;
296
+ }
297
+ };
298
+ var APP_DEPS = ["next", "nuxt", "vite", "react-scripts", "@angular/core", "@sveltejs/kit", "astro", "express", "fastify", "@nestjs/core", "koa", "hono", "electron"];
299
+ function guessKind(dir) {
300
+ const found = {};
301
+ const add = (kind, why) => (found[kind] ??= []).push(why);
302
+ const has2 = (name) => fs4.existsSync(path4.join(dir, name));
303
+ const pkg = readJson(path4.join(dir, "package.json"));
304
+ const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
305
+ if (has2("pubspec.yaml")) add("mobile", "Flutter (pubspec.yaml)");
306
+ if (deps["react-native"] || deps.expo) add("mobile", "React Native");
307
+ if (has2("android") && has2("ios")) add("mobile", "android/ and ios/");
308
+ else if (find(dir, /\.xcodeproj$/, 1)) add("mobile", "Xcode project");
309
+ if (find(dir, /\.ipynb$/, 2)) add("data", "notebooks");
310
+ if (has2("dvc.yaml") || has2("MLproject")) add("data", has2("dvc.yaml") ? "dvc.yaml" : "MLproject");
311
+ if (find(dir, /\.tf$/, 2)) add("infra", "Terraform");
312
+ if (has2("Pulumi.yaml")) add("infra", "Pulumi");
313
+ if (find(dir, /^(Chart|kustomization)\.ya?ml$/, 2)) add("infra", "Helm / Kustomize");
314
+ if (pkg && !pkg.private && !pkg.bin && (pkg.main || pkg.exports || pkg.module)) add("library", "package.json exports");
315
+ if (/^\[lib\]/m.test(read(path4.join(dir, "Cargo.toml")))) add("library", "Cargo [lib]");
316
+ if (pkg?.bin) add("app", "CLI (bin)");
317
+ const framework = APP_DEPS.find((d) => deps[d]);
318
+ if (framework) add("app", framework);
319
+ if (has2("manage.py")) add("app", "Django");
320
+ if (has2("main.go")) add("app", "main.go");
321
+ if (has2("Dockerfile")) add("app", "Dockerfile");
322
+ const kinds = Object.keys(found);
323
+ const signals = kinds.flatMap((k) => found[k]);
324
+ const specific = kinds.filter((k) => k !== "app");
325
+ if (!kinds.length) return { kind: "app", unclear: true, signals };
326
+ if (specific.length === 1) return { kind: specific[0], unclear: false, signals };
327
+ if (!specific.length) return { kind: "app", unclear: false, signals };
328
+ return { kind: specific.find((k) => k !== "library") ?? specific[0], unclear: true, signals };
329
+ }
330
+ function findContracts(root2) {
331
+ const hit = find(root2, /(\.proto|^openapi.*\.ya?ml|^openapi.*\.json|^asyncapi.*\.ya?ml)$/i, 3);
332
+ if (!hit) return null;
333
+ const dir = path4.relative(root2, path4.dirname(hit)).split(path4.sep).join("/");
334
+ return dir ? `${dir}/` : path4.basename(hit);
335
+ }
336
+ var looksLikeMicroservices = (root2, projects) => projects >= 2 && (!!findContracts(root2) || !!find(root2, /^(docker-)?compose(\.[\w-]+)?\.ya?ml$/, 0));
337
+ function detectProfile(dir, kind) {
338
+ const has2 = (name) => fs4.existsSync(path4.join(dir, name));
339
+ const pkg = readJson(path4.join(dir, "package.json"));
340
+ if (kind === "library") return { kind, published: !!pkg?.publishConfig || !!pkg?.name && !pkg.private };
341
+ if (kind === "mobile") {
342
+ const platforms = ["ios", "android", "web"].filter((p3) => has2(p3));
343
+ return { kind, platforms: platforms.length ? platforms : ["ios", "android"] };
344
+ }
345
+ if (kind === "data") {
346
+ const experiments = has2("dvc.yaml") ? "dvc" : has2("mlruns") ? "mlflow" : has2("wandb") ? "wandb" : "docs";
347
+ const data = ["data", "datasets"].find(has2);
348
+ return { kind, dataLocation: data ? `${data}/` : "", experiments };
349
+ }
350
+ if (kind === "infra") {
351
+ const envDir = ["envs", "environments", "env"].find((d) => has2(d));
352
+ const envs = envDir ? names(path4.join(dir, envDir)).filter((e) => e.isDirectory()).map((e) => e.name) : [];
353
+ return { kind, environments: envs.length ? envs.join(", ") : "dev, staging, prod" };
354
+ }
355
+ return { kind };
356
+ }
357
+ function kindGuidance(p3) {
358
+ switch (p3.kind) {
359
+ case "library":
360
+ 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." : ""}`;
361
+ case "mobile":
362
+ return `Mobile app (${(p3.platforms ?? []).join(", ") || "platforms not set"}): release and store decisions in \`docs/architecture/releases.md\`.`;
363
+ case "data":
364
+ 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"]}.`;
365
+ case "infra":
366
+ return `Infrastructure (environments: ${p3.environments || "dev, staging, prod"}): one runbook per operation in \`docs/runbooks/\`; changing an environment is a task.`;
367
+ default:
368
+ return null;
369
+ }
370
+ }
371
+ 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.`;
372
+ function existingDocs(root2) {
373
+ const docs = path4.join(root2, "docs");
374
+ const out = [];
375
+ for (const e of names(docs)) {
376
+ if (e.name.startsWith(".") || e.name === "tasks" || e.name === "proposals") continue;
377
+ if (e.isFile() && e.name.toLowerCase().endsWith(".md")) out.push(`docs/${e.name}`);
378
+ else if (e.isDirectory() && find(path4.join(docs, e.name), /\.md$/i, 2)) out.push(`docs/${e.name}/`);
379
+ }
380
+ for (const adr of ["adr", "decisions"]) if (find(path4.join(root2, adr), /\.md$/i, 1)) out.push(`${adr}/`);
381
+ return out.sort();
382
+ }
383
+
384
+ // src/core/scaffold.ts
385
+ import fs5 from "fs";
386
+ import path5 from "path";
387
+ var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
388
+ function fill(text3, vars) {
389
+ return text3.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? "");
390
+ }
391
+ function copyTree(src, dest, vars, created, root2) {
392
+ for (const e of fs5.readdirSync(src, { withFileTypes: true })) {
393
+ const from = path5.join(src, e.name);
394
+ const to = path5.join(dest, fill(e.name, vars));
395
+ if (e.isDirectory()) {
396
+ fs5.mkdirSync(to, { recursive: true });
397
+ copyTree(from, to, vars, created, root2);
398
+ } else if (!fs5.existsSync(to)) {
399
+ fs5.writeFileSync(to, fill(fs5.readFileSync(from, "utf8"), vars));
400
+ created.push(toPosix(path5.relative(root2, to)));
401
+ }
402
+ }
403
+ }
404
+ function createMainDoc(root2, name) {
405
+ const file = path5.join(root2, name);
406
+ if (fs5.existsSync(file)) return;
407
+ const text3 = fs5.readFileSync(path5.join(TEMPLATES_DIR, "main.md"), "utf8");
408
+ fs5.writeFileSync(file, fill(text3, { name: path5.basename(root2), date: today() }));
409
+ }
410
+ function installSkill(root2) {
411
+ const dir = path5.join(root2, ".claude", "skills", "markode");
412
+ fs5.mkdirSync(dir, { recursive: true });
413
+ fs5.copyFileSync(path5.join(TEMPLATES_DIR, "skill", "SKILL.md"), path5.join(dir, "SKILL.md"));
414
+ }
415
+ function ensureGitignore(root2) {
416
+ const file = path5.join(root2, ".gitignore");
417
+ const text3 = fs5.existsSync(file) ? fs5.readFileSync(file, "utf8") : "";
418
+ if (text3.split("\n").some((l) => [".markode", ".markode/", "/.markode", "/.markode/"].includes(l.trim()))) return;
419
+ fs5.writeFileSync(file, text3 + (text3 && !text3.endsWith("\n") ? "\n" : "") + ".markode/\n");
420
+ }
421
+ function hideInVsCode(root2) {
422
+ const file = path5.join(root2, ".vscode", "settings.json");
423
+ let settings = {};
424
+ if (fs5.existsSync(file)) {
425
+ try {
426
+ settings = JSON.parse(fs5.readFileSync(file, "utf8"));
427
+ } catch {
428
+ return false;
429
+ }
430
+ }
431
+ settings["files.exclude"] = { ...settings["files.exclude"] ?? {}, "**/.markode": true };
432
+ fs5.mkdirSync(path5.dirname(file), { recursive: true });
433
+ fs5.writeFileSync(file, JSON.stringify(settings, null, 2) + "\n");
434
+ return true;
435
+ }
436
+ function missingMainDocs(root2) {
437
+ return ["", ...findSubprojects(scanTree(root2))].flatMap((dir) => {
438
+ const doc = missingMainDoc(path5.join(root2, dir));
439
+ return doc ? [{ path: path5.posix.join(dir, doc), pointsTo: doc === "CLAUDE.md" ? "AGENTS.md" : "CLAUDE.md" }] : [];
440
+ });
441
+ }
442
+ function linkMainDocs(root2) {
443
+ return missingMainDocs(root2).flatMap((m) => linkMainDoc(path5.join(root2, path5.posix.dirname(m.path))) ? [m.path] : []);
444
+ }
445
+
446
+ // src/core/sdd.ts
447
+ import { spawnSync } from "child_process";
448
+ import fs6 from "fs";
449
+ import path6 from "path";
450
+ var SDD_AGENTS = {
451
+ claude: { label: "Claude Code", openspec: "claude", speckit: "claude" },
452
+ codex: { label: "Codex", openspec: "codex", speckit: "codex" },
453
+ copilot: { label: "GitHub Copilot", openspec: "github-copilot", speckit: "copilot" },
454
+ gemini: { label: "Gemini CLI", openspec: "gemini", speckit: "gemini" },
455
+ cursor: { label: "Cursor", openspec: "cursor", speckit: "cursor-agent" }
456
+ };
457
+ var OPENSPEC_PKG = "@fission-ai/openspec@latest";
458
+ var SPECKIT_SRC = "git+https://github.com/github/spec-kit.git";
459
+ function detectWorkflow(root2) {
460
+ if (fs6.existsSync(path6.join(root2, "openspec"))) return "openspec";
461
+ if (fs6.existsSync(path6.join(root2, ".specify"))) return "speckit";
462
+ return null;
463
+ }
464
+ var has = (cmd) => spawnSync(cmd, ["--version"], { stdio: "ignore", shell: process.platform === "win32" }).status === 0;
465
+ var hasUv = () => has("uvx");
466
+ function installCommand(workflow, agents) {
467
+ if (workflow === "openspec") {
468
+ const tools = agents.map((a) => SDD_AGENTS[a].openspec).join(",") || "none";
469
+ const runner = has("pnpm") ? ["pnpm", "dlx"] : has("npx") ? ["npx", "--yes"] : null;
470
+ return runner && [...runner, OPENSPEC_PKG, "init", "--tools", tools, "--no-animation", "--no-copilot-cloud"];
471
+ }
472
+ if (!hasUv()) return null;
473
+ return ["uvx", "--from", SPECKIT_SRC, "specify", "init", "--here", "--force", "--non-interactive", "--ignore-agent-tools", "--integration", SDD_AGENTS[agents[0] ?? "claude"].speckit];
474
+ }
475
+ function installWorkflow(root2, workflow, agents, official) {
476
+ const before = new Set(flattenFiles(scanTree(root2), true).map((f) => f.path));
477
+ const result = { official: false, created: [], ignored: [] };
478
+ const cmd = official ? installCommand(workflow, agents) : null;
479
+ if (cmd) {
480
+ const run = spawnSync(cmd[0], cmd.slice(1), { cwd: root2, encoding: "utf8", timeout: 3e5, shell: process.platform === "win32", env: { ...process.env, NO_COLOR: "1" } });
481
+ if (run.status === 0) result.official = true;
482
+ else result.error = (run.error?.message ?? `${run.stderr ?? ""}
483
+ ${run.stdout ?? ""}`).trim().split("\n").slice(-6).join("\n");
484
+ }
485
+ copyTree(path6.join(TEMPLATES_DIR, "sdd", workflow), root2, { date: today2() }, result.created, root2);
486
+ const dirs2 = /* @__PURE__ */ new Set();
487
+ for (const f of flattenFiles(scanTree(root2), true)) {
488
+ if (before.has(f.path) || !f.path.startsWith(".") || f.path.startsWith(".specify/memory/")) continue;
489
+ dirs2.add(path6.posix.dirname(f.path));
490
+ }
491
+ for (const dir of dirs2) setIgnored(root2, dir, "dir", true);
492
+ result.ignored = [...dirs2].sort();
493
+ return result;
494
+ }
495
+ var today2 = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
496
+ var WORKFLOW_TEXT = {
497
+ openspec: [
498
+ "## Workflow",
499
+ "",
500
+ "This project uses [OpenSpec](https://github.com/Fission-AI/OpenSpec) (spec-driven development; guide: `openspec/README.md`):",
501
+ "",
502
+ "- `openspec/specs/<domain>/spec.md` describes how the system behaves today.",
503
+ "- Every change starts in `openspec/changes/<change-id>/` with `proposal.md`, `tasks.md`, an optional `design.md` and delta specs, before any code.",
504
+ "- When it is done, archive it so its delta specs merge into `openspec/specs/`.",
505
+ "- Use the `/opsx:*` commands (or the `openspec` CLI) when available."
506
+ ].join("\n"),
507
+ speckit: [
508
+ "## Workflow",
509
+ "",
510
+ "This project uses [GitHub Spec Kit](https://github.com/github/spec-kit) (spec-driven development; guide: `specs/README.md`):",
511
+ "",
512
+ "- Principles every change must respect: `.specify/memory/constitution.md`.",
513
+ "- One folder per feature in `specs/NNN-feature-name/`: `spec.md` (what and why), then `plan.md` (how), then `tasks.md`.",
514
+ "- Use the `/speckit-*` commands when available."
515
+ ].join("\n")
516
+ };
517
+ function applyWorkflowToFile(file, workflow, text3) {
518
+ writeManagedBlock(file, workflow === "none" ? null : text3 ?? (workflow === "markode" ? null : WORKFLOW_TEXT[workflow]));
519
+ }
520
+ function writeManagedBlock(file, body, top = false, name = "workflow") {
521
+ const START2 = `<!-- markode:${name}:start -->`;
522
+ const END2 = `<!-- markode:${name}:end -->`;
523
+ const text3 = fs6.readFileSync(file, "utf8");
524
+ const s = text3.indexOf(START2);
525
+ const e = text3.indexOf(END2);
526
+ const existing = s >= 0 && e > s ? text3.slice(s, e + END2.length) : null;
527
+ if (body === null) {
528
+ if (existing) fs6.writeFileSync(file, text3.replace(new RegExp(`\\n*${escapeRe(existing)}\\n?`), "\n"));
529
+ return;
530
+ }
531
+ const section2 = `${START2}
532
+ ${body}
533
+ ${END2}`;
534
+ if (existing) return fs6.writeFileSync(file, text3.replace(existing, () => section2));
535
+ const title = top ? /^# .*\n/m.exec(text3) : null;
536
+ if (title) return fs6.writeFileSync(file, text3.slice(0, title.index + title[0].length) + "\n" + section2 + "\n" + text3.slice(title.index + title[0].length));
537
+ fs6.writeFileSync(file, text3.replace(/\s*$/, "") + "\n\n" + section2 + "\n");
538
+ }
539
+ var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
540
+ var dirs = (abs) => {
541
+ try {
542
+ return fs6.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
543
+ } catch {
544
+ return [];
545
+ }
546
+ };
547
+ var read2 = (abs) => {
548
+ try {
549
+ return fs6.readFileSync(abs, "utf8");
550
+ } catch {
551
+ return null;
552
+ }
553
+ };
554
+ var progress = (text3) => ({ done: text3?.match(/^\s*[-*+] \[[xX]\]/gm)?.length ?? 0, total: text3?.match(/^\s*[-*+] \[[ xX]\]/gm)?.length ?? 0 });
555
+ var heading = (text3) => text3?.match(/^#\s+(.+)$/m)?.[1].trim() ?? null;
556
+ var countSpecs = (abs) => {
557
+ try {
558
+ return fs6.readdirSync(abs, { recursive: true }).filter((f) => String(f).endsWith("spec.md")).length;
559
+ } catch {
560
+ return 0;
561
+ }
562
+ };
563
+ function summarizeSdd(root2) {
564
+ const out = {};
565
+ const os3 = path6.join(root2, "openspec");
566
+ if (fs6.existsSync(os3)) {
567
+ const rel = (...p3) => toPosix(path6.join("openspec", ...p3));
568
+ out.openspec = {
569
+ changes: dirs(path6.join(os3, "changes")).filter((id) => id !== "archive").map((id) => {
570
+ const dir = path6.join(os3, "changes", id);
571
+ const proposal = read2(path6.join(dir, "proposal.md"));
572
+ const tasks = read2(path6.join(dir, "tasks.md"));
573
+ return {
574
+ id,
575
+ // "Proposal: Add login" → "Add login"
576
+ title: heading(proposal)?.replace(/^(change\s+)?proposal:\s*/i, "") ?? id,
577
+ path: rel("changes", id, proposal !== null ? "proposal.md" : "tasks.md"),
578
+ artifacts: { proposal: proposal !== null, design: fs6.existsSync(path6.join(dir, "design.md")), tasks: tasks !== null, specs: countSpecs(path6.join(dir, "specs")) },
579
+ tasks: progress(tasks)
580
+ };
581
+ }),
582
+ specs: dirs(path6.join(os3, "specs")).filter((id) => fs6.existsSync(path6.join(os3, "specs", id, "spec.md"))).map((id) => ({ id, path: rel("specs", id, "spec.md") })),
583
+ archived: dirs(path6.join(os3, "changes", "archive")).reverse().slice(0, 12).map((id) => {
584
+ const file = ["proposal.md", "tasks.md"].find((f) => fs6.existsSync(path6.join(os3, "changes", "archive", id, f)));
585
+ return { id, path: file ? rel("changes", "archive", id, file) : "" };
586
+ })
587
+ };
588
+ }
589
+ if (fs6.existsSync(path6.join(root2, ".specify"))) {
590
+ const constitution = read2(path6.join(root2, ".specify", "memory", "constitution.md"));
591
+ out.speckit = {
592
+ // The template is full of [PLACEHOLDERS] until /speckit-constitution fills it.
593
+ constitution: constitution === null ? null : { path: ".specify/memory/constitution.md", filled: !/\[[A-Z_0-9]+\]/.test(constitution) },
594
+ features: dirs(path6.join(root2, "specs")).map((id) => {
595
+ const dir = path6.join(root2, "specs", id);
596
+ const spec = read2(path6.join(dir, "spec.md"));
597
+ const tasks = read2(path6.join(dir, "tasks.md"));
598
+ return {
599
+ id,
600
+ // "Feature Specification: Login" → "Login"
601
+ title: heading(spec)?.replace(/^feature specification:\s*/i, "") ?? id,
602
+ path: toPosix(path6.join("specs", id, spec !== null ? "spec.md" : "plan.md")),
603
+ artifacts: { spec: spec !== null, plan: fs6.existsSync(path6.join(dir, "plan.md")), tasks: tasks !== null },
604
+ tasks: progress(tasks)
605
+ };
606
+ })
607
+ };
608
+ }
609
+ return out;
610
+ }
611
+ function sddPrefixes(summary) {
612
+ return [...summary.openspec ? ["openspec/"] : [], ...summary.speckit ? ["specs/", ".specify/"] : []];
613
+ }
614
+
615
+ // src/core/method.ts
616
+ var SKIP2 = /* @__PURE__ */ new Set(["docs", "node_modules", "dist", "build", "coverage", "tmp", "vendor", "target"]);
617
+ var MANIFESTS = ["package.json", "pyproject.toml", "go.mod", "Cargo.toml", "pom.xml", "build.gradle", "build.gradle.kts", "composer.json", "Gemfile", "deno.json", ".git"];
618
+ var isDir = (abs) => !!fs7.statSync(abs, { throwIfNoEntry: false })?.isDirectory();
619
+ var subdirs = (abs) => {
620
+ try {
621
+ return fs7.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SKIP2.has(e.name)).map((e) => e.name);
622
+ } catch {
623
+ return [];
624
+ }
625
+ };
626
+ var isProject = (abs) => MANIFESTS.some((m) => fs7.existsSync(path7.join(abs, m)));
627
+ function detectAreas(root2) {
628
+ const globs = [];
629
+ try {
630
+ const ws = YAML2.parse(fs7.readFileSync(path7.join(root2, "pnpm-workspace.yaml"), "utf8"), { logLevel: "error" });
631
+ if (Array.isArray(ws?.packages)) globs.push(...ws.packages);
632
+ } catch {
633
+ }
634
+ try {
635
+ const ws = JSON.parse(fs7.readFileSync(path7.join(root2, "package.json"), "utf8")).workspaces;
636
+ globs.push(...Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : []);
637
+ } catch {
638
+ }
639
+ const found = new Set(findSubprojects(scanTree(root2)));
640
+ for (const glob of globs) {
641
+ const g = String(glob).replace(/^\.\//, "").replace(/\/+$/, "");
642
+ if (g.startsWith("!")) continue;
643
+ const base = g.replace(/\/\*\*?$/, "");
644
+ if (base.includes("*")) continue;
645
+ if (base === g) found.add(g);
646
+ else for (const name of subdirs(path7.join(root2, base))) found.add(`${base}/${name}`);
647
+ }
648
+ if (!isProject(root2)) {
649
+ for (const top of subdirs(root2)) {
650
+ if (isProject(path7.join(root2, top))) found.add(top);
651
+ else for (const name of subdirs(path7.join(root2, top))) if (isProject(path7.join(root2, top, name))) found.add(`${top}/${name}`);
652
+ }
653
+ }
654
+ let unsure = false;
655
+ if (!globs.length && isProject(root2)) {
656
+ const tops = subdirs(root2).filter((top) => isProject(path7.join(root2, top)));
657
+ if (tops.length >= 2 && tops.some((t) => !found.has(t))) tops.forEach((t) => found.add(t)), unsure = true;
658
+ }
659
+ const dirs2 = [...found].filter((d) => d && !d.split("/").some((part) => part.startsWith(".") || SKIP2.has(part)) && isDir(path7.join(root2, d)));
660
+ return { dirs: dirs2.filter((d) => !dirs2.some((o) => o.startsWith(d + "/"))).sort(), unsure };
661
+ }
662
+ function areaDescription(root2, dir) {
663
+ try {
664
+ return String(JSON.parse(fs7.readFileSync(path7.join(root2, dir, "package.json"), "utf8")).description ?? "").trim();
665
+ } catch {
666
+ return "";
667
+ }
668
+ }
669
+ var KNOWN_PREFIXES = {
670
+ apps: "APP",
671
+ app: "APP",
672
+ services: "SVC",
673
+ service: "SVC",
674
+ packages: "PKG",
675
+ libs: "LIB",
676
+ lib: "LIB",
677
+ infra: "INF",
678
+ infrastructure: "INF",
679
+ proto: "PRO",
680
+ backend: "BE",
681
+ frontend: "FE",
682
+ web: "WEB",
683
+ api: "API",
684
+ mobile: "MOB",
685
+ tools: "TLS",
686
+ scripts: "SCR"
687
+ };
688
+ function suggestPrefix(dir, used) {
689
+ dir = path7.posix.basename(dir);
690
+ const base = KNOWN_PREFIXES[dir.toLowerCase()] ?? (dir.replace(/[^a-z0-9]/gi, "").slice(0, 3).toUpperCase() || "AREA");
691
+ let prefix = base;
692
+ for (let i = 2; used.has(prefix) || prefix === "CORE"; i++) prefix = `${base}${i}`;
693
+ used.add(prefix);
694
+ return prefix;
695
+ }
696
+ var rootPrefix = (areas) => areas.length ? "CORE" : "T";
697
+ function scaffoldMethod(root2, mainDoc, areas) {
698
+ const created = [];
699
+ const name = path7.basename(root2);
700
+ copyTree(path7.join(TEMPLATES_DIR, "method", "root"), root2, { date: today(), name, prefix: rootPrefix(areas), area: areas.length ? "core" : name }, created, root2);
701
+ for (const a of areas) {
702
+ const vars = { date: today(), name, prefix: a.prefix, area: a.dir, up: "../".repeat(a.dir.split("/").length) };
703
+ const dir = path7.join(root2, a.dir);
704
+ copyTree(path7.join(TEMPLATES_DIR, "method", "area"), dir, vars, created, root2);
705
+ let doc = contentDoc(dir);
706
+ if (!doc) {
707
+ doc = path7.join(dir, mainDoc);
708
+ fs7.writeFileSync(doc, fill(fs7.readFileSync(path7.join(TEMPLATES_DIR, "method", "area-main.md"), "utf8"), vars));
709
+ created.push(path7.posix.join(a.dir, mainDoc));
710
+ }
711
+ if (claudeUsed(root2, mainDoc) && linkAgentsDoc(dir)) created.push(path7.posix.join(a.dir, "CLAUDE.md"));
712
+ writeManagedBlock(doc, areaBlock(a, vars.up, mainDoc), true);
713
+ }
714
+ return created;
715
+ }
716
+ var claudeUsed = (root2, mainDoc) => mainDoc === "CLAUDE.md" || fs7.existsSync(path7.join(root2, "CLAUDE.md"));
717
+ function areaBlock(a, up, mainDoc) {
718
+ const guidance = a.profile && kindGuidance(a.profile);
719
+ return [
720
+ `- Tasks: one file per task in \`docs/tasks/\`, prefix \`${a.prefix}\`; the status is in each file's frontmatter`,
721
+ ...guidance ? [`- ${guidance}`] : [],
722
+ `- Decisions of this project: \`docs/ARCHITECTURE.md\` (created with the first one) \xB7 shared by several projects: \`${up}docs/architecture/\``,
723
+ `- Overview of the repository and the other projects: \`${up}${mainDoc}\``
724
+ ].join("\n");
725
+ }
726
+ function methodSection(areas, root2, ctx = {}) {
727
+ const taskDirs = ["docs/tasks", ...areas.map((a) => `${a.dir}/docs/tasks`)].join(" ");
728
+ const map = areas.length ? [
729
+ "",
730
+ "### Projects",
731
+ "",
732
+ "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.",
733
+ "",
734
+ "| Project | Kind | What it is | Docs | Task prefix |",
735
+ "|---|---|---|---|---|",
736
+ ...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} |`),
737
+ `| \`docs/\` | | Vision, shared architecture, phases and tasks across projects | \`docs/\` | ${rootPrefix(areas)} |`,
738
+ ...ctx.contracts ? ["", contractsGuidance(ctx.contracts)] : []
739
+ ] : [];
740
+ const guidance = !areas.length && ctx.profile && kindGuidance(ctx.profile);
741
+ const about = guidance ? ["", "### This project", "", guidance] : [];
742
+ 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}\``)] : [];
743
+ return [
744
+ "## Workflow (markode method)",
745
+ "",
746
+ "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/`.",
747
+ ...about,
748
+ ...map,
749
+ ...existing,
750
+ "",
751
+ "### Session start",
752
+ "",
753
+ "1. `/markode sync`: apply proposals the user answered while no session was open, and fix what is out of date.",
754
+ `2. Find tasks in progress: \`grep -rl "^status: in-progress" --include=*.md ${taskDirs}\`.`,
755
+ '3. Open the one with the most recent `updated`, read its "Next step" and tell the user in one line where we left off.',
756
+ "4. If there are none, ask the user what to work on.",
757
+ "",
758
+ "When the user asks what is pending or where things stand: `/markode-status`.",
759
+ "",
760
+ "### During the session",
761
+ "",
762
+ `- 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.`,
763
+ '- Tick acceptance criteria as they are met and keep "Next step" current.',
764
+ "- Before replacing a decision, propose it with the reasons; then update it in its one place.",
765
+ "",
766
+ "### Tasks",
767
+ "",
768
+ "- The user defines tasks: propose them, but create them only when asked (`/markode-new-task`).",
769
+ `- 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.`,
770
+ "- At the end of a session: `/markode-close-session` (it ends with `/markode sync`).",
771
+ "",
772
+ "### Documentation without redundancy",
773
+ "",
774
+ "- Write only what the code can't tell: decisions and why, hidden conventions, the state of the work.",
775
+ "- Each fact lives in one place; everything else links to it.",
776
+ 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.",
777
+ "- Main docs hold orientation, rules and commands only: never decisions or task lists."
778
+ ].join("\n");
779
+ }
780
+ function installMethodSkills(root2) {
781
+ const src = path7.join(TEMPLATES_DIR, "method", "skills");
782
+ for (const name of fs7.readdirSync(src)) fs7.cpSync(path7.join(src, name), path7.join(root2, ".claude", "skills", name), { recursive: true });
783
+ }
784
+
785
+ // src/core/check.ts
786
+ var TASK_LINE = /^\s*[-*+] \[([ xX])\] .*?\[[^\]]*\]\(([^)\s]+)\)/;
787
+ var LINK = /!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
788
+ function checkDocs(root2) {
789
+ const docs = summarize(root2);
790
+ const status = new Map(docs.map((d) => [d.path, d.status ?? ""]));
791
+ const read3 = (rel) => fs8.readFileSync(path8.join(root2, rel), "utf8");
792
+ const phases = [];
793
+ for (const phase of docs.filter((d) => d.type === "phase")) {
794
+ for (const line of read3(phase.path).split("\n")) {
795
+ const m = TASK_LINE.exec(line);
796
+ if (!m) continue;
797
+ const task = path8.posix.normalize(path8.posix.join(path8.posix.dirname(phase.path), m[2].split("#")[0]));
798
+ const s = status.get(task);
799
+ if (s === void 0) continue;
800
+ const ticked = m[1] !== " ";
801
+ if (ticked !== (s === "done")) phases.push({ phase: phase.path, task, ticked, status: s });
802
+ }
803
+ }
804
+ const links = [];
805
+ for (const doc of docs) {
806
+ let fenced = false;
807
+ read3(doc.path).split("\n").forEach((line, i) => {
808
+ if (/^\s*(```|~~~)/.test(line)) fenced = !fenced;
809
+ if (fenced) return;
810
+ for (const m of line.matchAll(LINK)) {
811
+ const target = m[1];
812
+ if (/^[a-z][\w+.-]*:|^#|^\/|[<{]/i.test(target)) continue;
813
+ const file = decodeURIComponent(target.split(/[#?]/)[0]);
814
+ if (file && !fs8.existsSync(path8.join(root2, path8.dirname(doc.path), file))) links.push({ file: doc.path, line: i + 1, target });
815
+ }
816
+ });
817
+ }
818
+ return { proposals: docs.filter((d) => d.type === "proposal" && d.status === "answered").map((d) => d.path), phases, projects: projectsTable(root2), links };
819
+ }
820
+ function projectsTable(root2) {
821
+ const doc = mainDocOf(root2);
822
+ if (!doc) return null;
823
+ const text3 = fs8.readFileSync(path8.join(root2, doc), "utf8");
824
+ const section2 = /^### Projects\n([\s\S]*?)(?=^#{1,3} |<!-- markode:workflow:end -->)/m.exec(text3)?.[1];
825
+ if (!section2) return null;
826
+ const listed = [...section2.matchAll(/^\|\s*`([^`]+?)\/?`\s*\|/gm)].map((m) => m[1]).filter((d) => d !== "docs");
827
+ const found = detectAreas(root2).dirs;
828
+ return { missing: found.filter((d) => !listed.includes(d)), gone: listed.filter((d) => !fs8.existsSync(path8.join(root2, d))) };
829
+ }
830
+
831
+ // src/core/defaults.ts
832
+ import fs9 from "fs";
833
+ import path9 from "path";
834
+ var BASE_DEFAULTS = {
835
+ mode: "ask",
836
+ mainDoc: "CLAUDE.md",
837
+ workflow: "none",
838
+ agents: ["claude"],
839
+ monorepo: "per-project",
840
+ sourceLang: "en",
841
+ rules: [],
842
+ rulesInSubprojects: false,
843
+ hideInVsCode: true
844
+ };
845
+ var defaultsFile = () => path9.join(GLOBAL_DIR, "defaults.json");
846
+ function readDefaults() {
847
+ try {
848
+ const saved = { ...BASE_DEFAULTS, ...JSON.parse(fs9.readFileSync(defaultsFile(), "utf8")) };
849
+ if (!isLang(saved.sourceLang)) saved.sourceLang = BASE_DEFAULTS.sourceLang;
850
+ if (!MAIN_DOCS.includes(saved.mainDoc)) saved.mainDoc = BASE_DEFAULTS.mainDoc;
851
+ if (saved.monorepo !== "single") saved.monorepo = "per-project";
852
+ return saved;
853
+ } catch {
854
+ return null;
855
+ }
856
+ }
857
+ function writeDefaults(defaults2) {
858
+ fs9.mkdirSync(GLOBAL_DIR, { recursive: true });
859
+ fs9.writeFileSync(defaultsFile(), JSON.stringify(defaults2, null, 2) + "\n");
860
+ }
861
+
862
+ // src/core/i18n.ts
863
+ import crypto from "crypto";
864
+ import fs11 from "fs";
865
+ import path11 from "path";
866
+
867
+ // src/core/langdetect.ts
868
+ var STOPWORDS = {
869
+ 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"],
870
+ 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"],
871
+ 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"],
872
+ 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"],
873
+ 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"],
874
+ 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"]
875
+ };
876
+ var SETS = Object.fromEntries(Object.entries(STOPWORDS).map(([l, w]) => [l, new Set(w)]));
877
+ function prose(md) {
878
+ return md.replace(/^---\r?\n[\s\S]*?\r?\n---/, " ").replace(/```[\s\S]*?```/g, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/`[^`]*`/g, " ").replace(/\]\([^)]*\)/g, " ").replace(/https?:\/\/\S+/g, " ").replace(/[|#>*_\-[\]()]/g, " ");
879
+ }
880
+ function detectLang(md) {
881
+ const words = prose(md).toLowerCase().match(/[\p{L}]+/gu) ?? [];
882
+ if (words.length < 25) return null;
883
+ const scores = Object.keys(SETS).map((lang) => ({ lang, hits: words.reduce((n, w) => n + (SETS[lang].has(w) ? 1 : 0), 0) }));
884
+ scores.sort((a, b) => b.hits - a.hits);
885
+ const [best, second] = scores;
886
+ if (best.hits < 6 || best.hits < second.hits * 1.5) return null;
887
+ return best.lang;
888
+ }
889
+
890
+ // src/core/rules.ts
891
+ import fs10 from "fs";
892
+ import path10 from "path";
893
+ import YAML3 from "yaml";
894
+ var rulesDir = () => path10.join(GLOBAL_DIR, "rules");
895
+ var START = "<!-- markode:rules:start -->";
896
+ var END = "<!-- markode:rules:end -->";
897
+ var slugify = (s) => s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
898
+ function seedRules() {
899
+ if (fs10.existsSync(rulesDir())) return;
900
+ fs10.mkdirSync(rulesDir(), { recursive: true });
901
+ const src = path10.join(TEMPLATES_DIR, "rules");
902
+ for (const f of fs10.readdirSync(src)) fs10.copyFileSync(path10.join(src, f), path10.join(rulesDir(), f));
903
+ }
904
+ function parseRule(id, text3) {
905
+ const m = text3.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
906
+ const meta = m ? YAML3.parse(m[1], { logLevel: "error" }) ?? {} : {};
907
+ return { id, title: meta.title ?? id, body: (m ? m[2] : text3).trim() };
908
+ }
909
+ function listRules() {
910
+ seedRules();
911
+ return fs10.readdirSync(rulesDir()).filter((f) => f.endsWith(".md")).map((f) => parseRule(f.slice(0, -3), fs10.readFileSync(path10.join(rulesDir(), f), "utf8"))).sort((a, b) => a.title.localeCompare(b.title));
912
+ }
913
+ function saveRule(rule) {
914
+ seedRules();
915
+ const id = rule.id || slugify(rule.title) || `rule-${Date.now()}`;
916
+ if (!/^[\w-]+$/.test(id)) throw new Error(`Invalid rule id: ${id}`);
917
+ const text3 = `---
918
+ ${YAML3.stringify({ title: rule.title }).trim()}
919
+ ---
920
+ ${rule.body.trim()}
921
+ `;
922
+ fs10.writeFileSync(path10.join(rulesDir(), `${id}.md`), text3);
923
+ return { id, title: rule.title, body: rule.body.trim() };
924
+ }
925
+ function deleteRule(id) {
926
+ fs10.rmSync(path10.join(rulesDir(), `${path10.basename(id)}.md`), { force: true });
927
+ }
928
+ function appliedRuleIds(docText) {
929
+ const section2 = extractSection(docText);
930
+ if (!section2) return [];
931
+ return [...section2.matchAll(/<!-- rule:([\w-]+) -->/g)].map((m) => m[1]);
932
+ }
933
+ function extractSection(text3) {
934
+ const s = text3.indexOf(START);
935
+ const e = text3.indexOf(END);
936
+ return s >= 0 && e > s ? text3.slice(s, e + END.length) : null;
937
+ }
938
+ function writtenRules(docText) {
939
+ const section2 = extractSection(docText) ?? "";
940
+ const out = {};
941
+ 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() };
942
+ return out;
943
+ }
944
+ function sourceLangOf(docText) {
945
+ const m = /<!-- markode:source-lang:(\w+) -->/.exec(extractSection(docText) ?? "");
946
+ return m && isLang(m[1]) ? m[1] : DEFAULT_SOURCE;
947
+ }
948
+ function projectSourceLang(root2, mainDoc) {
949
+ const doc = mainDoc ?? mainDocOf(root2);
950
+ if (!doc) return DEFAULT_SOURCE;
951
+ try {
952
+ return sourceLangOf(fs10.readFileSync(path10.join(root2, doc), "utf8"));
953
+ } catch {
954
+ return DEFAULT_SOURCE;
955
+ }
956
+ }
957
+ function renderSection(rules, sourceLang) {
958
+ const body = rules.map((r) => `<!-- rule:${r.id} -->
959
+ ${r.body}`).join("\n\n");
960
+ return [
961
+ START,
962
+ "## Working rules",
963
+ "",
964
+ "This project uses markode: follow `.claude/skills/markode/SKILL.md` when editing any `.md` file.",
965
+ "",
966
+ `<!-- markode:source-lang:${sourceLang} -->`,
967
+ `- Source language of every \`.md\` file: **${LANG_NAMES[sourceLang]}** (\`${sourceLang}\`).`,
968
+ ...body ? ["", body] : [],
969
+ END
970
+ ].join("\n");
971
+ }
972
+ function applyRules(docText, rules, sourceLang = sourceLangOf(docText)) {
973
+ const section2 = renderSection(rules, sourceLang);
974
+ const existing = extractSection(docText);
975
+ if (existing) return docText.replace(existing, () => section2);
976
+ return docText.replace(/\s*$/, "") + "\n\n" + section2 + "\n";
977
+ }
978
+ function applyRulesToFile(file, ids, sourceLang) {
979
+ const text3 = fs10.existsSync(file) ? fs10.readFileSync(file, "utf8") : "";
980
+ const all = listRules();
981
+ const written = writtenRules(text3);
982
+ const rules = (ids ?? appliedRuleIds(text3)).map((id) => all.find((r) => r.id === id) ?? written[id]).filter((r) => !!r);
983
+ fs10.writeFileSync(file, applyRules(text3, rules, sourceLang ?? sourceLangOf(text3)));
984
+ }
985
+
986
+ // src/core/i18n.ts
987
+ var stateFile = (root2) => path11.join(stateDir(root2), "state.json");
988
+ var translationPath = (root2, lang, rel) => path11.join(stateDir(root2), "i18n", lang, rel);
989
+ var hash = (text3) => crypto.createHash("sha1").update(text3).digest("hex").slice(0, 12);
990
+ var readText2 = (abs) => {
991
+ try {
992
+ return fs11.readFileSync(abs, "utf8");
993
+ } catch {
994
+ return null;
995
+ }
996
+ };
997
+ function readState(root2) {
998
+ try {
999
+ const raw = JSON.parse(fs11.readFileSync(stateFile(root2), "utf8"));
1000
+ if (!raw.translations) return { translations: raw, keepLanguage: {} };
1001
+ return { translations: raw.translations ?? {}, keepLanguage: raw.keepLanguage ?? {} };
1002
+ } catch {
1003
+ return { translations: {}, keepLanguage: {} };
1004
+ }
1005
+ }
1006
+ function writeState(root2, state) {
1007
+ fs11.mkdirSync(stateDir(root2), { recursive: true });
1008
+ fs11.writeFileSync(stateFile(root2), JSON.stringify(state, null, 2) + "\n");
1009
+ }
1010
+ function fileStatus(root2, lang, rel) {
1011
+ const source = readText2(path11.join(root2, rel));
1012
+ const translation = readText2(translationPath(root2, lang, rel));
1013
+ if (translation === null || source === null) return "missing";
1014
+ const recorded = readState(root2).translations[lang]?.[rel];
1015
+ if (!recorded) return "outdated";
1016
+ const sourceChanged = hash(source) !== recorded.source;
1017
+ const translationChanged = hash(translation) !== recorded.translation;
1018
+ if (sourceChanged && translationChanged) return "conflict";
1019
+ if (sourceChanged) return "outdated";
1020
+ if (translationChanged) return "pending-sync";
1021
+ return "synced";
1022
+ }
1023
+ function allStatuses(root2, lang) {
1024
+ return flattenFiles(scanTree(root2)).map((f) => ({
1025
+ path: f.path,
1026
+ lang,
1027
+ status: fileStatus(root2, lang, f.path),
1028
+ translation: path11.relative(root2, translationPath(root2, lang, f.path))
1029
+ }));
1030
+ }
1031
+ function foreignFiles(root2, sourceLang) {
1032
+ const { keepLanguage: keepLanguage2 } = readState(root2);
1033
+ const out = [];
1034
+ for (const f of flattenFiles(scanTree(root2))) {
1035
+ const text3 = readText2(path11.join(root2, f.path));
1036
+ const detected = text3 ? detectLang(text3) : null;
1037
+ if (detected && detected !== sourceLang && keepLanguage2[f.path] !== detected) out.push({ path: f.path, detected });
1038
+ }
1039
+ return out;
1040
+ }
1041
+ function syncReport(root2) {
1042
+ const config = readConfig(root2);
1043
+ const sourceLang = projectSourceLang(root2, config?.mainDoc);
1044
+ const langs = (config?.langs ?? []).filter((l) => l !== sourceLang);
1045
+ return {
1046
+ sourceLang,
1047
+ langs,
1048
+ /** Language names by code: translate into these (codes added by hand have no built-in name). */
1049
+ names: Object.fromEntries([sourceLang, ...langs].map((l) => [l, langName(l, config)])),
1050
+ foreign: foreignFiles(root2, sourceLang),
1051
+ files: langs.flatMap((l) => allStatuses(root2, l))
1052
+ };
1053
+ }
1054
+ function markSynced(root2, lang, rel) {
1055
+ const source = readText2(path11.join(root2, rel));
1056
+ const translation = readText2(translationPath(root2, lang, rel));
1057
+ if (source === null) throw new Error(`Source not found: ${rel}`);
1058
+ if (translation === null) throw new Error(`Translation not found: ${path11.relative(root2, translationPath(root2, lang, rel))}`);
1059
+ const state = readState(root2);
1060
+ state.translations[lang] ??= {};
1061
+ state.translations[lang][rel] = { source: hash(source), translation: hash(translation) };
1062
+ writeState(root2, state);
1063
+ }
1064
+ function keepLanguage(root2, rel) {
1065
+ const text3 = readText2(path11.join(root2, rel));
1066
+ const detected = text3 ? detectLang(text3) : null;
1067
+ if (!detected) throw new Error(`Could not detect the language of ${rel}`);
1068
+ const state = readState(root2);
1069
+ state.keepLanguage[rel] = detected;
1070
+ writeState(root2, state);
1071
+ return detected;
1072
+ }
1073
+
1074
+ // src/core/wait.ts
1075
+ import fs12 from "fs";
1076
+ import path12 from "path";
1077
+ import YAML4 from "yaml";
1078
+ var waitsDir = (root2) => path12.join(stateDir(root2), "waits");
1079
+ var pidFile = (root2, rel) => path12.join(waitsDir(root2), `${encodeURIComponent(rel)}.pid`);
1080
+ var frontmatter = (text3) => {
1081
+ try {
1082
+ return YAML4.parse(/^---\r?\n([\s\S]*?)\r?\n---/.exec(text3)?.[1] ?? "", { logLevel: "error" }) ?? {};
1083
+ } catch {
1084
+ return {};
1085
+ }
1086
+ };
1087
+ var isAlive = (pid) => {
1088
+ try {
1089
+ process.kill(pid, 0);
1090
+ return true;
1091
+ } catch {
1092
+ return false;
1093
+ }
1094
+ };
1095
+ function waitingOn(root2) {
1096
+ let names2 = [];
1097
+ try {
1098
+ names2 = fs12.readdirSync(waitsDir(root2)).filter((f) => f.endsWith(".pid"));
1099
+ } catch {
1100
+ }
1101
+ return names2.flatMap((f) => {
1102
+ const pid = Number(fs12.readFileSync(path12.join(waitsDir(root2), f), "utf8"));
1103
+ return isAlive(pid) ? [decodeURIComponent(f.slice(0, -4))] : [];
1104
+ });
1105
+ }
1106
+ function waitForAnswer(root2, rel) {
1107
+ const abs = path12.join(root2, rel);
1108
+ fs12.mkdirSync(waitsDir(root2), { recursive: true });
1109
+ fs12.writeFileSync(pidFile(root2, rel), String(process.pid));
1110
+ const cleanup = () => fs12.rmSync(pidFile(root2, rel), { force: true });
1111
+ process.once("exit", cleanup);
1112
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.once(sig, () => process.exit(130));
1113
+ return new Promise((resolve) => {
1114
+ const poll = () => {
1115
+ let text3;
1116
+ try {
1117
+ text3 = fs12.readFileSync(abs, "utf8");
1118
+ } catch {
1119
+ return resolve("gone");
1120
+ }
1121
+ const fm = frontmatter(text3);
1122
+ if (fm.type !== "proposal") return resolve("gone");
1123
+ if (fm.status === "answered") return resolve("answered");
1124
+ setTimeout(poll, 1500);
1125
+ };
1126
+ poll();
1127
+ });
1128
+ }
1129
+ var COMMENT = /^\s*<!-- (comment|reply) by (.+?), (\d{4}-\d{2}-\d{2})(?:, on "((?:[^"\\]|\\.)*)")?:\s?([\s\S]*?)\s*-->\s*$/;
1130
+ function answerSummary(root2, rel) {
1131
+ const text3 = fs12.readFileSync(path12.join(root2, rel), "utf8");
1132
+ const fm = frontmatter(text3);
1133
+ const body = text3.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
1134
+ const lines = body.split("\n");
1135
+ const chosen = [];
1136
+ const unanswered = [];
1137
+ const comments = [];
1138
+ const after = [];
1139
+ let heading2 = "";
1140
+ let question = "";
1141
+ let group = null;
1142
+ let inAfter = false;
1143
+ let thread = [];
1144
+ const closeGroup = () => {
1145
+ if (group && !group.ticked) unanswered.push(group.label);
1146
+ group = null;
1147
+ };
1148
+ const closeThread = () => {
1149
+ const last = thread.at(-1);
1150
+ if (last && last.author !== "AI") comments.push(`${last.author}${thread[0].quote ? ` on "${thread[0].quote}"` : ""}: ${last.text}`);
1151
+ thread = [];
1152
+ };
1153
+ for (const line of lines) {
1154
+ const c = COMMENT.exec(line);
1155
+ if (c) {
1156
+ if (c[1] === "comment") closeThread();
1157
+ thread.push({ author: c[2], text: c[5].trim(), quote: c[4] ?? "" });
1158
+ continue;
1159
+ }
1160
+ if (line.trim()) closeThread();
1161
+ const h = /^#{1,6}\s+(.*)/.exec(line);
1162
+ if (h) {
1163
+ closeGroup();
1164
+ heading2 = h[1].trim();
1165
+ question = "";
1166
+ inAfter = /^after your answer/i.test(heading2);
1167
+ continue;
1168
+ }
1169
+ if (inAfter && line.trim()) after.push(line.trim().replace(/^([-*+]|\d+\.)\s+/, ""));
1170
+ const q = /^\s*\d+\.\s+(.*\?)\s*$/.exec(line) ?? /^(\S.*[:?])\s*$/.exec(line);
1171
+ if (q && !/^\s*[-*+] /.test(line)) {
1172
+ closeGroup();
1173
+ question = q[1].trim().replace(/:$/, "");
1174
+ }
1175
+ if (/^\s*<!--\s*choose:\s*one\s*-->/.test(line)) {
1176
+ closeGroup();
1177
+ group = { label: question || heading2, ticked: false };
1178
+ continue;
1179
+ }
1180
+ const task = /^\s*[-*+] \[([ xX])\]\s*(.*)/.exec(line);
1181
+ if (task) {
1182
+ if (task[1] !== " ") {
1183
+ chosen.push(`${question || heading2}: ${task[2].trim()}`);
1184
+ if (group) group.ticked = true;
1185
+ }
1186
+ } else if (group && line.trim() && !/^\s/.test(line)) closeGroup();
1187
+ }
1188
+ closeGroup();
1189
+ closeThread();
1190
+ const section2 = (title, items) => items.length ? [`${title}:`, ...items.map((i) => `- ${i}`), ""] : [];
1191
+ const affects = Array.isArray(fm.affects) ? fm.affects.map(String) : [];
1192
+ return [
1193
+ `Proposal ${rel} was answered.`,
1194
+ "",
1195
+ ...section2("Chosen", chosen),
1196
+ ...section2("Not answered", unanswered),
1197
+ ...section2("Comments waiting for your reply", comments),
1198
+ ...section2("Affects", affects),
1199
+ ...section2("After the answer, you planned to", after),
1200
+ 'Next: follow the markode-proposals skill ("When the user answers").'
1201
+ ].join("\n");
1202
+ }
1203
+
1204
+ // src/server/index.ts
1205
+ import fs15 from "fs";
1206
+ import http from "http";
1207
+ import path15 from "path";
1208
+ import { watch } from "chokidar";
1209
+ import open from "open";
1210
+ import pc from "picocolors";
1211
+ import { WebSocketServer } from "ws";
1212
+
1213
+ // src/core/git.ts
1214
+ import { execFileSync } from "child_process";
1215
+ function git(root2, args2) {
1216
+ try {
1217
+ return execFileSync("git", ["-c", "core.quotepath=off", ...args2], { cwd: root2, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 32 * 1024 * 1024 });
1218
+ } catch (err) {
1219
+ return typeof err?.stdout === "string" && err.stdout ? err.stdout : null;
1220
+ }
1221
+ }
1222
+ var isGitRepo = (root2) => git(root2, ["rev-parse", "--is-inside-work-tree"])?.trim() === "true";
1223
+ function gitStatuses(root2) {
1224
+ const prefix = git(root2, ["rev-parse", "--show-prefix"])?.trim() ?? "";
1225
+ const out = git(root2, ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", "."]);
1226
+ const result = {};
1227
+ if (!out) return result;
1228
+ const parts = out.split("\0");
1229
+ for (let i = 0; i < parts.length; i++) {
1230
+ const entry = parts[i];
1231
+ if (entry.length < 4) continue;
1232
+ const code = entry.slice(0, 2);
1233
+ let file = entry.slice(3);
1234
+ if (code[0] === "R" || code[0] === "C") i++;
1235
+ if (!file.toLowerCase().endsWith(".md")) continue;
1236
+ if (prefix && file.startsWith(prefix)) file = file.slice(prefix.length);
1237
+ if (file.startsWith(".markode/")) continue;
1238
+ result[file] = code === "??" ? "U" : code.includes("D") ? "D" : code.includes("A") ? "A" : "M";
1239
+ }
1240
+ return result;
1241
+ }
1242
+ function gitDiff(root2, rel) {
1243
+ const status = gitStatuses(root2)[rel];
1244
+ if (status === "U") return git(root2, ["diff", "--no-index", "--", "/dev/null", rel]) ?? "";
1245
+ const hasHead = git(root2, ["rev-parse", "--verify", "HEAD"]) !== null;
1246
+ return git(root2, hasHead ? ["diff", "HEAD", "--", rel] : ["diff", "--cached", "--", rel]) ?? "";
1247
+ }
1248
+
1249
+ // src/core/proposals.ts
1250
+ import { execFileSync as execFileSync2 } from "child_process";
1251
+ import fs13 from "fs";
1252
+ import path13 from "path";
1253
+ var SKILL = path13.join(".claude", "skills", "markode-proposals");
1254
+ function proposalsState(root2) {
1255
+ return { active: fs13.existsSync(path13.join(root2, SKILL, "SKILL.md")), layout: readConfig(root2)?.proposals ?? "general" };
1256
+ }
1257
+ var WHERE = {
1258
+ general: "`docs/proposals/NNN-short-name.md` (NNN: the next number in that folder)",
1259
+ structured: "`docs/proposals/<topic>/NNN-short-name.md`, one folder per topic (e.g. `sidebar`, `auth`); 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"
1260
+ };
1261
+ function section(layout) {
1262
+ return [
1263
+ "## Proposals",
1264
+ "",
1265
+ "When an answer would be long, has options or a design to validate, or the user asks for one, write a",
1266
+ "temporary proposal the user answers in markode instead of a chat answer (skill: `markode-proposals`).",
1267
+ "",
1268
+ `- Where: ${WHERE[layout]}.`,
1269
+ "- After writing one, run `markode wait <path>` in the background: it returns when the user answers in markode.",
1270
+ "- The user ticks options and comments (`<!-- comment by \u2026 -->`). When it is answered, reply to the comments,",
1271
+ " update every doc that depends on the decision (tasks, phases, architecture\u2026), then delete the proposal."
1272
+ ].join("\n");
1273
+ }
1274
+ function mainDocs(root2, mainDoc) {
1275
+ const doc = mainDoc ?? mainDocOf(root2);
1276
+ return doc ? [path13.join(root2, doc)] : [];
1277
+ }
1278
+ function activateProposals(root2, layout, mainDoc) {
1279
+ const dir = path13.join(root2, SKILL);
1280
+ fs13.mkdirSync(dir, { recursive: true });
1281
+ fs13.writeFileSync(path13.join(dir, "SKILL.md"), fill(fs13.readFileSync(path13.join(TEMPLATES_DIR, "proposals", "SKILL.md"), "utf8"), { where: WHERE[layout] }));
1282
+ fs13.mkdirSync(path13.join(root2, "docs", "proposals"), { recursive: true });
1283
+ for (const file of mainDocs(root2, mainDoc)) writeManagedBlock(file, section(layout), false, "proposals");
1284
+ writeConfig(root2, { ...readConfig(root2) ?? { mainDoc, langs: [] }, proposals: layout });
1285
+ }
1286
+ function deactivateProposals(root2, mainDoc) {
1287
+ fs13.rmSync(path13.join(root2, SKILL), { recursive: true, force: true });
1288
+ for (const file of mainDocs(root2, mainDoc)) writeManagedBlock(file, null, false, "proposals");
1289
+ }
1290
+ function authorName(root2) {
1291
+ try {
1292
+ return execFileSync2("git", ["config", "user.name"], { cwd: root2, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
1293
+ } catch {
1294
+ return null;
1295
+ }
1296
+ }
1297
+
1298
+ // src/core/skills.ts
1299
+ import fs14 from "fs";
1300
+ import os2 from "os";
1301
+ import path14 from "path";
1302
+ import YAML5 from "yaml";
1303
+ var SOURCE_DIRS = {
1304
+ claude: [".claude", "skills"],
1305
+ // Shared folder read by Codex, Copilot and Gemini CLI, and where `npx skills` installs.
1306
+ agents: [".agents", "skills"],
1307
+ codex: [".codex", "skills"],
1308
+ gemini: [".gemini", "skills"],
1309
+ copilot: [".copilot", "skills"],
1310
+ cursor: [".cursor", "skills"]
1311
+ };
1312
+ var PROJECT_TARGETS = { claude: ".claude/skills", agents: ".agents/skills" };
1313
+ var sourceDir = (source) => path14.join(os2.homedir(), ...SOURCE_DIRS[source]);
1314
+ var display = (p3) => p3.startsWith(os2.homedir() + path14.sep) ? "~" + p3.slice(os2.homedir().length) : p3;
1315
+ function skillSources() {
1316
+ return Object.keys(SOURCE_DIRS).map((id) => ({ id, path: display(sourceDir(id)), exists: fs14.existsSync(sourceDir(id)) }));
1317
+ }
1318
+ var tagsFile = () => path14.join(GLOBAL_DIR, "skills.json");
1319
+ function readTags() {
1320
+ try {
1321
+ return JSON.parse(fs14.readFileSync(tagsFile(), "utf8")).tags ?? {};
1322
+ } catch {
1323
+ return {};
1324
+ }
1325
+ }
1326
+ function setSkillTags(name, tags) {
1327
+ const all = readTags();
1328
+ const clean = [...new Set(tags.map((t) => t.trim().toLowerCase()).filter(Boolean))];
1329
+ if (clean.length) all[name] = clean;
1330
+ else delete all[name];
1331
+ fs14.mkdirSync(GLOBAL_DIR, { recursive: true });
1332
+ fs14.writeFileSync(tagsFile(), JSON.stringify({ tags: all }, null, 2) + "\n");
1333
+ return clean;
1334
+ }
1335
+ function findSkills(dir, source, managed, depth = 0) {
1336
+ let entries;
1337
+ try {
1338
+ entries = fs14.readdirSync(dir, { withFileTypes: true });
1339
+ } catch {
1340
+ return [];
1341
+ }
1342
+ return entries.flatMap((e) => {
1343
+ const abs = path14.join(dir, e.name);
1344
+ let real;
1345
+ try {
1346
+ if (!fs14.statSync(abs).isDirectory()) return [];
1347
+ real = fs14.realpathSync(abs);
1348
+ } catch {
1349
+ return [];
1350
+ }
1351
+ if (fs14.existsSync(path14.join(abs, "SKILL.md"))) return [{ dir: abs, real, source, managed }];
1352
+ return depth < 2 ? findSkills(abs, source, managed || e.name === "synced" || e.name.startsWith("."), depth + 1) : [];
1353
+ });
1354
+ }
1355
+ function frontmatter2(text3) {
1356
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text3);
1357
+ if (!m) return null;
1358
+ try {
1359
+ const data = YAML5.parse(m[1], { logLevel: "error" });
1360
+ return data && typeof data === "object" ? data : null;
1361
+ } catch {
1362
+ return null;
1363
+ }
1364
+ }
1365
+ function listFiles(dir) {
1366
+ try {
1367
+ return fs14.readdirSync(dir, { recursive: true, withFileTypes: true }).map((e) => ({ e, rel: path14.relative(dir, path14.join(e.parentPath ?? e.path ?? dir, e.name)).split(path14.sep).join("/") })).filter(({ e, rel }) => e.isFile() && rel !== "SKILL.md").map(({ rel }) => rel).sort().slice(0, 100);
1368
+ } catch {
1369
+ return [];
1370
+ }
1371
+ }
1372
+ function listSkills(root2) {
1373
+ const byReal = /* @__PURE__ */ new Map();
1374
+ for (const source of Object.keys(SOURCE_DIRS)) {
1375
+ for (const f of findSkills(sourceDir(source), source, false)) byReal.set(f.real, [...byReal.get(f.real) ?? [], f]);
1376
+ }
1377
+ const userTags = readTags();
1378
+ const skills = [];
1379
+ for (const [real, found] of byReal) {
1380
+ const text3 = fs14.readFileSync(path14.join(real, "SKILL.md"), "utf8");
1381
+ const fm = frontmatter2(text3);
1382
+ const folder = path14.basename(found[0].dir);
1383
+ const name = typeof fm?.name === "string" ? fm.name : folder;
1384
+ const description = typeof fm?.description === "string" ? fm.description.trim() : "";
1385
+ const issues = [];
1386
+ if (!fm) issues.push("frontmatter");
1387
+ else {
1388
+ if (typeof fm.name !== "string" || !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(fm.name) || fm.name.length > 64) issues.push("name");
1389
+ else if (fm.name !== folder) issues.push("name-dir");
1390
+ if (!description) issues.push("description");
1391
+ else if (description.length > 1024) issues.push("description-long");
1392
+ }
1393
+ const declared = fm?.metadata?.tags;
1394
+ const metaTags = Array.isArray(declared) ? declared.map(String) : typeof declared === "string" ? declared.split(",") : [];
1395
+ skills.push({
1396
+ key: real,
1397
+ name,
1398
+ folder,
1399
+ description,
1400
+ locations: found.map((f) => ({ source: f.source, path: display(f.dir) })),
1401
+ managed: found.every((f) => f.managed),
1402
+ files: listFiles(real),
1403
+ tags: [.../* @__PURE__ */ new Set([...userTags[name] ?? [], ...metaTags.map((t) => t.trim().toLowerCase()).filter(Boolean)])],
1404
+ issues,
1405
+ installed: Object.keys(PROJECT_TARGETS).filter((t) => fs14.existsSync(path14.join(root2, PROJECT_TARGETS[t], folder, "SKILL.md")))
1406
+ });
1407
+ }
1408
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
1409
+ }
1410
+ function getSkill(root2, key) {
1411
+ const skill = typeof key === "string" ? listSkills(root2).find((s) => s.key === key) : void 0;
1412
+ if (!skill) throw Object.assign(new Error("Skill not found"), { code: "ENOENT" });
1413
+ return skill;
1414
+ }
1415
+ function projectTargets(root2) {
1416
+ const has2 = (p3) => fs14.existsSync(path14.join(root2, p3));
1417
+ const out = [];
1418
+ if (has2(".claude") || has2("CLAUDE.md")) out.push("claude");
1419
+ if (has2(".agents") || has2("AGENTS.md") || has2(".codex") || has2(".gemini") || has2(".github/skills")) out.push("agents");
1420
+ return out.length ? out : ["claude"];
1421
+ }
1422
+ function installSkill2(root2, skill, targets, overwrite) {
1423
+ const dests = targets.map((t) => path14.join(root2, PROJECT_TARGETS[t], skill.folder));
1424
+ const existing = dests.filter((d) => fs14.existsSync(d));
1425
+ if (existing.length && !overwrite) return { conflict: existing.map((d) => path14.relative(root2, d).split(path14.sep).join("/")) };
1426
+ for (const dest of dests) {
1427
+ fs14.rmSync(dest, { recursive: true, force: true });
1428
+ fs14.mkdirSync(path14.dirname(dest), { recursive: true });
1429
+ fs14.cpSync(skill.key, dest, { recursive: true, dereference: true });
1430
+ }
1431
+ return { ok: true };
1432
+ }
1433
+
1434
+ // src/server/index.ts
1435
+ var IMAGE_MIME = {
1436
+ ".png": "image/png",
1437
+ ".jpg": "image/jpeg",
1438
+ ".jpeg": "image/jpeg",
1439
+ ".gif": "image/gif",
1440
+ ".webp": "image/webp",
1441
+ ".avif": "image/avif",
1442
+ ".svg": "image/svg+xml",
1443
+ ".bmp": "image/bmp",
1444
+ ".ico": "image/x-icon"
1445
+ };
1446
+ var MIME = {
1447
+ ".html": "text/html; charset=utf-8",
1448
+ ".js": "text/javascript",
1449
+ ".css": "text/css",
1450
+ ".svg": "image/svg+xml",
1451
+ ".json": "application/json",
1452
+ ".woff2": "font/woff2"
1453
+ };
1454
+ var HttpError = class extends Error {
1455
+ constructor(status, message) {
1456
+ super(message);
1457
+ this.status = status;
1458
+ }
1459
+ status;
1460
+ };
1461
+ async function readBody(req) {
1462
+ let data = "";
1463
+ for await (const chunk of req) data += chunk;
1464
+ if (!data) return {};
1465
+ if (!req.headers["content-type"]?.startsWith("application/json")) throw new HttpError(415, "Expected application/json");
1466
+ return JSON.parse(data);
1467
+ }
1468
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
1469
+ function isLocalRequest(req) {
1470
+ const host = req.headers.host ?? "";
1471
+ try {
1472
+ if (!LOCAL_HOSTS.has(new URL(`http://${host}`).hostname)) return false;
1473
+ return !req.headers.origin || new URL(req.headers.origin).host === host;
1474
+ } catch {
1475
+ return false;
1476
+ }
1477
+ }
1478
+ async function startServer({ root: root2, port, open: openBrowser }) {
1479
+ const config = () => readConfig(root2) ?? { mainDoc: null, langs: [] };
1480
+ const sourceLang = () => projectSourceLang(root2, readConfig(root2)?.mainDoc);
1481
+ const viewLang = (value) => isLangCode(value) && value !== sourceLang() && config().langs.includes(value) ? value : null;
1482
+ const mdPath = (rel) => {
1483
+ if (typeof rel !== "string" || !rel.toLowerCase().endsWith(".md")) throw new HttpError(400, "Expected a .md path");
1484
+ safeResolve(root2, rel);
1485
+ return rel;
1486
+ };
1487
+ const deletable = (rel) => {
1488
+ if (typeof rel !== "string" || !rel.trim()) throw new HttpError(400, "Expected a path");
1489
+ const abs = safeResolve(root2, rel);
1490
+ if (abs === root2) throw new HttpError(400, "Refusing to delete the project root");
1491
+ if (!fs15.existsSync(abs)) throw new HttpError(404, "Not found");
1492
+ return abs;
1493
+ };
1494
+ const routes = {
1495
+ "GET /api/info": () => {
1496
+ const c = config();
1497
+ return {
1498
+ name: path15.basename(root2),
1499
+ mainDoc: c.mainDoc ?? null,
1500
+ sourceLang: sourceLang(),
1501
+ langs: c.langs.filter((l) => l !== sourceLang()),
1502
+ langNames: Object.fromEntries(c.langs.map((l) => [l, langName(l, c)])),
1503
+ git: isGitRepo(root2),
1504
+ proposals: proposalsState(root2),
1505
+ author: authorName(root2),
1506
+ unlinked: c.skipLink ? [] : missingMainDocs(root2)
1507
+ };
1508
+ },
1509
+ "PUT /api/config": (_, body) => {
1510
+ const c = config();
1511
+ if (Array.isArray(body.langs)) c.langs = body.langs.filter(isLangCode);
1512
+ if (body.langNames && typeof body.langNames === "object") {
1513
+ for (const [code, name] of Object.entries(body.langNames))
1514
+ if (isLangCode(code) && !isLang(code) && typeof name === "string" && name.trim()) c.langNames = { ...c.langNames, [code]: name.trim().slice(0, 40) };
1515
+ }
1516
+ if (isLang(body.sourceLang) && body.sourceLang !== sourceLang()) {
1517
+ if (!c.mainDoc) throw new HttpError(400, "No main document configured; run `markode init`");
1518
+ applyRulesToFile(path15.join(root2, c.mainDoc), void 0, body.sourceLang);
1519
+ }
1520
+ writeConfig(root2, c);
1521
+ return { ...c, langs: c.langs.filter((l) => l !== sourceLang()) };
1522
+ },
1523
+ /** Add the CLAUDE.md / AGENTS.md a folder lacks, pointing to the other; or stop offering it. */
1524
+ "POST /api/link": (_, body) => {
1525
+ if (body.skip) return writeConfig(root2, { ...config(), skipLink: true }), { created: [] };
1526
+ return { created: linkMainDocs(root2) };
1527
+ },
1528
+ "PUT /api/proposals": (_, body) => {
1529
+ if (body.active) activateProposals(root2, body.layout === "structured" ? "structured" : "general", config().mainDoc);
1530
+ else deactivateProposals(root2, config().mainDoc);
1531
+ return proposalsState(root2);
1532
+ },
1533
+ /** Translation files behind their source, in every enabled language: work for `/markode translate`. */
1534
+ "GET /api/translations": () => ({ pending: syncReport(root2).files.filter((f) => f.status !== "synced").length + foreignFiles(root2, sourceLang()).length }),
1535
+ /** Proposals an agent is waiting on (`markode wait`): answering them wakes it up. */
1536
+ "GET /api/waiting": () => ({ paths: waitingOn(root2) }),
1537
+ "GET /api/settings": () => readSettings(),
1538
+ "PUT /api/settings": (_, body) => writeSettings(body),
1539
+ "GET /api/tree": (url2) => {
1540
+ const l = viewLang(url2.searchParams.get("lang"));
1541
+ const sync2 = l ? Object.fromEntries(allStatuses(root2, l).map((e) => [e.path, e.status])) : {};
1542
+ const foreign = Object.fromEntries(foreignFiles(root2, sourceLang()).map((f) => [f.path, f.detected]));
1543
+ return { tree: scanTree(root2), git: gitStatuses(root2), sync: sync2, foreign };
1544
+ },
1545
+ "POST /api/keep-language": (_, body) => ({ lang: keepLanguage(root2, mdPath(body.path)) }),
1546
+ "GET /api/file": (url2) => {
1547
+ const rel = mdPath(url2.searchParams.get("path"));
1548
+ const l = viewLang(url2.searchParams.get("lang"));
1549
+ const source = fs15.readFileSync(safeResolve(root2, rel), "utf8");
1550
+ if (l) {
1551
+ const tPath = translationPath(root2, l, rel);
1552
+ if (fs15.existsSync(tPath)) return { path: rel, content: fs15.readFileSync(tPath, "utf8"), target: "translation", status: fileStatus(root2, l, rel) };
1553
+ return { path: rel, content: source, target: "source", status: "missing" };
1554
+ }
1555
+ return { path: rel, content: source, target: "source", status: null };
1556
+ },
1557
+ "PUT /api/file": (_, body) => {
1558
+ const rel = mdPath(body.path);
1559
+ const l = viewLang(body.lang);
1560
+ const file = body.target === "translation" && l ? translationPath(root2, l, rel) : safeResolve(root2, rel);
1561
+ fs15.mkdirSync(path15.dirname(file), { recursive: true });
1562
+ fs15.writeFileSync(file, String(body.content ?? ""));
1563
+ return { ok: true, status: l ? fileStatus(root2, l, rel) : null };
1564
+ },
1565
+ "POST /api/file": (_, body) => {
1566
+ const rel = mdPath(body.path);
1567
+ const file = safeResolve(root2, rel);
1568
+ if (fs15.existsSync(file)) throw new HttpError(409, "File already exists");
1569
+ fs15.mkdirSync(path15.dirname(file), { recursive: true });
1570
+ const title = path15.basename(rel, ".md").replace(/^\d+[-_]/, "").replace(/[-_]/g, " ");
1571
+ fs15.writeFileSync(file, `# ${title.charAt(0).toUpperCase() + title.slice(1)}
1572
+
1573
+ `);
1574
+ return { ok: true, path: rel };
1575
+ },
1576
+ // Only a mkdir: nothing else is touched, so editors that have the project open are unaffected.
1577
+ "POST /api/dir": (_, body) => {
1578
+ if (typeof body.path !== "string" || !body.path.trim()) throw new HttpError(400, "Expected a path");
1579
+ const dir = safeResolve(root2, body.path);
1580
+ if (dir === root2) throw new HttpError(400, "Expected a path");
1581
+ if (fs15.existsSync(dir)) throw new HttpError(409, "A file or folder with that name already exists");
1582
+ fs15.mkdirSync(dir, { recursive: true });
1583
+ return { ok: true, path: toPosix(path15.relative(root2, dir)) };
1584
+ },
1585
+ "GET /api/delete-preview": (url2) => {
1586
+ const abs = deletable(url2.searchParams.get("path"));
1587
+ if (!fs15.statSync(abs).isDirectory()) return { kind: "file", files: 1, md: 1 };
1588
+ const files = fs15.readdirSync(abs, { recursive: true, withFileTypes: true }).filter((e) => e.isFile());
1589
+ return { kind: "dir", files: files.length, md: files.filter((e) => e.name.toLowerCase().endsWith(".md")).length };
1590
+ },
1591
+ "DELETE /api/path": (url2) => {
1592
+ const rel = toPosix(path15.relative(root2, deletable(url2.searchParams.get("path"))));
1593
+ fs15.rmSync(safeResolve(root2, rel), { recursive: true });
1594
+ for (const l of config().langs) fs15.rmSync(translationPath(root2, l, rel), { recursive: true, force: true });
1595
+ return { ok: true };
1596
+ },
1597
+ "POST /api/ignore": (_, body) => {
1598
+ safeResolve(root2, body.path);
1599
+ setIgnored(root2, body.path, body.kind === "dir" ? "dir" : "file", !!body.ignored);
1600
+ return { ok: true };
1601
+ },
1602
+ "GET /api/diff": (url2) => ({ diff: gitDiff(root2, mdPath(url2.searchParams.get("path"))) }),
1603
+ "GET /api/dashboard": () => {
1604
+ const sdd = summarizeSdd(root2);
1605
+ const shown = sddPrefixes(sdd);
1606
+ return { docs: summarize(root2).filter((d) => !shown.some((p3) => d.path.startsWith(p3))), sdd };
1607
+ },
1608
+ "GET /api/rules": () => {
1609
+ const mainDoc = readConfig(root2)?.mainDoc;
1610
+ const text3 = mainDoc && fs15.existsSync(path15.join(root2, mainDoc)) ? fs15.readFileSync(path15.join(root2, mainDoc), "utf8") : "";
1611
+ return { rules: listRules(), applied: appliedRuleIds(text3), mainDoc: mainDoc ?? null };
1612
+ },
1613
+ "PUT /api/rules": (_, body) => saveRule(body),
1614
+ "DELETE /api/rules": (url2) => {
1615
+ deleteRule(url2.searchParams.get("id") ?? "");
1616
+ return { ok: true };
1617
+ },
1618
+ // Global skills: reads and writes are limited to SKILL.md of discovered skill folders.
1619
+ "GET /api/skills": () => ({ skills: listSkills(root2), sources: skillSources(), targets: projectTargets(root2) }),
1620
+ "PUT /api/skills/tags": (_, body) => {
1621
+ if (typeof body.name !== "string" || !Array.isArray(body.tags)) throw new HttpError(400, "Expected name and tags");
1622
+ return { tags: setSkillTags(body.name, body.tags.map(String)) };
1623
+ },
1624
+ "GET /api/skills/file": (url2) => {
1625
+ const skill = getSkill(root2, url2.searchParams.get("key"));
1626
+ return { path: skill.key, content: fs15.readFileSync(path15.join(skill.key, "SKILL.md"), "utf8"), target: "source", status: null };
1627
+ },
1628
+ "PUT /api/skills/file": (_, body) => {
1629
+ const skill = getSkill(root2, body.key);
1630
+ if (skill.managed) throw new HttpError(403, "This skill is managed by the app that installed it");
1631
+ fs15.writeFileSync(path15.join(skill.key, "SKILL.md"), String(body.content ?? ""));
1632
+ return { ok: true, status: null };
1633
+ },
1634
+ "POST /api/skills/install": (_, body) => {
1635
+ const targets = (Array.isArray(body.targets) ? body.targets : []).filter((t) => typeof t === "string" && t in PROJECT_TARGETS);
1636
+ if (!targets.length) throw new HttpError(400, "Expected at least one target");
1637
+ const result = installSkill2(root2, getSkill(root2, body.key), targets, !!body.overwrite);
1638
+ if (result.conflict) throw new HttpError(409, result.conflict.join(", "));
1639
+ return result;
1640
+ },
1641
+ "POST /api/rules/apply": (_, body) => {
1642
+ const mainDoc = readConfig(root2)?.mainDoc;
1643
+ if (!mainDoc) throw new HttpError(400, "No main document configured; run `markode init`");
1644
+ applyRulesToFile(path15.join(root2, mainDoc), body.ids ?? []);
1645
+ return { ok: true };
1646
+ }
1647
+ };
1648
+ const server = http.createServer(async (req, res) => {
1649
+ if (!isLocalRequest(req)) {
1650
+ res.writeHead(403).end("Forbidden");
1651
+ return;
1652
+ }
1653
+ const url2 = new URL(req.url ?? "/", "http://localhost");
1654
+ const route = routes[`${req.method} ${url2.pathname}`];
1655
+ if (route) {
1656
+ try {
1657
+ const result = await route(url2, req.method === "GET" || req.method === "DELETE" ? {} : await readBody(req));
1658
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(result));
1659
+ } catch (err) {
1660
+ const status = err instanceof HttpError ? err.status : err.code === "ENOENT" ? 404 : 500;
1661
+ res.writeHead(status, { "content-type": "application/json" }).end(JSON.stringify({ error: err.message }));
1662
+ }
1663
+ return;
1664
+ }
1665
+ if (req.method === "GET" && url2.pathname === "/api/asset") return serveAsset(url2.searchParams.get("path") ?? "", res);
1666
+ serveStatic(url2.pathname, res);
1667
+ });
1668
+ function serveAsset(rel, res) {
1669
+ let file;
1670
+ try {
1671
+ file = safeResolve(root2, rel);
1672
+ } catch {
1673
+ res.writeHead(404).end();
1674
+ return;
1675
+ }
1676
+ const type = IMAGE_MIME[path15.extname(file).toLowerCase()];
1677
+ if (!type || !fs15.existsSync(file) || !fs15.statSync(file).isFile()) {
1678
+ res.writeHead(404).end();
1679
+ return;
1680
+ }
1681
+ res.writeHead(200, { "content-type": type, "cache-control": "no-cache", "content-security-policy": "sandbox", "x-content-type-options": "nosniff" });
1682
+ fs15.createReadStream(file).pipe(res);
1683
+ }
1684
+ const actualPort = await listen(server, port);
1685
+ const wss = new WebSocketServer({ server, path: "/ws", verifyClient: ({ req }) => isLocalRequest(req) });
1686
+ const broadcast = (msg) => {
1687
+ const data = JSON.stringify(msg);
1688
+ for (const client of wss.clients) client.send(data);
1689
+ };
1690
+ const skips = watchSkips(root2);
1691
+ watch(root2, {
1692
+ ignoreInitial: true,
1693
+ // Unreadable folders (e.g. caches owned by another user) are skipped instead of failing.
1694
+ ignorePermissionErrors: true,
1695
+ ignored: (p3, stats) => {
1696
+ const rel = toPosix(path15.relative(root2, p3));
1697
+ if (rel === ".markode") return false;
1698
+ if (rel.startsWith(".markode/")) return !rel.startsWith(".markode/i18n") && rel !== ".markode/state.json" && rel !== ".markode/config.json";
1699
+ return skips(rel, stats ? stats.isDirectory() : void 0);
1700
+ }
1701
+ }).on("error", (err) => console.warn(pc.yellow(` file watcher: ${err.message}`))).on("all", (event, file) => {
1702
+ const abs = path15.resolve(file);
1703
+ if (!abs.endsWith(".md") && !abs.endsWith(".json") && !abs.endsWith(".markodeignore") && event !== "addDir" && event !== "unlinkDir") return;
1704
+ let rel = toPosix(path15.relative(root2, abs));
1705
+ const i18n = toPosix(path15.relative(root2, path15.join(stateDir(root2), "i18n")));
1706
+ if (rel.startsWith(i18n + "/")) rel = rel.split("/").slice(3).join("/");
1707
+ broadcast({ type: "fs", event, path: rel });
1708
+ });
1709
+ const url = `http://localhost:${actualPort}`;
1710
+ console.log(`
1711
+ ${pc.bold("markode")} ${pc.dim("\u2192")} ${pc.cyan(url)} ${pc.dim(root2)}
1712
+ `);
1713
+ if (!fs15.existsSync(WEB_DIR)) console.log(pc.yellow(" Web UI not built: run `pnpm build`, or `pnpm dev` and open http://localhost:4320\n"));
1714
+ else if (openBrowser) open(url).catch(() => {
1715
+ });
1716
+ }
1717
+ function listen(server, port) {
1718
+ return new Promise((resolve, reject) => {
1719
+ server.once("error", (err) => {
1720
+ if (err.code === "EADDRINUSE") resolve(listen(server, port + 1));
1721
+ else reject(err);
1722
+ });
1723
+ server.listen(port, "127.0.0.1", () => resolve(port));
1724
+ });
1725
+ }
1726
+ function serveStatic(pathname, res) {
1727
+ let file = path15.join(WEB_DIR, path15.normalize(pathname).replace(/^(\.\.[/\\])+/, ""));
1728
+ if (!file.startsWith(WEB_DIR) || !fs15.existsSync(file) || fs15.statSync(file).isDirectory()) file = path15.join(WEB_DIR, "index.html");
1729
+ if (!fs15.existsSync(file)) {
1730
+ res.writeHead(404).end("Web UI not built. Run `pnpm build`.");
1731
+ return;
1732
+ }
1733
+ res.writeHead(200, { "content-type": MIME[path15.extname(file)] ?? "application/octet-stream" });
1734
+ fs15.createReadStream(file).pipe(res);
1735
+ }
1736
+
1737
+ // src/cli/init.ts
1738
+ import fs16 from "fs";
1739
+ import path16 from "path";
1740
+ import * as p from "@clack/prompts";
1741
+ import pc2 from "picocolors";
1742
+ function check(value) {
1743
+ if (p.isCancel(value)) {
1744
+ p.cancel("Cancelled.");
1745
+ process.exit(0);
1746
+ }
1747
+ return value;
1748
+ }
1749
+ async function offerClaudeLink(root2, d) {
1750
+ if (fs16.existsSync(path16.join(root2, "CLAUDE.md"))) return;
1751
+ 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 }));
1752
+ if (link2 && linkAgentsDoc(root2)) p.log.success("Created CLAUDE.md (imports AGENTS.md)");
1753
+ }
1754
+ async function chooseMainDoc(root2, d) {
1755
+ const existing = MAIN_DOCS.filter((d2) => fs16.existsSync(path16.join(root2, d2)) && !isPointer(fs16.readFileSync(path16.join(root2, d2), "utf8"), d2));
1756
+ if (d) {
1757
+ if (existing.length === 1 && existing[0] === "AGENTS.md") await offerClaudeLink(root2, d);
1758
+ if (existing.length) return existing.includes(d.mainDoc) ? d.mainDoc : existing[0];
1759
+ createMainDoc(root2, d.mainDoc);
1760
+ p.log.success(`Created ${d.mainDoc}`);
1761
+ return d.mainDoc;
1762
+ }
1763
+ if (existing.length === 2) {
1764
+ return check(
1765
+ await p.select({
1766
+ message: "Both CLAUDE.md and AGENTS.md exist. Which one is the main document?",
1767
+ options: existing.map((d2) => ({ value: d2, label: d2 }))
1768
+ })
1769
+ );
1770
+ }
1771
+ if (existing.length === 1) {
1772
+ const choice2 = check(
1773
+ await p.select({
1774
+ message: `Found ${pc2.cyan(existing[0])}. Use it as the main document?`,
1775
+ options: [
1776
+ { value: existing[0], label: `Yes, use ${existing[0]}` },
1777
+ ...MAIN_DOCS.filter((d2) => d2 !== existing[0]).map((d2) => ({ value: d2, label: `No, create ${d2}` }))
1778
+ ]
1779
+ })
1780
+ );
1781
+ createMainDoc(root2, choice2);
1782
+ if (choice2 === "AGENTS.md") await offerClaudeLink(root2, d);
1783
+ return choice2;
1784
+ }
1785
+ const choice = check(
1786
+ await p.select({
1787
+ message: "No CLAUDE.md or AGENTS.md found. Which one should I create?",
1788
+ options: MAIN_DOCS.map((d2) => ({ value: d2, label: d2, hint: d2 === "CLAUDE.md" ? "Claude Code" : "other agents" }))
1789
+ })
1790
+ );
1791
+ createMainDoc(root2, choice);
1792
+ p.log.success(`Created ${choice}`);
1793
+ return choice;
1794
+ }
1795
+ var WORKFLOW_OPTIONS = [
1796
+ { value: "none", label: "None" },
1797
+ { value: "markode", label: "markode method", hint: "one file per task; vision, architecture and phases as they are needed; per project in monorepos" },
1798
+ { value: "openspec", label: "OpenSpec", hint: "specs + proposed changes, good for existing projects" },
1799
+ { value: "speckit", label: "GitHub Spec Kit", hint: "constitution \u2192 spec \u2192 plan \u2192 tasks, per feature" }
1800
+ ];
1801
+ var PROPOSALS_QUESTION = "The AI can write proposals you answer by ticking options and commenting. How should they be organized?";
1802
+ var PROPOSAL_OPTIONS = [
1803
+ { value: "structured", label: "By topic and area", hint: "docs/proposals/auth/\u2026, services/docs/proposals/\u2026" },
1804
+ { value: "general", label: "One folder", hint: "docs/proposals/" }
1805
+ ];
1806
+ var MONOREPO_QUESTION = "How should the docs of a monorepo be organized?";
1807
+ var MONOREPO_OPTIONS = [
1808
+ { 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" },
1809
+ { value: "single", label: "One docs/ for everything", hint: "at the root" }
1810
+ ];
1811
+ var SOURCE_LANG_QUESTION = "Language you work in (every .md file is written in it)";
1812
+ var SOURCE_LANG_MARK = /<!-- markode:source-lang:\w+ -->/;
1813
+ var LANG_OPTIONS = LANGS.map((l) => ({ value: l, label: LANG_NAMES[l], hint: l === "en" ? "default" : void 0 }));
1814
+ var HIDE_QUESTION = "Hide the .markode/ folder in VS Code?";
1815
+ var ruleOptions = (rules) => rules.map((r) => ({ value: r.id, label: r.title, hint: r.body.split("\n")[0].replace(/^- /, "") }));
1816
+ async function chooseRules(message, rules, initial) {
1817
+ const pick = check(
1818
+ await p.select({
1819
+ message,
1820
+ options: [
1821
+ { value: "none", label: "None", hint: "start from scratch; add your own later in the Rules tab" },
1822
+ { value: "pick", label: "Choose rules", hint: "common rules for AI agents, plus the ones you create" }
1823
+ ],
1824
+ initialValue: initial.length ? "pick" : "none"
1825
+ })
1826
+ );
1827
+ if (pick === "none") return [];
1828
+ return check(await p.multiselect({ message: "Which rules?", options: ruleOptions(rules), initialValues: initial, required: false }));
1829
+ }
1830
+ var agentOptions = () => Object.keys(SDD_AGENTS).map((a) => ({ value: a, label: SDD_AGENTS[a].label }));
1831
+ var WORKFLOW_LABEL = { none: "None", markode: "markode", openspec: "OpenSpec", speckit: "GitHub Spec Kit" };
1832
+ var WORKFLOW_GUIDE = { none: "", markode: ".claude/skills/markode-method/SKILL.md", openspec: "openspec/README.md", speckit: "specs/README.md" };
1833
+ var canAsk = () => !!process.stdin.isTTY;
1834
+ async function chooseAreas(root2, d) {
1835
+ const { dirs: candidates, unsure } = detectAreas(root2);
1836
+ if (!candidates.length) return [];
1837
+ if (unsure && canAsk()) {
1838
+ const separate = check(
1839
+ await p.confirm({
1840
+ message: `${candidates.map((c) => `${c}/`).join(", ")} each have their own manifest. Are they separate projects (e.g. a front end and a back end)?`,
1841
+ initialValue: true
1842
+ })
1843
+ );
1844
+ if (!separate) return [];
1845
+ }
1846
+ if (d) {
1847
+ const used2 = /* @__PURE__ */ new Set();
1848
+ return d.monorepo === "single" ? [] : candidates.map((dir) => ({ dir, prefix: suggestPrefix(dir, used2) }));
1849
+ }
1850
+ p.log.info(`Monorepo detected: ${candidates.map((c) => `${c}/`).join(", ")}`);
1851
+ const layout = check(await p.select({ message: MONOREPO_QUESTION, options: MONOREPO_OPTIONS }));
1852
+ if (layout === "single") return [];
1853
+ const dirs2 = check(
1854
+ await p.multiselect({
1855
+ message: "Which projects get their own docs?",
1856
+ options: candidates.map((d2) => ({ value: d2, label: `${d2}/` })),
1857
+ initialValues: candidates,
1858
+ required: false
1859
+ })
1860
+ );
1861
+ const used = /* @__PURE__ */ new Set();
1862
+ const areas = dirs2.map((dir) => ({ dir, prefix: suggestPrefix(dir, used) }));
1863
+ if (!areas.length) return areas;
1864
+ 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 }));
1865
+ if (ok) return areas;
1866
+ for (const a of areas) {
1867
+ a.prefix = check(
1868
+ await p.text({
1869
+ message: `Task prefix for ${a.dir}/`,
1870
+ initialValue: a.prefix,
1871
+ 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"
1872
+ })
1873
+ );
1874
+ }
1875
+ return areas;
1876
+ }
1877
+ async function chooseKind(root2, dir) {
1878
+ const guess = guessKind(path16.join(root2, dir));
1879
+ if (!guess.unclear || !canAsk()) return guess.kind;
1880
+ const where = dir ? `${dir}/` : "this project";
1881
+ const seen = guess.signals.length ? `found: ${guess.signals.join(", ")}` : "no code yet";
1882
+ return check(
1883
+ await p.select({
1884
+ message: `What kind of project is ${where}? (${seen})`,
1885
+ options: Object.keys(KIND_LABEL).map((k) => ({ value: k, label: KIND_LABEL[k], hint: KIND_HINT[k] })),
1886
+ initialValue: guess.kind
1887
+ })
1888
+ );
1889
+ }
1890
+ async function askProfile(root2, dir, kind, d) {
1891
+ const base = detectProfile(path16.join(root2, dir), kind);
1892
+ if (d || kind === "app") return base;
1893
+ const where = dir ? ` (${dir}/)` : "";
1894
+ if (kind === "library") return { kind, published: check(await p.confirm({ message: `Is the library${where} published (npm, PyPI\u2026)?`, initialValue: base.published })) };
1895
+ if (kind === "mobile") {
1896
+ const platforms = check(
1897
+ await p.multiselect({
1898
+ message: `Target platforms${where}`,
1899
+ options: ["ios", "android", "web"].map((v) => ({ value: v, label: v === "ios" ? "iOS" : v === "android" ? "Android" : "Web" })),
1900
+ initialValues: base.platforms,
1901
+ required: false
1902
+ })
1903
+ );
1904
+ return { kind, platforms };
1905
+ }
1906
+ if (kind === "data") {
1907
+ const dataLocation = check(await p.text({ message: `Where does the data live${where}? (folder, bucket, database; empty if not decided)`, initialValue: base.dataLocation, defaultValue: "" }));
1908
+ const experiments = check(
1909
+ await p.select({
1910
+ message: "How are experiments tracked?",
1911
+ options: Object.keys(EXPERIMENTS_LABEL).map((v) => ({ value: v, label: EXPERIMENTS_LABEL[v].replace(/`/g, "") })),
1912
+ initialValue: base.experiments
1913
+ })
1914
+ );
1915
+ return { kind, dataLocation: dataLocation.trim(), experiments };
1916
+ }
1917
+ const environments = check(await p.text({ message: `Environments${where}`, initialValue: base.environments, defaultValue: base.environments }));
1918
+ return { kind, environments: environments.trim() };
1919
+ }
1920
+ async function chooseContracts(root2, areas, d) {
1921
+ if (!looksLikeMicroservices(root2, areas.length)) return null;
1922
+ const found = findContracts(root2);
1923
+ if (d && found || !canAsk()) return found;
1924
+ const answer = check(
1925
+ await p.text({
1926
+ message: "Several services: where do their contracts (APIs, events) live? (e.g. proto/, an OpenAPI file; empty if none)",
1927
+ initialValue: found ?? "",
1928
+ defaultValue: ""
1929
+ })
1930
+ );
1931
+ return answer.trim() || null;
1932
+ }
1933
+ async function chooseExisting(root2, d) {
1934
+ const found = existingDocs(root2);
1935
+ if (!found.length || d) return found;
1936
+ return check(
1937
+ await p.multiselect({
1938
+ message: "These docs already exist. Which are the official ones? (the agent updates them instead of creating new ones)",
1939
+ options: found.map((f) => ({ value: f, label: f })),
1940
+ initialValues: found,
1941
+ required: false
1942
+ })
1943
+ );
1944
+ }
1945
+ async function chooseWorkflow(root2, mainDoc, previous, d) {
1946
+ const detected = detectWorkflow(root2);
1947
+ if (detected) {
1948
+ p.log.info(`${WORKFLOW_LABEL[detected]} found: markode shows it in the dashboard.`);
1949
+ return { workflow: detected };
1950
+ }
1951
+ for (; ; ) {
1952
+ const choice = d ? d.workflow : check(await p.select({ message: "Create a base structure for specs and planning?", options: WORKFLOW_OPTIONS, initialValue: previous ?? "none" }));
1953
+ if (choice === "none") return { workflow: choice };
1954
+ if (choice === "markode") {
1955
+ const existing = await chooseExisting(root2, d);
1956
+ const areas = await chooseAreas(root2, d);
1957
+ for (const a of areas) a.profile = await askProfile(root2, a.dir, await chooseKind(root2, a.dir), d);
1958
+ const contracts = await chooseContracts(root2, areas, d);
1959
+ const profile = areas.length ? void 0 : await askProfile(root2, "", await chooseKind(root2, ""), d);
1960
+ const created = scaffoldMethod(root2, mainDoc, areas);
1961
+ installMethodSkills(root2);
1962
+ const big = !areas.length && flattenFiles(scanTree(root2)).length > 40;
1963
+ const layout = areas.length ? "structured" : !big || d ? "general" : check(await p.select({ message: PROPOSALS_QUESTION, options: PROPOSAL_OPTIONS }));
1964
+ activateProposals(root2, layout, mainDoc);
1965
+ if (created.length) p.log.success(`Created ${created.length} files: ${created.slice(0, 8).join(", ")}${created.length > 8 ? "\u2026" : ""}`);
1966
+ p.log.info(`Docs grow when there is something to write. Skills: /markode-status, /markode-new-task, /markode-close-session.`);
1967
+ return { workflow: choice, section: methodSection(areas, root2, { profile, contracts, existing }) };
1968
+ }
1969
+ let official = true;
1970
+ if (choice === "speckit" && !hasUv() && d) {
1971
+ p.log.warn("Spec Kit needs uv (Python) to install its AI commands, and uv was not found: creating the structure only.");
1972
+ official = false;
1973
+ } else if (choice === "speckit" && !hasUv()) {
1974
+ const next = check(
1975
+ await p.select({
1976
+ message: "Spec Kit needs uv (Python) to install its AI commands, and uv was not found.",
1977
+ options: [
1978
+ { value: "back", label: "Choose another option" },
1979
+ { value: "skeleton", label: "Create the structure only", hint: "the guide explains how to install Spec Kit later" }
1980
+ ]
1981
+ })
1982
+ );
1983
+ if (next === "back") continue;
1984
+ official = false;
1985
+ }
1986
+ const initial = mainDoc === "AGENTS.md" ? "codex" : "claude";
1987
+ const agents = !official ? [initial] : d ? choice === "speckit" ? [d.agents[0] ?? initial] : d.agents : 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 }))];
1988
+ const spin = p.spinner();
1989
+ spin.start(official ? `Installing ${WORKFLOW_LABEL[choice]}\u2026` : "Creating the structure\u2026");
1990
+ const res = installWorkflow(root2, choice, agents, official);
1991
+ spin.stop(res.official ? `${WORKFLOW_LABEL[choice]} installed` : "Structure created");
1992
+ if (official && !res.official) p.log.warn(`Couldn't run the ${WORKFLOW_LABEL[choice]} installer:
1993
+ ${pc2.dim(res.error ?? "")}
1994
+ Created the structure only.`);
1995
+ if (!res.official) p.log.info(`To add the AI commands later, see ${pc2.cyan(WORKFLOW_GUIDE[choice])}.`);
1996
+ if (res.ignored.length) p.log.info(`Agent commands hidden in markode (${res.ignored.length} folders); the eye button in the sidebar shows them.`);
1997
+ return { workflow: choice };
1998
+ }
1999
+ }
2000
+ async function runInit(root2, d = null) {
2001
+ p.intro(pc2.bgCyan(pc2.black(" markode ")));
2002
+ if (d) p.log.info(`Using your defaults (${pc2.cyan(defaultsFile())}). To choose differently for this project: ${pc2.cyan("markode init --custom")}`);
2003
+ const previous = readConfig(root2);
2004
+ const mainDoc = await chooseMainDoc(root2, d);
2005
+ const mainFile = path16.join(root2, mainDoc);
2006
+ const { workflow, section: section2 } = await chooseWorkflow(root2, mainDoc, previous?.workflow, d);
2007
+ applyWorkflowToFile(mainFile, workflow, section2);
2008
+ const mainText = fs16.readFileSync(mainFile, "utf8");
2009
+ const sourceLang = d ? SOURCE_LANG_MARK.test(mainText) ? sourceLangOf(mainText) : d.sourceLang : check(await p.select({ message: SOURCE_LANG_QUESTION, options: LANG_OPTIONS, initialValue: sourceLangOf(mainText) }));
2010
+ const langs = (previous?.langs ?? []).filter((l) => l !== sourceLang);
2011
+ const rules = listRules();
2012
+ const applied = appliedRuleIds(mainText);
2013
+ const selected = d ? [.../* @__PURE__ */ new Set([...applied, ...d.rules.filter((id) => rules.some((r) => r.id === id))])] : await chooseRules("Working rules for this project", rules, applied);
2014
+ applyRulesToFile(mainFile, selected, sourceLang);
2015
+ const subprojects = findSubprojects(scanTree(root2));
2016
+ if (subprojects.length && selected.length) {
2017
+ const targets = d ? d.rulesInSubprojects ? subprojects : [] : check(
2018
+ await p.multiselect({
2019
+ message: "Monorepo detected. Apply the same rules to these projects too?",
2020
+ options: subprojects.map((s) => ({ value: s, label: s })),
2021
+ required: false
2022
+ })
2023
+ );
2024
+ for (const dir of targets) {
2025
+ applyRulesToFile(contentDoc(path16.join(root2, dir)), selected, sourceLang);
2026
+ }
2027
+ }
2028
+ ensureGitignore(root2);
2029
+ if (!previous?.initializedAt) {
2030
+ const hide = d ? d.hideInVsCode : check(await p.confirm({ message: HIDE_QUESTION, initialValue: true }));
2031
+ if (hide && !hideInVsCode(root2)) p.log.warn('.vscode/settings.json has comments; add "**/.markode": true to files.exclude by hand.');
2032
+ }
2033
+ installSkill(root2);
2034
+ const config = { mainDoc, langs, langNames: previous?.langNames, workflow, proposals: readConfig(root2)?.proposals, skipLink: previous?.skipLink, initializedAt: previous?.initializedAt ?? (/* @__PURE__ */ new Date()).toISOString() };
2035
+ writeConfig(root2, config);
2036
+ p.outro(`Ready. Skill installed in ${pc2.cyan(".claude/skills/markode")}. To review the docs in another language: ${pc2.cyan("markode translate")}`);
2037
+ return config;
2038
+ }
2039
+ async function runSetup(quick = false) {
2040
+ const previous = readDefaults() ?? BASE_DEFAULTS;
2041
+ if (quick) {
2042
+ const d2 = { ...previous, mode: "ask" };
2043
+ writeDefaults(d2);
2044
+ return d2;
2045
+ }
2046
+ p.intro(pc2.bgCyan(pc2.black(" markode setup ")));
2047
+ const mode = check(
2048
+ await p.select({
2049
+ message: "How should markode set up your projects?",
2050
+ options: [
2051
+ { value: "defaults", label: "With my answers", hint: "asked now, once; every project uses them" },
2052
+ { value: "ask", label: "Ask in each project" }
2053
+ ],
2054
+ initialValue: previous.mode === "ask" && !readDefaults() ? "defaults" : previous.mode
2055
+ })
2056
+ );
2057
+ if (mode === "ask") {
2058
+ const d2 = { ...previous, mode };
2059
+ writeDefaults(d2);
2060
+ p.outro(`Saved. Each project will ask its own questions. Change it with ${pc2.cyan("markode setup")}`);
2061
+ return d2;
2062
+ }
2063
+ const mainDoc = check(
2064
+ await p.select({
2065
+ message: "Main document to create when a project has none",
2066
+ options: MAIN_DOCS.map((d2) => ({ value: d2, label: d2, hint: d2 === "CLAUDE.md" ? "Claude Code" : "other agents" })),
2067
+ initialValue: previous.mainDoc
2068
+ })
2069
+ );
2070
+ const workflow = check(await p.select({ message: "Base structure for specs and planning", options: WORKFLOW_OPTIONS, initialValue: previous.workflow }));
2071
+ let agents = previous.agents;
2072
+ let monorepo = previous.monorepo;
2073
+ if (workflow === "openspec")
2074
+ agents = check(await p.multiselect({ message: "Install the OpenSpec commands for which agents?", options: agentOptions(), initialValues: previous.agents, required: false }));
2075
+ if (workflow === "speckit") agents = [check(await p.select({ message: "Install the Spec Kit commands for which agent?", options: agentOptions(), initialValue: previous.agents[0] }))];
2076
+ if (workflow === "markode") monorepo = check(await p.select({ message: MONOREPO_QUESTION, options: MONOREPO_OPTIONS, initialValue: previous.monorepo }));
2077
+ const sourceLang = check(await p.select({ message: `${SOURCE_LANG_QUESTION}; projects that already declare one keep it`, options: LANG_OPTIONS, initialValue: previous.sourceLang }));
2078
+ const rules = listRules();
2079
+ const selected = await chooseRules("Working rules for your projects", rules, previous.rules.filter((id) => rules.some((r) => r.id === id)));
2080
+ const rulesInSubprojects = selected.length ? check(await p.confirm({ message: "In monorepos, apply the rules to the subprojects too?", initialValue: previous.rulesInSubprojects })) : false;
2081
+ const hideInVsCode2 = check(await p.confirm({ message: HIDE_QUESTION, initialValue: previous.hideInVsCode }));
2082
+ const d = { mode, mainDoc, workflow, agents, monorepo, sourceLang, rules: selected, rulesInSubprojects, hideInVsCode: hideInVsCode2 };
2083
+ writeDefaults(d);
2084
+ p.outro(`Saved in ${pc2.cyan(defaultsFile())}. Change them with ${pc2.cyan("markode setup")}; for a single project: ${pc2.cyan("markode init --custom")}`);
2085
+ return d;
2086
+ }
2087
+
2088
+ // src/cli/translate.ts
2089
+ import * as p2 from "@clack/prompts";
2090
+ import pc3 from "picocolors";
2091
+ function check2(value) {
2092
+ if (p2.isCancel(value)) {
2093
+ p2.cancel("Cancelled.");
2094
+ process.exit(0);
2095
+ }
2096
+ return value;
2097
+ }
2098
+ var OTHER = "__other__";
2099
+ async function runTranslate(root2) {
2100
+ const config = readConfig(root2);
2101
+ if (!config) {
2102
+ console.log(`Run ${pc3.cyan("markode init")} first.`);
2103
+ process.exitCode = 1;
2104
+ return;
2105
+ }
2106
+ p2.intro(pc3.bgCyan(pc3.black(" markode translate ")));
2107
+ const source = projectSourceLang(root2, config.mainDoc);
2108
+ const custom = config.langs.filter((l) => !isLang(l));
2109
+ const picked = check2(
2110
+ await p2.multiselect({
2111
+ message: `Languages to review the docs in (the files stay in ${langName(source)})`,
2112
+ options: [
2113
+ ...LANGS.filter((l) => l !== source).map((l) => ({ value: l, label: LANG_NAMES[l] })),
2114
+ ...custom.map((l) => ({ value: l, label: `${langName(l, config)} (${l})`, hint: "interface in English" })),
2115
+ { value: OTHER, label: "Another language\u2026", hint: "type it; the interface stays in English" }
2116
+ ],
2117
+ initialValues: config.langs.filter((l) => l !== source),
2118
+ required: false
2119
+ })
2120
+ );
2121
+ const langs = picked.filter((l) => l !== OTHER);
2122
+ const names2 = { ...config.langNames ?? {} };
2123
+ if (picked.includes(OTHER)) {
2124
+ for (; ; ) {
2125
+ 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();
2126
+ const code = check2(
2127
+ await p2.text({
2128
+ message: `Short code for ${name} (folder name of its translations)`,
2129
+ initialValue: name.slice(0, 2).toLowerCase(),
2130
+ 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
2131
+ })
2132
+ ).toLowerCase();
2133
+ langs.push(code);
2134
+ names2[code] = name;
2135
+ if (!check2(await p2.confirm({ message: "Add another language?", initialValue: false }))) break;
2136
+ }
2137
+ }
2138
+ writeConfig(root2, { ...config, langs, langNames: names2 });
2139
+ if (!langs.length) {
2140
+ p2.outro("No translation languages: markode shows the docs in their source language only.");
2141
+ return;
2142
+ }
2143
+ p2.outro(
2144
+ [
2145
+ `Translations: ${langs.map((l) => langName(l, { ...config, langNames: names2 })).join(", ")}.`,
2146
+ `Ask your agent for ${pc3.cyan("/markode translate")} to translate the docs; then pick the language at the top of markode.`,
2147
+ ...langs.some((l) => !isLang(l)) ? ["Menus stay in English for languages typed by hand."] : []
2148
+ ].join("\n")
2149
+ );
2150
+ }
2151
+
2152
+ // src/cli/index.ts
2153
+ var HELP = `markode \u2014 visual editor for your project's Markdown
2154
+
2155
+ Usage
2156
+ markode init if needed, then open the editor
2157
+ markode init (re)run the project setup: main doc, specs structure, rules, languages
2158
+ markode setup choose how projects are set up: with your saved answers, or asking each time
2159
+ markode serve open the editor without running init
2160
+ markode translate choose languages to review the docs in (or: markode --translate)
2161
+ markode check [--json] what /markode sync fixes: answered proposals, phases vs tasks, projects table, broken links
2162
+ markode wait <proposal> wait until the user answers a proposal in markode, then print the answer
2163
+ markode link [--dry-run] where there is only CLAUDE.md or only AGENTS.md, add the other one pointing to it
2164
+ markode link --skip don't offer it again in this project
2165
+
2166
+ For the agent's /markode translate:
2167
+ markode translate --status [--json] translations + files not in the source language
2168
+ markode translate --mark [--lang xx] <path...>|--all record translations as in sync
2169
+ markode translate --keep <path...> keep files in their current language
2170
+
2171
+ Options
2172
+ -v, --version print the version
2173
+ -D first run: skip saving defaults; every project asks its own questions
2174
+ --custom init: ask every question for this project, ignoring your defaults
2175
+ --port <n> server port (default 4319)
2176
+ --no-open do not open the browser
2177
+ `;
2178
+ var args = process.argv.slice(2);
2179
+ var flag = (name) => args.includes(name);
2180
+ var option = (name) => {
2181
+ const i = args.indexOf(name);
2182
+ return i >= 0 ? args[i + 1] : void 0;
2183
+ };
2184
+ var command = args[0] && !args[0].startsWith("-") ? args[0] : "start";
2185
+ var root = process.env.MARKODE_ROOT ?? process.cwd();
2186
+ async function main() {
2187
+ if (flag("--help") || flag("-h")) return console.log(HELP);
2188
+ if (flag("--version") || flag("-v")) return console.log(JSON.parse(fs17.readFileSync(path17.join(PKG_ROOT, "package.json"), "utf8")).version);
2189
+ const translation = flag("--status") || flag("--mark") || flag("--keep");
2190
+ if ((command === "sync" || command === "translate") && translation) return sync();
2191
+ if (command === "translate" || flag("--translate")) return runTranslate(root);
2192
+ if (command === "check") return check3();
2193
+ if (command === "wait") return wait();
2194
+ if (command === "link") return link();
2195
+ if (command === "setup") {
2196
+ await runSetup(flag("-D"));
2197
+ return;
2198
+ }
2199
+ if (command === "init") {
2200
+ await runInit(root, flag("--custom") ? null : await defaults());
2201
+ return;
2202
+ }
2203
+ if (command === "start" && !readConfig(root)?.initializedAt) {
2204
+ if (process.stdin.isTTY) await runInit(root, flag("--custom") ? null : await defaults());
2205
+ else if (readDefaults()?.mode === "defaults") await runInit(root, readDefaults());
2206
+ }
2207
+ const unlinked = command === "start" && process.stdout.isTTY && !readConfig(root)?.skipLink ? missingMainDocs(root) : [];
2208
+ if (unlinked.length)
2209
+ 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\`).`);
2210
+ if (command === "start" || command === "serve") {
2211
+ await startServer({ root, port: Number(option("--port") ?? 4319), open: !flag("--no-open") });
2212
+ return;
2213
+ }
2214
+ console.log(HELP);
2215
+ process.exitCode = 1;
2216
+ }
2217
+ async function defaults() {
2218
+ const d = readDefaults() ?? (process.stdin.isTTY ? await runSetup(flag("-D")) : null);
2219
+ return d?.mode === "defaults" ? d : null;
2220
+ }
2221
+ function check3() {
2222
+ const report = checkDocs(root);
2223
+ if (flag("--json")) return console.log(JSON.stringify(report, null, 2));
2224
+ const lines = [
2225
+ ...report.proposals.map((p3) => `answered proposal ${p3}`),
2226
+ ...report.phases.map((p3) => `phase vs task ${p3.phase}: ${p3.task} is ${p3.status || "without status"} but ${p3.ticked ? "ticked" : "not ticked"}`),
2227
+ ...(report.projects?.missing ?? []).map((d) => `project not listed ${d}/`),
2228
+ ...(report.projects?.gone ?? []).map((d) => `project gone ${d}/`),
2229
+ ...report.links.map((l) => `broken link ${l.file}:${l.line} \u2192 ${l.target}`)
2230
+ ];
2231
+ console.log(lines.length ? lines.join("\n") : "Docs are consistent.");
2232
+ }
2233
+ function link() {
2234
+ const config = readConfig(root);
2235
+ if (flag("--skip")) {
2236
+ writeConfig(root, { ...config ?? { mainDoc: null, langs: [] }, skipLink: true });
2237
+ return console.log("markode won't offer to link CLAUDE.md and AGENTS.md in this project again.");
2238
+ }
2239
+ if (flag("--dry-run") && config?.skipLink) return console.log("Skipped: the user chose not to link the main docs.");
2240
+ const missing = missingMainDocs(root);
2241
+ if (!missing.length) return console.log("Every main doc has its CLAUDE.md and AGENTS.md (or neither).");
2242
+ if (flag("--dry-run")) return console.log(missing.map((m) => `would create ${m.path} \u2192 ${m.pointsTo}`).join("\n"));
2243
+ for (const p3 of linkMainDocs(root)) console.log(`created ${p3}`);
2244
+ }
2245
+ async function wait() {
2246
+ const rel = args[1];
2247
+ if (!rel || !fs17.existsSync(path17.join(root, rel))) {
2248
+ console.error(`Usage: markode wait <proposal.md> (relative to ${root})`);
2249
+ process.exitCode = 1;
2250
+ return;
2251
+ }
2252
+ const result = await waitForAnswer(root, rel);
2253
+ if (result === "gone") {
2254
+ console.log(`${rel} was deleted or is no longer a proposal.`);
2255
+ process.exit(0);
2256
+ }
2257
+ console.log(answerSummary(root, rel));
2258
+ process.exit(0);
2259
+ }
2260
+ function sync() {
2261
+ const report = syncReport(root);
2262
+ const paths = (after) => args.slice(args.indexOf(after) + 1).filter((a, i, all) => !a.startsWith("--") && all[i - 1] !== "--lang");
2263
+ if (flag("--keep")) {
2264
+ for (const p3 of paths("--keep")) {
2265
+ try {
2266
+ console.log(`kept ${p3} (${keepLanguage(root, p3)})`);
2267
+ } catch (err) {
2268
+ console.error(`skipped ${p3}: ${err.message}`);
2269
+ }
2270
+ }
2271
+ return;
2272
+ }
2273
+ if (flag("--mark")) {
2274
+ const only = option("--lang");
2275
+ const langs = only && report.langs.includes(only) ? [only] : report.langs;
2276
+ const files = flag("--all") ? [...new Set(report.files.map((f) => f.path))] : paths("--mark");
2277
+ for (const p3 of files) {
2278
+ for (const l of langs) {
2279
+ try {
2280
+ markSynced(root, l, p3);
2281
+ console.log(`synced ${l} ${p3}`);
2282
+ } catch (err) {
2283
+ if (!flag("--all")) console.error(`skipped ${l} ${p3}: ${err.message}`);
2284
+ }
2285
+ }
2286
+ }
2287
+ return;
2288
+ }
2289
+ if (flag("--json")) return console.log(JSON.stringify(report, null, 2));
2290
+ console.log(`Source language: ${report.sourceLang} Translations: ${report.langs.join(", ") || "none"}`);
2291
+ if (report.foreign.length) {
2292
+ console.log(`
2293
+ Not in ${report.sourceLang}:`);
2294
+ for (const f of report.foreign) console.log(` ${f.detected} ${f.path}`);
2295
+ }
2296
+ const pending = report.files.filter((f) => f.status !== "synced");
2297
+ if (pending.length) console.log("");
2298
+ for (const e of pending) console.log(`${e.lang} ${e.status.padEnd(13)} ${e.path}`);
2299
+ if (!pending.length && !report.foreign.length) console.log("Everything in sync.");
2300
+ }
2301
+ main().catch((err) => {
2302
+ console.error(err);
2303
+ process.exit(1);
2304
+ });