@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.
- package/CHANGELOG.md +955 -0
- package/LICENSE +674 -0
- package/README.ar.md +314 -0
- package/README.de.md +314 -0
- package/README.es.md +314 -0
- package/README.fr.md +314 -0
- package/README.it.md +314 -0
- package/README.ja.md +316 -0
- package/README.ko.md +314 -0
- package/README.md +373 -0
- package/README.pt.md +314 -0
- package/README.ru.md +314 -0
- package/README.zh.md +314 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +264 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/index-args.test.d.ts +2 -0
- package/dist/__tests__/index-args.test.d.ts.map +1 -0
- package/dist/__tests__/index-args.test.js +43 -0
- package/dist/__tests__/index-args.test.js.map +1 -0
- package/dist/__tests__/index-entrypoint.test.d.ts +2 -0
- package/dist/__tests__/index-entrypoint.test.d.ts.map +1 -0
- package/dist/__tests__/index-entrypoint.test.js +23 -0
- package/dist/__tests__/index-entrypoint.test.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +188 -0
- package/dist/config.js.map +1 -0
- package/dist/core/__tests__/git-utils.test.d.ts +2 -0
- package/dist/core/__tests__/git-utils.test.d.ts.map +1 -0
- package/dist/core/__tests__/git-utils.test.js +179 -0
- package/dist/core/__tests__/git-utils.test.js.map +1 -0
- package/dist/core/__tests__/mdx-validator.test.d.ts +2 -0
- package/dist/core/__tests__/mdx-validator.test.d.ts.map +1 -0
- package/dist/core/__tests__/mdx-validator.test.js +42 -0
- package/dist/core/__tests__/mdx-validator.test.js.map +1 -0
- package/dist/core/__tests__/path-utils.test.d.ts +2 -0
- package/dist/core/__tests__/path-utils.test.d.ts.map +1 -0
- package/dist/core/__tests__/path-utils.test.js +344 -0
- package/dist/core/__tests__/path-utils.test.js.map +1 -0
- package/dist/core/__tests__/project-registry.test.d.ts +2 -0
- package/dist/core/__tests__/project-registry.test.d.ts.map +1 -0
- package/dist/core/__tests__/project-registry.test.js +62 -0
- package/dist/core/__tests__/project-registry.test.js.map +1 -0
- package/dist/core/__tests__/security-utils.test.d.ts +2 -0
- package/dist/core/__tests__/security-utils.test.d.ts.map +1 -0
- package/dist/core/__tests__/security-utils.test.js +643 -0
- package/dist/core/__tests__/security-utils.test.js.map +1 -0
- package/dist/core/__tests__/task-validator.test.d.ts +2 -0
- package/dist/core/__tests__/task-validator.test.d.ts.map +1 -0
- package/dist/core/__tests__/task-validator.test.js +237 -0
- package/dist/core/__tests__/task-validator.test.js.map +1 -0
- package/dist/core/archive-service.d.ts +10 -0
- package/dist/core/archive-service.d.ts.map +1 -0
- package/dist/core/archive-service.js +99 -0
- package/dist/core/archive-service.js.map +1 -0
- package/dist/core/dashboard-session.d.ts +49 -0
- package/dist/core/dashboard-session.d.ts.map +1 -0
- package/dist/core/dashboard-session.js +132 -0
- package/dist/core/dashboard-session.js.map +1 -0
- package/dist/core/git-utils.d.ts +25 -0
- package/dist/core/git-utils.d.ts.map +1 -0
- package/dist/core/git-utils.js +87 -0
- package/dist/core/git-utils.js.map +1 -0
- package/dist/core/global-dir.d.ts +44 -0
- package/dist/core/global-dir.d.ts.map +1 -0
- package/dist/core/global-dir.js +74 -0
- package/dist/core/global-dir.js.map +1 -0
- package/dist/core/implementation-log-migrator.d.ts +41 -0
- package/dist/core/implementation-log-migrator.d.ts.map +1 -0
- package/dist/core/implementation-log-migrator.js +258 -0
- package/dist/core/implementation-log-migrator.js.map +1 -0
- package/dist/core/mdx-validator.d.ts +14 -0
- package/dist/core/mdx-validator.d.ts.map +1 -0
- package/dist/core/mdx-validator.js +34 -0
- package/dist/core/mdx-validator.js.map +1 -0
- package/dist/core/parser.d.ts +11 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +126 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/path-utils.d.ts +63 -0
- package/dist/core/path-utils.d.ts.map +1 -0
- package/dist/core/path-utils.js +288 -0
- package/dist/core/path-utils.js.map +1 -0
- package/dist/core/project-registry.d.ts +94 -0
- package/dist/core/project-registry.d.ts.map +1 -0
- package/dist/core/project-registry.js +297 -0
- package/dist/core/project-registry.js.map +1 -0
- package/dist/core/security-utils.d.ts +97 -0
- package/dist/core/security-utils.d.ts.map +1 -0
- package/dist/core/security-utils.js +264 -0
- package/dist/core/security-utils.js.map +1 -0
- package/dist/core/task-parser.d.ts +63 -0
- package/dist/core/task-parser.d.ts.map +1 -0
- package/dist/core/task-parser.js +332 -0
- package/dist/core/task-parser.js.map +1 -0
- package/dist/core/task-validator.d.ts +35 -0
- package/dist/core/task-validator.d.ts.map +1 -0
- package/dist/core/task-validator.js +236 -0
- package/dist/core/task-validator.js.map +1 -0
- package/dist/core/workspace-initializer.d.ts +16 -0
- package/dist/core/workspace-initializer.d.ts.map +1 -0
- package/dist/core/workspace-initializer.js +165 -0
- package/dist/core/workspace-initializer.js.map +1 -0
- package/dist/dashboard/__tests__/approval-storage-path-resolution.test.d.ts +2 -0
- package/dist/dashboard/__tests__/approval-storage-path-resolution.test.d.ts.map +1 -0
- package/dist/dashboard/__tests__/approval-storage-path-resolution.test.js +69 -0
- package/dist/dashboard/__tests__/approval-storage-path-resolution.test.js.map +1 -0
- package/dist/dashboard/__tests__/multi-server-approvals-content.test.d.ts +2 -0
- package/dist/dashboard/__tests__/multi-server-approvals-content.test.d.ts.map +1 -0
- package/dist/dashboard/__tests__/multi-server-approvals-content.test.js +116 -0
- package/dist/dashboard/__tests__/multi-server-approvals-content.test.js.map +1 -0
- package/dist/dashboard/__tests__/watcher-error-handling.test.d.ts +2 -0
- package/dist/dashboard/__tests__/watcher-error-handling.test.d.ts.map +1 -0
- package/dist/dashboard/__tests__/watcher-error-handling.test.js +118 -0
- package/dist/dashboard/__tests__/watcher-error-handling.test.js.map +1 -0
- package/dist/dashboard/approval-storage.d.ts +139 -0
- package/dist/dashboard/approval-storage.d.ts.map +1 -0
- package/dist/dashboard/approval-storage.js +586 -0
- package/dist/dashboard/approval-storage.js.map +1 -0
- package/dist/dashboard/execution-history-manager.d.ts +52 -0
- package/dist/dashboard/execution-history-manager.d.ts.map +1 -0
- package/dist/dashboard/execution-history-manager.js +161 -0
- package/dist/dashboard/execution-history-manager.js.map +1 -0
- package/dist/dashboard/implementation-log-manager.d.ts +97 -0
- package/dist/dashboard/implementation-log-manager.d.ts.map +1 -0
- package/dist/dashboard/implementation-log-manager.js +586 -0
- package/dist/dashboard/implementation-log-manager.js.map +1 -0
- package/dist/dashboard/job-scheduler.d.ts +91 -0
- package/dist/dashboard/job-scheduler.d.ts.map +1 -0
- package/dist/dashboard/job-scheduler.js +321 -0
- package/dist/dashboard/job-scheduler.js.map +1 -0
- package/dist/dashboard/multi-server.d.ts +42 -0
- package/dist/dashboard/multi-server.d.ts.map +1 -0
- package/dist/dashboard/multi-server.js +1313 -0
- package/dist/dashboard/multi-server.js.map +1 -0
- package/dist/dashboard/parser.d.ts +18 -0
- package/dist/dashboard/parser.d.ts.map +1 -0
- package/dist/dashboard/parser.js +243 -0
- package/dist/dashboard/parser.js.map +1 -0
- package/dist/dashboard/project-manager.d.ts +82 -0
- package/dist/dashboard/project-manager.d.ts.map +1 -0
- package/dist/dashboard/project-manager.js +257 -0
- package/dist/dashboard/project-manager.js.map +1 -0
- package/dist/dashboard/public/assets/Inter-Bold-CD3Pr7BX.woff2 +0 -0
- package/dist/dashboard/public/assets/Inter-Medium-B_8v_WHh.woff2 +0 -0
- package/dist/dashboard/public/assets/Inter-Regular-DRVdRqcI.woff2 +0 -0
- package/dist/dashboard/public/assets/Inter-SemiBold-CtskMddL.woff2 +0 -0
- package/dist/dashboard/public/assets/JetBrainsMono-Bold-D4WEaHbo.woff2 +0 -0
- package/dist/dashboard/public/assets/JetBrainsMono-Medium-3S3k2nMz.woff2 +0 -0
- package/dist/dashboard/public/assets/JetBrainsMono-Regular-BQaDgvhP.woff2 +0 -0
- package/dist/dashboard/public/assets/Tableau10-B-NsZVaP.js +1 -0
- package/dist/dashboard/public/assets/apl-B4CMkyY2.js +1 -0
- package/dist/dashboard/public/assets/arc-C8LPXB-J.js +1 -0
- package/dist/dashboard/public/assets/array-BKyUJesY.js +1 -0
- package/dist/dashboard/public/assets/asciiarmor-Df11BRmG.js +1 -0
- package/dist/dashboard/public/assets/asn1-EdZsLKOL.js +1 -0
- package/dist/dashboard/public/assets/asterisk-B-8jnY81.js +1 -0
- package/dist/dashboard/public/assets/blockDiagram-c4efeb88-RidjsOEy.js +118 -0
- package/dist/dashboard/public/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/dist/dashboard/public/assets/c4Diagram-c83219d4-CAH3hSpm.js +10 -0
- package/dist/dashboard/public/assets/channel-CmDIZRCD.js +1 -0
- package/dist/dashboard/public/assets/classDiagram-beda092f-Bo46Efmw.js +2 -0
- package/dist/dashboard/public/assets/classDiagram-v2-2358418a-Be57sb3z.js +2 -0
- package/dist/dashboard/public/assets/clike-B9uivgTg.js +1 -0
- package/dist/dashboard/public/assets/clojure-BMjYHr_A.js +1 -0
- package/dist/dashboard/public/assets/clone-BiekPeZp.js +1 -0
- package/dist/dashboard/public/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/dashboard/public/assets/cobol-CWcv1MsR.js +1 -0
- package/dist/dashboard/public/assets/coffeescript-S37ZYGWr.js +1 -0
- package/dist/dashboard/public/assets/commonlisp-DBKNyK5s.js +1 -0
- package/dist/dashboard/public/assets/createText-1719965b-YurEYFNx.js +7 -0
- package/dist/dashboard/public/assets/crystal-SjHAIU92.js +1 -0
- package/dist/dashboard/public/assets/css-BnMrqG3P.js +1 -0
- package/dist/dashboard/public/assets/cypher-C_CwsFkJ.js +1 -0
- package/dist/dashboard/public/assets/d-pRatUO7H.js +1 -0
- package/dist/dashboard/public/assets/diff-DbItnlRl.js +1 -0
- package/dist/dashboard/public/assets/dockerfile-BKs6k2Af.js +1 -0
- package/dist/dashboard/public/assets/dtd-DF_7sFjM.js +1 -0
- package/dist/dashboard/public/assets/dylan-DwRh75JA.js +1 -0
- package/dist/dashboard/public/assets/ebnf-CDyGwa7X.js +1 -0
- package/dist/dashboard/public/assets/ecl-Cabwm37j.js +1 -0
- package/dist/dashboard/public/assets/edges-96097737--BjsAXwD.js +4 -0
- package/dist/dashboard/public/assets/eiffel-CnydiIhH.js +1 -0
- package/dist/dashboard/public/assets/elm-vLlmbW-K.js +1 -0
- package/dist/dashboard/public/assets/erDiagram-0228fc6a-BLGuJz36.js +51 -0
- package/dist/dashboard/public/assets/erlang-BNw1qcRV.js +1 -0
- package/dist/dashboard/public/assets/factor-kuTfRLto.js +1 -0
- package/dist/dashboard/public/assets/fcl-Kvtd6kyn.js +1 -0
- package/dist/dashboard/public/assets/flowDb-c6c81e3f-C8vD2iEO.js +10 -0
- package/dist/dashboard/public/assets/flowDiagram-50d868cf-BhxgVmOU.js +4 -0
- package/dist/dashboard/public/assets/flowDiagram-v2-4f6560a1-DvKCh0ha.js +1 -0
- package/dist/dashboard/public/assets/flowchart-elk-definition-6af322e1-CxOZDcEC.js +139 -0
- package/dist/dashboard/public/assets/forth-Ffai-XNe.js +1 -0
- package/dist/dashboard/public/assets/fortran-DYz_wnZ1.js +1 -0
- package/dist/dashboard/public/assets/ganttDiagram-a2739b55-vP9JOLba.js +257 -0
- package/dist/dashboard/public/assets/gas-Bneqetm1.js +1 -0
- package/dist/dashboard/public/assets/gherkin-heZmZLOM.js +1 -0
- package/dist/dashboard/public/assets/gitGraphDiagram-82fe8481-Cw0sm0i1.js +70 -0
- package/dist/dashboard/public/assets/graph-DKTWMcEG.js +1 -0
- package/dist/dashboard/public/assets/groovy-D9Dt4D0W.js +1 -0
- package/dist/dashboard/public/assets/haskell-Cw1EW3IL.js +1 -0
- package/dist/dashboard/public/assets/haxe-H-WmDvRZ.js +1 -0
- package/dist/dashboard/public/assets/http-DBlCnlav.js +1 -0
- package/dist/dashboard/public/assets/idl-BEugSyMb.js +1 -0
- package/dist/dashboard/public/assets/index-1zJPiVa8.js +3 -0
- package/dist/dashboard/public/assets/index-5325376f-DWs4kCT4.js +1 -0
- package/dist/dashboard/public/assets/index-BITJ9OoM.js +1 -0
- package/dist/dashboard/public/assets/index-C38JlXWp.js +1 -0
- package/dist/dashboard/public/assets/index-CCjPelL2.js +2 -0
- package/dist/dashboard/public/assets/index-CD9WQNmE.js +1 -0
- package/dist/dashboard/public/assets/index-CU7K5Zcb.js +1 -0
- package/dist/dashboard/public/assets/index-CXQVOhJV.js +1 -0
- package/dist/dashboard/public/assets/index-CXcaRrZ2.js +1 -0
- package/dist/dashboard/public/assets/index-ChLAL6g5.css +1 -0
- package/dist/dashboard/public/assets/index-D0o1vVOe.js +7 -0
- package/dist/dashboard/public/assets/index-DCsxqRvu.js +1 -0
- package/dist/dashboard/public/assets/index-DL3iiiRz.js +1 -0
- package/dist/dashboard/public/assets/index-DMv2_K2V.js +1 -0
- package/dist/dashboard/public/assets/index-DX7EEJ21.js +1 -0
- package/dist/dashboard/public/assets/index-Dey_HIH7.js +1 -0
- package/dist/dashboard/public/assets/index-DzDTRLhf.js +1 -0
- package/dist/dashboard/public/assets/index-OePkEWBg.js +1 -0
- package/dist/dashboard/public/assets/index-_d82jdTP.js +1 -0
- package/dist/dashboard/public/assets/index-yCKz4OXA.js +319 -0
- package/dist/dashboard/public/assets/infoDiagram-8eee0895-BRq08fZf.js +7 -0
- package/dist/dashboard/public/assets/init-Gi6I4Gst.js +1 -0
- package/dist/dashboard/public/assets/javascript-iXu5QeM3.js +1 -0
- package/dist/dashboard/public/assets/journeyDiagram-c64418c1-Cf8D2OC8.js +139 -0
- package/dist/dashboard/public/assets/julia-DuME0IfC.js +1 -0
- package/dist/dashboard/public/assets/katex-XbL3y5x-.js +261 -0
- package/dist/dashboard/public/assets/layout-CVdidYA-.js +1 -0
- package/dist/dashboard/public/assets/line-BdckgA27.js +1 -0
- package/dist/dashboard/public/assets/linear-C9Nh3JLa.js +1 -0
- package/dist/dashboard/public/assets/livescript-BwQOo05w.js +1 -0
- package/dist/dashboard/public/assets/lua-BgMRiT3U.js +1 -0
- package/dist/dashboard/public/assets/mathematica-DTrFuWx2.js +1 -0
- package/dist/dashboard/public/assets/mbox-CNhZ1qSd.js +1 -0
- package/dist/dashboard/public/assets/mindmap-definition-8da855dc-CK-y1AmO.js +415 -0
- package/dist/dashboard/public/assets/mirc-CjQqDB4T.js +1 -0
- package/dist/dashboard/public/assets/mllike-CXdrOF99.js +1 -0
- package/dist/dashboard/public/assets/modelica-Dc1JOy9r.js +1 -0
- package/dist/dashboard/public/assets/mscgen-BA5vi2Kp.js +1 -0
- package/dist/dashboard/public/assets/mumps-BT43cFF4.js +1 -0
- package/dist/dashboard/public/assets/nginx-DdIZxoE0.js +1 -0
- package/dist/dashboard/public/assets/nsis-LdVXkNf5.js +1 -0
- package/dist/dashboard/public/assets/ntriples-BfvgReVJ.js +1 -0
- package/dist/dashboard/public/assets/octave-Ck1zUtKM.js +1 -0
- package/dist/dashboard/public/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/dashboard/public/assets/oz-BzwKVEFT.js +1 -0
- package/dist/dashboard/public/assets/pascal--L3eBynH.js +1 -0
- package/dist/dashboard/public/assets/path-CbwjOpE9.js +1 -0
- package/dist/dashboard/public/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/dashboard/public/assets/pieDiagram-a8764435-T8V0JN2R.js +35 -0
- package/dist/dashboard/public/assets/pig-CevX1Tat.js +1 -0
- package/dist/dashboard/public/assets/powershell-CFHJl5sT.js +1 -0
- package/dist/dashboard/public/assets/properties-C78fOPTZ.js +1 -0
- package/dist/dashboard/public/assets/protobuf-ChK-085T.js +1 -0
- package/dist/dashboard/public/assets/pug-DeIclll2.js +1 -0
- package/dist/dashboard/public/assets/puppet-DMA9R1ak.js +1 -0
- package/dist/dashboard/public/assets/python-BuPzkPfP.js +1 -0
- package/dist/dashboard/public/assets/q-pXgVlZs6.js +1 -0
- package/dist/dashboard/public/assets/quadrantDiagram-1e28029f-CmtVsb5L.js +7 -0
- package/dist/dashboard/public/assets/r-B6wPVr8A.js +1 -0
- package/dist/dashboard/public/assets/requirementDiagram-08caed73-BUcTnzDl.js +52 -0
- package/dist/dashboard/public/assets/rpm-CTu-6PCP.js +1 -0
- package/dist/dashboard/public/assets/ruby-B2Rjki9n.js +1 -0
- package/dist/dashboard/public/assets/sankeyDiagram-a04cb91d-FswuxQ9M.js +8 -0
- package/dist/dashboard/public/assets/sas-B4kiWyti.js +1 -0
- package/dist/dashboard/public/assets/scheme-C41bIUwD.js +1 -0
- package/dist/dashboard/public/assets/sequenceDiagram-c5b8d532-BJQ15rhX.js +122 -0
- package/dist/dashboard/public/assets/shell-CjFT_Tl9.js +1 -0
- package/dist/dashboard/public/assets/sieve-C3Gn_uJK.js +1 -0
- package/dist/dashboard/public/assets/simple-mode-GW_nhZxv.js +1 -0
- package/dist/dashboard/public/assets/smalltalk-CnHTOXQT.js +1 -0
- package/dist/dashboard/public/assets/solr-DehyRSwq.js +1 -0
- package/dist/dashboard/public/assets/sparql-DkYu6x3z.js +1 -0
- package/dist/dashboard/public/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/dist/dashboard/public/assets/sql-D0XecflT.js +1 -0
- package/dist/dashboard/public/assets/stateDiagram-1ecb1508-BfyE0DYv.js +1 -0
- package/dist/dashboard/public/assets/stateDiagram-v2-c2b004d7-pcGOYyiW.js +1 -0
- package/dist/dashboard/public/assets/stex-C3f8Ysf7.js +1 -0
- package/dist/dashboard/public/assets/styles-b4e223ce--lUviH7V.js +160 -0
- package/dist/dashboard/public/assets/styles-ca3715f6-BXbrD1Av.js +207 -0
- package/dist/dashboard/public/assets/styles-d45a18b0-GyiMrLKu.js +116 -0
- package/dist/dashboard/public/assets/stylus-B533Al4x.js +1 -0
- package/dist/dashboard/public/assets/svgDrawCommon-b86b1483-DI4Z1GTS.js +1 -0
- package/dist/dashboard/public/assets/swift-BzpIVaGY.js +1 -0
- package/dist/dashboard/public/assets/tcl-DVfN8rqt.js +1 -0
- package/dist/dashboard/public/assets/textile-CnDTJFAw.js +1 -0
- package/dist/dashboard/public/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/dist/dashboard/public/assets/tiki-DGYXhP31.js +1 -0
- package/dist/dashboard/public/assets/timeline-definition-faaaa080-B1IgohU4.js +61 -0
- package/dist/dashboard/public/assets/toml-Bm5Em-hy.js +1 -0
- package/dist/dashboard/public/assets/troff-wAsdV37c.js +1 -0
- package/dist/dashboard/public/assets/ttcn-CfJYG6tj.js +1 -0
- package/dist/dashboard/public/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/dist/dashboard/public/assets/turtle-B1tBg_DP.js +1 -0
- package/dist/dashboard/public/assets/vb-CmGdzxic.js +1 -0
- package/dist/dashboard/public/assets/vbscript-BuJXcnF6.js +1 -0
- package/dist/dashboard/public/assets/velocity-D8B20fx6.js +1 -0
- package/dist/dashboard/public/assets/verilog-C6RDOZhf.js +1 -0
- package/dist/dashboard/public/assets/vhdl-lSbBsy5d.js +1 -0
- package/dist/dashboard/public/assets/webidl-ZXfAyPTL.js +1 -0
- package/dist/dashboard/public/assets/xquery-DzFWVndE.js +1 -0
- package/dist/dashboard/public/assets/xychartDiagram-f5964ef8-B5oRDe_I.js +7 -0
- package/dist/dashboard/public/assets/yacas-BJ4BC0dw.js +1 -0
- package/dist/dashboard/public/assets/z80-Hz9HOZM7.js +1 -0
- package/dist/dashboard/public/claude-icon-dark.svg +1 -0
- package/dist/dashboard/public/claude-icon.svg +1 -0
- package/dist/dashboard/public/index.html +16 -0
- package/dist/dashboard/settings-manager.d.ts +47 -0
- package/dist/dashboard/settings-manager.d.ts.map +1 -0
- package/dist/dashboard/settings-manager.js +180 -0
- package/dist/dashboard/settings-manager.js.map +1 -0
- package/dist/dashboard/utils.d.ts +31 -0
- package/dist/dashboard/utils.d.ts.map +1 -0
- package/dist/dashboard/utils.js +102 -0
- package/dist/dashboard/utils.js.map +1 -0
- package/dist/dashboard/watcher.d.ts +32 -0
- package/dist/dashboard/watcher.d.ts.map +1 -0
- package/dist/dashboard/watcher.js +173 -0
- package/dist/dashboard/watcher.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +380 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/templates/design-template.md +96 -0
- package/dist/markdown/templates/product-template.md +51 -0
- package/dist/markdown/templates/requirements-template.md +50 -0
- package/dist/markdown/templates/structure-template.md +145 -0
- package/dist/markdown/templates/tasks-template.md +139 -0
- package/dist/markdown/templates/tech-template.md +99 -0
- package/dist/prompts/create-spec.d.ts +3 -0
- package/dist/prompts/create-spec.d.ts.map +1 -0
- package/dist/prompts/create-spec.js +93 -0
- package/dist/prompts/create-spec.js.map +1 -0
- package/dist/prompts/create-steering-doc.d.ts +3 -0
- package/dist/prompts/create-steering-doc.d.ts.map +1 -0
- package/dist/prompts/create-steering-doc.js +73 -0
- package/dist/prompts/create-steering-doc.js.map +1 -0
- package/dist/prompts/implement-task.d.ts +3 -0
- package/dist/prompts/implement-task.d.ts.map +1 -0
- package/dist/prompts/implement-task.js +173 -0
- package/dist/prompts/implement-task.js.map +1 -0
- package/dist/prompts/index.d.ts +15 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +49 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/inject-spec-workflow-guide.d.ts +3 -0
- package/dist/prompts/inject-spec-workflow-guide.d.ts.map +1 -0
- package/dist/prompts/inject-spec-workflow-guide.js +47 -0
- package/dist/prompts/inject-spec-workflow-guide.js.map +1 -0
- package/dist/prompts/inject-steering-guide.d.ts +3 -0
- package/dist/prompts/inject-steering-guide.d.ts.map +1 -0
- package/dist/prompts/inject-steering-guide.js +51 -0
- package/dist/prompts/inject-steering-guide.js.map +1 -0
- package/dist/prompts/refresh-tasks.d.ts +3 -0
- package/dist/prompts/refresh-tasks.d.ts.map +1 -0
- package/dist/prompts/refresh-tasks.js +224 -0
- package/dist/prompts/refresh-tasks.js.map +1 -0
- package/dist/prompts/spec-status.d.ts +3 -0
- package/dist/prompts/spec-status.d.ts.map +1 -0
- package/dist/prompts/spec-status.js +75 -0
- package/dist/prompts/spec-status.js.map +1 -0
- package/dist/prompts/types.d.ts +13 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +2 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +175 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/__tests__/projectPath.test.d.ts +2 -0
- package/dist/tools/__tests__/projectPath.test.d.ts.map +1 -0
- package/dist/tools/__tests__/projectPath.test.js +187 -0
- package/dist/tools/__tests__/projectPath.test.js.map +1 -0
- package/dist/tools/approvals.d.ts +14 -0
- package/dist/tools/approvals.d.ts.map +1 -0
- package/dist/tools/approvals.js +490 -0
- package/dist/tools/approvals.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +52 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/log-implementation.d.ts +5 -0
- package/dist/tools/log-implementation.d.ts.map +1 -0
- package/dist/tools/log-implementation.js +397 -0
- package/dist/tools/log-implementation.js.map +1 -0
- package/dist/tools/spec-status.d.ts +5 -0
- package/dist/tools/spec-status.d.ts.map +1 -0
- package/dist/tools/spec-status.js +178 -0
- package/dist/tools/spec-status.js.map +1 -0
- package/dist/tools/spec-workflow-guide.d.ts +5 -0
- package/dist/tools/spec-workflow-guide.d.ts.map +1 -0
- package/dist/tools/spec-workflow-guide.js +291 -0
- package/dist/tools/spec-workflow-guide.js.map +1 -0
- package/dist/tools/steering-guide.d.ts +5 -0
- package/dist/tools/steering-guide.d.ts.map +1 -0
- package/dist/tools/steering-guide.js +192 -0
- package/dist/tools/steering-guide.js.map +1 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import { basename, resolve } from 'path';
|
|
4
|
+
import { createHash } from 'crypto';
|
|
5
|
+
import { getGlobalDir, getPermissionErrorHelp } from './global-dir.js';
|
|
6
|
+
/**
|
|
7
|
+
* Generate a stable projectId from an absolute path
|
|
8
|
+
* Uses SHA-1 hash encoded as base64url
|
|
9
|
+
*/
|
|
10
|
+
export function generateProjectId(absolutePath) {
|
|
11
|
+
const hash = createHash('sha1').update(absolutePath).digest('base64url');
|
|
12
|
+
// Take first 16 characters for readability
|
|
13
|
+
return hash.substring(0, 16);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build display name for a workspace.
|
|
17
|
+
* - Main repo: "repo"
|
|
18
|
+
* - Worktree: "repo · worktree"
|
|
19
|
+
*/
|
|
20
|
+
export function generateProjectDisplayName(workspacePath, workflowRootPath) {
|
|
21
|
+
const workspaceName = basename(workspacePath);
|
|
22
|
+
const repoName = basename(workflowRootPath);
|
|
23
|
+
if (workspacePath === workflowRootPath) {
|
|
24
|
+
return repoName;
|
|
25
|
+
}
|
|
26
|
+
return `${repoName} · ${workspaceName}`;
|
|
27
|
+
}
|
|
28
|
+
export class ProjectRegistry {
|
|
29
|
+
registryPath;
|
|
30
|
+
registryDir;
|
|
31
|
+
needsInitialization = false;
|
|
32
|
+
constructor() {
|
|
33
|
+
this.registryDir = getGlobalDir();
|
|
34
|
+
this.registryPath = join(this.registryDir, 'activeProjects.json');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Ensure the registry directory exists
|
|
38
|
+
*/
|
|
39
|
+
async ensureRegistryDir() {
|
|
40
|
+
try {
|
|
41
|
+
await fs.mkdir(this.registryDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Directory might already exist, ignore EEXIST errors
|
|
45
|
+
if (error.code === 'EEXIST') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// For permission errors, provide helpful guidance
|
|
49
|
+
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
50
|
+
console.error(getPermissionErrorHelp('create directory', this.registryDir));
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
// Re-throw other errors
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Read the registry file with atomic operations
|
|
59
|
+
* Returns a map keyed by projectId
|
|
60
|
+
*/
|
|
61
|
+
async readRegistry() {
|
|
62
|
+
await this.ensureRegistryDir();
|
|
63
|
+
try {
|
|
64
|
+
const content = await fs.readFile(this.registryPath, 'utf-8');
|
|
65
|
+
// Handle empty or whitespace-only files
|
|
66
|
+
const trimmedContent = content.trim();
|
|
67
|
+
if (!trimmedContent) {
|
|
68
|
+
console.error(`[ProjectRegistry] Warning: ${this.registryPath} is empty, initializing with empty registry`);
|
|
69
|
+
// Mark that we need to write the file
|
|
70
|
+
this.needsInitialization = true;
|
|
71
|
+
return new Map();
|
|
72
|
+
}
|
|
73
|
+
const data = JSON.parse(trimmedContent);
|
|
74
|
+
const registry = new Map();
|
|
75
|
+
// Ensure backward compatibility with older formats:
|
|
76
|
+
// - instances may be missing
|
|
77
|
+
// - workflowRootPath may be missing
|
|
78
|
+
for (const [projectId, entry] of Object.entries(data)) {
|
|
79
|
+
const normalizedProjectPath = resolve(entry.projectPath);
|
|
80
|
+
const normalizedWorkflowRootPath = resolve(entry.workflowRootPath || entry.projectPath);
|
|
81
|
+
registry.set(projectId, {
|
|
82
|
+
...entry,
|
|
83
|
+
projectPath: normalizedProjectPath,
|
|
84
|
+
workflowRootPath: normalizedWorkflowRootPath,
|
|
85
|
+
projectName: entry.projectName || generateProjectDisplayName(normalizedProjectPath, normalizedWorkflowRootPath),
|
|
86
|
+
instances: Array.isArray(entry.instances) ? entry.instances : []
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return registry;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (error.code === 'ENOENT') {
|
|
93
|
+
// File doesn't exist yet, return empty map
|
|
94
|
+
this.needsInitialization = true;
|
|
95
|
+
return new Map();
|
|
96
|
+
}
|
|
97
|
+
if (error instanceof SyntaxError) {
|
|
98
|
+
// JSON parsing error - file is corrupted or invalid
|
|
99
|
+
console.error(`[ProjectRegistry] Error: Failed to parse ${this.registryPath}: ${error.message}`);
|
|
100
|
+
console.error(`[ProjectRegistry] The file may be corrupted. Initializing with empty registry.`);
|
|
101
|
+
// Back up the corrupted file
|
|
102
|
+
try {
|
|
103
|
+
const backupPath = `${this.registryPath}.corrupted.${Date.now()}`;
|
|
104
|
+
await fs.copyFile(this.registryPath, backupPath);
|
|
105
|
+
console.error(`[ProjectRegistry] Corrupted file backed up to: ${backupPath}`);
|
|
106
|
+
}
|
|
107
|
+
catch (backupError) {
|
|
108
|
+
// Ignore backup errors
|
|
109
|
+
}
|
|
110
|
+
this.needsInitialization = true;
|
|
111
|
+
return new Map();
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Write the registry file atomically
|
|
118
|
+
*/
|
|
119
|
+
async writeRegistry(registry) {
|
|
120
|
+
await this.ensureRegistryDir();
|
|
121
|
+
const data = Object.fromEntries(registry);
|
|
122
|
+
const content = JSON.stringify(data, null, 2);
|
|
123
|
+
// Write to temporary file first, then rename for atomic operation
|
|
124
|
+
const tempPath = `${this.registryPath}.tmp`;
|
|
125
|
+
await fs.writeFile(tempPath, content, 'utf-8');
|
|
126
|
+
await fs.rename(tempPath, this.registryPath);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if a process is still running
|
|
130
|
+
* Note: When running in Docker with path translation, we can't check host PIDs,
|
|
131
|
+
* so we assume processes are alive if path translation is enabled.
|
|
132
|
+
*/
|
|
133
|
+
isProcessAlive(pid) {
|
|
134
|
+
// If path translation is enabled, we're in Docker and can't check host PIDs
|
|
135
|
+
const hostPrefix = process.env.SPEC_WORKFLOW_HOST_PATH_PREFIX;
|
|
136
|
+
const containerPrefix = process.env.SPEC_WORKFLOW_CONTAINER_PATH_PREFIX;
|
|
137
|
+
if (hostPrefix && containerPrefix) {
|
|
138
|
+
// Can't verify host PIDs from inside Docker, assume alive
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
// Sending signal 0 checks if process exists without actually sending a signal
|
|
143
|
+
process.kill(pid, 0);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Register a project in the global registry
|
|
152
|
+
* Self-healing: If a project exists with dead PIDs, cleans them up and adds new PID
|
|
153
|
+
* Multi-instance: Allows unlimited MCP server instances per project
|
|
154
|
+
*/
|
|
155
|
+
async registerProject(projectPath, pid, options = {}) {
|
|
156
|
+
const registry = await this.readRegistry();
|
|
157
|
+
const workspacePath = resolve(projectPath);
|
|
158
|
+
const workflowRootPath = resolve(options.workflowRootPath || projectPath);
|
|
159
|
+
const projectId = generateProjectId(workspacePath);
|
|
160
|
+
const projectName = options.projectName || generateProjectDisplayName(workspacePath, workflowRootPath);
|
|
161
|
+
const existing = registry.get(projectId);
|
|
162
|
+
if (existing) {
|
|
163
|
+
// Self-healing: Filter out dead PIDs
|
|
164
|
+
const liveInstances = existing.instances.filter(i => this.isProcessAlive(i.pid));
|
|
165
|
+
// Check if this PID is already registered (avoid duplicates)
|
|
166
|
+
if (!liveInstances.some(i => i.pid === pid)) {
|
|
167
|
+
liveInstances.push({ pid, registeredAt: new Date().toISOString() });
|
|
168
|
+
}
|
|
169
|
+
// Update with live instances (no limit on number of instances)
|
|
170
|
+
existing.projectPath = workspacePath;
|
|
171
|
+
existing.workflowRootPath = workflowRootPath;
|
|
172
|
+
existing.projectName = projectName;
|
|
173
|
+
existing.instances = liveInstances;
|
|
174
|
+
registry.set(projectId, existing);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// New project
|
|
178
|
+
const entry = {
|
|
179
|
+
projectId,
|
|
180
|
+
projectPath: workspacePath,
|
|
181
|
+
workflowRootPath,
|
|
182
|
+
projectName,
|
|
183
|
+
instances: [{ pid, registeredAt: new Date().toISOString() }]
|
|
184
|
+
};
|
|
185
|
+
registry.set(projectId, entry);
|
|
186
|
+
}
|
|
187
|
+
await this.writeRegistry(registry);
|
|
188
|
+
return projectId;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Unregister a project from the global registry by path
|
|
192
|
+
* If pid is provided, only removes that specific instance
|
|
193
|
+
* If no pid provided, removes the entire project (backwards compat)
|
|
194
|
+
*/
|
|
195
|
+
async unregisterProject(projectPath, pid) {
|
|
196
|
+
const registry = await this.readRegistry();
|
|
197
|
+
const absolutePath = resolve(projectPath);
|
|
198
|
+
const projectId = generateProjectId(absolutePath);
|
|
199
|
+
const entry = registry.get(projectId);
|
|
200
|
+
if (!entry)
|
|
201
|
+
return;
|
|
202
|
+
if (pid !== undefined) {
|
|
203
|
+
// Remove only this PID's instance
|
|
204
|
+
entry.instances = entry.instances.filter(i => i.pid !== pid);
|
|
205
|
+
if (entry.instances.length === 0) {
|
|
206
|
+
registry.delete(projectId);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
registry.set(projectId, entry);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// Remove entire project (backwards compat)
|
|
214
|
+
registry.delete(projectId);
|
|
215
|
+
}
|
|
216
|
+
await this.writeRegistry(registry);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Unregister a project by projectId
|
|
220
|
+
*/
|
|
221
|
+
async unregisterProjectById(projectId) {
|
|
222
|
+
const registry = await this.readRegistry();
|
|
223
|
+
registry.delete(projectId);
|
|
224
|
+
await this.writeRegistry(registry);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get all active projects from the registry
|
|
228
|
+
*/
|
|
229
|
+
async getAllProjects() {
|
|
230
|
+
const registry = await this.readRegistry();
|
|
231
|
+
return Array.from(registry.values());
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get a specific project by path
|
|
235
|
+
*/
|
|
236
|
+
async getProject(projectPath) {
|
|
237
|
+
const registry = await this.readRegistry();
|
|
238
|
+
const absolutePath = resolve(projectPath);
|
|
239
|
+
const projectId = generateProjectId(absolutePath);
|
|
240
|
+
return registry.get(projectId) || null;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get a specific project by projectId
|
|
244
|
+
*/
|
|
245
|
+
async getProjectById(projectId) {
|
|
246
|
+
const registry = await this.readRegistry();
|
|
247
|
+
return registry.get(projectId) || null;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Clean up stale instances (where the process is no longer running)
|
|
251
|
+
* Projects with no live instances are removed entirely
|
|
252
|
+
* Returns the count of removed instances
|
|
253
|
+
*/
|
|
254
|
+
async cleanupStaleProjects() {
|
|
255
|
+
const registry = await this.readRegistry();
|
|
256
|
+
let removedInstanceCount = 0;
|
|
257
|
+
let needsWrite = this.needsInitialization; // Write if file needs initialization
|
|
258
|
+
for (const [projectId, entry] of registry.entries()) {
|
|
259
|
+
const liveInstances = entry.instances.filter(i => this.isProcessAlive(i.pid));
|
|
260
|
+
const deadCount = entry.instances.length - liveInstances.length;
|
|
261
|
+
if (deadCount > 0) {
|
|
262
|
+
removedInstanceCount += deadCount;
|
|
263
|
+
needsWrite = true;
|
|
264
|
+
if (liveInstances.length === 0) {
|
|
265
|
+
// No live instances, remove entire project
|
|
266
|
+
registry.delete(projectId);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
// Keep project with only live instances
|
|
270
|
+
entry.instances = liveInstances;
|
|
271
|
+
registry.set(projectId, entry);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (needsWrite) {
|
|
276
|
+
await this.writeRegistry(registry);
|
|
277
|
+
this.needsInitialization = false; // Reset flag after successful write
|
|
278
|
+
}
|
|
279
|
+
return removedInstanceCount;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Check if a project is registered by path
|
|
283
|
+
*/
|
|
284
|
+
async isProjectRegistered(projectPath) {
|
|
285
|
+
const registry = await this.readRegistry();
|
|
286
|
+
const absolutePath = resolve(projectPath);
|
|
287
|
+
const projectId = generateProjectId(absolutePath);
|
|
288
|
+
return registry.has(projectId);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get the registry file path for watching
|
|
292
|
+
*/
|
|
293
|
+
getRegistryPath() {
|
|
294
|
+
return this.registryPath;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=project-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-registry.js","sourceRoot":"","sources":["../../src/core/project-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security utilities for rate limiting and audit logging
|
|
3
|
+
* Implements security best practices for MCP servers
|
|
4
|
+
*/
|
|
5
|
+
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
6
|
+
import { SecurityConfig } from '../types.js';
|
|
7
|
+
export declare const DEFAULT_DASHBOARD_PORT = 5000;
|
|
8
|
+
export declare const DEFAULT_SECURITY_CONFIG: SecurityConfig;
|
|
9
|
+
export declare const VITE_DEV_PORT = 5173;
|
|
10
|
+
/**
|
|
11
|
+
* Generate allowed origins for CORS based on the actual port
|
|
12
|
+
* @param port - The port the dashboard is running on
|
|
13
|
+
* @returns Array of allowed origin URLs
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateAllowedOrigins(port: number): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Check if an IP address is localhost
|
|
18
|
+
* @param address - IP address or hostname to check
|
|
19
|
+
* @returns true if the address is localhost (127.x.x.x, localhost, or ::1)
|
|
20
|
+
*/
|
|
21
|
+
export declare function isLocalhostAddress(address: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Get security configuration with secure defaults
|
|
24
|
+
* Note: Network binding validation (bindAddress/allowExternalAccess) is handled separately at the config layer
|
|
25
|
+
* @param userConfig - Optional user-provided security configuration overrides
|
|
26
|
+
* @param port - The port the dashboard is running on (used to generate dynamic allowedOrigins)
|
|
27
|
+
*/
|
|
28
|
+
export declare function getSecurityConfig(userConfig?: Partial<SecurityConfig>, port?: number): SecurityConfig;
|
|
29
|
+
/**
|
|
30
|
+
* Rate limiting implementation
|
|
31
|
+
*/
|
|
32
|
+
export declare class RateLimiter {
|
|
33
|
+
private requests;
|
|
34
|
+
private config;
|
|
35
|
+
constructor(config: SecurityConfig);
|
|
36
|
+
/**
|
|
37
|
+
* Check if request should be rate limited
|
|
38
|
+
*/
|
|
39
|
+
checkLimit(clientId: string): {
|
|
40
|
+
allowed: boolean;
|
|
41
|
+
retryAfter?: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Create rate limiting middleware
|
|
45
|
+
*/
|
|
46
|
+
middleware(): (request: FastifyRequest, reply: FastifyReply) => Promise<undefined>;
|
|
47
|
+
/**
|
|
48
|
+
* Clean up old request records
|
|
49
|
+
*/
|
|
50
|
+
private cleanup;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Audit log entry
|
|
54
|
+
*/
|
|
55
|
+
export interface AuditLogEntry {
|
|
56
|
+
timestamp: string;
|
|
57
|
+
actor: string;
|
|
58
|
+
action: string;
|
|
59
|
+
resource: string;
|
|
60
|
+
result: 'success' | 'failure' | 'denied';
|
|
61
|
+
details?: Record<string, any>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Audit logger for security events
|
|
65
|
+
*/
|
|
66
|
+
export declare class AuditLogger {
|
|
67
|
+
private config;
|
|
68
|
+
private logPath;
|
|
69
|
+
constructor(config: SecurityConfig, workspaceRoot?: string);
|
|
70
|
+
/**
|
|
71
|
+
* Initialize audit log (create directory if needed)
|
|
72
|
+
*/
|
|
73
|
+
initialize(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Log an audit event
|
|
76
|
+
*/
|
|
77
|
+
log(entry: AuditLogEntry): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Create audit logging middleware
|
|
80
|
+
*/
|
|
81
|
+
middleware(): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Security headers middleware
|
|
85
|
+
* @param port - The port the dashboard is running on (used for CSP connect-src for WebSocket)
|
|
86
|
+
*/
|
|
87
|
+
export declare function createSecurityHeadersMiddleware(port?: number): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* CORS configuration
|
|
90
|
+
*/
|
|
91
|
+
export declare function getCorsConfig(config: SecurityConfig): false | {
|
|
92
|
+
origin: (origin: string, callback: (error: Error | null, allow?: boolean) => void) => void;
|
|
93
|
+
credentials: boolean;
|
|
94
|
+
methods: string[];
|
|
95
|
+
allowedHeaders: string[];
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=security-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-utils.d.ts","sourceRoot":"","sources":["../../src/core/security-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAI3C,eAAO,MAAM,uBAAuB,EAAE,cAOrC,CAAC;AAGF,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAW7D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAI3D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CAarG;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,EAAE,cAAc;IAOlC;;OAEG;IACI,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IA6B9E;;OAEG;IACI,UAAU,KACD,SAAS,cAAc,EAAE,OAAO,YAAY;IAmB5D;;OAEG;IACH,OAAO,CAAC,OAAO;CAahB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,cAAc,EAAE,aAAa,CAAC,EAAE,MAAM;IAc1D;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAajC;;OAEG;IACG,GAAG,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAa9C;;OAEG;IACH,UAAU,KACM,SAAS,cAAc,EAAE,OAAO,YAAY;CA0B7D;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,IAAI,CAAC,EAAE,MAAM,IAW7C,SAAS,cAAc,EAAE,OAAO,YAAY,mBAe3D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc;qBAM/B,MAAM,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI;;;;EAkBpF"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security utilities for rate limiting and audit logging
|
|
3
|
+
* Implements security best practices for MCP servers
|
|
4
|
+
*/
|
|
5
|
+
import { appendFile, mkdir } from 'fs/promises';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
// Default port for the dashboard
|
|
8
|
+
export const DEFAULT_DASHBOARD_PORT = 5000;
|
|
9
|
+
// Default security configuration (secure by default)
|
|
10
|
+
// Note: allowedOrigins should be dynamically generated based on the actual port
|
|
11
|
+
export const DEFAULT_SECURITY_CONFIG = {
|
|
12
|
+
rateLimitEnabled: true,
|
|
13
|
+
rateLimitPerMinute: 120, // 120 requests per minute per client
|
|
14
|
+
auditLogEnabled: true,
|
|
15
|
+
auditLogRetentionDays: 30,
|
|
16
|
+
corsEnabled: true,
|
|
17
|
+
allowedOrigins: [`http://localhost:${DEFAULT_DASHBOARD_PORT}`, `http://127.0.0.1:${DEFAULT_DASHBOARD_PORT}`]
|
|
18
|
+
};
|
|
19
|
+
// Default Vite dev server port (used when running frontend in dev mode)
|
|
20
|
+
export const VITE_DEV_PORT = 5173;
|
|
21
|
+
/**
|
|
22
|
+
* Generate allowed origins for CORS based on the actual port
|
|
23
|
+
* @param port - The port the dashboard is running on
|
|
24
|
+
* @returns Array of allowed origin URLs
|
|
25
|
+
*/
|
|
26
|
+
export function generateAllowedOrigins(port) {
|
|
27
|
+
const origins = [`http://localhost:${port}`, `http://127.0.0.1:${port}`];
|
|
28
|
+
// In non-production environments, also allow Vite dev server origin (port 5173)
|
|
29
|
+
// The Vite proxy forwards requests but preserves the Origin header
|
|
30
|
+
// Use !== 'production' to be permissive by default for local dev tools
|
|
31
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
32
|
+
origins.push(`http://localhost:${VITE_DEV_PORT}`, `http://127.0.0.1:${VITE_DEV_PORT}`);
|
|
33
|
+
}
|
|
34
|
+
return origins;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if an IP address is localhost
|
|
38
|
+
* @param address - IP address or hostname to check
|
|
39
|
+
* @returns true if the address is localhost (127.x.x.x, localhost, or ::1)
|
|
40
|
+
*/
|
|
41
|
+
export function isLocalhostAddress(address) {
|
|
42
|
+
return address === 'localhost' ||
|
|
43
|
+
address === '::1' || // IPv6 localhost
|
|
44
|
+
address.startsWith('127.'); // Any 127.x.x.x address (includes 127.0.0.1)
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get security configuration with secure defaults
|
|
48
|
+
* Note: Network binding validation (bindAddress/allowExternalAccess) is handled separately at the config layer
|
|
49
|
+
* @param userConfig - Optional user-provided security configuration overrides
|
|
50
|
+
* @param port - The port the dashboard is running on (used to generate dynamic allowedOrigins)
|
|
51
|
+
*/
|
|
52
|
+
export function getSecurityConfig(userConfig, port) {
|
|
53
|
+
const actualPort = port || DEFAULT_DASHBOARD_PORT;
|
|
54
|
+
// Generate dynamic allowedOrigins based on the actual port if not explicitly provided
|
|
55
|
+
const dynamicAllowedOrigins = generateAllowedOrigins(actualPort);
|
|
56
|
+
const config = {
|
|
57
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
58
|
+
allowedOrigins: dynamicAllowedOrigins,
|
|
59
|
+
...userConfig
|
|
60
|
+
};
|
|
61
|
+
return config;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Rate limiting implementation
|
|
65
|
+
*/
|
|
66
|
+
export class RateLimiter {
|
|
67
|
+
requests = new Map();
|
|
68
|
+
config;
|
|
69
|
+
constructor(config) {
|
|
70
|
+
this.config = config;
|
|
71
|
+
// Clean up old entries every minute
|
|
72
|
+
setInterval(() => this.cleanup(), 60000);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if request should be rate limited
|
|
76
|
+
*/
|
|
77
|
+
checkLimit(clientId) {
|
|
78
|
+
if (!this.config.rateLimitEnabled) {
|
|
79
|
+
return { allowed: true };
|
|
80
|
+
}
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const windowMs = 60000; // 1 minute window
|
|
83
|
+
const maxRequests = this.config.rateLimitPerMinute;
|
|
84
|
+
// Get client request history
|
|
85
|
+
const requests = this.requests.get(clientId) || [];
|
|
86
|
+
// Filter to requests within the current window
|
|
87
|
+
const recentRequests = requests.filter(timestamp => now - timestamp < windowMs);
|
|
88
|
+
// Check if limit exceeded
|
|
89
|
+
if (recentRequests.length >= maxRequests) {
|
|
90
|
+
const oldestRequest = recentRequests[0];
|
|
91
|
+
const retryAfter = Math.ceil((oldestRequest + windowMs - now) / 1000);
|
|
92
|
+
return { allowed: false, retryAfter };
|
|
93
|
+
}
|
|
94
|
+
// Add current request
|
|
95
|
+
recentRequests.push(now);
|
|
96
|
+
this.requests.set(clientId, recentRequests);
|
|
97
|
+
return { allowed: true };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create rate limiting middleware
|
|
101
|
+
*/
|
|
102
|
+
middleware() {
|
|
103
|
+
return async (request, reply) => {
|
|
104
|
+
// Use IP address as client identifier
|
|
105
|
+
const clientId = request.ip || 'unknown';
|
|
106
|
+
const result = this.checkLimit(clientId);
|
|
107
|
+
if (!result.allowed) {
|
|
108
|
+
return reply
|
|
109
|
+
.code(429)
|
|
110
|
+
.header('Retry-After', String(result.retryAfter || 60))
|
|
111
|
+
.send({
|
|
112
|
+
error: 'Too Many Requests',
|
|
113
|
+
message: `Rate limit exceeded. Maximum ${this.config.rateLimitPerMinute} requests per minute.`,
|
|
114
|
+
retryAfter: result.retryAfter
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Clean up old request records
|
|
121
|
+
*/
|
|
122
|
+
cleanup() {
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
const windowMs = 60000;
|
|
125
|
+
for (const [clientId, requests] of this.requests.entries()) {
|
|
126
|
+
const recentRequests = requests.filter(timestamp => now - timestamp < windowMs);
|
|
127
|
+
if (recentRequests.length === 0) {
|
|
128
|
+
this.requests.delete(clientId);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
this.requests.set(clientId, recentRequests);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Audit logger for security events
|
|
138
|
+
*/
|
|
139
|
+
export class AuditLogger {
|
|
140
|
+
config;
|
|
141
|
+
logPath;
|
|
142
|
+
constructor(config, workspaceRoot) {
|
|
143
|
+
this.config = config;
|
|
144
|
+
// Determine audit log path
|
|
145
|
+
if (config.auditLogPath) {
|
|
146
|
+
this.logPath = config.auditLogPath;
|
|
147
|
+
}
|
|
148
|
+
else if (workspaceRoot) {
|
|
149
|
+
this.logPath = join(workspaceRoot, '.spec-workflow', 'audit.log');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Fallback to temp directory
|
|
153
|
+
this.logPath = join(process.cwd(), 'audit.log');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Initialize audit log (create directory if needed)
|
|
158
|
+
*/
|
|
159
|
+
async initialize() {
|
|
160
|
+
if (!this.config.auditLogEnabled) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const logDir = join(this.logPath, '..');
|
|
165
|
+
await mkdir(logDir, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
console.error('Failed to initialize audit log:', error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Log an audit event
|
|
173
|
+
*/
|
|
174
|
+
async log(entry) {
|
|
175
|
+
if (!this.config.auditLogEnabled) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const logLine = JSON.stringify(entry) + '\n';
|
|
180
|
+
await appendFile(this.logPath, logLine, 'utf-8');
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error('Failed to write audit log:', error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Create audit logging middleware
|
|
188
|
+
*/
|
|
189
|
+
middleware() {
|
|
190
|
+
return async (request, reply) => {
|
|
191
|
+
const startTime = Date.now();
|
|
192
|
+
// Log after response is sent using reply.then() which fires after response completes
|
|
193
|
+
reply.then(() => {
|
|
194
|
+
const entry = {
|
|
195
|
+
timestamp: new Date().toISOString(),
|
|
196
|
+
actor: request.ip || 'unknown',
|
|
197
|
+
action: `${request.method} ${request.url}`,
|
|
198
|
+
resource: request.url,
|
|
199
|
+
result: reply.statusCode < 400 ? 'success' : reply.statusCode === 401 || reply.statusCode === 403 ? 'denied' : 'failure',
|
|
200
|
+
details: {
|
|
201
|
+
statusCode: reply.statusCode,
|
|
202
|
+
duration: Date.now() - startTime,
|
|
203
|
+
userAgent: request.headers['user-agent']
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
// Fire and forget - don't await to avoid blocking
|
|
207
|
+
this.log(entry).catch(() => { });
|
|
208
|
+
}, () => { } // Ignore errors from reply.then
|
|
209
|
+
);
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Security headers middleware
|
|
215
|
+
* @param port - The port the dashboard is running on (used for CSP connect-src for WebSocket)
|
|
216
|
+
*/
|
|
217
|
+
export function createSecurityHeadersMiddleware(port) {
|
|
218
|
+
const actualPort = port || DEFAULT_DASHBOARD_PORT;
|
|
219
|
+
// Build connect-src directive with WebSocket endpoints
|
|
220
|
+
let connectSrc = `'self' ws://localhost:${actualPort} ws://127.0.0.1:${actualPort}`;
|
|
221
|
+
// In non-production environments, also allow Vite dev server connections
|
|
222
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
223
|
+
connectSrc += ` ws://localhost:${VITE_DEV_PORT} ws://127.0.0.1:${VITE_DEV_PORT}`;
|
|
224
|
+
}
|
|
225
|
+
return async (request, reply) => {
|
|
226
|
+
// Add security headers
|
|
227
|
+
reply.header('X-Content-Type-Options', 'nosniff'); // Prevent MIME type sniffing
|
|
228
|
+
reply.header('X-Frame-Options', 'DENY'); // Prevent clickjacking
|
|
229
|
+
reply.header('X-XSS-Protection', '1; mode=block'); // Enable XSS protection
|
|
230
|
+
reply.header('Referrer-Policy', 'strict-origin-when-cross-origin'); // Prevent referrer leakage
|
|
231
|
+
// CSP for dashboard
|
|
232
|
+
// Note: cdn.jsdelivr.net is required for highlight.js stylesheets used by the MDX editor
|
|
233
|
+
// connect-src allows WebSocket connections to the dashboard on the actual port
|
|
234
|
+
reply.header('Content-Security-Policy', `default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data:; connect-src ${connectSrc};`);
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* CORS configuration
|
|
239
|
+
*/
|
|
240
|
+
export function getCorsConfig(config) {
|
|
241
|
+
if (!config.corsEnabled) {
|
|
242
|
+
return false; // Disable CORS
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
origin: (origin, callback) => {
|
|
246
|
+
// Allow requests with no origin (e.g., curl, Postman)
|
|
247
|
+
if (!origin) {
|
|
248
|
+
callback(null, true);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Check if origin is in allowed list
|
|
252
|
+
if (config.allowedOrigins.includes(origin)) {
|
|
253
|
+
callback(null, true);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
callback(new Error('Not allowed by CORS'));
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
credentials: true,
|
|
260
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
261
|
+
allowedHeaders: ['Content-Type']
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=security-utils.js.map
|