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