@lbruton/spec-workflow-mcp 2.2.4

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 (407) hide show
  1. package/CHANGELOG.md +955 -0
  2. package/LICENSE +674 -0
  3. package/README.ar.md +314 -0
  4. package/README.de.md +314 -0
  5. package/README.es.md +314 -0
  6. package/README.fr.md +314 -0
  7. package/README.it.md +314 -0
  8. package/README.ja.md +316 -0
  9. package/README.ko.md +314 -0
  10. package/README.md +373 -0
  11. package/README.pt.md +314 -0
  12. package/README.ru.md +314 -0
  13. package/README.zh.md +314 -0
  14. package/dist/__tests__/config.test.d.ts +2 -0
  15. package/dist/__tests__/config.test.d.ts.map +1 -0
  16. package/dist/__tests__/config.test.js +264 -0
  17. package/dist/__tests__/config.test.js.map +1 -0
  18. package/dist/__tests__/index-args.test.d.ts +2 -0
  19. package/dist/__tests__/index-args.test.d.ts.map +1 -0
  20. package/dist/__tests__/index-args.test.js +43 -0
  21. package/dist/__tests__/index-args.test.js.map +1 -0
  22. package/dist/__tests__/index-entrypoint.test.d.ts +2 -0
  23. package/dist/__tests__/index-entrypoint.test.d.ts.map +1 -0
  24. package/dist/__tests__/index-entrypoint.test.js +23 -0
  25. package/dist/__tests__/index-entrypoint.test.js.map +1 -0
  26. package/dist/config.d.ts +26 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +188 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/core/__tests__/git-utils.test.d.ts +2 -0
  31. package/dist/core/__tests__/git-utils.test.d.ts.map +1 -0
  32. package/dist/core/__tests__/git-utils.test.js +179 -0
  33. package/dist/core/__tests__/git-utils.test.js.map +1 -0
  34. package/dist/core/__tests__/mdx-validator.test.d.ts +2 -0
  35. package/dist/core/__tests__/mdx-validator.test.d.ts.map +1 -0
  36. package/dist/core/__tests__/mdx-validator.test.js +42 -0
  37. package/dist/core/__tests__/mdx-validator.test.js.map +1 -0
  38. package/dist/core/__tests__/path-utils.test.d.ts +2 -0
  39. package/dist/core/__tests__/path-utils.test.d.ts.map +1 -0
  40. package/dist/core/__tests__/path-utils.test.js +344 -0
  41. package/dist/core/__tests__/path-utils.test.js.map +1 -0
  42. package/dist/core/__tests__/project-registry.test.d.ts +2 -0
  43. package/dist/core/__tests__/project-registry.test.d.ts.map +1 -0
  44. package/dist/core/__tests__/project-registry.test.js +62 -0
  45. package/dist/core/__tests__/project-registry.test.js.map +1 -0
  46. package/dist/core/__tests__/security-utils.test.d.ts +2 -0
  47. package/dist/core/__tests__/security-utils.test.d.ts.map +1 -0
  48. package/dist/core/__tests__/security-utils.test.js +643 -0
  49. package/dist/core/__tests__/security-utils.test.js.map +1 -0
  50. package/dist/core/__tests__/task-validator.test.d.ts +2 -0
  51. package/dist/core/__tests__/task-validator.test.d.ts.map +1 -0
  52. package/dist/core/__tests__/task-validator.test.js +237 -0
  53. package/dist/core/__tests__/task-validator.test.js.map +1 -0
  54. package/dist/core/archive-service.d.ts +10 -0
  55. package/dist/core/archive-service.d.ts.map +1 -0
  56. package/dist/core/archive-service.js +99 -0
  57. package/dist/core/archive-service.js.map +1 -0
  58. package/dist/core/dashboard-session.d.ts +49 -0
  59. package/dist/core/dashboard-session.d.ts.map +1 -0
  60. package/dist/core/dashboard-session.js +132 -0
  61. package/dist/core/dashboard-session.js.map +1 -0
  62. package/dist/core/git-utils.d.ts +25 -0
  63. package/dist/core/git-utils.d.ts.map +1 -0
  64. package/dist/core/git-utils.js +87 -0
  65. package/dist/core/git-utils.js.map +1 -0
  66. package/dist/core/global-dir.d.ts +44 -0
  67. package/dist/core/global-dir.d.ts.map +1 -0
  68. package/dist/core/global-dir.js +74 -0
  69. package/dist/core/global-dir.js.map +1 -0
  70. package/dist/core/implementation-log-migrator.d.ts +41 -0
  71. package/dist/core/implementation-log-migrator.d.ts.map +1 -0
  72. package/dist/core/implementation-log-migrator.js +258 -0
  73. package/dist/core/implementation-log-migrator.js.map +1 -0
  74. package/dist/core/mdx-validator.d.ts +14 -0
  75. package/dist/core/mdx-validator.d.ts.map +1 -0
  76. package/dist/core/mdx-validator.js +34 -0
  77. package/dist/core/mdx-validator.js.map +1 -0
  78. package/dist/core/parser.d.ts +11 -0
  79. package/dist/core/parser.d.ts.map +1 -0
  80. package/dist/core/parser.js +126 -0
  81. package/dist/core/parser.js.map +1 -0
  82. package/dist/core/path-utils.d.ts +63 -0
  83. package/dist/core/path-utils.d.ts.map +1 -0
  84. package/dist/core/path-utils.js +288 -0
  85. package/dist/core/path-utils.js.map +1 -0
  86. package/dist/core/project-registry.d.ts +94 -0
  87. package/dist/core/project-registry.d.ts.map +1 -0
  88. package/dist/core/project-registry.js +297 -0
  89. package/dist/core/project-registry.js.map +1 -0
  90. package/dist/core/security-utils.d.ts +97 -0
  91. package/dist/core/security-utils.d.ts.map +1 -0
  92. package/dist/core/security-utils.js +264 -0
  93. package/dist/core/security-utils.js.map +1 -0
  94. package/dist/core/task-parser.d.ts +63 -0
  95. package/dist/core/task-parser.d.ts.map +1 -0
  96. package/dist/core/task-parser.js +332 -0
  97. package/dist/core/task-parser.js.map +1 -0
  98. package/dist/core/task-validator.d.ts +35 -0
  99. package/dist/core/task-validator.d.ts.map +1 -0
  100. package/dist/core/task-validator.js +236 -0
  101. package/dist/core/task-validator.js.map +1 -0
  102. package/dist/core/workspace-initializer.d.ts +16 -0
  103. package/dist/core/workspace-initializer.d.ts.map +1 -0
  104. package/dist/core/workspace-initializer.js +165 -0
  105. package/dist/core/workspace-initializer.js.map +1 -0
  106. package/dist/dashboard/__tests__/approval-storage-path-resolution.test.d.ts +2 -0
  107. package/dist/dashboard/__tests__/approval-storage-path-resolution.test.d.ts.map +1 -0
  108. package/dist/dashboard/__tests__/approval-storage-path-resolution.test.js +69 -0
  109. package/dist/dashboard/__tests__/approval-storage-path-resolution.test.js.map +1 -0
  110. package/dist/dashboard/__tests__/multi-server-approvals-content.test.d.ts +2 -0
  111. package/dist/dashboard/__tests__/multi-server-approvals-content.test.d.ts.map +1 -0
  112. package/dist/dashboard/__tests__/multi-server-approvals-content.test.js +116 -0
  113. package/dist/dashboard/__tests__/multi-server-approvals-content.test.js.map +1 -0
  114. package/dist/dashboard/__tests__/watcher-error-handling.test.d.ts +2 -0
  115. package/dist/dashboard/__tests__/watcher-error-handling.test.d.ts.map +1 -0
  116. package/dist/dashboard/__tests__/watcher-error-handling.test.js +118 -0
  117. package/dist/dashboard/__tests__/watcher-error-handling.test.js.map +1 -0
  118. package/dist/dashboard/approval-storage.d.ts +139 -0
  119. package/dist/dashboard/approval-storage.d.ts.map +1 -0
  120. package/dist/dashboard/approval-storage.js +586 -0
  121. package/dist/dashboard/approval-storage.js.map +1 -0
  122. package/dist/dashboard/execution-history-manager.d.ts +52 -0
  123. package/dist/dashboard/execution-history-manager.d.ts.map +1 -0
  124. package/dist/dashboard/execution-history-manager.js +161 -0
  125. package/dist/dashboard/execution-history-manager.js.map +1 -0
  126. package/dist/dashboard/implementation-log-manager.d.ts +97 -0
  127. package/dist/dashboard/implementation-log-manager.d.ts.map +1 -0
  128. package/dist/dashboard/implementation-log-manager.js +586 -0
  129. package/dist/dashboard/implementation-log-manager.js.map +1 -0
  130. package/dist/dashboard/job-scheduler.d.ts +91 -0
  131. package/dist/dashboard/job-scheduler.d.ts.map +1 -0
  132. package/dist/dashboard/job-scheduler.js +321 -0
  133. package/dist/dashboard/job-scheduler.js.map +1 -0
  134. package/dist/dashboard/multi-server.d.ts +42 -0
  135. package/dist/dashboard/multi-server.d.ts.map +1 -0
  136. package/dist/dashboard/multi-server.js +1313 -0
  137. package/dist/dashboard/multi-server.js.map +1 -0
  138. package/dist/dashboard/parser.d.ts +18 -0
  139. package/dist/dashboard/parser.d.ts.map +1 -0
  140. package/dist/dashboard/parser.js +243 -0
  141. package/dist/dashboard/parser.js.map +1 -0
  142. package/dist/dashboard/project-manager.d.ts +82 -0
  143. package/dist/dashboard/project-manager.d.ts.map +1 -0
  144. package/dist/dashboard/project-manager.js +257 -0
  145. package/dist/dashboard/project-manager.js.map +1 -0
  146. package/dist/dashboard/public/assets/Inter-Bold-CD3Pr7BX.woff2 +0 -0
  147. package/dist/dashboard/public/assets/Inter-Medium-B_8v_WHh.woff2 +0 -0
  148. package/dist/dashboard/public/assets/Inter-Regular-DRVdRqcI.woff2 +0 -0
  149. package/dist/dashboard/public/assets/Inter-SemiBold-CtskMddL.woff2 +0 -0
  150. package/dist/dashboard/public/assets/JetBrainsMono-Bold-D4WEaHbo.woff2 +0 -0
  151. package/dist/dashboard/public/assets/JetBrainsMono-Medium-3S3k2nMz.woff2 +0 -0
  152. package/dist/dashboard/public/assets/JetBrainsMono-Regular-BQaDgvhP.woff2 +0 -0
  153. package/dist/dashboard/public/assets/Tableau10-B-NsZVaP.js +1 -0
  154. package/dist/dashboard/public/assets/apl-B4CMkyY2.js +1 -0
  155. package/dist/dashboard/public/assets/arc-C8LPXB-J.js +1 -0
  156. package/dist/dashboard/public/assets/array-BKyUJesY.js +1 -0
  157. package/dist/dashboard/public/assets/asciiarmor-Df11BRmG.js +1 -0
  158. package/dist/dashboard/public/assets/asn1-EdZsLKOL.js +1 -0
  159. package/dist/dashboard/public/assets/asterisk-B-8jnY81.js +1 -0
  160. package/dist/dashboard/public/assets/blockDiagram-c4efeb88-RidjsOEy.js +118 -0
  161. package/dist/dashboard/public/assets/brainfuck-C4LP7Hcl.js +1 -0
  162. package/dist/dashboard/public/assets/c4Diagram-c83219d4-CAH3hSpm.js +10 -0
  163. package/dist/dashboard/public/assets/channel-CmDIZRCD.js +1 -0
  164. package/dist/dashboard/public/assets/classDiagram-beda092f-Bo46Efmw.js +2 -0
  165. package/dist/dashboard/public/assets/classDiagram-v2-2358418a-Be57sb3z.js +2 -0
  166. package/dist/dashboard/public/assets/clike-B9uivgTg.js +1 -0
  167. package/dist/dashboard/public/assets/clojure-BMjYHr_A.js +1 -0
  168. package/dist/dashboard/public/assets/clone-BiekPeZp.js +1 -0
  169. package/dist/dashboard/public/assets/cmake-BQqOBYOt.js +1 -0
  170. package/dist/dashboard/public/assets/cobol-CWcv1MsR.js +1 -0
  171. package/dist/dashboard/public/assets/coffeescript-S37ZYGWr.js +1 -0
  172. package/dist/dashboard/public/assets/commonlisp-DBKNyK5s.js +1 -0
  173. package/dist/dashboard/public/assets/createText-1719965b-YurEYFNx.js +7 -0
  174. package/dist/dashboard/public/assets/crystal-SjHAIU92.js +1 -0
  175. package/dist/dashboard/public/assets/css-BnMrqG3P.js +1 -0
  176. package/dist/dashboard/public/assets/cypher-C_CwsFkJ.js +1 -0
  177. package/dist/dashboard/public/assets/d-pRatUO7H.js +1 -0
  178. package/dist/dashboard/public/assets/diff-DbItnlRl.js +1 -0
  179. package/dist/dashboard/public/assets/dockerfile-BKs6k2Af.js +1 -0
  180. package/dist/dashboard/public/assets/dtd-DF_7sFjM.js +1 -0
  181. package/dist/dashboard/public/assets/dylan-DwRh75JA.js +1 -0
  182. package/dist/dashboard/public/assets/ebnf-CDyGwa7X.js +1 -0
  183. package/dist/dashboard/public/assets/ecl-Cabwm37j.js +1 -0
  184. package/dist/dashboard/public/assets/edges-96097737--BjsAXwD.js +4 -0
  185. package/dist/dashboard/public/assets/eiffel-CnydiIhH.js +1 -0
  186. package/dist/dashboard/public/assets/elm-vLlmbW-K.js +1 -0
  187. package/dist/dashboard/public/assets/erDiagram-0228fc6a-BLGuJz36.js +51 -0
  188. package/dist/dashboard/public/assets/erlang-BNw1qcRV.js +1 -0
  189. package/dist/dashboard/public/assets/factor-kuTfRLto.js +1 -0
  190. package/dist/dashboard/public/assets/fcl-Kvtd6kyn.js +1 -0
  191. package/dist/dashboard/public/assets/flowDb-c6c81e3f-C8vD2iEO.js +10 -0
  192. package/dist/dashboard/public/assets/flowDiagram-50d868cf-BhxgVmOU.js +4 -0
  193. package/dist/dashboard/public/assets/flowDiagram-v2-4f6560a1-DvKCh0ha.js +1 -0
  194. package/dist/dashboard/public/assets/flowchart-elk-definition-6af322e1-CxOZDcEC.js +139 -0
  195. package/dist/dashboard/public/assets/forth-Ffai-XNe.js +1 -0
  196. package/dist/dashboard/public/assets/fortran-DYz_wnZ1.js +1 -0
  197. package/dist/dashboard/public/assets/ganttDiagram-a2739b55-vP9JOLba.js +257 -0
  198. package/dist/dashboard/public/assets/gas-Bneqetm1.js +1 -0
  199. package/dist/dashboard/public/assets/gherkin-heZmZLOM.js +1 -0
  200. package/dist/dashboard/public/assets/gitGraphDiagram-82fe8481-Cw0sm0i1.js +70 -0
  201. package/dist/dashboard/public/assets/graph-DKTWMcEG.js +1 -0
  202. package/dist/dashboard/public/assets/groovy-D9Dt4D0W.js +1 -0
  203. package/dist/dashboard/public/assets/haskell-Cw1EW3IL.js +1 -0
  204. package/dist/dashboard/public/assets/haxe-H-WmDvRZ.js +1 -0
  205. package/dist/dashboard/public/assets/http-DBlCnlav.js +1 -0
  206. package/dist/dashboard/public/assets/idl-BEugSyMb.js +1 -0
  207. package/dist/dashboard/public/assets/index-1zJPiVa8.js +3 -0
  208. package/dist/dashboard/public/assets/index-5325376f-DWs4kCT4.js +1 -0
  209. package/dist/dashboard/public/assets/index-BITJ9OoM.js +1 -0
  210. package/dist/dashboard/public/assets/index-C38JlXWp.js +1 -0
  211. package/dist/dashboard/public/assets/index-CCjPelL2.js +2 -0
  212. package/dist/dashboard/public/assets/index-CD9WQNmE.js +1 -0
  213. package/dist/dashboard/public/assets/index-CU7K5Zcb.js +1 -0
  214. package/dist/dashboard/public/assets/index-CXQVOhJV.js +1 -0
  215. package/dist/dashboard/public/assets/index-CXcaRrZ2.js +1 -0
  216. package/dist/dashboard/public/assets/index-ChLAL6g5.css +1 -0
  217. package/dist/dashboard/public/assets/index-D0o1vVOe.js +7 -0
  218. package/dist/dashboard/public/assets/index-DCsxqRvu.js +1 -0
  219. package/dist/dashboard/public/assets/index-DL3iiiRz.js +1 -0
  220. package/dist/dashboard/public/assets/index-DMv2_K2V.js +1 -0
  221. package/dist/dashboard/public/assets/index-DX7EEJ21.js +1 -0
  222. package/dist/dashboard/public/assets/index-Dey_HIH7.js +1 -0
  223. package/dist/dashboard/public/assets/index-DzDTRLhf.js +1 -0
  224. package/dist/dashboard/public/assets/index-OePkEWBg.js +1 -0
  225. package/dist/dashboard/public/assets/index-_d82jdTP.js +1 -0
  226. package/dist/dashboard/public/assets/index-yCKz4OXA.js +319 -0
  227. package/dist/dashboard/public/assets/infoDiagram-8eee0895-BRq08fZf.js +7 -0
  228. package/dist/dashboard/public/assets/init-Gi6I4Gst.js +1 -0
  229. package/dist/dashboard/public/assets/javascript-iXu5QeM3.js +1 -0
  230. package/dist/dashboard/public/assets/journeyDiagram-c64418c1-Cf8D2OC8.js +139 -0
  231. package/dist/dashboard/public/assets/julia-DuME0IfC.js +1 -0
  232. package/dist/dashboard/public/assets/katex-XbL3y5x-.js +261 -0
  233. package/dist/dashboard/public/assets/layout-CVdidYA-.js +1 -0
  234. package/dist/dashboard/public/assets/line-BdckgA27.js +1 -0
  235. package/dist/dashboard/public/assets/linear-C9Nh3JLa.js +1 -0
  236. package/dist/dashboard/public/assets/livescript-BwQOo05w.js +1 -0
  237. package/dist/dashboard/public/assets/lua-BgMRiT3U.js +1 -0
  238. package/dist/dashboard/public/assets/mathematica-DTrFuWx2.js +1 -0
  239. package/dist/dashboard/public/assets/mbox-CNhZ1qSd.js +1 -0
  240. package/dist/dashboard/public/assets/mindmap-definition-8da855dc-CK-y1AmO.js +415 -0
  241. package/dist/dashboard/public/assets/mirc-CjQqDB4T.js +1 -0
  242. package/dist/dashboard/public/assets/mllike-CXdrOF99.js +1 -0
  243. package/dist/dashboard/public/assets/modelica-Dc1JOy9r.js +1 -0
  244. package/dist/dashboard/public/assets/mscgen-BA5vi2Kp.js +1 -0
  245. package/dist/dashboard/public/assets/mumps-BT43cFF4.js +1 -0
  246. package/dist/dashboard/public/assets/nginx-DdIZxoE0.js +1 -0
  247. package/dist/dashboard/public/assets/nsis-LdVXkNf5.js +1 -0
  248. package/dist/dashboard/public/assets/ntriples-BfvgReVJ.js +1 -0
  249. package/dist/dashboard/public/assets/octave-Ck1zUtKM.js +1 -0
  250. package/dist/dashboard/public/assets/ordinal-Cboi1Yqb.js +1 -0
  251. package/dist/dashboard/public/assets/oz-BzwKVEFT.js +1 -0
  252. package/dist/dashboard/public/assets/pascal--L3eBynH.js +1 -0
  253. package/dist/dashboard/public/assets/path-CbwjOpE9.js +1 -0
  254. package/dist/dashboard/public/assets/perl-CdXCOZ3F.js +1 -0
  255. package/dist/dashboard/public/assets/pieDiagram-a8764435-T8V0JN2R.js +35 -0
  256. package/dist/dashboard/public/assets/pig-CevX1Tat.js +1 -0
  257. package/dist/dashboard/public/assets/powershell-CFHJl5sT.js +1 -0
  258. package/dist/dashboard/public/assets/properties-C78fOPTZ.js +1 -0
  259. package/dist/dashboard/public/assets/protobuf-ChK-085T.js +1 -0
  260. package/dist/dashboard/public/assets/pug-DeIclll2.js +1 -0
  261. package/dist/dashboard/public/assets/puppet-DMA9R1ak.js +1 -0
  262. package/dist/dashboard/public/assets/python-BuPzkPfP.js +1 -0
  263. package/dist/dashboard/public/assets/q-pXgVlZs6.js +1 -0
  264. package/dist/dashboard/public/assets/quadrantDiagram-1e28029f-CmtVsb5L.js +7 -0
  265. package/dist/dashboard/public/assets/r-B6wPVr8A.js +1 -0
  266. package/dist/dashboard/public/assets/requirementDiagram-08caed73-BUcTnzDl.js +52 -0
  267. package/dist/dashboard/public/assets/rpm-CTu-6PCP.js +1 -0
  268. package/dist/dashboard/public/assets/ruby-B2Rjki9n.js +1 -0
  269. package/dist/dashboard/public/assets/sankeyDiagram-a04cb91d-FswuxQ9M.js +8 -0
  270. package/dist/dashboard/public/assets/sas-B4kiWyti.js +1 -0
  271. package/dist/dashboard/public/assets/scheme-C41bIUwD.js +1 -0
  272. package/dist/dashboard/public/assets/sequenceDiagram-c5b8d532-BJQ15rhX.js +122 -0
  273. package/dist/dashboard/public/assets/shell-CjFT_Tl9.js +1 -0
  274. package/dist/dashboard/public/assets/sieve-C3Gn_uJK.js +1 -0
  275. package/dist/dashboard/public/assets/simple-mode-GW_nhZxv.js +1 -0
  276. package/dist/dashboard/public/assets/smalltalk-CnHTOXQT.js +1 -0
  277. package/dist/dashboard/public/assets/solr-DehyRSwq.js +1 -0
  278. package/dist/dashboard/public/assets/sparql-DkYu6x3z.js +1 -0
  279. package/dist/dashboard/public/assets/spreadsheet-BCZA_wO0.js +1 -0
  280. package/dist/dashboard/public/assets/sql-D0XecflT.js +1 -0
  281. package/dist/dashboard/public/assets/stateDiagram-1ecb1508-BfyE0DYv.js +1 -0
  282. package/dist/dashboard/public/assets/stateDiagram-v2-c2b004d7-pcGOYyiW.js +1 -0
  283. package/dist/dashboard/public/assets/stex-C3f8Ysf7.js +1 -0
  284. package/dist/dashboard/public/assets/styles-b4e223ce--lUviH7V.js +160 -0
  285. package/dist/dashboard/public/assets/styles-ca3715f6-BXbrD1Av.js +207 -0
  286. package/dist/dashboard/public/assets/styles-d45a18b0-GyiMrLKu.js +116 -0
  287. package/dist/dashboard/public/assets/stylus-B533Al4x.js +1 -0
  288. package/dist/dashboard/public/assets/svgDrawCommon-b86b1483-DI4Z1GTS.js +1 -0
  289. package/dist/dashboard/public/assets/swift-BzpIVaGY.js +1 -0
  290. package/dist/dashboard/public/assets/tcl-DVfN8rqt.js +1 -0
  291. package/dist/dashboard/public/assets/textile-CnDTJFAw.js +1 -0
  292. package/dist/dashboard/public/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  293. package/dist/dashboard/public/assets/tiki-DGYXhP31.js +1 -0
  294. package/dist/dashboard/public/assets/timeline-definition-faaaa080-B1IgohU4.js +61 -0
  295. package/dist/dashboard/public/assets/toml-Bm5Em-hy.js +1 -0
  296. package/dist/dashboard/public/assets/troff-wAsdV37c.js +1 -0
  297. package/dist/dashboard/public/assets/ttcn-CfJYG6tj.js +1 -0
  298. package/dist/dashboard/public/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  299. package/dist/dashboard/public/assets/turtle-B1tBg_DP.js +1 -0
  300. package/dist/dashboard/public/assets/vb-CmGdzxic.js +1 -0
  301. package/dist/dashboard/public/assets/vbscript-BuJXcnF6.js +1 -0
  302. package/dist/dashboard/public/assets/velocity-D8B20fx6.js +1 -0
  303. package/dist/dashboard/public/assets/verilog-C6RDOZhf.js +1 -0
  304. package/dist/dashboard/public/assets/vhdl-lSbBsy5d.js +1 -0
  305. package/dist/dashboard/public/assets/webidl-ZXfAyPTL.js +1 -0
  306. package/dist/dashboard/public/assets/xquery-DzFWVndE.js +1 -0
  307. package/dist/dashboard/public/assets/xychartDiagram-f5964ef8-B5oRDe_I.js +7 -0
  308. package/dist/dashboard/public/assets/yacas-BJ4BC0dw.js +1 -0
  309. package/dist/dashboard/public/assets/z80-Hz9HOZM7.js +1 -0
  310. package/dist/dashboard/public/claude-icon-dark.svg +1 -0
  311. package/dist/dashboard/public/claude-icon.svg +1 -0
  312. package/dist/dashboard/public/index.html +16 -0
  313. package/dist/dashboard/settings-manager.d.ts +47 -0
  314. package/dist/dashboard/settings-manager.d.ts.map +1 -0
  315. package/dist/dashboard/settings-manager.js +180 -0
  316. package/dist/dashboard/settings-manager.js.map +1 -0
  317. package/dist/dashboard/utils.d.ts +31 -0
  318. package/dist/dashboard/utils.d.ts.map +1 -0
  319. package/dist/dashboard/utils.js +102 -0
  320. package/dist/dashboard/utils.js.map +1 -0
  321. package/dist/dashboard/watcher.d.ts +32 -0
  322. package/dist/dashboard/watcher.d.ts.map +1 -0
  323. package/dist/dashboard/watcher.js +173 -0
  324. package/dist/dashboard/watcher.js.map +1 -0
  325. package/dist/index.d.ts +13 -0
  326. package/dist/index.d.ts.map +1 -0
  327. package/dist/index.js +380 -0
  328. package/dist/index.js.map +1 -0
  329. package/dist/markdown/templates/design-template.md +96 -0
  330. package/dist/markdown/templates/product-template.md +51 -0
  331. package/dist/markdown/templates/requirements-template.md +50 -0
  332. package/dist/markdown/templates/structure-template.md +145 -0
  333. package/dist/markdown/templates/tasks-template.md +139 -0
  334. package/dist/markdown/templates/tech-template.md +99 -0
  335. package/dist/prompts/create-spec.d.ts +3 -0
  336. package/dist/prompts/create-spec.d.ts.map +1 -0
  337. package/dist/prompts/create-spec.js +93 -0
  338. package/dist/prompts/create-spec.js.map +1 -0
  339. package/dist/prompts/create-steering-doc.d.ts +3 -0
  340. package/dist/prompts/create-steering-doc.d.ts.map +1 -0
  341. package/dist/prompts/create-steering-doc.js +73 -0
  342. package/dist/prompts/create-steering-doc.js.map +1 -0
  343. package/dist/prompts/implement-task.d.ts +3 -0
  344. package/dist/prompts/implement-task.d.ts.map +1 -0
  345. package/dist/prompts/implement-task.js +173 -0
  346. package/dist/prompts/implement-task.js.map +1 -0
  347. package/dist/prompts/index.d.ts +15 -0
  348. package/dist/prompts/index.d.ts.map +1 -0
  349. package/dist/prompts/index.js +49 -0
  350. package/dist/prompts/index.js.map +1 -0
  351. package/dist/prompts/inject-spec-workflow-guide.d.ts +3 -0
  352. package/dist/prompts/inject-spec-workflow-guide.d.ts.map +1 -0
  353. package/dist/prompts/inject-spec-workflow-guide.js +47 -0
  354. package/dist/prompts/inject-spec-workflow-guide.js.map +1 -0
  355. package/dist/prompts/inject-steering-guide.d.ts +3 -0
  356. package/dist/prompts/inject-steering-guide.d.ts.map +1 -0
  357. package/dist/prompts/inject-steering-guide.js +51 -0
  358. package/dist/prompts/inject-steering-guide.js.map +1 -0
  359. package/dist/prompts/refresh-tasks.d.ts +3 -0
  360. package/dist/prompts/refresh-tasks.d.ts.map +1 -0
  361. package/dist/prompts/refresh-tasks.js +224 -0
  362. package/dist/prompts/refresh-tasks.js.map +1 -0
  363. package/dist/prompts/spec-status.d.ts +3 -0
  364. package/dist/prompts/spec-status.d.ts.map +1 -0
  365. package/dist/prompts/spec-status.js +75 -0
  366. package/dist/prompts/spec-status.js.map +1 -0
  367. package/dist/prompts/types.d.ts +13 -0
  368. package/dist/prompts/types.d.ts.map +1 -0
  369. package/dist/prompts/types.js +2 -0
  370. package/dist/prompts/types.js.map +1 -0
  371. package/dist/server.d.ts +17 -0
  372. package/dist/server.d.ts.map +1 -0
  373. package/dist/server.js +175 -0
  374. package/dist/server.js.map +1 -0
  375. package/dist/tools/__tests__/projectPath.test.d.ts +2 -0
  376. package/dist/tools/__tests__/projectPath.test.d.ts.map +1 -0
  377. package/dist/tools/__tests__/projectPath.test.js +187 -0
  378. package/dist/tools/__tests__/projectPath.test.js.map +1 -0
  379. package/dist/tools/approvals.d.ts +14 -0
  380. package/dist/tools/approvals.d.ts.map +1 -0
  381. package/dist/tools/approvals.js +490 -0
  382. package/dist/tools/approvals.js.map +1 -0
  383. package/dist/tools/index.d.ts +5 -0
  384. package/dist/tools/index.d.ts.map +1 -0
  385. package/dist/tools/index.js +52 -0
  386. package/dist/tools/index.js.map +1 -0
  387. package/dist/tools/log-implementation.d.ts +5 -0
  388. package/dist/tools/log-implementation.d.ts.map +1 -0
  389. package/dist/tools/log-implementation.js +397 -0
  390. package/dist/tools/log-implementation.js.map +1 -0
  391. package/dist/tools/spec-status.d.ts +5 -0
  392. package/dist/tools/spec-status.d.ts.map +1 -0
  393. package/dist/tools/spec-status.js +178 -0
  394. package/dist/tools/spec-status.js.map +1 -0
  395. package/dist/tools/spec-workflow-guide.d.ts +5 -0
  396. package/dist/tools/spec-workflow-guide.d.ts.map +1 -0
  397. package/dist/tools/spec-workflow-guide.js +291 -0
  398. package/dist/tools/spec-workflow-guide.js.map +1 -0
  399. package/dist/tools/steering-guide.d.ts +5 -0
  400. package/dist/tools/steering-guide.d.ts.map +1 -0
  401. package/dist/tools/steering-guide.js +192 -0
  402. package/dist/tools/steering-guide.js.map +1 -0
  403. package/dist/types.d.ts +172 -0
  404. package/dist/types.d.ts.map +1 -0
  405. package/dist/types.js +13 -0
  406. package/dist/types.js.map +1 -0
  407. package/package.json +105 -0
@@ -0,0 +1,643 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { promises as fs } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+ import { isLocalhostAddress, getSecurityConfig, generateAllowedOrigins, DEFAULT_SECURITY_CONFIG, VITE_DEV_PORT, RateLimiter, AuditLogger, getCorsConfig, createSecurityHeadersMiddleware } from '../security-utils.js';
6
+ describe('security-utils', () => {
7
+ describe('isLocalhostAddress', () => {
8
+ it('should return true for "localhost"', () => {
9
+ expect(isLocalhostAddress('localhost')).toBe(true);
10
+ });
11
+ it('should return true for "127.0.0.1"', () => {
12
+ expect(isLocalhostAddress('127.0.0.1')).toBe(true);
13
+ });
14
+ it('should return true for IPv6 localhost "::1"', () => {
15
+ expect(isLocalhostAddress('::1')).toBe(true);
16
+ });
17
+ it('should return true for any 127.x.x.x address', () => {
18
+ expect(isLocalhostAddress('127.0.0.2')).toBe(true);
19
+ expect(isLocalhostAddress('127.1.2.3')).toBe(true);
20
+ expect(isLocalhostAddress('127.255.255.255')).toBe(true);
21
+ });
22
+ it('should return false for "0.0.0.0"', () => {
23
+ expect(isLocalhostAddress('0.0.0.0')).toBe(false);
24
+ });
25
+ it('should return false for external IP addresses', () => {
26
+ expect(isLocalhostAddress('192.168.1.1')).toBe(false);
27
+ expect(isLocalhostAddress('10.0.0.1')).toBe(false);
28
+ expect(isLocalhostAddress('8.8.8.8')).toBe(false);
29
+ });
30
+ it('should return false for hostnames', () => {
31
+ expect(isLocalhostAddress('example.com')).toBe(false);
32
+ expect(isLocalhostAddress('myserver')).toBe(false);
33
+ });
34
+ it('should return false for empty string', () => {
35
+ expect(isLocalhostAddress('')).toBe(false);
36
+ });
37
+ });
38
+ describe('DEFAULT_SECURITY_CONFIG', () => {
39
+ it('should have secure defaults', () => {
40
+ expect(DEFAULT_SECURITY_CONFIG.rateLimitEnabled).toBe(true);
41
+ expect(DEFAULT_SECURITY_CONFIG.rateLimitPerMinute).toBe(120);
42
+ expect(DEFAULT_SECURITY_CONFIG.auditLogEnabled).toBe(true);
43
+ expect(DEFAULT_SECURITY_CONFIG.auditLogRetentionDays).toBe(30);
44
+ expect(DEFAULT_SECURITY_CONFIG.corsEnabled).toBe(true);
45
+ expect(DEFAULT_SECURITY_CONFIG.allowedOrigins).toContain('http://localhost:5000');
46
+ expect(DEFAULT_SECURITY_CONFIG.allowedOrigins).toContain('http://127.0.0.1:5000');
47
+ });
48
+ });
49
+ describe('generateAllowedOrigins', () => {
50
+ const originalNodeEnv = process.env.NODE_ENV;
51
+ afterEach(() => {
52
+ process.env.NODE_ENV = originalNodeEnv;
53
+ });
54
+ it('should include dashboard port origins', () => {
55
+ const origins = generateAllowedOrigins(5000);
56
+ expect(origins).toContain('http://localhost:5000');
57
+ expect(origins).toContain('http://127.0.0.1:5000');
58
+ });
59
+ it('should include Vite dev port in non-production environments', () => {
60
+ process.env.NODE_ENV = 'development';
61
+ const origins = generateAllowedOrigins(5000);
62
+ expect(origins).toContain(`http://localhost:${VITE_DEV_PORT}`);
63
+ expect(origins).toContain(`http://127.0.0.1:${VITE_DEV_PORT}`);
64
+ });
65
+ it('should include Vite dev port when NODE_ENV is undefined', () => {
66
+ // This is the key test - when NODE_ENV is not set, we should still include Vite dev port
67
+ // because we check !== 'production' rather than === 'development'
68
+ delete process.env.NODE_ENV;
69
+ const origins = generateAllowedOrigins(5000);
70
+ expect(origins).toContain(`http://localhost:${VITE_DEV_PORT}`);
71
+ expect(origins).toContain(`http://127.0.0.1:${VITE_DEV_PORT}`);
72
+ });
73
+ it('should NOT include Vite dev port in production', () => {
74
+ process.env.NODE_ENV = 'production';
75
+ const origins = generateAllowedOrigins(5000);
76
+ expect(origins).not.toContain(`http://localhost:${VITE_DEV_PORT}`);
77
+ expect(origins).not.toContain(`http://127.0.0.1:${VITE_DEV_PORT}`);
78
+ });
79
+ it('should use custom port for dashboard origins', () => {
80
+ const origins = generateAllowedOrigins(3000);
81
+ expect(origins).toContain('http://localhost:3000');
82
+ expect(origins).toContain('http://127.0.0.1:3000');
83
+ });
84
+ });
85
+ describe('getSecurityConfig', () => {
86
+ it('should return defaults when no config provided', () => {
87
+ const config = getSecurityConfig();
88
+ // In non-production, dynamic origins include Vite dev server ports
89
+ expect(config.rateLimitEnabled).toBe(DEFAULT_SECURITY_CONFIG.rateLimitEnabled);
90
+ expect(config.rateLimitPerMinute).toBe(DEFAULT_SECURITY_CONFIG.rateLimitPerMinute);
91
+ expect(config.auditLogEnabled).toBe(DEFAULT_SECURITY_CONFIG.auditLogEnabled);
92
+ expect(config.auditLogRetentionDays).toBe(DEFAULT_SECURITY_CONFIG.auditLogRetentionDays);
93
+ expect(config.corsEnabled).toBe(DEFAULT_SECURITY_CONFIG.corsEnabled);
94
+ // allowedOrigins includes default port + Vite dev port (5173) in non-production
95
+ expect(config.allowedOrigins).toContain('http://localhost:5000');
96
+ expect(config.allowedOrigins).toContain('http://127.0.0.1:5000');
97
+ });
98
+ it('should merge user config with defaults', () => {
99
+ const userConfig = {
100
+ rateLimitPerMinute: 100,
101
+ auditLogEnabled: false
102
+ };
103
+ const config = getSecurityConfig(userConfig);
104
+ expect(config.rateLimitPerMinute).toBe(100);
105
+ expect(config.auditLogEnabled).toBe(false);
106
+ // Other defaults preserved
107
+ expect(config.rateLimitEnabled).toBe(true);
108
+ expect(config.corsEnabled).toBe(true);
109
+ });
110
+ it('should allow complete override', () => {
111
+ const fullConfig = {
112
+ rateLimitEnabled: false,
113
+ rateLimitPerMinute: 30,
114
+ auditLogEnabled: false,
115
+ auditLogRetentionDays: 7,
116
+ corsEnabled: false,
117
+ allowedOrigins: ['http://custom:3000']
118
+ };
119
+ const config = getSecurityConfig(fullConfig);
120
+ expect(config).toEqual(fullConfig);
121
+ });
122
+ });
123
+ describe('RateLimiter', () => {
124
+ let rateLimiter;
125
+ beforeEach(() => {
126
+ vi.useFakeTimers();
127
+ rateLimiter = new RateLimiter({
128
+ ...DEFAULT_SECURITY_CONFIG,
129
+ rateLimitPerMinute: 3 // Low limit for testing
130
+ });
131
+ });
132
+ afterEach(() => {
133
+ vi.useRealTimers();
134
+ });
135
+ it('should allow requests under the limit', () => {
136
+ const result1 = rateLimiter.checkLimit('client1');
137
+ const result2 = rateLimiter.checkLimit('client1');
138
+ const result3 = rateLimiter.checkLimit('client1');
139
+ expect(result1.allowed).toBe(true);
140
+ expect(result2.allowed).toBe(true);
141
+ expect(result3.allowed).toBe(true);
142
+ });
143
+ it('should block requests over the limit', () => {
144
+ // Use up all 3 allowed requests
145
+ rateLimiter.checkLimit('client1');
146
+ rateLimiter.checkLimit('client1');
147
+ rateLimiter.checkLimit('client1');
148
+ // 4th request should be blocked
149
+ const result = rateLimiter.checkLimit('client1');
150
+ expect(result.allowed).toBe(false);
151
+ expect(result.retryAfter).toBeDefined();
152
+ expect(result.retryAfter).toBeGreaterThan(0);
153
+ });
154
+ it('should track clients separately', () => {
155
+ // Use up client1's limit
156
+ rateLimiter.checkLimit('client1');
157
+ rateLimiter.checkLimit('client1');
158
+ rateLimiter.checkLimit('client1');
159
+ // client2 should still be allowed
160
+ const result = rateLimiter.checkLimit('client2');
161
+ expect(result.allowed).toBe(true);
162
+ });
163
+ it('should reset after time window', () => {
164
+ // Use up all requests
165
+ rateLimiter.checkLimit('client1');
166
+ rateLimiter.checkLimit('client1');
167
+ rateLimiter.checkLimit('client1');
168
+ // Should be blocked
169
+ expect(rateLimiter.checkLimit('client1').allowed).toBe(false);
170
+ // Advance time by 1 minute
171
+ vi.advanceTimersByTime(60001);
172
+ // Should be allowed again
173
+ expect(rateLimiter.checkLimit('client1').allowed).toBe(true);
174
+ });
175
+ it('should return true when rate limiting is disabled', () => {
176
+ const disabledLimiter = new RateLimiter({
177
+ ...DEFAULT_SECURITY_CONFIG,
178
+ rateLimitEnabled: false,
179
+ rateLimitPerMinute: 1
180
+ });
181
+ // Make many requests
182
+ for (let i = 0; i < 100; i++) {
183
+ expect(disabledLimiter.checkLimit('client1').allowed).toBe(true);
184
+ }
185
+ });
186
+ });
187
+ describe('getCorsConfig', () => {
188
+ it('should return false when CORS is disabled', () => {
189
+ const config = {
190
+ ...DEFAULT_SECURITY_CONFIG,
191
+ corsEnabled: false
192
+ };
193
+ expect(getCorsConfig(config)).toBe(false);
194
+ });
195
+ it('should return config object when CORS is enabled', () => {
196
+ const config = {
197
+ ...DEFAULT_SECURITY_CONFIG,
198
+ corsEnabled: true
199
+ };
200
+ const corsConfig = getCorsConfig(config);
201
+ expect(corsConfig).not.toBe(false);
202
+ expect(typeof corsConfig).toBe('object');
203
+ expect(corsConfig).toHaveProperty('origin');
204
+ expect(corsConfig).toHaveProperty('credentials', true);
205
+ expect(corsConfig).toHaveProperty('methods');
206
+ });
207
+ it('should allow requests with no origin', () => {
208
+ const config = {
209
+ ...DEFAULT_SECURITY_CONFIG,
210
+ corsEnabled: true
211
+ };
212
+ const corsConfig = getCorsConfig(config);
213
+ const callback = vi.fn();
214
+ corsConfig.origin('', callback);
215
+ expect(callback).toHaveBeenCalledWith(null, true);
216
+ });
217
+ it('should allow requests from allowed origins', () => {
218
+ const config = {
219
+ ...DEFAULT_SECURITY_CONFIG,
220
+ corsEnabled: true,
221
+ allowedOrigins: ['http://localhost:5000', 'http://custom:3000']
222
+ };
223
+ const corsConfig = getCorsConfig(config);
224
+ const callback = vi.fn();
225
+ corsConfig.origin('http://localhost:5000', callback);
226
+ expect(callback).toHaveBeenCalledWith(null, true);
227
+ });
228
+ it('should reject requests from non-allowed origins', () => {
229
+ const config = {
230
+ ...DEFAULT_SECURITY_CONFIG,
231
+ corsEnabled: true,
232
+ allowedOrigins: ['http://localhost:5000']
233
+ };
234
+ const corsConfig = getCorsConfig(config);
235
+ const callback = vi.fn();
236
+ corsConfig.origin('http://malicious:8080', callback);
237
+ expect(callback).toHaveBeenCalledWith(expect.any(Error));
238
+ });
239
+ });
240
+ describe('createSecurityHeadersMiddleware', () => {
241
+ it('should return a middleware function', () => {
242
+ const middleware = createSecurityHeadersMiddleware();
243
+ expect(typeof middleware).toBe('function');
244
+ });
245
+ it('should set security headers on reply', async () => {
246
+ const middleware = createSecurityHeadersMiddleware();
247
+ const headers = {};
248
+ const mockReply = {
249
+ header: vi.fn((name, value) => {
250
+ headers[name] = value;
251
+ return mockReply;
252
+ })
253
+ };
254
+ const mockRequest = {};
255
+ await middleware(mockRequest, mockReply);
256
+ expect(mockReply.header).toHaveBeenCalledWith('X-Content-Type-Options', 'nosniff');
257
+ expect(mockReply.header).toHaveBeenCalledWith('X-Frame-Options', 'DENY');
258
+ expect(mockReply.header).toHaveBeenCalledWith('X-XSS-Protection', '1; mode=block');
259
+ expect(mockReply.header).toHaveBeenCalledWith('Referrer-Policy', 'strict-origin-when-cross-origin');
260
+ expect(mockReply.header).toHaveBeenCalledWith('Content-Security-Policy', expect.any(String));
261
+ });
262
+ });
263
+ describe('AuditLogger', () => {
264
+ let testDir;
265
+ beforeEach(async () => {
266
+ testDir = join(tmpdir(), `audit-log-test-${Date.now()}`);
267
+ await fs.mkdir(testDir, { recursive: true });
268
+ });
269
+ afterEach(async () => {
270
+ try {
271
+ await fs.rm(testDir, { recursive: true, force: true });
272
+ }
273
+ catch {
274
+ // Ignore cleanup errors
275
+ }
276
+ });
277
+ it('should not log when audit logging is disabled', async () => {
278
+ const logPath = join(testDir, 'audit.log');
279
+ const logger = new AuditLogger({
280
+ ...DEFAULT_SECURITY_CONFIG,
281
+ auditLogEnabled: false,
282
+ auditLogPath: logPath
283
+ });
284
+ await logger.log({
285
+ timestamp: new Date().toISOString(),
286
+ actor: '127.0.0.1',
287
+ action: 'GET /api/test',
288
+ resource: '/api/test',
289
+ result: 'success'
290
+ });
291
+ // File should not exist since logging is disabled
292
+ await expect(fs.access(logPath)).rejects.toThrow();
293
+ });
294
+ it('should initialize without throwing when disabled', async () => {
295
+ const logger = new AuditLogger({
296
+ ...DEFAULT_SECURITY_CONFIG,
297
+ auditLogEnabled: false
298
+ });
299
+ await expect(logger.initialize()).resolves.not.toThrow();
300
+ });
301
+ it('should create log directory on initialize when enabled', async () => {
302
+ const logDir = join(testDir, 'logs');
303
+ const logPath = join(logDir, 'audit.log');
304
+ const logger = new AuditLogger({
305
+ ...DEFAULT_SECURITY_CONFIG,
306
+ auditLogEnabled: true,
307
+ auditLogPath: logPath
308
+ });
309
+ await logger.initialize();
310
+ // Directory should exist
311
+ const stats = await fs.stat(logDir);
312
+ expect(stats.isDirectory()).toBe(true);
313
+ });
314
+ it('should write log entries as JSON lines', async () => {
315
+ const logPath = join(testDir, 'audit.log');
316
+ const logger = new AuditLogger({
317
+ ...DEFAULT_SECURITY_CONFIG,
318
+ auditLogEnabled: true,
319
+ auditLogPath: logPath
320
+ });
321
+ await logger.initialize();
322
+ const entry = {
323
+ timestamp: '2024-01-15T10:30:00.000Z',
324
+ actor: '192.168.1.100',
325
+ action: 'POST /api/specs',
326
+ resource: '/api/specs',
327
+ result: 'success',
328
+ details: {
329
+ statusCode: 201,
330
+ duration: 45
331
+ }
332
+ };
333
+ await logger.log(entry);
334
+ // Read and verify log content
335
+ const logContent = await fs.readFile(logPath, 'utf-8');
336
+ const loggedEntry = JSON.parse(logContent.trim());
337
+ expect(loggedEntry.timestamp).toBe('2024-01-15T10:30:00.000Z');
338
+ expect(loggedEntry.actor).toBe('192.168.1.100');
339
+ expect(loggedEntry.action).toBe('POST /api/specs');
340
+ expect(loggedEntry.resource).toBe('/api/specs');
341
+ expect(loggedEntry.result).toBe('success');
342
+ expect(loggedEntry.details.statusCode).toBe(201);
343
+ expect(loggedEntry.details.duration).toBe(45);
344
+ });
345
+ it('should append multiple log entries', async () => {
346
+ const logPath = join(testDir, 'audit.log');
347
+ const logger = new AuditLogger({
348
+ ...DEFAULT_SECURITY_CONFIG,
349
+ auditLogEnabled: true,
350
+ auditLogPath: logPath
351
+ });
352
+ await logger.initialize();
353
+ // Log three entries
354
+ await logger.log({
355
+ timestamp: '2024-01-15T10:00:00.000Z',
356
+ actor: '127.0.0.1',
357
+ action: 'GET /api/projects',
358
+ resource: '/api/projects',
359
+ result: 'success'
360
+ });
361
+ await logger.log({
362
+ timestamp: '2024-01-15T10:01:00.000Z',
363
+ actor: '127.0.0.1',
364
+ action: 'POST /api/specs',
365
+ resource: '/api/specs',
366
+ result: 'success'
367
+ });
368
+ await logger.log({
369
+ timestamp: '2024-01-15T10:02:00.000Z',
370
+ actor: '10.0.0.5',
371
+ action: 'DELETE /api/specs/test',
372
+ resource: '/api/specs/test',
373
+ result: 'denied'
374
+ });
375
+ // Read and verify all entries
376
+ const logContent = await fs.readFile(logPath, 'utf-8');
377
+ const lines = logContent.trim().split('\n');
378
+ expect(lines).toHaveLength(3);
379
+ const entry1 = JSON.parse(lines[0]);
380
+ const entry2 = JSON.parse(lines[1]);
381
+ const entry3 = JSON.parse(lines[2]);
382
+ expect(entry1.action).toBe('GET /api/projects');
383
+ expect(entry2.action).toBe('POST /api/specs');
384
+ expect(entry3.action).toBe('DELETE /api/specs/test');
385
+ expect(entry3.result).toBe('denied');
386
+ });
387
+ it('should log different result types correctly', async () => {
388
+ const logPath = join(testDir, 'audit.log');
389
+ const logger = new AuditLogger({
390
+ ...DEFAULT_SECURITY_CONFIG,
391
+ auditLogEnabled: true,
392
+ auditLogPath: logPath
393
+ });
394
+ await logger.initialize();
395
+ // Test all result types
396
+ await logger.log({
397
+ timestamp: new Date().toISOString(),
398
+ actor: '127.0.0.1',
399
+ action: 'GET /api/test',
400
+ resource: '/api/test',
401
+ result: 'success'
402
+ });
403
+ await logger.log({
404
+ timestamp: new Date().toISOString(),
405
+ actor: '127.0.0.1',
406
+ action: 'GET /api/error',
407
+ resource: '/api/error',
408
+ result: 'failure'
409
+ });
410
+ await logger.log({
411
+ timestamp: new Date().toISOString(),
412
+ actor: '127.0.0.1',
413
+ action: 'GET /api/protected',
414
+ resource: '/api/protected',
415
+ result: 'denied'
416
+ });
417
+ const logContent = await fs.readFile(logPath, 'utf-8');
418
+ const lines = logContent.trim().split('\n');
419
+ expect(JSON.parse(lines[0]).result).toBe('success');
420
+ expect(JSON.parse(lines[1]).result).toBe('failure');
421
+ expect(JSON.parse(lines[2]).result).toBe('denied');
422
+ });
423
+ it('should use workspace root path when auditLogPath not specified', async () => {
424
+ const workspaceRoot = testDir;
425
+ const logger = new AuditLogger({
426
+ ...DEFAULT_SECURITY_CONFIG,
427
+ auditLogEnabled: true
428
+ // No auditLogPath specified
429
+ }, workspaceRoot);
430
+ await logger.initialize();
431
+ await logger.log({
432
+ timestamp: new Date().toISOString(),
433
+ actor: '127.0.0.1',
434
+ action: 'GET /test',
435
+ resource: '/test',
436
+ result: 'success'
437
+ });
438
+ // Should have created log at workspaceRoot/.spec-workflow/audit.log
439
+ const expectedLogPath = join(workspaceRoot, '.spec-workflow', 'audit.log');
440
+ const logContent = await fs.readFile(expectedLogPath, 'utf-8');
441
+ const entry = JSON.parse(logContent.trim());
442
+ expect(entry.action).toBe('GET /test');
443
+ });
444
+ it('should include optional details in log entries', async () => {
445
+ const logPath = join(testDir, 'audit.log');
446
+ const logger = new AuditLogger({
447
+ ...DEFAULT_SECURITY_CONFIG,
448
+ auditLogEnabled: true,
449
+ auditLogPath: logPath
450
+ });
451
+ await logger.initialize();
452
+ await logger.log({
453
+ timestamp: new Date().toISOString(),
454
+ actor: '192.168.1.50',
455
+ action: 'PUT /api/specs/feature/tasks',
456
+ resource: '/api/specs/feature/tasks',
457
+ result: 'success',
458
+ details: {
459
+ statusCode: 200,
460
+ duration: 123,
461
+ userAgent: 'Mozilla/5.0 Test Browser',
462
+ customField: 'custom-value'
463
+ }
464
+ });
465
+ const logContent = await fs.readFile(logPath, 'utf-8');
466
+ const entry = JSON.parse(logContent.trim());
467
+ expect(entry.details).toBeDefined();
468
+ expect(entry.details.statusCode).toBe(200);
469
+ expect(entry.details.duration).toBe(123);
470
+ expect(entry.details.userAgent).toBe('Mozilla/5.0 Test Browser');
471
+ expect(entry.details.customField).toBe('custom-value');
472
+ });
473
+ describe('middleware', () => {
474
+ it('should return a middleware function', () => {
475
+ const logger = new AuditLogger({
476
+ ...DEFAULT_SECURITY_CONFIG,
477
+ auditLogEnabled: true
478
+ });
479
+ const middleware = logger.middleware();
480
+ expect(typeof middleware).toBe('function');
481
+ });
482
+ it('should register then callback on reply for post-response logging', async () => {
483
+ const logPath = join(testDir, 'audit.log');
484
+ const logger = new AuditLogger({
485
+ ...DEFAULT_SECURITY_CONFIG,
486
+ auditLogEnabled: true,
487
+ auditLogPath: logPath
488
+ });
489
+ await logger.initialize();
490
+ const middleware = logger.middleware();
491
+ let thenCallback = null;
492
+ const mockReply = {
493
+ statusCode: 200,
494
+ then: vi.fn((onFulfilled, _onRejected) => {
495
+ thenCallback = onFulfilled;
496
+ })
497
+ };
498
+ const mockRequest = {
499
+ ip: '10.0.0.1',
500
+ method: 'GET',
501
+ url: '/api/projects/list',
502
+ headers: {
503
+ 'user-agent': 'Test Agent/1.0'
504
+ }
505
+ };
506
+ await middleware(mockRequest, mockReply);
507
+ // Verify reply.then was called
508
+ expect(mockReply.then).toHaveBeenCalled();
509
+ expect(thenCallback).not.toBeNull();
510
+ // Trigger the then callback to actually log
511
+ await thenCallback();
512
+ // Wait for async log write to complete
513
+ await new Promise(resolve => setTimeout(resolve, 50));
514
+ // Verify log was written
515
+ const logContent = await fs.readFile(logPath, 'utf-8');
516
+ const entry = JSON.parse(logContent.trim());
517
+ expect(entry.actor).toBe('10.0.0.1');
518
+ expect(entry.action).toBe('GET /api/projects/list');
519
+ expect(entry.resource).toBe('/api/projects/list');
520
+ expect(entry.result).toBe('success');
521
+ expect(entry.details.statusCode).toBe(200);
522
+ expect(entry.details.userAgent).toBe('Test Agent/1.0');
523
+ });
524
+ it('should log "denied" result for 401 status code', async () => {
525
+ const logPath = join(testDir, 'audit.log');
526
+ const logger = new AuditLogger({
527
+ ...DEFAULT_SECURITY_CONFIG,
528
+ auditLogEnabled: true,
529
+ auditLogPath: logPath
530
+ });
531
+ await logger.initialize();
532
+ const middleware = logger.middleware();
533
+ let thenCallback = null;
534
+ const mockReply = {
535
+ statusCode: 401,
536
+ then: vi.fn((onFulfilled, _onRejected) => {
537
+ thenCallback = onFulfilled;
538
+ })
539
+ };
540
+ const mockRequest = {
541
+ ip: '192.168.1.1',
542
+ method: 'GET',
543
+ url: '/api/admin',
544
+ headers: {}
545
+ };
546
+ await middleware(mockRequest, mockReply);
547
+ await thenCallback();
548
+ await new Promise(resolve => setTimeout(resolve, 50));
549
+ const logContent = await fs.readFile(logPath, 'utf-8');
550
+ const entry = JSON.parse(logContent.trim());
551
+ expect(entry.result).toBe('denied');
552
+ });
553
+ it('should log "denied" result for 403 status code', async () => {
554
+ const logPath = join(testDir, 'audit.log');
555
+ const logger = new AuditLogger({
556
+ ...DEFAULT_SECURITY_CONFIG,
557
+ auditLogEnabled: true,
558
+ auditLogPath: logPath
559
+ });
560
+ await logger.initialize();
561
+ const middleware = logger.middleware();
562
+ let thenCallback = null;
563
+ const mockReply = {
564
+ statusCode: 403,
565
+ then: vi.fn((onFulfilled, _onRejected) => {
566
+ thenCallback = onFulfilled;
567
+ })
568
+ };
569
+ const mockRequest = {
570
+ ip: '192.168.1.1',
571
+ method: 'DELETE',
572
+ url: '/api/protected-resource',
573
+ headers: {}
574
+ };
575
+ await middleware(mockRequest, mockReply);
576
+ await thenCallback();
577
+ await new Promise(resolve => setTimeout(resolve, 50));
578
+ const logContent = await fs.readFile(logPath, 'utf-8');
579
+ const entry = JSON.parse(logContent.trim());
580
+ expect(entry.result).toBe('denied');
581
+ });
582
+ it('should log "failure" result for 500 status code', async () => {
583
+ const logPath = join(testDir, 'audit.log');
584
+ const logger = new AuditLogger({
585
+ ...DEFAULT_SECURITY_CONFIG,
586
+ auditLogEnabled: true,
587
+ auditLogPath: logPath
588
+ });
589
+ await logger.initialize();
590
+ const middleware = logger.middleware();
591
+ let thenCallback = null;
592
+ const mockReply = {
593
+ statusCode: 500,
594
+ then: vi.fn((onFulfilled, _onRejected) => {
595
+ thenCallback = onFulfilled;
596
+ })
597
+ };
598
+ const mockRequest = {
599
+ ip: '127.0.0.1',
600
+ method: 'POST',
601
+ url: '/api/specs',
602
+ headers: {}
603
+ };
604
+ await middleware(mockRequest, mockReply);
605
+ await thenCallback();
606
+ await new Promise(resolve => setTimeout(resolve, 50));
607
+ const logContent = await fs.readFile(logPath, 'utf-8');
608
+ const entry = JSON.parse(logContent.trim());
609
+ expect(entry.result).toBe('failure');
610
+ });
611
+ it('should use "unknown" for missing IP address', async () => {
612
+ const logPath = join(testDir, 'audit.log');
613
+ const logger = new AuditLogger({
614
+ ...DEFAULT_SECURITY_CONFIG,
615
+ auditLogEnabled: true,
616
+ auditLogPath: logPath
617
+ });
618
+ await logger.initialize();
619
+ const middleware = logger.middleware();
620
+ let thenCallback = null;
621
+ const mockReply = {
622
+ statusCode: 200,
623
+ then: vi.fn((onFulfilled, _onRejected) => {
624
+ thenCallback = onFulfilled;
625
+ })
626
+ };
627
+ const mockRequest = {
628
+ // No ip property
629
+ method: 'GET',
630
+ url: '/api/test',
631
+ headers: {}
632
+ };
633
+ await middleware(mockRequest, mockReply);
634
+ await thenCallback();
635
+ await new Promise(resolve => setTimeout(resolve, 50));
636
+ const logContent = await fs.readFile(logPath, 'utf-8');
637
+ const entry = JSON.parse(logContent.trim());
638
+ expect(entry.actor).toBe('unknown');
639
+ });
640
+ });
641
+ });
642
+ });
643
+ //# sourceMappingURL=security-utils.test.js.map