@a-company/paradigm 5.10.0 → 5.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accept-orchestration-IUAWQZUN.js +18 -0
- package/dist/add-P76GEMGF.js +8 -0
- package/dist/agent-UUTYOFTH.js +33 -0
- package/dist/agent-loader-EPXURMO7.js +2 -0
- package/dist/agent-loader-PYGPX6D2.js +2 -0
- package/dist/agent-state-KSQ3S7OB.js +2 -0
- package/dist/agents-suggest-HYTFMQD3.js +10 -0
- package/dist/aggregate-W66DM3GA.js +10 -0
- package/dist/ambient-HYZR42NX.js +35 -0
- package/dist/assess-UFPYEJKP.js +9 -0
- package/dist/auto-RHJXOZFL.js +14 -0
- package/dist/beacon-5QVYV5DF.js +13 -0
- package/dist/calibration-OLJYB5HN.js +7 -0
- package/dist/check-THVGY4R5.js +14 -0
- package/dist/chunk-22ISTMKI.js +31 -0
- package/dist/chunk-2QO33NYK.js +3 -0
- package/dist/chunk-33LKBMVK.js +134 -0
- package/dist/chunk-4QADCWPU.js +7 -0
- package/dist/chunk-4TXOVRWD.js +3 -0
- package/dist/chunk-4UJ4NIEQ.js +4 -0
- package/dist/chunk-4ZGGWZ32.js +3 -0
- package/dist/chunk-5TAVYPOV.js +2 -0
- package/dist/chunk-6EX37L4Q.js +8 -0
- package/dist/chunk-AD5TAD2A.js +5 -0
- package/dist/chunk-AGVAHVUA.js +2 -0
- package/dist/chunk-AO7ZSRME.js +13 -0
- package/dist/chunk-BFD3GFRK.js +2 -0
- package/dist/chunk-D34YFK4M.js +6 -0
- package/dist/chunk-DVZWCXB6.js +2 -0
- package/dist/chunk-EKZDFEJW.js +2 -0
- package/dist/chunk-ET5XJTSX.js +2 -0
- package/dist/chunk-FYDRENK7.js +3 -0
- package/dist/chunk-HMQ5BHP2.js +743 -0
- package/dist/chunk-HXKWL3VK.js +29 -0
- package/dist/chunk-IZSBGW6E.js +2 -0
- package/dist/chunk-J32OPJEX.js +2 -0
- package/dist/chunk-JBDMCRPP.js +7 -0
- package/dist/chunk-JIXHEBGK.js +7 -0
- package/dist/chunk-JNSJVCTU.js +2 -0
- package/dist/chunk-JQKKVAAN.js +2 -0
- package/dist/chunk-KLBH26PA.js +429 -0
- package/dist/chunk-L6K5HNDL.js +24 -0
- package/dist/chunk-LKAT7IAK.js +2 -0
- package/dist/chunk-M4UMM6DC.js +5 -0
- package/dist/chunk-NKYNHSA5.js +424 -0
- package/dist/chunk-NW6OKHE6.js +5 -0
- package/dist/chunk-NXRVR37G.js +60 -0
- package/dist/chunk-ODVKPZZ4.js +2 -0
- package/dist/chunk-PUBB7LIT.js +5 -0
- package/dist/chunk-Q2J542ST.js +2 -0
- package/dist/chunk-QGZRM6ZB.js +2 -0
- package/dist/chunk-QNZEG7IT.js +13 -0
- package/dist/chunk-QT2LKB3P.js +7 -0
- package/dist/chunk-RVRARWSY.js +9 -0
- package/dist/chunk-T6IDXUUA.js +2 -0
- package/dist/chunk-TYWB5IQJ.js +3 -0
- package/dist/chunk-UIKLE3WD.js +2 -0
- package/dist/chunk-V7K6CQVX.js +111 -0
- package/dist/chunk-VCKKJDLP.js +8 -0
- package/dist/chunk-VG7FN2TU.js +4 -0
- package/dist/chunk-W5IWDW4Y.js +16 -0
- package/dist/chunk-W7C6FFMO.js +8 -0
- package/dist/chunk-WESTEMIM.js +10 -0
- package/dist/chunk-WR6D3SC6.js +5 -0
- package/dist/chunk-WS2N27RX.js +3 -0
- package/dist/chunk-XMAV5AG6.js +2 -0
- package/dist/chunk-XROULIQN.js +2 -0
- package/dist/chunk-XUFYOXOU.js +3 -0
- package/dist/chunk-Y4XFVDZC.js +3 -0
- package/dist/chunk-Y6KXTQBG.js +2 -0
- package/dist/{chunk-GTR2TBIJ.js → chunk-Z2UZUCY6.js} +237 -1047
- package/dist/chunk-Z34K6GY2.js +8 -0
- package/dist/claude-4LR3LJQZ.js +13 -0
- package/dist/claude-cli-UP6HGH7C.js +6 -0
- package/dist/claude-code-RLJ4GX77.js +11 -0
- package/dist/claude-code-teams-R37HJY3Y.js +29 -0
- package/dist/commands-LMUD5L6R.js +455 -0
- package/dist/compliance-GZ7UAOG3.js +6 -0
- package/dist/conductor-Y5IXELTL.js +4 -0
- package/dist/config-schema-HR7LDEQ6.js +2 -0
- package/dist/constellation-CG7C4WFE.js +13 -0
- package/dist/context-audit-RSSRX6XC.js +6 -0
- package/dist/cost-IDNVMAEV.js +10 -0
- package/dist/cost-PK4KIF7R.js +2 -0
- package/dist/cursor-cli-QKF7Z6M2.js +4 -0
- package/dist/cursorrules-U5O4G5T4.js +14 -0
- package/dist/decision-loader-2XPZE4EZ.js +2 -0
- package/dist/delete-P5VULXR4.js +14 -0
- package/dist/diff-RTFUKJHF.js +9 -0
- package/dist/discipline-H7LDI6NT.js +2 -0
- package/dist/dist-3ZCH25SG.js +2 -0
- package/dist/dist-KGRCLBJP-2QAPFYNF.js +39 -0
- package/dist/dist-R3RWD35F.js +2 -0
- package/dist/dist-VGFSP3XM.js +2 -0
- package/dist/dist-VXCZWVVJ.js +2 -0
- package/dist/dist-W3XCATBJ.js +2 -0
- package/dist/docs-EDQ2STFK.js +2 -0
- package/dist/docs-RXPN6CK7.js +12 -0
- package/dist/doctor-2T2QL6VM.js +2 -0
- package/dist/drift-ILZE5BFJ.js +13 -0
- package/dist/echo-UPTQUEDU.js +46 -0
- package/dist/edit-GUU3HBVW.js +9 -0
- package/dist/event-Y3VXC2RV.js +3 -0
- package/dist/explain-files-3GPZUETV.js +4 -0
- package/dist/export-CV5KCTPS.js +2 -0
- package/dist/flow-S3RQ4YPY.js +11 -0
- package/dist/global-C44FW4G2.js +13 -0
- package/dist/graduate-3BBSC27A.js +5 -0
- package/dist/graph-CNDE5TAT.js +2 -0
- package/dist/graph-server-COZR5C3Z.js +14 -0
- package/dist/habits-4J2KRYUL.js +42 -0
- package/dist/history-UW454SDP.js +12 -0
- package/dist/hooks-XH52VGOU.js +2 -0
- package/dist/index.js +15 -1039
- package/dist/init-24MAQJFM.js +2 -0
- package/dist/integrity-UYDOOJDP.js +7 -0
- package/dist/integrity-checker-DHGMZQDG.js +2 -0
- package/dist/journal-loader-GLH7XFTK.js +2 -0
- package/dist/lint-IGKE6UPS.js +26 -0
- package/dist/list-5IUGP3ZB.js +7 -0
- package/dist/list-YKIQNKGB.js +6 -0
- package/dist/lore-loader-RVQI5GXL.js +2 -0
- package/dist/lore-loader-XY5MZRR2.js +2 -0
- package/dist/lore-server-FC2GMDLT.js +14 -0
- package/dist/manual-RXSPSFLL.js +20 -0
- package/dist/mcp.js +93 -20484
- package/dist/migrate-X5OMAFAQ.js +59 -0
- package/dist/migrate-assessments-GEI5WMI2.js +4 -0
- package/dist/model-discovery-HMB3YI4L.js +2 -0
- package/dist/nomination-engine-XF7TWPAP.js +2 -0
- package/dist/notebook-PE3JSYZI.js +11 -0
- package/dist/notebook-loader-CENTDDUJ.js +2 -0
- package/dist/orchestrate-35HBQ3O3.js +8 -0
- package/dist/peers-P2KXU7ZK.js +5 -0
- package/dist/persona-STQWZH5P.js +29 -0
- package/dist/pipeline-MZUITRVN.js +4 -0
- package/dist/platform-server-2KXHGHFV.js +25 -0
- package/dist/plugin-update-checker-M7PW434O.js +2 -0
- package/dist/portal-check-3JVNLOEP.js +8 -0
- package/dist/portal-compliance-2UG4VKWH.js +2 -0
- package/dist/probe-5L5BQDGE.js +6 -0
- package/dist/project-type-SURTOIG7.js +2 -0
- package/dist/promote-NJQDZBZA.js +8 -0
- package/dist/providers-4PXMWA7V.js +8 -0
- package/dist/quiz-FE5UGAY2.js +10 -0
- package/dist/record-YXPB34MY.js +6 -0
- package/dist/reindex-PGDQSM42.js +2 -0
- package/dist/remember-MJRNTXYS.js +14 -0
- package/dist/retag-N5XF3KXP.js +6 -0
- package/dist/review-6UAH6V3R.js +11 -0
- package/dist/review-77QI6VOC.js +6 -0
- package/dist/ripple-ZGDITCGB.js +10 -0
- package/dist/roster-FLRZ5J42.js +2 -0
- package/dist/sentinel-HYAZ3CO5.js +19 -0
- package/dist/sentinel-bridge-VR357PKL.js +2 -0
- package/dist/sentinel-mcp.js +38 -3808
- package/dist/sentinel.js +1 -34
- package/dist/serve-JE6UF5P2.js +8 -0
- package/dist/serve-L52ZUTU6.js +10 -0
- package/dist/serve-OY6XYL7F.js +12 -0
- package/dist/server-2MNROHF6.js +7 -0
- package/dist/server-4YNUIK4W.js +8 -0
- package/dist/session-tracker-SKOMBDOW.js +2 -0
- package/dist/session-work-log-3MNUDEZ6.js +2 -0
- package/dist/session-work-log-J2H5OZBR.js +2 -0
- package/dist/setup-3F5IK7MO.js +31 -0
- package/dist/setup-KPIMRZ4Q.js +2 -0
- package/dist/shift-6GCNH666.js +11 -0
- package/dist/show-BOAVWZPZ.js +7 -0
- package/dist/show-PJ5LFLIL.js +5 -0
- package/dist/snapshot-L2G56RPL.js +8 -0
- package/dist/spawn-UH5RENSE.js +12 -0
- package/dist/status-77M3SDIF.js +8 -0
- package/dist/status-A37ECYNJ.js +6 -0
- package/dist/summary-ZJLQ6KHB.js +7 -0
- package/dist/sweep-HU74OPVW.js +17 -0
- package/dist/switch-CTW4PDGI.js +17 -0
- package/dist/symphony-3QMBGCZY.js +2 -0
- package/dist/symphony-QI63XL62.js +53 -0
- package/dist/symphony-loader-XACVV2E3.js +2 -0
- package/dist/symphony-peers-U4KHMKGI.js +2 -0
- package/dist/symphony-peers-X5NGWXFP.js +2 -0
- package/dist/symphony-relay-5GY5JIPT.js +3 -0
- package/dist/sync-QRDSFETO.js +2 -0
- package/dist/sync-llms-HL5PPW3M.js +15 -0
- package/dist/task-loader-NZFDTUQ5.js +2 -0
- package/dist/team-WGERKGNP.js +2 -0
- package/dist/test-BQJMS4Y2.js +111 -0
- package/dist/thread-HFXK65D4.js +41 -0
- package/dist/timeline-K3ZFKJ3R.js +6 -0
- package/dist/triage-FCWOZASE.js +18 -0
- package/dist/tutorial-UC6YQMNN.js +305 -0
- package/dist/university-content/courses/.purpose +74 -5
- package/dist/university-content/courses/para-701.json +807 -0
- package/dist/university-content/plsat/.purpose +3 -2
- package/dist/university-ui/assets/{index-BjewBB6s.js → index-CGFJczb1.js} +2 -2
- package/dist/university-ui/assets/{index-BjewBB6s.js.map → index-CGFJczb1.js.map} +1 -1
- package/dist/university-ui/index.html +1 -1
- package/dist/upgrade-B4IOLZYK.js +53 -0
- package/dist/validate-C6SMKGYD.js +9 -0
- package/dist/validate-LSCDOLBO.js +13 -0
- package/dist/validate-VZXTJHGO.js +6 -0
- package/dist/watch-LRM5XD46.js +8 -0
- package/dist/watch-PZCCUP6K.js +9 -0
- package/dist/wisdom-XZ3QKPNP.js +7 -0
- package/dist/work-log-loader-DL5GZ2BQ.js +2 -0
- package/dist/workspace-2ODL5WLY.js +2 -0
- package/package.json +1 -1
- package/platform-ui/dist/assets/AmbientSection-DqQ2mfCO.js +1 -0
- package/platform-ui/dist/assets/{CanvasSection-DwPTRdIN.js → CanvasSection-DLW0s-Bu.js} +1 -1
- package/platform-ui/dist/assets/{DocsSection-BJc_ws4I.js → DocsSection-BQ9Hi51g.js} +1 -1
- package/platform-ui/dist/assets/{GitSection-DhqBo1xc.js → GitSection-QQeUX6o3.js} +1 -1
- package/platform-ui/dist/assets/{GraphSection-D9fhVnZ0.js → GraphSection-0c9C-w7K.js} +2 -2
- package/platform-ui/dist/assets/{GraphSection-BlgXTl53.css → GraphSection-Bbs2a8sC.css} +1 -1
- package/platform-ui/dist/assets/LoreSection--t56kCj2.js +1 -0
- package/platform-ui/dist/assets/LoreSection-qnmm3o4y.css +1 -0
- package/platform-ui/dist/assets/SentinelSection-C4OoME9U.js +1 -0
- package/platform-ui/dist/assets/{SymphonySection-BRoBaUSS.js → SymphonySection-B-rvsLxq.js} +1 -1
- package/platform-ui/dist/assets/TeamSection-CBFmNEEo.js +1 -0
- package/platform-ui/dist/assets/index-Bxp0TVaf.css +1 -0
- package/platform-ui/dist/assets/index-DPpOdAtC.js +57 -0
- package/platform-ui/dist/index.html +2 -2
- package/dist/accept-orchestration-UQLM7PTQ.js +0 -189
- package/dist/add-FZRKEGH4.js +0 -81
- package/dist/agent-WERIO2XV.js +0 -523
- package/dist/agent-loader-TFIANSF4.js +0 -44
- package/dist/agent-state-S5DAWPTF.js +0 -24
- package/dist/agents-suggest-DNSYJ6IA.js +0 -83
- package/dist/aggregate-H57K7PNV.js +0 -88
- package/dist/assess-4WVXZLZQ.js +0 -68
- package/dist/auto-QFS5NHQU.js +0 -504
- package/dist/beacon-KXZXYQHX.js +0 -251
- package/dist/calibration-V46G7JTY.js +0 -135
- package/dist/check-OWAIWV23.js +0 -175
- package/dist/chunk-2IO7JAG2.js +0 -148
- package/dist/chunk-2T6BTYBN.js +0 -712
- package/dist/chunk-36TKPM5Z.js +0 -400
- package/dist/chunk-45MUDW6E.js +0 -804
- package/dist/chunk-4BLYIB7J.js +0 -5701
- package/dist/chunk-4L3UTYQX.js +0 -677
- package/dist/chunk-4NCFWYGG.js +0 -110
- package/dist/chunk-54LTTQBH.js +0 -138
- package/dist/chunk-5EWAQHFY.js +0 -269
- package/dist/chunk-5JGJACDU.js +0 -37
- package/dist/chunk-5OUOLN6M.js +0 -659
- package/dist/chunk-5VKJBNJL.js +0 -444
- package/dist/chunk-6N3JTACN.js +0 -2260
- package/dist/chunk-6QC3YGB6.js +0 -114
- package/dist/chunk-7N7GSU6K.js +0 -34
- package/dist/chunk-ABVQGRF7.js +0 -268
- package/dist/chunk-CL7JSK52.js +0 -129
- package/dist/chunk-CSD7IHSN.js +0 -152
- package/dist/chunk-EDOAWN7J.js +0 -717
- package/dist/chunk-EKGMAM62.js +0 -525
- package/dist/chunk-EZ3GOCYC.js +0 -132
- package/dist/chunk-F3BCHPYT.js +0 -143
- package/dist/chunk-FKJUBQU3.js +0 -3081
- package/dist/chunk-GT5QGC2H.js +0 -253
- package/dist/chunk-GZDFVP2N.js +0 -540
- package/dist/chunk-IRKUEJVW.js +0 -405
- package/dist/chunk-J2JEQRT3.js +0 -164
- package/dist/chunk-JASGXLK3.js +0 -679
- package/dist/chunk-KVDYJLTC.js +0 -121
- package/dist/chunk-L27I3CPZ.js +0 -357
- package/dist/chunk-LHLIAYQ3.js +0 -185
- package/dist/chunk-MCMOGQMU.js +0 -145
- package/dist/chunk-NQKQG45G.js +0 -688
- package/dist/chunk-PBHIFAL4.js +0 -259
- package/dist/chunk-PDX44BCA.js +0 -11
- package/dist/chunk-PMXRGPRQ.js +0 -305
- package/dist/chunk-RAB5IKPR.js +0 -487
- package/dist/chunk-RJE5G7WO.js +0 -590
- package/dist/chunk-RTHA3XRE.js +0 -880
- package/dist/chunk-S2HO5MLR.js +0 -621
- package/dist/chunk-S3ORKP3V.js +0 -716
- package/dist/chunk-TAIJOFOE.js +0 -124
- package/dist/chunk-TXESEO7Y.js +0 -245
- package/dist/chunk-UPLDI7CN.js +0 -1334
- package/dist/chunk-VL67H5IC.js +0 -506
- package/dist/chunk-VPPK3SY4.js +0 -1136
- package/dist/chunk-YMDLDELF.js +0 -314
- package/dist/chunk-YO6DVTL7.js +0 -99
- package/dist/chunk-YRZ5RPEB.js +0 -1561
- package/dist/chunk-ZDHLG5VP.js +0 -5676
- package/dist/chunk-ZGUAAVMA.js +0 -756
- package/dist/chunk-ZSYVKSY6.js +0 -2602
- package/dist/claude-FRRWJSTJ.js +0 -362
- package/dist/claude-cli-XJLK2X4L.js +0 -276
- package/dist/claude-code-HTBA4XRB.js +0 -126
- package/dist/claude-code-teams-T4SP24MD.js +0 -199
- package/dist/commands-5N4ILTPH.js +0 -5859
- package/dist/conductor-CQVGTEQN.js +0 -161
- package/dist/config-schema-EA4XALGG.js +0 -154
- package/dist/constellation-A26CCGQS.js +0 -225
- package/dist/context-audit-RLO3ETRP.js +0 -682
- package/dist/cost-BGM32XJU.js +0 -93
- package/dist/cost-VI46A4XL.js +0 -174
- package/dist/cursor-cli-JVEZGHWQ.js +0 -269
- package/dist/cursorrules-HLIKJJZT.js +0 -84
- package/dist/decision-loader-WWCLIQPJ.js +0 -20
- package/dist/delete-KBRPQLPC.js +0 -56
- package/dist/diff-D4X53HAC.js +0 -126
- package/dist/discipline-FA4OZXIS.js +0 -24
- package/dist/dist-34NA5RS5.js +0 -74
- package/dist/dist-5QE2BB2B-5S3T6Y3T.js +0 -6919
- package/dist/dist-77JDTVAY.js +0 -62
- package/dist/dist-OGTSAZ55.js +0 -1358
- package/dist/dist-QK4SQAK7.js +0 -64
- package/dist/dist-TA6LSC2Q.js +0 -20
- package/dist/docs-PBZB7LYP.js +0 -89
- package/dist/docs-QIYKO3BR.js +0 -155
- package/dist/doctor-ULBOHEIC.js +0 -11
- package/dist/drift-R5NRKFHI.js +0 -251
- package/dist/echo-O2LY7CC2.js +0 -248
- package/dist/edit-R2HNLMOG.js +0 -63
- package/dist/event-25OJKDCE.js +0 -31
- package/dist/export-IWVL7XLF.js +0 -38
- package/dist/flow-CRRVV3O3.js +0 -185
- package/dist/global-3NG5JXUB.js +0 -136
- package/dist/graduate-USAWGBJM.js +0 -160
- package/dist/graph-VHUMAAS6.js +0 -12
- package/dist/graph-server-YL22VBBN.js +0 -116
- package/dist/habits-OL5NGPXO.js +0 -1405
- package/dist/history-WOWC573W.js +0 -277
- package/dist/hooks-HFWSCGPV.js +0 -12
- package/dist/integrity-IHO4FZTS.js +0 -194
- package/dist/integrity-checker-PSKJA5SB.js +0 -12
- package/dist/journal-loader-5EYSBFFY.js +0 -18
- package/dist/lint-K6CJGGPH.js +0 -456
- package/dist/list-4YK7QKFF.js +0 -57
- package/dist/list-ENR7Q4CR.js +0 -56
- package/dist/lore-loader-7NO6N6FT.js +0 -26
- package/dist/lore-server-UNJY5KC3.js +0 -118
- package/dist/manual-G6FISID5.js +0 -204
- package/dist/migrate-LS45DNEV.js +0 -889
- package/dist/migrate-assessments-RGH4O6IX.js +0 -97
- package/dist/model-discovery-D2H3VBGC.js +0 -8
- package/dist/nomination-engine-RV5CNO5B.js +0 -42
- package/dist/notebook-YWIYGEHV.js +0 -155
- package/dist/orchestrate-JLILBBJE.js +0 -324
- package/dist/peers-DEOUIZM6.js +0 -82
- package/dist/persona-UHAHIVST.js +0 -390
- package/dist/pipeline-L4HCSBGN.js +0 -263
- package/dist/platform-server-H5YO3DQD.js +0 -1475
- package/dist/plugin-update-checker-KRRSGXMV.js +0 -12
- package/dist/portal-check-NPYGII2D.js +0 -42
- package/dist/portal-compliance-J7DGAPFX.js +0 -18
- package/dist/probe-MHL5HQZ2.js +0 -167
- package/dist/promote-F6ZYZZAL.js +0 -182
- package/dist/providers-GK7PB2OL.js +0 -104
- package/dist/quiz-M66SC7F7.js +0 -99
- package/dist/record-RA4WR2BO.js +0 -49
- package/dist/reindex-5LTD53ZC.js +0 -16
- package/dist/remember-HBWJ655S.js +0 -77
- package/dist/retag-3OLCVDEQ.js +0 -62
- package/dist/review-27ATYTD2.js +0 -33
- package/dist/review-57QMURZV.js +0 -334
- package/dist/ripple-JPBXP5I3.js +0 -215
- package/dist/sentinel-4XIG4STA.js +0 -63
- package/dist/sentinel-bridge-MDUXTQRL.js +0 -109
- package/dist/serve-CAH3PHE7.js +0 -33
- package/dist/serve-INL7SNBK.js +0 -36
- package/dist/serve-KBSE36PL.js +0 -49
- package/dist/server-54SKYFFY.js +0 -1647
- package/dist/server-XUOIO7E6.js +0 -338
- package/dist/session-tracker-C4BMD5WG.js +0 -13
- package/dist/session-work-log-MZ47OAPB.js +0 -20
- package/dist/setup-EDS27WUR.js +0 -25
- package/dist/setup-KO5AFC4K.js +0 -296
- package/dist/shift-D2JOHHBF.js +0 -668
- package/dist/show-5PV5KFJE.js +0 -176
- package/dist/show-NQKYX6WQ.js +0 -82
- package/dist/snapshot-BK4RBPCG.js +0 -62
- package/dist/spawn-RCHNXDHE.js +0 -196
- package/dist/status-WGIAQODY.js +0 -71
- package/dist/summary-NIRABMF5.js +0 -140
- package/dist/sweep-QMHNSIY5.js +0 -934
- package/dist/switch-6EJPZDIA.js +0 -232
- package/dist/symphony-4OCY36AI.js +0 -791
- package/dist/symphony-B75X2MME.js +0 -326
- package/dist/symphony-loader-UZGON56V.js +0 -90
- package/dist/symphony-peers-2ZQYLRNI.js +0 -34
- package/dist/symphony-peers-OL7F6M5S.js +0 -121
- package/dist/symphony-relay-UJYUXN65.js +0 -710
- package/dist/sync-VEHUH4OA.js +0 -11
- package/dist/sync-llms-YHCFIE6X.js +0 -166
- package/dist/task-loader-LDYWQSLM.js +0 -20
- package/dist/team-O5MIIFMA.js +0 -34
- package/dist/test-566CP5KC.js +0 -1017
- package/dist/thread-N754I4D5.js +0 -274
- package/dist/timeline-M3CICQFE.js +0 -112
- package/dist/triage-HHYGT3HY.js +0 -671
- package/dist/tutorial-KD22SUNO.js +0 -666
- package/dist/upgrade-H5PF32BW.js +0 -551
- package/dist/validate-CNKEKO6A.js +0 -134
- package/dist/validate-MB5ULIHS.js +0 -39
- package/dist/validate-QH3LADM6.js +0 -64
- package/dist/watch-2TKP5PVL.js +0 -199
- package/dist/watch-ZF4ML6CD.js +0 -111
- package/dist/wisdom-AATMGNFA.js +0 -319
- package/dist/work-log-loader-5L45XNYZ.js +0 -14
- package/dist/workspace-6E6OSRNU.js +0 -21
- package/platform-ui/dist/assets/AmbientSection-WtHfNedO.js +0 -1
- package/platform-ui/dist/assets/LoreSection-C3EixkjW.css +0 -1
- package/platform-ui/dist/assets/LoreSection-CwDDHrz9.js +0 -1
- package/platform-ui/dist/assets/SentinelSection-Cl7e5oZ6.js +0 -1
- package/platform-ui/dist/assets/TeamSection-CqSIMrKz.js +0 -1
- package/platform-ui/dist/assets/index-CfpZFjea.css +0 -1
- package/platform-ui/dist/assets/index-F0KL9xrN.js +0 -57
package/dist/sentinel-mcp.js
CHANGED
|
@@ -1,41 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// ../sentinel/src/mcp.ts
|
|
4
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
import {
|
|
7
|
-
CallToolRequestSchema,
|
|
8
|
-
ListToolsRequestSchema
|
|
9
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
-
|
|
11
|
-
// ../sentinel/src/storage.ts
|
|
12
|
-
import initSqlJs from "sql.js";
|
|
13
|
-
import { v4 as uuidv4 } from "uuid";
|
|
14
|
-
import * as path from "path";
|
|
15
|
-
import * as fs from "fs";
|
|
16
|
-
var SCHEMA_VERSION = 5;
|
|
17
|
-
var DEFAULT_CONFIDENCE = {
|
|
18
|
-
score: 50,
|
|
19
|
-
timesMatched: 0,
|
|
20
|
-
timesResolved: 0,
|
|
21
|
-
timesRecurred: 0
|
|
22
|
-
};
|
|
23
|
-
var SQL = null;
|
|
24
|
-
var SentinelStorage = class {
|
|
25
|
-
db = null;
|
|
26
|
-
dbPath;
|
|
27
|
-
incidentCounter = 0;
|
|
28
|
-
initialized = false;
|
|
29
|
-
constructor(dbPath) {
|
|
30
|
-
this.dbPath = dbPath || this.getDefaultDbPath();
|
|
31
|
-
}
|
|
32
|
-
getDefaultDbPath() {
|
|
33
|
-
const dataDir = process.env.SENTINEL_DATA_DIR || process.env.PARADIGM_DATA_DIR || path.join(process.cwd(), ".paradigm", "sentinel");
|
|
34
|
-
return path.join(dataDir, "sentinel.db");
|
|
35
|
-
}
|
|
36
|
-
createSchema() {
|
|
37
|
-
if (!this.db) return;
|
|
38
|
-
this.db.run(`
|
|
2
|
+
import {Server}from'@modelcontextprotocol/sdk/server/index.js';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import {ListToolsRequestSchema,CallToolRequestSchema}from'@modelcontextprotocol/sdk/types.js';import H from'sql.js';import {v4}from'uuid';import*as I from'path';import*as y from'fs';import {fileURLToPath}from'url';var J=5,$={score:50,timesMatched:0,timesResolved:0,timesRecurred:0},b=null,O=class{db=null;dbPath;incidentCounter=0;initialized=false;constructor(e){this.dbPath=e||this.getDefaultDbPath();}getDefaultDbPath(){let e=process.env.SENTINEL_DATA_DIR||process.env.PARADIGM_DATA_DIR||I.join(process.cwd(),".paradigm","sentinel");return I.join(e,"sentinel.db")}createSchema(){this.db&&(this.db.run(`
|
|
39
3
|
-- Metadata table for schema versioning
|
|
40
4
|
CREATE TABLE IF NOT EXISTS metadata (
|
|
41
5
|
key TEXT PRIMARY KEY,
|
|
@@ -220,109 +184,12 @@ var SentinelStorage = class {
|
|
|
220
184
|
CREATE INDEX IF NOT EXISTS idx_traces_trace_id ON traces(trace_id);
|
|
221
185
|
CREATE INDEX IF NOT EXISTS idx_traces_service ON traces(service);
|
|
222
186
|
CREATE INDEX IF NOT EXISTS idx_traces_start_time ON traces(start_time);
|
|
223
|
-
`);
|
|
224
|
-
this.db.run(
|
|
225
|
-
"INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', ?)",
|
|
226
|
-
[String(SCHEMA_VERSION)]
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
save() {
|
|
230
|
-
if (!this.db) return;
|
|
231
|
-
const data = this.db.export();
|
|
232
|
-
const buffer = Buffer.from(data);
|
|
233
|
-
fs.writeFileSync(this.dbPath, buffer);
|
|
234
|
-
}
|
|
235
|
-
// ─── Incidents ───────────────────────────────────────────────────
|
|
236
|
-
recordIncident(input) {
|
|
237
|
-
const db = this.db;
|
|
238
|
-
if (!db) {
|
|
239
|
-
this.initializeSync();
|
|
240
|
-
}
|
|
241
|
-
this.incidentCounter++;
|
|
242
|
-
const id = `INC-${String(this.incidentCounter).padStart(3, "0")}`;
|
|
243
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
244
|
-
this.db.run(
|
|
245
|
-
`INSERT INTO incidents (
|
|
187
|
+
`),this.db.run("INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', ?)",[String(J)]));}save(){if(!this.db)return;let e=this.db.export(),t=Buffer.from(e);y.writeFileSync(this.dbPath,t);}recordIncident(e){this.db||this.initializeSync(),this.incidentCounter++;let s=`INC-${String(this.incidentCounter).padStart(3,"0")}`,n=new Date().toISOString();return this.db.run(`INSERT INTO incidents (
|
|
246
188
|
id, timestamp, status, error_message, error_stack, error_code, error_type,
|
|
247
189
|
symbols, flow_position, environment, service, version, user_id, request_id,
|
|
248
190
|
group_id, notes, related_incidents, resolved_at, resolved_by, resolution,
|
|
249
191
|
created_at, updated_at
|
|
250
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
251
|
-
[
|
|
252
|
-
id,
|
|
253
|
-
input.timestamp || now,
|
|
254
|
-
input.status || "open",
|
|
255
|
-
input.error.message,
|
|
256
|
-
input.error.stack || null,
|
|
257
|
-
input.error.code || null,
|
|
258
|
-
input.error.type || null,
|
|
259
|
-
JSON.stringify(input.symbols),
|
|
260
|
-
input.flowPosition ? JSON.stringify(input.flowPosition) : null,
|
|
261
|
-
input.environment,
|
|
262
|
-
input.service || null,
|
|
263
|
-
input.version || null,
|
|
264
|
-
input.userId || null,
|
|
265
|
-
input.requestId || null,
|
|
266
|
-
input.groupId || null,
|
|
267
|
-
"[]",
|
|
268
|
-
"[]",
|
|
269
|
-
input.resolvedAt || null,
|
|
270
|
-
input.resolvedBy || null,
|
|
271
|
-
input.resolution ? JSON.stringify(input.resolution) : null,
|
|
272
|
-
now,
|
|
273
|
-
now
|
|
274
|
-
]
|
|
275
|
-
);
|
|
276
|
-
this.save();
|
|
277
|
-
return id;
|
|
278
|
-
}
|
|
279
|
-
initializeSync() {
|
|
280
|
-
if (this.initialized && this.db) return;
|
|
281
|
-
if (!this.db && SQL) {
|
|
282
|
-
const dir = path.dirname(this.dbPath);
|
|
283
|
-
if (!fs.existsSync(dir)) {
|
|
284
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
285
|
-
}
|
|
286
|
-
if (fs.existsSync(this.dbPath)) {
|
|
287
|
-
const fileData = fs.readFileSync(this.dbPath);
|
|
288
|
-
this.db = new SQL.Database(fileData);
|
|
289
|
-
} else {
|
|
290
|
-
this.db = new SQL.Database();
|
|
291
|
-
this.createSchema();
|
|
292
|
-
}
|
|
293
|
-
try {
|
|
294
|
-
const result = this.db.exec(
|
|
295
|
-
"SELECT MAX(CAST(SUBSTR(id, 5) AS INTEGER)) as max FROM incidents"
|
|
296
|
-
);
|
|
297
|
-
if (result.length > 0 && result[0].values.length > 0 && result[0].values[0][0]) {
|
|
298
|
-
this.incidentCounter = result[0].values[0][0];
|
|
299
|
-
}
|
|
300
|
-
} catch {
|
|
301
|
-
this.incidentCounter = 0;
|
|
302
|
-
}
|
|
303
|
-
this.migrateSchema();
|
|
304
|
-
this.initialized = true;
|
|
305
|
-
this.save();
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Run schema migrations from older versions
|
|
310
|
-
*/
|
|
311
|
-
migrateSchema() {
|
|
312
|
-
if (!this.db) return;
|
|
313
|
-
let currentVersion = 1;
|
|
314
|
-
try {
|
|
315
|
-
const result = this.db.exec(
|
|
316
|
-
"SELECT value FROM metadata WHERE key = 'schema_version'"
|
|
317
|
-
);
|
|
318
|
-
if (result.length > 0 && result[0].values.length > 0) {
|
|
319
|
-
currentVersion = parseInt(result[0].values[0][0], 10) || 1;
|
|
320
|
-
}
|
|
321
|
-
} catch {
|
|
322
|
-
}
|
|
323
|
-
if (currentVersion < 2) {
|
|
324
|
-
try {
|
|
325
|
-
this.db.run(`
|
|
192
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s,e.timestamp||n,e.status||"open",e.error.message,e.error.stack||null,e.error.code||null,e.error.type||null,JSON.stringify(e.symbols),e.flowPosition?JSON.stringify(e.flowPosition):null,e.environment,e.service||null,e.version||null,e.userId||null,e.requestId||null,e.groupId||null,"[]","[]",e.resolvedAt||null,e.resolvedBy||null,e.resolution?JSON.stringify(e.resolution):null,n,n]),this.save(),s}initializeSync(){if(!(this.initialized&&this.db)&&!this.db&&b){let e=I.dirname(this.dbPath);if(y.existsSync(e)||y.mkdirSync(e,{recursive:true}),y.existsSync(this.dbPath)){let t=y.readFileSync(this.dbPath);this.db=new b.Database(t);}else this.db=new b.Database,this.createSchema();try{let t=this.db.exec("SELECT MAX(CAST(SUBSTR(id, 5) AS INTEGER)) as max FROM incidents");t.length>0&&t[0].values.length>0&&t[0].values[0][0]&&(this.incidentCounter=t[0].values[0][0]);}catch{this.incidentCounter=0;}this.migrateSchema(),this.initialized=true,this.save();}}migrateSchema(){if(!this.db)return;let e=1;try{let t=this.db.exec("SELECT value FROM metadata WHERE key = 'schema_version'");t.length>0&&t[0].values.length>0&&(e=parseInt(t[0].values[0][0],10)||1);}catch{}if(e<2){try{this.db.run(`
|
|
326
193
|
CREATE TABLE IF NOT EXISTS practice_events (
|
|
327
194
|
id TEXT PRIMARY KEY,
|
|
328
195
|
timestamp TEXT NOT NULL,
|
|
@@ -343,17 +210,7 @@ var SentinelStorage = class {
|
|
|
343
210
|
CREATE INDEX IF NOT EXISTS idx_practice_events_habit_id ON practice_events(habit_id);
|
|
344
211
|
CREATE INDEX IF NOT EXISTS idx_practice_events_engineer ON practice_events(engineer);
|
|
345
212
|
CREATE INDEX IF NOT EXISTS idx_practice_events_session_id ON practice_events(session_id);
|
|
346
|
-
`);
|
|
347
|
-
} catch {
|
|
348
|
-
}
|
|
349
|
-
this.db.run(
|
|
350
|
-
"INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '2')"
|
|
351
|
-
);
|
|
352
|
-
currentVersion = 2;
|
|
353
|
-
}
|
|
354
|
-
if (currentVersion < 3) {
|
|
355
|
-
try {
|
|
356
|
-
this.db.run(`
|
|
213
|
+
`);}catch{}this.db.run("INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '2')"),e=2;}if(e<3){try{this.db.run(`
|
|
357
214
|
CREATE TABLE IF NOT EXISTS logs (
|
|
358
215
|
id TEXT PRIMARY KEY,
|
|
359
216
|
timestamp TEXT NOT NULL,
|
|
@@ -395,17 +252,7 @@ var SentinelStorage = class {
|
|
|
395
252
|
CREATE INDEX IF NOT EXISTS idx_logs_service ON logs(service);
|
|
396
253
|
CREATE INDEX IF NOT EXISTS idx_logs_session_id ON logs(session_id);
|
|
397
254
|
CREATE INDEX IF NOT EXISTS idx_logs_correlation_id ON logs(correlation_id);
|
|
398
|
-
`);
|
|
399
|
-
} catch {
|
|
400
|
-
}
|
|
401
|
-
this.db.run(
|
|
402
|
-
"INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '3')"
|
|
403
|
-
);
|
|
404
|
-
currentVersion = 3;
|
|
405
|
-
}
|
|
406
|
-
if (currentVersion < 4) {
|
|
407
|
-
try {
|
|
408
|
-
this.db.run(`
|
|
255
|
+
`);}catch{}this.db.run("INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '3')"),e=3;}if(e<4){try{this.db.run(`
|
|
409
256
|
CREATE TABLE IF NOT EXISTS metrics (
|
|
410
257
|
id TEXT PRIMARY KEY,
|
|
411
258
|
timestamp TEXT NOT NULL,
|
|
@@ -438,17 +285,7 @@ var SentinelStorage = class {
|
|
|
438
285
|
CREATE INDEX IF NOT EXISTS idx_traces_trace_id ON traces(trace_id);
|
|
439
286
|
CREATE INDEX IF NOT EXISTS idx_traces_service ON traces(service);
|
|
440
287
|
CREATE INDEX IF NOT EXISTS idx_traces_start_time ON traces(start_time);
|
|
441
|
-
`);
|
|
442
|
-
} catch {
|
|
443
|
-
}
|
|
444
|
-
this.db.run(
|
|
445
|
-
"INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '4')"
|
|
446
|
-
);
|
|
447
|
-
currentVersion = 4;
|
|
448
|
-
}
|
|
449
|
-
if (currentVersion < 5) {
|
|
450
|
-
try {
|
|
451
|
-
this.db.run(`
|
|
288
|
+
`);}catch{}this.db.run("INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '4')"),e=4;}if(e<5){try{this.db.run(`
|
|
452
289
|
CREATE TABLE IF NOT EXISTS schemas (
|
|
453
290
|
id TEXT PRIMARY KEY,
|
|
454
291
|
version TEXT NOT NULL,
|
|
@@ -486,1619 +323,69 @@ var SentinelStorage = class {
|
|
|
486
323
|
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
487
324
|
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
488
325
|
CREATE INDEX IF NOT EXISTS idx_events_service ON events(service);
|
|
489
|
-
`);
|
|
490
|
-
} catch {
|
|
491
|
-
}
|
|
492
|
-
this.db.run(
|
|
493
|
-
"INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '5')"
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Ensure the storage is ready for use. Must be called once before using storage methods.
|
|
499
|
-
*/
|
|
500
|
-
async ensureReady() {
|
|
501
|
-
if (!SQL) {
|
|
502
|
-
SQL = await initSqlJs();
|
|
503
|
-
}
|
|
504
|
-
this.initializeSync();
|
|
505
|
-
}
|
|
506
|
-
getIncident(id) {
|
|
507
|
-
this.initializeSync();
|
|
508
|
-
const result = this.db.exec("SELECT * FROM incidents WHERE id = ?", [id]);
|
|
509
|
-
if (result.length === 0 || result[0].values.length === 0) return null;
|
|
510
|
-
return this.rowToIncident(result[0].columns, result[0].values[0]);
|
|
511
|
-
}
|
|
512
|
-
getRecentIncidents(options = {}) {
|
|
513
|
-
this.initializeSync();
|
|
514
|
-
const { limit = 50, offset = 0 } = options;
|
|
515
|
-
const conditions = [];
|
|
516
|
-
const params = [];
|
|
517
|
-
if (options.status && options.status !== "all") {
|
|
518
|
-
conditions.push("status = ?");
|
|
519
|
-
params.push(options.status);
|
|
520
|
-
}
|
|
521
|
-
if (options.environment) {
|
|
522
|
-
conditions.push("environment = ?");
|
|
523
|
-
params.push(options.environment);
|
|
524
|
-
}
|
|
525
|
-
if (options.symbol) {
|
|
526
|
-
conditions.push("symbols LIKE ?");
|
|
527
|
-
params.push(`%${options.symbol}%`);
|
|
528
|
-
}
|
|
529
|
-
if (options.search) {
|
|
530
|
-
conditions.push("(error_message LIKE ? OR notes LIKE ?)");
|
|
531
|
-
params.push(`%${options.search}%`, `%${options.search}%`);
|
|
532
|
-
}
|
|
533
|
-
if (options.dateFrom) {
|
|
534
|
-
conditions.push("timestamp >= ?");
|
|
535
|
-
params.push(options.dateFrom);
|
|
536
|
-
}
|
|
537
|
-
if (options.dateTo) {
|
|
538
|
-
conditions.push("timestamp <= ?");
|
|
539
|
-
params.push(options.dateTo);
|
|
540
|
-
}
|
|
541
|
-
if (options.groupId) {
|
|
542
|
-
conditions.push("group_id = ?");
|
|
543
|
-
params.push(options.groupId);
|
|
544
|
-
}
|
|
545
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
546
|
-
const result = this.db.exec(
|
|
547
|
-
`SELECT * FROM incidents ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
|
|
548
|
-
[...params, limit, offset]
|
|
549
|
-
);
|
|
550
|
-
if (result.length === 0) return [];
|
|
551
|
-
return result[0].values.map(
|
|
552
|
-
(row) => this.rowToIncident(result[0].columns, row)
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
updateIncident(id, updates) {
|
|
556
|
-
this.initializeSync();
|
|
557
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
558
|
-
const setClauses = ["updated_at = ?"];
|
|
559
|
-
const params = [now];
|
|
560
|
-
if (updates.status !== void 0) {
|
|
561
|
-
setClauses.push("status = ?");
|
|
562
|
-
params.push(updates.status);
|
|
563
|
-
}
|
|
564
|
-
if (updates.error !== void 0) {
|
|
565
|
-
setClauses.push("error_message = ?");
|
|
566
|
-
params.push(updates.error.message);
|
|
567
|
-
if (updates.error.stack !== void 0) {
|
|
568
|
-
setClauses.push("error_stack = ?");
|
|
569
|
-
params.push(updates.error.stack || null);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
if (updates.symbols !== void 0) {
|
|
573
|
-
setClauses.push("symbols = ?");
|
|
574
|
-
params.push(JSON.stringify(updates.symbols));
|
|
575
|
-
}
|
|
576
|
-
if (updates.flowPosition !== void 0) {
|
|
577
|
-
setClauses.push("flow_position = ?");
|
|
578
|
-
params.push(
|
|
579
|
-
updates.flowPosition ? JSON.stringify(updates.flowPosition) : null
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
if (updates.groupId !== void 0) {
|
|
583
|
-
setClauses.push("group_id = ?");
|
|
584
|
-
params.push(updates.groupId || null);
|
|
585
|
-
}
|
|
586
|
-
if (updates.resolvedAt !== void 0) {
|
|
587
|
-
setClauses.push("resolved_at = ?");
|
|
588
|
-
params.push(updates.resolvedAt || null);
|
|
589
|
-
}
|
|
590
|
-
if (updates.resolvedBy !== void 0) {
|
|
591
|
-
setClauses.push("resolved_by = ?");
|
|
592
|
-
params.push(updates.resolvedBy || null);
|
|
593
|
-
}
|
|
594
|
-
if (updates.resolution !== void 0) {
|
|
595
|
-
setClauses.push("resolution = ?");
|
|
596
|
-
params.push(
|
|
597
|
-
updates.resolution ? JSON.stringify(updates.resolution) : null
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
params.push(id);
|
|
601
|
-
this.db.run(
|
|
602
|
-
`UPDATE incidents SET ${setClauses.join(", ")} WHERE id = ?`,
|
|
603
|
-
params
|
|
604
|
-
);
|
|
605
|
-
this.save();
|
|
606
|
-
}
|
|
607
|
-
resolveIncident(id, options) {
|
|
608
|
-
this.updateIncident(id, {
|
|
609
|
-
status: "resolved",
|
|
610
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
611
|
-
});
|
|
612
|
-
if (options?.notes) {
|
|
613
|
-
this.addIncidentNote(id, {
|
|
614
|
-
author: "system",
|
|
615
|
-
content: options.notes,
|
|
616
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
addIncidentNote(incidentId, note) {
|
|
621
|
-
this.initializeSync();
|
|
622
|
-
const incident = this.getIncident(incidentId);
|
|
623
|
-
if (!incident) return;
|
|
624
|
-
const newNote = {
|
|
625
|
-
id: uuidv4(),
|
|
626
|
-
...note
|
|
627
|
-
};
|
|
628
|
-
const notes = [...incident.notes, newNote];
|
|
629
|
-
this.db.run(
|
|
630
|
-
"UPDATE incidents SET notes = ?, updated_at = ? WHERE id = ?",
|
|
631
|
-
[JSON.stringify(notes), (/* @__PURE__ */ new Date()).toISOString(), incidentId]
|
|
632
|
-
);
|
|
633
|
-
this.save();
|
|
634
|
-
}
|
|
635
|
-
linkIncidents(incidentId, relatedId) {
|
|
636
|
-
this.initializeSync();
|
|
637
|
-
const incident = this.getIncident(incidentId);
|
|
638
|
-
if (!incident) return;
|
|
639
|
-
if (!incident.relatedIncidents.includes(relatedId)) {
|
|
640
|
-
const related = [...incident.relatedIncidents, relatedId];
|
|
641
|
-
this.db.run(
|
|
642
|
-
"UPDATE incidents SET related_incidents = ?, updated_at = ? WHERE id = ?",
|
|
643
|
-
[JSON.stringify(related), (/* @__PURE__ */ new Date()).toISOString(), incidentId]
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
const relatedIncident = this.getIncident(relatedId);
|
|
647
|
-
if (relatedIncident && !relatedIncident.relatedIncidents.includes(incidentId)) {
|
|
648
|
-
const related = [...relatedIncident.relatedIncidents, incidentId];
|
|
649
|
-
this.db.run(
|
|
650
|
-
"UPDATE incidents SET related_incidents = ?, updated_at = ? WHERE id = ?",
|
|
651
|
-
[JSON.stringify(related), (/* @__PURE__ */ new Date()).toISOString(), relatedId]
|
|
652
|
-
);
|
|
653
|
-
}
|
|
654
|
-
this.save();
|
|
655
|
-
}
|
|
656
|
-
getIncidentCount(options = {}) {
|
|
657
|
-
this.initializeSync();
|
|
658
|
-
const conditions = [];
|
|
659
|
-
const params = [];
|
|
660
|
-
if (options.status && options.status !== "all") {
|
|
661
|
-
conditions.push("status = ?");
|
|
662
|
-
params.push(options.status);
|
|
663
|
-
}
|
|
664
|
-
if (options.environment) {
|
|
665
|
-
conditions.push("environment = ?");
|
|
666
|
-
params.push(options.environment);
|
|
667
|
-
}
|
|
668
|
-
if (options.dateFrom) {
|
|
669
|
-
conditions.push("timestamp >= ?");
|
|
670
|
-
params.push(options.dateFrom);
|
|
671
|
-
}
|
|
672
|
-
if (options.dateTo) {
|
|
673
|
-
conditions.push("timestamp <= ?");
|
|
674
|
-
params.push(options.dateTo);
|
|
675
|
-
}
|
|
676
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
677
|
-
const result = this.db.exec(
|
|
678
|
-
`SELECT COUNT(*) as count FROM incidents ${whereClause}`,
|
|
679
|
-
params
|
|
680
|
-
);
|
|
681
|
-
if (result.length === 0 || result[0].values.length === 0) return 0;
|
|
682
|
-
return result[0].values[0][0];
|
|
683
|
-
}
|
|
684
|
-
// ─── Patterns ────────────────────────────────────────────────────
|
|
685
|
-
addPattern(input) {
|
|
686
|
-
this.initializeSync();
|
|
687
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
688
|
-
const confidence = {
|
|
689
|
-
...DEFAULT_CONFIDENCE,
|
|
690
|
-
...input.confidence
|
|
691
|
-
};
|
|
692
|
-
this.db.run(
|
|
693
|
-
`INSERT INTO patterns (
|
|
326
|
+
`);}catch{}this.db.run("INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '5')");}}async ensureReady(){b||(b=await H()),this.initializeSync();}getIncident(e){this.initializeSync();let t=this.db.exec("SELECT * FROM incidents WHERE id = ?",[e]);return t.length===0||t[0].values.length===0?null:this.rowToIncident(t[0].columns,t[0].values[0])}getRecentIncidents(e={}){this.initializeSync();let{limit:t=50,offset:s=0}=e,n=[],i=[];e.status&&e.status!=="all"&&(n.push("status = ?"),i.push(e.status)),e.environment&&(n.push("environment = ?"),i.push(e.environment)),e.symbol&&(n.push("symbols LIKE ?"),i.push(`%${e.symbol}%`)),e.search&&(n.push("(error_message LIKE ? OR notes LIKE ?)"),i.push(`%${e.search}%`,`%${e.search}%`)),e.dateFrom&&(n.push("timestamp >= ?"),i.push(e.dateFrom)),e.dateTo&&(n.push("timestamp <= ?"),i.push(e.dateTo)),e.groupId&&(n.push("group_id = ?"),i.push(e.groupId));let r=n.length>0?`WHERE ${n.join(" AND ")}`:"",a=this.db.exec(`SELECT * FROM incidents ${r} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,[...i,t,s]);return a.length===0?[]:a[0].values.map(o=>this.rowToIncident(a[0].columns,o))}updateIncident(e,t){this.initializeSync();let s=new Date().toISOString(),n=["updated_at = ?"],i=[s];t.status!==void 0&&(n.push("status = ?"),i.push(t.status)),t.error!==void 0&&(n.push("error_message = ?"),i.push(t.error.message),t.error.stack!==void 0&&(n.push("error_stack = ?"),i.push(t.error.stack||null))),t.symbols!==void 0&&(n.push("symbols = ?"),i.push(JSON.stringify(t.symbols))),t.flowPosition!==void 0&&(n.push("flow_position = ?"),i.push(t.flowPosition?JSON.stringify(t.flowPosition):null)),t.groupId!==void 0&&(n.push("group_id = ?"),i.push(t.groupId||null)),t.resolvedAt!==void 0&&(n.push("resolved_at = ?"),i.push(t.resolvedAt||null)),t.resolvedBy!==void 0&&(n.push("resolved_by = ?"),i.push(t.resolvedBy||null)),t.resolution!==void 0&&(n.push("resolution = ?"),i.push(t.resolution?JSON.stringify(t.resolution):null)),i.push(e),this.db.run(`UPDATE incidents SET ${n.join(", ")} WHERE id = ?`,i),this.save();}resolveIncident(e,t){this.updateIncident(e,{status:"resolved",resolvedAt:new Date().toISOString()}),t?.notes&&this.addIncidentNote(e,{author:"system",content:t.notes,timestamp:new Date().toISOString()});}addIncidentNote(e,t){this.initializeSync();let s=this.getIncident(e);if(!s)return;let n={id:v4(),...t},i=[...s.notes,n];this.db.run("UPDATE incidents SET notes = ?, updated_at = ? WHERE id = ?",[JSON.stringify(i),new Date().toISOString(),e]),this.save();}linkIncidents(e,t){this.initializeSync();let s=this.getIncident(e);if(!s)return;if(!s.relatedIncidents.includes(t)){let i=[...s.relatedIncidents,t];this.db.run("UPDATE incidents SET related_incidents = ?, updated_at = ? WHERE id = ?",[JSON.stringify(i),new Date().toISOString(),e]);}let n=this.getIncident(t);if(n&&!n.relatedIncidents.includes(e)){let i=[...n.relatedIncidents,e];this.db.run("UPDATE incidents SET related_incidents = ?, updated_at = ? WHERE id = ?",[JSON.stringify(i),new Date().toISOString(),t]);}this.save();}getIncidentCount(e={}){this.initializeSync();let t=[],s=[];e.status&&e.status!=="all"&&(t.push("status = ?"),s.push(e.status)),e.environment&&(t.push("environment = ?"),s.push(e.environment)),e.dateFrom&&(t.push("timestamp >= ?"),s.push(e.dateFrom)),e.dateTo&&(t.push("timestamp <= ?"),s.push(e.dateTo));let n=t.length>0?`WHERE ${t.join(" AND ")}`:"",i=this.db.exec(`SELECT COUNT(*) as count FROM incidents ${n}`,s);return i.length===0||i[0].values.length===0?0:i[0].values[0][0]}addPattern(e){this.initializeSync();let t=new Date().toISOString(),s={...$,...e.confidence};return this.db.run(`INSERT INTO patterns (
|
|
694
327
|
id, name, description, pattern, resolution, confidence,
|
|
695
328
|
source, private, tags, created_at, updated_at
|
|
696
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
697
|
-
[
|
|
698
|
-
input.id,
|
|
699
|
-
input.name,
|
|
700
|
-
input.description || null,
|
|
701
|
-
JSON.stringify(input.pattern),
|
|
702
|
-
JSON.stringify(input.resolution),
|
|
703
|
-
JSON.stringify(confidence),
|
|
704
|
-
input.source,
|
|
705
|
-
input.private ? 1 : 0,
|
|
706
|
-
JSON.stringify(input.tags || []),
|
|
707
|
-
now,
|
|
708
|
-
now
|
|
709
|
-
]
|
|
710
|
-
);
|
|
711
|
-
this.save();
|
|
712
|
-
return input.id;
|
|
713
|
-
}
|
|
714
|
-
getPattern(id) {
|
|
715
|
-
this.initializeSync();
|
|
716
|
-
const result = this.db.exec("SELECT * FROM patterns WHERE id = ?", [id]);
|
|
717
|
-
if (result.length === 0 || result[0].values.length === 0) return null;
|
|
718
|
-
return this.rowToPattern(result[0].columns, result[0].values[0]);
|
|
719
|
-
}
|
|
720
|
-
getAllPatterns(options = {}) {
|
|
721
|
-
this.initializeSync();
|
|
722
|
-
const conditions = [];
|
|
723
|
-
const params = [];
|
|
724
|
-
if (options.source) {
|
|
725
|
-
conditions.push("source = ?");
|
|
726
|
-
params.push(options.source);
|
|
727
|
-
}
|
|
728
|
-
if (options.minConfidence !== void 0) {
|
|
729
|
-
conditions.push("json_extract(confidence, '$.score') >= ?");
|
|
730
|
-
params.push(options.minConfidence);
|
|
731
|
-
}
|
|
732
|
-
if (!options.includePrivate) {
|
|
733
|
-
conditions.push("private = 0");
|
|
734
|
-
}
|
|
735
|
-
if (options.tags && options.tags.length > 0) {
|
|
736
|
-
const tagConditions = options.tags.map(() => "tags LIKE ?");
|
|
737
|
-
conditions.push(`(${tagConditions.join(" OR ")})`);
|
|
738
|
-
params.push(...options.tags.map((tag) => `%"${tag}"%`));
|
|
739
|
-
}
|
|
740
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
741
|
-
const result = this.db.exec(
|
|
742
|
-
`SELECT * FROM patterns ${whereClause} ORDER BY json_extract(confidence, '$.score') DESC`,
|
|
743
|
-
params
|
|
744
|
-
);
|
|
745
|
-
if (result.length === 0) return [];
|
|
746
|
-
return result[0].values.map(
|
|
747
|
-
(row) => this.rowToPattern(result[0].columns, row)
|
|
748
|
-
);
|
|
749
|
-
}
|
|
750
|
-
updatePattern(id, updates) {
|
|
751
|
-
this.initializeSync();
|
|
752
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
753
|
-
const setClauses = ["updated_at = ?"];
|
|
754
|
-
const params = [now];
|
|
755
|
-
if (updates.name !== void 0) {
|
|
756
|
-
setClauses.push("name = ?");
|
|
757
|
-
params.push(updates.name);
|
|
758
|
-
}
|
|
759
|
-
if (updates.description !== void 0) {
|
|
760
|
-
setClauses.push("description = ?");
|
|
761
|
-
params.push(updates.description || null);
|
|
762
|
-
}
|
|
763
|
-
if (updates.pattern !== void 0) {
|
|
764
|
-
setClauses.push("pattern = ?");
|
|
765
|
-
params.push(JSON.stringify(updates.pattern));
|
|
766
|
-
}
|
|
767
|
-
if (updates.resolution !== void 0) {
|
|
768
|
-
setClauses.push("resolution = ?");
|
|
769
|
-
params.push(JSON.stringify(updates.resolution));
|
|
770
|
-
}
|
|
771
|
-
if (updates.confidence !== void 0) {
|
|
772
|
-
setClauses.push("confidence = ?");
|
|
773
|
-
params.push(JSON.stringify(updates.confidence));
|
|
774
|
-
}
|
|
775
|
-
if (updates.source !== void 0) {
|
|
776
|
-
setClauses.push("source = ?");
|
|
777
|
-
params.push(updates.source);
|
|
778
|
-
}
|
|
779
|
-
if (updates.private !== void 0) {
|
|
780
|
-
setClauses.push("private = ?");
|
|
781
|
-
params.push(updates.private ? 1 : 0);
|
|
782
|
-
}
|
|
783
|
-
if (updates.tags !== void 0) {
|
|
784
|
-
setClauses.push("tags = ?");
|
|
785
|
-
params.push(JSON.stringify(updates.tags));
|
|
786
|
-
}
|
|
787
|
-
params.push(id);
|
|
788
|
-
this.db.run(
|
|
789
|
-
`UPDATE patterns SET ${setClauses.join(", ")} WHERE id = ?`,
|
|
790
|
-
params
|
|
791
|
-
);
|
|
792
|
-
this.save();
|
|
793
|
-
}
|
|
794
|
-
deletePattern(id) {
|
|
795
|
-
this.initializeSync();
|
|
796
|
-
this.db.run("DELETE FROM patterns WHERE id = ?", [id]);
|
|
797
|
-
this.save();
|
|
798
|
-
}
|
|
799
|
-
updatePatternConfidence(patternId, event) {
|
|
800
|
-
const pattern = this.getPattern(patternId);
|
|
801
|
-
if (!pattern) return;
|
|
802
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
803
|
-
const confidence = { ...pattern.confidence };
|
|
804
|
-
switch (event) {
|
|
805
|
-
case "matched":
|
|
806
|
-
confidence.timesMatched++;
|
|
807
|
-
confidence.lastMatched = now;
|
|
808
|
-
break;
|
|
809
|
-
case "resolved":
|
|
810
|
-
confidence.timesResolved++;
|
|
811
|
-
confidence.lastResolved = now;
|
|
812
|
-
confidence.score = Math.min(100, confidence.score + 2);
|
|
813
|
-
break;
|
|
814
|
-
case "recurred":
|
|
815
|
-
confidence.timesRecurred++;
|
|
816
|
-
confidence.score = Math.max(10, confidence.score - 5);
|
|
817
|
-
break;
|
|
818
|
-
}
|
|
819
|
-
this.updatePattern(patternId, { confidence });
|
|
820
|
-
}
|
|
821
|
-
// ─── Groups ──────────────────────────────────────────────────────
|
|
822
|
-
createGroup(input) {
|
|
823
|
-
this.initializeSync();
|
|
824
|
-
const id = `GRP-${uuidv4().substring(0, 8)}`;
|
|
825
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
826
|
-
this.db.run(
|
|
827
|
-
`INSERT INTO groups (
|
|
329
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.id,e.name,e.description||null,JSON.stringify(e.pattern),JSON.stringify(e.resolution),JSON.stringify(s),e.source,e.private?1:0,JSON.stringify(e.tags||[]),t,t]),this.save(),e.id}getPattern(e){this.initializeSync();let t=this.db.exec("SELECT * FROM patterns WHERE id = ?",[e]);return t.length===0||t[0].values.length===0?null:this.rowToPattern(t[0].columns,t[0].values[0])}getAllPatterns(e={}){this.initializeSync();let t=[],s=[];if(e.source&&(t.push("source = ?"),s.push(e.source)),e.minConfidence!==void 0&&(t.push("json_extract(confidence, '$.score') >= ?"),s.push(e.minConfidence)),e.includePrivate||t.push("private = 0"),e.tags&&e.tags.length>0){let r=e.tags.map(()=>"tags LIKE ?");t.push(`(${r.join(" OR ")})`),s.push(...e.tags.map(a=>`%"${a}"%`));}let n=t.length>0?`WHERE ${t.join(" AND ")}`:"",i=this.db.exec(`SELECT * FROM patterns ${n} ORDER BY json_extract(confidence, '$.score') DESC`,s);return i.length===0?[]:i[0].values.map(r=>this.rowToPattern(i[0].columns,r))}updatePattern(e,t){this.initializeSync();let s=new Date().toISOString(),n=["updated_at = ?"],i=[s];t.name!==void 0&&(n.push("name = ?"),i.push(t.name)),t.description!==void 0&&(n.push("description = ?"),i.push(t.description||null)),t.pattern!==void 0&&(n.push("pattern = ?"),i.push(JSON.stringify(t.pattern))),t.resolution!==void 0&&(n.push("resolution = ?"),i.push(JSON.stringify(t.resolution))),t.confidence!==void 0&&(n.push("confidence = ?"),i.push(JSON.stringify(t.confidence))),t.source!==void 0&&(n.push("source = ?"),i.push(t.source)),t.private!==void 0&&(n.push("private = ?"),i.push(t.private?1:0)),t.tags!==void 0&&(n.push("tags = ?"),i.push(JSON.stringify(t.tags))),i.push(e),this.db.run(`UPDATE patterns SET ${n.join(", ")} WHERE id = ?`,i),this.save();}deletePattern(e){this.initializeSync(),this.db.run("DELETE FROM patterns WHERE id = ?",[e]),this.save();}updatePatternConfidence(e,t){let s=this.getPattern(e);if(!s)return;let n=new Date().toISOString(),i={...s.confidence};switch(t){case "matched":i.timesMatched++,i.lastMatched=n;break;case "resolved":i.timesResolved++,i.lastResolved=n,i.score=Math.min(100,i.score+2);break;case "recurred":i.timesRecurred++,i.score=Math.max(10,i.score-5);break}this.updatePattern(e,{confidence:i});}createGroup(e){this.initializeSync();let t=`GRP-${v4().substring(0,8)}`,s=new Date().toISOString();this.db.run(`INSERT INTO groups (
|
|
828
330
|
id, name, common_symbols, common_error_patterns,
|
|
829
331
|
suggested_pattern_id, first_seen, last_seen, created_at, updated_at
|
|
830
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
831
|
-
[
|
|
832
|
-
id,
|
|
833
|
-
input.name || null,
|
|
834
|
-
JSON.stringify(input.commonSymbols),
|
|
835
|
-
JSON.stringify(input.commonErrorPatterns),
|
|
836
|
-
input.suggestedPattern?.id || null,
|
|
837
|
-
input.firstSeen,
|
|
838
|
-
input.lastSeen,
|
|
839
|
-
now,
|
|
840
|
-
now
|
|
841
|
-
]
|
|
842
|
-
);
|
|
843
|
-
for (const incidentId of input.incidents) {
|
|
844
|
-
this.addToGroup(id, incidentId);
|
|
845
|
-
}
|
|
846
|
-
this.save();
|
|
847
|
-
return id;
|
|
848
|
-
}
|
|
849
|
-
getGroup(id) {
|
|
850
|
-
this.initializeSync();
|
|
851
|
-
const result = this.db.exec("SELECT * FROM groups WHERE id = ?", [id]);
|
|
852
|
-
if (result.length === 0 || result[0].values.length === 0) return null;
|
|
853
|
-
return this.rowToGroup(result[0].columns, result[0].values[0]);
|
|
854
|
-
}
|
|
855
|
-
getGroups(options = {}) {
|
|
856
|
-
this.initializeSync();
|
|
857
|
-
const limit = options.limit || 100;
|
|
858
|
-
const result = this.db.exec(
|
|
859
|
-
"SELECT * FROM groups ORDER BY last_seen DESC LIMIT ?",
|
|
860
|
-
[limit]
|
|
861
|
-
);
|
|
862
|
-
if (result.length === 0) return [];
|
|
863
|
-
return result[0].values.map(
|
|
864
|
-
(row) => this.rowToGroup(result[0].columns, row)
|
|
865
|
-
);
|
|
866
|
-
}
|
|
867
|
-
addToGroup(groupId, incidentId) {
|
|
868
|
-
this.initializeSync();
|
|
869
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
870
|
-
this.db.run(
|
|
871
|
-
"INSERT OR IGNORE INTO group_members (group_id, incident_id, added_at) VALUES (?, ?, ?)",
|
|
872
|
-
[groupId, incidentId, now]
|
|
873
|
-
);
|
|
874
|
-
this.db.run("UPDATE incidents SET group_id = ? WHERE id = ?", [
|
|
875
|
-
groupId,
|
|
876
|
-
incidentId
|
|
877
|
-
]);
|
|
878
|
-
this.db.run(
|
|
879
|
-
"UPDATE groups SET last_seen = ?, updated_at = ? WHERE id = ?",
|
|
880
|
-
[now, now, groupId]
|
|
881
|
-
);
|
|
882
|
-
this.save();
|
|
883
|
-
}
|
|
884
|
-
// ─── Resolutions ─────────────────────────────────────────────────
|
|
885
|
-
recordResolution(resolution) {
|
|
886
|
-
this.initializeSync();
|
|
887
|
-
const id = uuidv4();
|
|
888
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
889
|
-
this.db.run(
|
|
890
|
-
`INSERT INTO resolutions (id, incident_id, pattern_id, commit_hash, pr_url, notes, resolved_at)
|
|
891
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
892
|
-
[
|
|
893
|
-
id,
|
|
894
|
-
resolution.incidentId,
|
|
895
|
-
resolution.patternId || null,
|
|
896
|
-
resolution.commitHash || null,
|
|
897
|
-
resolution.prUrl || null,
|
|
898
|
-
resolution.notes || null,
|
|
899
|
-
now
|
|
900
|
-
]
|
|
901
|
-
);
|
|
902
|
-
this.updateIncident(resolution.incidentId, {
|
|
903
|
-
status: "resolved",
|
|
904
|
-
resolvedAt: now,
|
|
905
|
-
resolvedBy: resolution.patternId || "manual",
|
|
906
|
-
resolution: {
|
|
907
|
-
patternId: resolution.patternId,
|
|
908
|
-
commitHash: resolution.commitHash,
|
|
909
|
-
prUrl: resolution.prUrl,
|
|
910
|
-
notes: resolution.notes
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
if (resolution.patternId) {
|
|
914
|
-
this.updatePatternConfidence(resolution.patternId, "resolved");
|
|
915
|
-
}
|
|
916
|
-
this.save();
|
|
917
|
-
}
|
|
918
|
-
markRecurred(incidentId) {
|
|
919
|
-
this.initializeSync();
|
|
920
|
-
this.db.run(
|
|
921
|
-
"UPDATE resolutions SET recurred = 1 WHERE incident_id = ?",
|
|
922
|
-
[incidentId]
|
|
923
|
-
);
|
|
924
|
-
const result = this.db.exec(
|
|
925
|
-
"SELECT pattern_id FROM resolutions WHERE incident_id = ?",
|
|
926
|
-
[incidentId]
|
|
927
|
-
);
|
|
928
|
-
if (result.length > 0 && result[0].values.length > 0 && result[0].values[0][0]) {
|
|
929
|
-
this.updatePatternConfidence(result[0].values[0][0], "recurred");
|
|
930
|
-
}
|
|
931
|
-
this.save();
|
|
932
|
-
}
|
|
933
|
-
getResolutionHistory(options = {}) {
|
|
934
|
-
this.initializeSync();
|
|
935
|
-
const conditions = [];
|
|
936
|
-
const params = [];
|
|
937
|
-
if (options.patternId) {
|
|
938
|
-
conditions.push("pattern_id = ?");
|
|
939
|
-
params.push(options.patternId);
|
|
940
|
-
}
|
|
941
|
-
if (options.symbol) {
|
|
942
|
-
conditions.push(`incident_id IN (
|
|
332
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[t,e.name||null,JSON.stringify(e.commonSymbols),JSON.stringify(e.commonErrorPatterns),e.suggestedPattern?.id||null,e.firstSeen,e.lastSeen,s,s]);for(let n of e.incidents)this.addToGroup(t,n);return this.save(),t}getGroup(e){this.initializeSync();let t=this.db.exec("SELECT * FROM groups WHERE id = ?",[e]);return t.length===0||t[0].values.length===0?null:this.rowToGroup(t[0].columns,t[0].values[0])}getGroups(e={}){this.initializeSync();let t=e.limit||100,s=this.db.exec("SELECT * FROM groups ORDER BY last_seen DESC LIMIT ?",[t]);return s.length===0?[]:s[0].values.map(n=>this.rowToGroup(s[0].columns,n))}addToGroup(e,t){this.initializeSync();let s=new Date().toISOString();this.db.run("INSERT OR IGNORE INTO group_members (group_id, incident_id, added_at) VALUES (?, ?, ?)",[e,t,s]),this.db.run("UPDATE incidents SET group_id = ? WHERE id = ?",[e,t]),this.db.run("UPDATE groups SET last_seen = ?, updated_at = ? WHERE id = ?",[s,s,e]),this.save();}recordResolution(e){this.initializeSync();let t=v4(),s=new Date().toISOString();this.db.run(`INSERT INTO resolutions (id, incident_id, pattern_id, commit_hash, pr_url, notes, resolved_at)
|
|
333
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,[t,e.incidentId,e.patternId||null,e.commitHash||null,e.prUrl||null,e.notes||null,s]),this.updateIncident(e.incidentId,{status:"resolved",resolvedAt:s,resolvedBy:e.patternId||"manual",resolution:{patternId:e.patternId,commitHash:e.commitHash,prUrl:e.prUrl,notes:e.notes}}),e.patternId&&this.updatePatternConfidence(e.patternId,"resolved"),this.save();}markRecurred(e){this.initializeSync(),this.db.run("UPDATE resolutions SET recurred = 1 WHERE incident_id = ?",[e]);let t=this.db.exec("SELECT pattern_id FROM resolutions WHERE incident_id = ?",[e]);t.length>0&&t[0].values.length>0&&t[0].values[0][0]&&this.updatePatternConfidence(t[0].values[0][0],"recurred"),this.save();}getResolutionHistory(e={}){this.initializeSync();let t=[],s=[];e.patternId&&(t.push("pattern_id = ?"),s.push(e.patternId)),e.symbol&&(t.push(`incident_id IN (
|
|
943
334
|
SELECT id FROM incidents WHERE symbols LIKE ?
|
|
944
|
-
)`);
|
|
945
|
-
params.push(`%${options.symbol}%`);
|
|
946
|
-
}
|
|
947
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
948
|
-
const limit = options.limit || 100;
|
|
949
|
-
const result = this.db.exec(
|
|
950
|
-
`SELECT * FROM resolutions ${whereClause} ORDER BY resolved_at DESC LIMIT ?`,
|
|
951
|
-
[...params, limit]
|
|
952
|
-
);
|
|
953
|
-
if (result.length === 0) return [];
|
|
954
|
-
const columns = result[0].columns;
|
|
955
|
-
return result[0].values.map((row) => {
|
|
956
|
-
const obj = {};
|
|
957
|
-
columns.forEach((col, i) => {
|
|
958
|
-
obj[col] = row[i];
|
|
959
|
-
});
|
|
960
|
-
return {
|
|
961
|
-
id: obj.id,
|
|
962
|
-
incidentId: obj.incident_id,
|
|
963
|
-
patternId: obj.pattern_id || void 0,
|
|
964
|
-
commitHash: obj.commit_hash || void 0,
|
|
965
|
-
prUrl: obj.pr_url || void 0,
|
|
966
|
-
notes: obj.notes || void 0,
|
|
967
|
-
resolvedAt: obj.resolved_at,
|
|
968
|
-
recurred: obj.recurred === 1
|
|
969
|
-
};
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
// ─── Stats ───────────────────────────────────────────────────────
|
|
973
|
-
getStats(period) {
|
|
974
|
-
this.initializeSync();
|
|
975
|
-
const { start, end } = period;
|
|
976
|
-
const total = this.getIncidentCount({ dateFrom: start, dateTo: end });
|
|
977
|
-
const open = this.getIncidentCount({
|
|
978
|
-
dateFrom: start,
|
|
979
|
-
dateTo: end,
|
|
980
|
-
status: "open"
|
|
981
|
-
});
|
|
982
|
-
const resolved = this.getIncidentCount({
|
|
983
|
-
dateFrom: start,
|
|
984
|
-
dateTo: end,
|
|
985
|
-
status: "resolved"
|
|
986
|
-
});
|
|
987
|
-
const envResult = this.db.exec(
|
|
988
|
-
`SELECT environment, COUNT(*) as count
|
|
335
|
+
)`),s.push(`%${e.symbol}%`));let n=t.length>0?`WHERE ${t.join(" AND ")}`:"",i=e.limit||100,r=this.db.exec(`SELECT * FROM resolutions ${n} ORDER BY resolved_at DESC LIMIT ?`,[...s,i]);if(r.length===0)return [];let a=r[0].columns;return r[0].values.map(o=>{let c={};return a.forEach((l,d)=>{c[l]=o[d];}),{id:c.id,incidentId:c.incident_id,patternId:c.pattern_id||void 0,commitHash:c.commit_hash||void 0,prUrl:c.pr_url||void 0,notes:c.notes||void 0,resolvedAt:c.resolved_at,recurred:c.recurred===1}})}getStats(e){this.initializeSync();let{start:t,end:s}=e,n=this.getIncidentCount({dateFrom:t,dateTo:s}),i=this.getIncidentCount({dateFrom:t,dateTo:s,status:"open"}),r=this.getIncidentCount({dateFrom:t,dateTo:s,status:"resolved"}),a=this.db.exec(`SELECT environment, COUNT(*) as count
|
|
989
336
|
FROM incidents
|
|
990
337
|
WHERE timestamp >= ? AND timestamp <= ?
|
|
991
|
-
GROUP BY environment`,
|
|
992
|
-
[start, end]
|
|
993
|
-
);
|
|
994
|
-
const byEnvironment = {};
|
|
995
|
-
if (envResult.length > 0) {
|
|
996
|
-
for (const row of envResult[0].values) {
|
|
997
|
-
byEnvironment[row[0]] = row[1];
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
const dayResult = this.db.exec(
|
|
1001
|
-
`SELECT DATE(timestamp) as date, COUNT(*) as count
|
|
338
|
+
GROUP BY environment`,[t,s]),o={};if(a.length>0)for(let u of a[0].values)o[u[0]]=u[1];let c=this.db.exec(`SELECT DATE(timestamp) as date, COUNT(*) as count
|
|
1002
339
|
FROM incidents
|
|
1003
340
|
WHERE timestamp >= ? AND timestamp <= ?
|
|
1004
341
|
GROUP BY DATE(timestamp)
|
|
1005
|
-
ORDER BY date`,
|
|
1006
|
-
[start, end]
|
|
1007
|
-
);
|
|
1008
|
-
const byDay = [];
|
|
1009
|
-
if (dayResult.length > 0) {
|
|
1010
|
-
for (const row of dayResult[0].values) {
|
|
1011
|
-
byDay.push({ date: row[0], count: row[1] });
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
const patterns = this.getAllPatterns({ includePrivate: true });
|
|
1015
|
-
const avgConfidence = patterns.length > 0 ? patterns.reduce((sum, p) => sum + p.confidence.score, 0) / patterns.length : 0;
|
|
1016
|
-
const mostEffective = patterns.sort((a, b) => b.confidence.timesResolved - a.confidence.timesResolved).slice(0, 5).map((p) => ({ patternId: p.id, resolvedCount: p.confidence.timesResolved }));
|
|
1017
|
-
const leastEffective = patterns.filter((p) => p.confidence.timesMatched > 0).map((p) => ({
|
|
1018
|
-
patternId: p.id,
|
|
1019
|
-
recurrenceRate: p.confidence.timesRecurred / Math.max(1, p.confidence.timesResolved)
|
|
1020
|
-
})).sort((a, b) => b.recurrenceRate - a.recurrenceRate).slice(0, 5);
|
|
1021
|
-
const symbolCounts = /* @__PURE__ */ new Map();
|
|
1022
|
-
const incidents = this.getRecentIncidents({
|
|
1023
|
-
dateFrom: start,
|
|
1024
|
-
dateTo: end,
|
|
1025
|
-
limit: 1e3
|
|
1026
|
-
});
|
|
1027
|
-
for (const incident of incidents) {
|
|
1028
|
-
for (const [, value] of Object.entries(incident.symbols)) {
|
|
1029
|
-
if (value) {
|
|
1030
|
-
symbolCounts.set(value, (symbolCounts.get(value) || 0) + 1);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
const mostIncidents = Array.from(symbolCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([symbol, count]) => ({ symbol, count }));
|
|
1035
|
-
const resolutions = this.getResolutionHistory({ limit: 1e3 });
|
|
1036
|
-
const periodResolutions = resolutions.filter(
|
|
1037
|
-
(r) => r.resolvedAt >= start && r.resolvedAt <= end
|
|
1038
|
-
);
|
|
1039
|
-
const resolvedWithPattern = periodResolutions.filter(
|
|
1040
|
-
(r) => r.patternId
|
|
1041
|
-
).length;
|
|
1042
|
-
const resolvedManually = periodResolutions.length - resolvedWithPattern;
|
|
1043
|
-
return {
|
|
1044
|
-
period: { start, end },
|
|
1045
|
-
incidents: {
|
|
1046
|
-
total,
|
|
1047
|
-
open,
|
|
1048
|
-
resolved,
|
|
1049
|
-
byEnvironment,
|
|
1050
|
-
byDay
|
|
1051
|
-
},
|
|
1052
|
-
patterns: {
|
|
1053
|
-
total: patterns.length,
|
|
1054
|
-
avgConfidence: Math.round(avgConfidence),
|
|
1055
|
-
mostEffective,
|
|
1056
|
-
leastEffective
|
|
1057
|
-
},
|
|
1058
|
-
symbols: {
|
|
1059
|
-
mostIncidents,
|
|
1060
|
-
mostResolved: [],
|
|
1061
|
-
hotspots: mostIncidents.slice(0, 5).map((s) => ({
|
|
1062
|
-
symbol: s.symbol,
|
|
1063
|
-
incidentRate: s.count / Math.max(1, total)
|
|
1064
|
-
}))
|
|
1065
|
-
},
|
|
1066
|
-
resolution: {
|
|
1067
|
-
avgTimeToResolve: 0,
|
|
1068
|
-
resolvedWithPattern,
|
|
1069
|
-
resolvedManually,
|
|
1070
|
-
resolutionRate: total > 0 ? resolved / total * 100 : 0
|
|
1071
|
-
}
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
getSymbolHealth(symbol) {
|
|
1075
|
-
const incidents = this.getRecentIncidents({ symbol, limit: 1e3 });
|
|
1076
|
-
const incidentCount = incidents.length;
|
|
1077
|
-
const patternCounts = /* @__PURE__ */ new Map();
|
|
1078
|
-
for (const incident of incidents) {
|
|
1079
|
-
if (incident.resolution?.patternId) {
|
|
1080
|
-
const count = patternCounts.get(incident.resolution.patternId) || 0;
|
|
1081
|
-
patternCounts.set(incident.resolution.patternId, count + 1);
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
const topPatterns = Array.from(patternCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([patternId, count]) => ({ patternId, count }));
|
|
1085
|
-
return {
|
|
1086
|
-
incidentCount,
|
|
1087
|
-
avgTimeToResolve: 0,
|
|
1088
|
-
topPatterns
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
// ─── Import/Export ───────────────────────────────────────────────
|
|
1092
|
-
exportPatterns(options = {}) {
|
|
1093
|
-
const patterns = this.getAllPatterns({
|
|
1094
|
-
includePrivate: options.includePrivate
|
|
1095
|
-
});
|
|
1096
|
-
return {
|
|
1097
|
-
version: "1.0.0",
|
|
1098
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1099
|
-
patterns
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
importPatterns(data, options = {}) {
|
|
1103
|
-
let imported = 0;
|
|
1104
|
-
let skipped = 0;
|
|
1105
|
-
for (const pattern of data.patterns) {
|
|
1106
|
-
const existing = this.getPattern(pattern.id);
|
|
1107
|
-
if (existing && !options.overwrite) {
|
|
1108
|
-
skipped++;
|
|
1109
|
-
continue;
|
|
1110
|
-
}
|
|
1111
|
-
if (existing) {
|
|
1112
|
-
this.updatePattern(pattern.id, pattern);
|
|
1113
|
-
} else {
|
|
1114
|
-
this.addPattern({
|
|
1115
|
-
...pattern,
|
|
1116
|
-
source: "imported"
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
imported++;
|
|
1120
|
-
}
|
|
1121
|
-
return { imported, skipped };
|
|
1122
|
-
}
|
|
1123
|
-
exportBackup() {
|
|
1124
|
-
const incidents = this.getRecentIncidents({ limit: 1e5 });
|
|
1125
|
-
const patterns = this.getAllPatterns({ includePrivate: true });
|
|
1126
|
-
const groups = this.getGroups({ limit: 1e4 });
|
|
1127
|
-
return {
|
|
1128
|
-
version: "1.0.0",
|
|
1129
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1130
|
-
incidents,
|
|
1131
|
-
patterns,
|
|
1132
|
-
groups
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
importBackup(data) {
|
|
1136
|
-
this.initializeSync();
|
|
1137
|
-
this.db.run("DELETE FROM group_members");
|
|
1138
|
-
this.db.run("DELETE FROM resolutions");
|
|
1139
|
-
this.db.run("DELETE FROM groups");
|
|
1140
|
-
this.db.run("DELETE FROM incidents");
|
|
1141
|
-
this.db.run("DELETE FROM patterns");
|
|
1142
|
-
for (const pattern of data.patterns) {
|
|
1143
|
-
this.addPattern(pattern);
|
|
1144
|
-
}
|
|
1145
|
-
for (const incident of data.incidents) {
|
|
1146
|
-
const now2 = incident.timestamp;
|
|
1147
|
-
this.db.run(
|
|
1148
|
-
`INSERT INTO incidents (
|
|
342
|
+
ORDER BY date`,[t,s]),l=[];if(c.length>0)for(let u of c[0].values)l.push({date:u[0],count:u[1]});let d=this.getAllPatterns({includePrivate:true}),m=d.length>0?d.reduce((u,T)=>u+T.confidence.score,0)/d.length:0,g=d.sort((u,T)=>T.confidence.timesResolved-u.confidence.timesResolved).slice(0,5).map(u=>({patternId:u.id,resolvedCount:u.confidence.timesResolved})),p=d.filter(u=>u.confidence.timesMatched>0).map(u=>({patternId:u.id,recurrenceRate:u.confidence.timesRecurred/Math.max(1,u.confidence.timesResolved)})).sort((u,T)=>T.recurrenceRate-u.recurrenceRate).slice(0,5),E=new Map,S=this.getRecentIncidents({dateFrom:t,dateTo:s,limit:1e3});for(let u of S)for(let[,T]of Object.entries(u.symbols))T&&E.set(T,(E.get(T)||0)+1);let v=Array.from(E.entries()).sort((u,T)=>T[1]-u[1]).slice(0,10).map(([u,T])=>({symbol:u,count:T})),P=this.getResolutionHistory({limit:1e3}).filter(u=>u.resolvedAt>=t&&u.resolvedAt<=s),D=P.filter(u=>u.patternId).length,j=P.length-D;return {period:{start:t,end:s},incidents:{total:n,open:i,resolved:r,byEnvironment:o,byDay:l},patterns:{total:d.length,avgConfidence:Math.round(m),mostEffective:g,leastEffective:p},symbols:{mostIncidents:v,mostResolved:[],hotspots:v.slice(0,5).map(u=>({symbol:u.symbol,incidentRate:u.count/Math.max(1,n)}))},resolution:{avgTimeToResolve:0,resolvedWithPattern:D,resolvedManually:j,resolutionRate:n>0?r/n*100:0}}}getSymbolHealth(e){let t=this.getRecentIncidents({symbol:e,limit:1e3}),s=t.length,n=new Map;for(let r of t)if(r.resolution?.patternId){let a=n.get(r.resolution.patternId)||0;n.set(r.resolution.patternId,a+1);}let i=Array.from(n.entries()).sort((r,a)=>a[1]-r[1]).slice(0,5).map(([r,a])=>({patternId:r,count:a}));return {incidentCount:s,avgTimeToResolve:0,topPatterns:i}}exportPatterns(e={}){let t=this.getAllPatterns({includePrivate:e.includePrivate});return {version:"1.0.0",exportedAt:new Date().toISOString(),patterns:t}}importPatterns(e,t={}){let s=0,n=0;for(let i of e.patterns){let r=this.getPattern(i.id);if(r&&!t.overwrite){n++;continue}r?this.updatePattern(i.id,i):this.addPattern({...i,source:"imported"}),s++;}return {imported:s,skipped:n}}exportBackup(){let e=this.getRecentIncidents({limit:1e5}),t=this.getAllPatterns({includePrivate:true}),s=this.getGroups({limit:1e4});return {version:"1.0.0",exportedAt:new Date().toISOString(),incidents:e,patterns:t,groups:s}}importBackup(e){this.initializeSync(),this.db.run("DELETE FROM group_members"),this.db.run("DELETE FROM resolutions"),this.db.run("DELETE FROM groups"),this.db.run("DELETE FROM incidents"),this.db.run("DELETE FROM patterns");for(let n of e.patterns)this.addPattern(n);for(let n of e.incidents){let i=n.timestamp;this.db.run(`INSERT INTO incidents (
|
|
1149
343
|
id, timestamp, status, error_message, error_stack, error_code, error_type,
|
|
1150
344
|
symbols, flow_position, environment, service, version, user_id, request_id,
|
|
1151
345
|
group_id, notes, related_incidents, resolved_at, resolved_by, resolution,
|
|
1152
346
|
created_at, updated_at
|
|
1153
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1154
|
-
[
|
|
1155
|
-
incident.id,
|
|
1156
|
-
incident.timestamp,
|
|
1157
|
-
incident.status,
|
|
1158
|
-
incident.error.message,
|
|
1159
|
-
incident.error.stack || null,
|
|
1160
|
-
incident.error.code || null,
|
|
1161
|
-
incident.error.type || null,
|
|
1162
|
-
JSON.stringify(incident.symbols),
|
|
1163
|
-
incident.flowPosition ? JSON.stringify(incident.flowPosition) : null,
|
|
1164
|
-
incident.environment,
|
|
1165
|
-
incident.service || null,
|
|
1166
|
-
incident.version || null,
|
|
1167
|
-
incident.userId || null,
|
|
1168
|
-
incident.requestId || null,
|
|
1169
|
-
incident.groupId || null,
|
|
1170
|
-
JSON.stringify(incident.notes),
|
|
1171
|
-
JSON.stringify(incident.relatedIncidents),
|
|
1172
|
-
incident.resolvedAt || null,
|
|
1173
|
-
incident.resolvedBy || null,
|
|
1174
|
-
incident.resolution ? JSON.stringify(incident.resolution) : null,
|
|
1175
|
-
now2,
|
|
1176
|
-
now2
|
|
1177
|
-
]
|
|
1178
|
-
);
|
|
1179
|
-
}
|
|
1180
|
-
const result = this.db.exec(
|
|
1181
|
-
"SELECT MAX(CAST(SUBSTR(id, 5) AS INTEGER)) as max FROM incidents"
|
|
1182
|
-
);
|
|
1183
|
-
if (result.length > 0 && result[0].values.length > 0 && result[0].values[0][0]) {
|
|
1184
|
-
this.incidentCounter = result[0].values[0][0];
|
|
1185
|
-
}
|
|
1186
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1187
|
-
for (const group of data.groups) {
|
|
1188
|
-
this.db.run(
|
|
1189
|
-
`INSERT INTO groups (
|
|
347
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[n.id,n.timestamp,n.status,n.error.message,n.error.stack||null,n.error.code||null,n.error.type||null,JSON.stringify(n.symbols),n.flowPosition?JSON.stringify(n.flowPosition):null,n.environment,n.service||null,n.version||null,n.userId||null,n.requestId||null,n.groupId||null,JSON.stringify(n.notes),JSON.stringify(n.relatedIncidents),n.resolvedAt||null,n.resolvedBy||null,n.resolution?JSON.stringify(n.resolution):null,i,i]);}let t=this.db.exec("SELECT MAX(CAST(SUBSTR(id, 5) AS INTEGER)) as max FROM incidents");t.length>0&&t[0].values.length>0&&t[0].values[0][0]&&(this.incidentCounter=t[0].values[0][0]);let s=new Date().toISOString();for(let n of e.groups){this.db.run(`INSERT INTO groups (
|
|
1190
348
|
id, name, common_symbols, common_error_patterns,
|
|
1191
349
|
suggested_pattern_id, first_seen, last_seen, created_at, updated_at
|
|
1192
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1193
|
-
|
|
1194
|
-
group.id,
|
|
1195
|
-
group.name || null,
|
|
1196
|
-
JSON.stringify(group.commonSymbols),
|
|
1197
|
-
JSON.stringify(group.commonErrorPatterns),
|
|
1198
|
-
group.suggestedPattern?.id || null,
|
|
1199
|
-
group.firstSeen,
|
|
1200
|
-
group.lastSeen,
|
|
1201
|
-
now,
|
|
1202
|
-
now
|
|
1203
|
-
]
|
|
1204
|
-
);
|
|
1205
|
-
for (const incidentId of group.incidents) {
|
|
1206
|
-
this.db.run(
|
|
1207
|
-
"INSERT OR IGNORE INTO group_members (group_id, incident_id, added_at) VALUES (?, ?, ?)",
|
|
1208
|
-
[group.id, incidentId, now]
|
|
1209
|
-
);
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
this.save();
|
|
1213
|
-
}
|
|
1214
|
-
// ─── Helper Methods ──────────────────────────────────────────────
|
|
1215
|
-
rowToIncident(columns, row) {
|
|
1216
|
-
const obj = {};
|
|
1217
|
-
columns.forEach((col, i) => {
|
|
1218
|
-
obj[col] = row[i];
|
|
1219
|
-
});
|
|
1220
|
-
return {
|
|
1221
|
-
id: obj.id,
|
|
1222
|
-
timestamp: obj.timestamp,
|
|
1223
|
-
status: obj.status,
|
|
1224
|
-
error: {
|
|
1225
|
-
message: obj.error_message,
|
|
1226
|
-
stack: obj.error_stack || void 0,
|
|
1227
|
-
code: obj.error_code || void 0,
|
|
1228
|
-
type: obj.error_type || void 0
|
|
1229
|
-
},
|
|
1230
|
-
symbols: JSON.parse(obj.symbols || "{}"),
|
|
1231
|
-
flowPosition: obj.flow_position ? JSON.parse(obj.flow_position) : void 0,
|
|
1232
|
-
environment: obj.environment,
|
|
1233
|
-
service: obj.service || void 0,
|
|
1234
|
-
version: obj.version || void 0,
|
|
1235
|
-
userId: obj.user_id || void 0,
|
|
1236
|
-
requestId: obj.request_id || void 0,
|
|
1237
|
-
groupId: obj.group_id || void 0,
|
|
1238
|
-
notes: JSON.parse(obj.notes || "[]"),
|
|
1239
|
-
relatedIncidents: JSON.parse(obj.related_incidents || "[]"),
|
|
1240
|
-
resolvedAt: obj.resolved_at || void 0,
|
|
1241
|
-
resolvedBy: obj.resolved_by || void 0,
|
|
1242
|
-
resolution: obj.resolution ? JSON.parse(obj.resolution) : void 0
|
|
1243
|
-
};
|
|
1244
|
-
}
|
|
1245
|
-
rowToPattern(columns, row) {
|
|
1246
|
-
const obj = {};
|
|
1247
|
-
columns.forEach((col, i) => {
|
|
1248
|
-
obj[col] = row[i];
|
|
1249
|
-
});
|
|
1250
|
-
return {
|
|
1251
|
-
id: obj.id,
|
|
1252
|
-
name: obj.name,
|
|
1253
|
-
description: obj.description || "",
|
|
1254
|
-
pattern: JSON.parse(obj.pattern),
|
|
1255
|
-
resolution: JSON.parse(obj.resolution),
|
|
1256
|
-
confidence: JSON.parse(obj.confidence),
|
|
1257
|
-
source: obj.source,
|
|
1258
|
-
private: obj.private === 1,
|
|
1259
|
-
tags: JSON.parse(obj.tags || "[]"),
|
|
1260
|
-
createdAt: obj.created_at,
|
|
1261
|
-
updatedAt: obj.updated_at
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
rowToGroup(columns, row) {
|
|
1265
|
-
const obj = {};
|
|
1266
|
-
columns.forEach((col, i) => {
|
|
1267
|
-
obj[col] = row[i];
|
|
1268
|
-
});
|
|
1269
|
-
const membersResult = this.db.exec(
|
|
1270
|
-
"SELECT incident_id FROM group_members WHERE group_id = ?",
|
|
1271
|
-
[obj.id]
|
|
1272
|
-
);
|
|
1273
|
-
const incidents = [];
|
|
1274
|
-
if (membersResult.length > 0) {
|
|
1275
|
-
for (const r of membersResult[0].values) {
|
|
1276
|
-
incidents.push(r[0]);
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
const envResult = this.db.exec(
|
|
1280
|
-
`SELECT DISTINCT environment FROM incidents
|
|
1281
|
-
WHERE id IN (SELECT incident_id FROM group_members WHERE group_id = ?)`,
|
|
1282
|
-
[obj.id]
|
|
1283
|
-
);
|
|
1284
|
-
const environments = [];
|
|
1285
|
-
if (envResult.length > 0) {
|
|
1286
|
-
for (const r of envResult[0].values) {
|
|
1287
|
-
environments.push(r[0]);
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
return {
|
|
1291
|
-
id: obj.id,
|
|
1292
|
-
name: obj.name || void 0,
|
|
1293
|
-
incidents,
|
|
1294
|
-
commonSymbols: JSON.parse(obj.common_symbols || "{}"),
|
|
1295
|
-
commonErrorPatterns: JSON.parse(
|
|
1296
|
-
obj.common_error_patterns || "[]"
|
|
1297
|
-
),
|
|
1298
|
-
count: incidents.length,
|
|
1299
|
-
firstSeen: obj.first_seen,
|
|
1300
|
-
lastSeen: obj.last_seen,
|
|
1301
|
-
environments,
|
|
1302
|
-
suggestedPattern: obj.suggested_pattern_id ? this.getPattern(obj.suggested_pattern_id) || void 0 : void 0
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
|
-
// ─── Practice Events ─────────────────────────────────────────────
|
|
1306
|
-
recordPracticeEvent(input) {
|
|
1307
|
-
this.initializeSync();
|
|
1308
|
-
const id = `PE-${uuidv4().substring(0, 8)}`;
|
|
1309
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1310
|
-
this.db.run(
|
|
1311
|
-
`INSERT INTO practice_events (
|
|
350
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[n.id,n.name||null,JSON.stringify(n.commonSymbols),JSON.stringify(n.commonErrorPatterns),n.suggestedPattern?.id||null,n.firstSeen,n.lastSeen,s,s]);for(let i of n.incidents)this.db.run("INSERT OR IGNORE INTO group_members (group_id, incident_id, added_at) VALUES (?, ?, ?)",[n.id,i,s]);}this.save();}rowToIncident(e,t){let s={};return e.forEach((n,i)=>{s[n]=t[i];}),{id:s.id,timestamp:s.timestamp,status:s.status,error:{message:s.error_message,stack:s.error_stack||void 0,code:s.error_code||void 0,type:s.error_type||void 0},symbols:JSON.parse(s.symbols||"{}"),flowPosition:s.flow_position?JSON.parse(s.flow_position):void 0,environment:s.environment,service:s.service||void 0,version:s.version||void 0,userId:s.user_id||void 0,requestId:s.request_id||void 0,groupId:s.group_id||void 0,notes:JSON.parse(s.notes||"[]"),relatedIncidents:JSON.parse(s.related_incidents||"[]"),resolvedAt:s.resolved_at||void 0,resolvedBy:s.resolved_by||void 0,resolution:s.resolution?JSON.parse(s.resolution):void 0}}rowToPattern(e,t){let s={};return e.forEach((n,i)=>{s[n]=t[i];}),{id:s.id,name:s.name,description:s.description||"",pattern:JSON.parse(s.pattern),resolution:JSON.parse(s.resolution),confidence:JSON.parse(s.confidence),source:s.source,private:s.private===1,tags:JSON.parse(s.tags||"[]"),createdAt:s.created_at,updatedAt:s.updated_at}}rowToGroup(e,t){let s={};e.forEach((o,c)=>{s[o]=t[c];});let n=this.db.exec("SELECT incident_id FROM group_members WHERE group_id = ?",[s.id]),i=[];if(n.length>0)for(let o of n[0].values)i.push(o[0]);let r=this.db.exec(`SELECT DISTINCT environment FROM incidents
|
|
351
|
+
WHERE id IN (SELECT incident_id FROM group_members WHERE group_id = ?)`,[s.id]),a=[];if(r.length>0)for(let o of r[0].values)a.push(o[0]);return {id:s.id,name:s.name||void 0,incidents:i,commonSymbols:JSON.parse(s.common_symbols||"{}"),commonErrorPatterns:JSON.parse(s.common_error_patterns||"[]"),count:i.length,firstSeen:s.first_seen,lastSeen:s.last_seen,environments:a,suggestedPattern:s.suggested_pattern_id&&this.getPattern(s.suggested_pattern_id)||void 0}}recordPracticeEvent(e){this.initializeSync();let t=`PE-${v4().substring(0,8)}`,s=new Date().toISOString();return this.db.run(`INSERT INTO practice_events (
|
|
1312
352
|
id, timestamp, habit_id, habit_category, result,
|
|
1313
353
|
engineer, session_id, lore_entry_id, task_description,
|
|
1314
354
|
symbols_touched, files_modified, related_incident_id, notes
|
|
1315
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
now,
|
|
1319
|
-
input.habitId,
|
|
1320
|
-
input.habitCategory,
|
|
1321
|
-
input.result,
|
|
1322
|
-
input.engineer,
|
|
1323
|
-
input.sessionId,
|
|
1324
|
-
input.loreEntryId || null,
|
|
1325
|
-
input.taskDescription || null,
|
|
1326
|
-
JSON.stringify(input.symbolsTouched || []),
|
|
1327
|
-
JSON.stringify(input.filesModified || []),
|
|
1328
|
-
input.relatedIncidentId || null,
|
|
1329
|
-
input.notes || null
|
|
1330
|
-
]
|
|
1331
|
-
);
|
|
1332
|
-
this.save();
|
|
1333
|
-
return id;
|
|
1334
|
-
}
|
|
1335
|
-
getPracticeEvents(options = {}) {
|
|
1336
|
-
this.initializeSync();
|
|
1337
|
-
const { limit = 100, offset = 0 } = options;
|
|
1338
|
-
const conditions = [];
|
|
1339
|
-
const params = [];
|
|
1340
|
-
if (options.habitId) {
|
|
1341
|
-
conditions.push("habit_id = ?");
|
|
1342
|
-
params.push(options.habitId);
|
|
1343
|
-
}
|
|
1344
|
-
if (options.habitCategory) {
|
|
1345
|
-
conditions.push("habit_category = ?");
|
|
1346
|
-
params.push(options.habitCategory);
|
|
1347
|
-
}
|
|
1348
|
-
if (options.result) {
|
|
1349
|
-
conditions.push("result = ?");
|
|
1350
|
-
params.push(options.result);
|
|
1351
|
-
}
|
|
1352
|
-
if (options.engineer) {
|
|
1353
|
-
conditions.push("engineer = ?");
|
|
1354
|
-
params.push(options.engineer);
|
|
1355
|
-
}
|
|
1356
|
-
if (options.sessionId) {
|
|
1357
|
-
conditions.push("session_id = ?");
|
|
1358
|
-
params.push(options.sessionId);
|
|
1359
|
-
}
|
|
1360
|
-
if (options.dateFrom) {
|
|
1361
|
-
conditions.push("timestamp >= ?");
|
|
1362
|
-
params.push(options.dateFrom);
|
|
1363
|
-
}
|
|
1364
|
-
if (options.dateTo) {
|
|
1365
|
-
conditions.push("timestamp <= ?");
|
|
1366
|
-
params.push(options.dateTo);
|
|
1367
|
-
}
|
|
1368
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1369
|
-
const result = this.db.exec(
|
|
1370
|
-
`SELECT * FROM practice_events ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
|
|
1371
|
-
[...params, limit, offset]
|
|
1372
|
-
);
|
|
1373
|
-
if (result.length === 0) return [];
|
|
1374
|
-
return result[0].values.map(
|
|
1375
|
-
(row) => this.rowToPracticeEvent(result[0].columns, row)
|
|
1376
|
-
);
|
|
1377
|
-
}
|
|
1378
|
-
getPracticeEventCount(options = {}) {
|
|
1379
|
-
this.initializeSync();
|
|
1380
|
-
const conditions = [];
|
|
1381
|
-
const params = [];
|
|
1382
|
-
if (options.habitId) {
|
|
1383
|
-
conditions.push("habit_id = ?");
|
|
1384
|
-
params.push(options.habitId);
|
|
1385
|
-
}
|
|
1386
|
-
if (options.habitCategory) {
|
|
1387
|
-
conditions.push("habit_category = ?");
|
|
1388
|
-
params.push(options.habitCategory);
|
|
1389
|
-
}
|
|
1390
|
-
if (options.result) {
|
|
1391
|
-
conditions.push("result = ?");
|
|
1392
|
-
params.push(options.result);
|
|
1393
|
-
}
|
|
1394
|
-
if (options.engineer) {
|
|
1395
|
-
conditions.push("engineer = ?");
|
|
1396
|
-
params.push(options.engineer);
|
|
1397
|
-
}
|
|
1398
|
-
if (options.dateFrom) {
|
|
1399
|
-
conditions.push("timestamp >= ?");
|
|
1400
|
-
params.push(options.dateFrom);
|
|
1401
|
-
}
|
|
1402
|
-
if (options.dateTo) {
|
|
1403
|
-
conditions.push("timestamp <= ?");
|
|
1404
|
-
params.push(options.dateTo);
|
|
1405
|
-
}
|
|
1406
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1407
|
-
const result = this.db.exec(
|
|
1408
|
-
`SELECT COUNT(*) as count FROM practice_events ${whereClause}`,
|
|
1409
|
-
params
|
|
1410
|
-
);
|
|
1411
|
-
if (result.length === 0 || result[0].values.length === 0) return 0;
|
|
1412
|
-
return result[0].values[0][0];
|
|
1413
|
-
}
|
|
1414
|
-
getComplianceRate(options = {}) {
|
|
1415
|
-
this.initializeSync();
|
|
1416
|
-
const conditions = [];
|
|
1417
|
-
const params = [];
|
|
1418
|
-
if (options.habitId) {
|
|
1419
|
-
conditions.push("habit_id = ?");
|
|
1420
|
-
params.push(options.habitId);
|
|
1421
|
-
}
|
|
1422
|
-
if (options.habitCategory) {
|
|
1423
|
-
conditions.push("habit_category = ?");
|
|
1424
|
-
params.push(options.habitCategory);
|
|
1425
|
-
}
|
|
1426
|
-
if (options.engineer) {
|
|
1427
|
-
conditions.push("engineer = ?");
|
|
1428
|
-
params.push(options.engineer);
|
|
1429
|
-
}
|
|
1430
|
-
if (options.dateFrom) {
|
|
1431
|
-
conditions.push("timestamp >= ?");
|
|
1432
|
-
params.push(options.dateFrom);
|
|
1433
|
-
}
|
|
1434
|
-
if (options.dateTo) {
|
|
1435
|
-
conditions.push("timestamp <= ?");
|
|
1436
|
-
params.push(options.dateTo);
|
|
1437
|
-
}
|
|
1438
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1439
|
-
const result = this.db.exec(
|
|
1440
|
-
`SELECT result, COUNT(*) as count
|
|
1441
|
-
FROM practice_events ${whereClause}
|
|
1442
|
-
GROUP BY result`,
|
|
1443
|
-
params
|
|
1444
|
-
);
|
|
1445
|
-
let followed = 0;
|
|
1446
|
-
let skipped = 0;
|
|
1447
|
-
let partial = 0;
|
|
1448
|
-
if (result.length > 0) {
|
|
1449
|
-
for (const row of result[0].values) {
|
|
1450
|
-
const r = row[0];
|
|
1451
|
-
const count = row[1];
|
|
1452
|
-
if (r === "followed") followed = count;
|
|
1453
|
-
else if (r === "skipped") skipped = count;
|
|
1454
|
-
else if (r === "partial") partial = count;
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
const total = followed + skipped + partial;
|
|
1458
|
-
const rate = total > 0 ? (followed + partial * 0.5) / total * 100 : 100;
|
|
1459
|
-
return { total, followed, skipped, partial, rate: Math.round(rate) };
|
|
1460
|
-
}
|
|
1461
|
-
rowToPracticeEvent(columns, row) {
|
|
1462
|
-
const obj = {};
|
|
1463
|
-
columns.forEach((col, i) => {
|
|
1464
|
-
obj[col] = row[i];
|
|
1465
|
-
});
|
|
1466
|
-
return {
|
|
1467
|
-
id: obj.id,
|
|
1468
|
-
timestamp: obj.timestamp,
|
|
1469
|
-
habitId: obj.habit_id,
|
|
1470
|
-
habitCategory: obj.habit_category,
|
|
1471
|
-
result: obj.result,
|
|
1472
|
-
engineer: obj.engineer,
|
|
1473
|
-
sessionId: obj.session_id,
|
|
1474
|
-
loreEntryId: obj.lore_entry_id || void 0,
|
|
1475
|
-
taskDescription: obj.task_description || void 0,
|
|
1476
|
-
symbolsTouched: JSON.parse(obj.symbols_touched || "[]"),
|
|
1477
|
-
filesModified: JSON.parse(obj.files_modified || "[]"),
|
|
1478
|
-
relatedIncidentId: obj.related_incident_id || void 0,
|
|
1479
|
-
notes: obj.notes || void 0
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
// ─── Structured Logs ─────────────────────────────────────────────
|
|
1483
|
-
inferSymbolType(symbol) {
|
|
1484
|
-
if (symbol.startsWith("#")) return "component";
|
|
1485
|
-
if (symbol.startsWith("^")) return "gate";
|
|
1486
|
-
if (symbol.startsWith("!")) return "signal";
|
|
1487
|
-
if (symbol.startsWith("$")) return "flow";
|
|
1488
|
-
if (symbol.startsWith("~")) return "aspect";
|
|
1489
|
-
return "raw";
|
|
1490
|
-
}
|
|
1491
|
-
insertLog(input) {
|
|
1492
|
-
this.initializeSync();
|
|
1493
|
-
const id = input.id || uuidv4();
|
|
1494
|
-
const timestamp = input.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
1495
|
-
const symbolType = input.symbolType || this.inferSymbolType(input.symbol);
|
|
1496
|
-
this.db.run(
|
|
1497
|
-
`INSERT INTO logs (
|
|
355
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[t,s,e.habitId,e.habitCategory,e.result,e.engineer,e.sessionId,e.loreEntryId||null,e.taskDescription||null,JSON.stringify(e.symbolsTouched||[]),JSON.stringify(e.filesModified||[]),e.relatedIncidentId||null,e.notes||null]),this.save(),t}getPracticeEvents(e={}){this.initializeSync();let{limit:t=100,offset:s=0}=e,n=[],i=[];e.habitId&&(n.push("habit_id = ?"),i.push(e.habitId)),e.habitCategory&&(n.push("habit_category = ?"),i.push(e.habitCategory)),e.result&&(n.push("result = ?"),i.push(e.result)),e.engineer&&(n.push("engineer = ?"),i.push(e.engineer)),e.sessionId&&(n.push("session_id = ?"),i.push(e.sessionId)),e.dateFrom&&(n.push("timestamp >= ?"),i.push(e.dateFrom)),e.dateTo&&(n.push("timestamp <= ?"),i.push(e.dateTo));let r=n.length>0?`WHERE ${n.join(" AND ")}`:"",a=this.db.exec(`SELECT * FROM practice_events ${r} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,[...i,t,s]);return a.length===0?[]:a[0].values.map(o=>this.rowToPracticeEvent(a[0].columns,o))}getPracticeEventCount(e={}){this.initializeSync();let t=[],s=[];e.habitId&&(t.push("habit_id = ?"),s.push(e.habitId)),e.habitCategory&&(t.push("habit_category = ?"),s.push(e.habitCategory)),e.result&&(t.push("result = ?"),s.push(e.result)),e.engineer&&(t.push("engineer = ?"),s.push(e.engineer)),e.dateFrom&&(t.push("timestamp >= ?"),s.push(e.dateFrom)),e.dateTo&&(t.push("timestamp <= ?"),s.push(e.dateTo));let n=t.length>0?`WHERE ${t.join(" AND ")}`:"",i=this.db.exec(`SELECT COUNT(*) as count FROM practice_events ${n}`,s);return i.length===0||i[0].values.length===0?0:i[0].values[0][0]}getComplianceRate(e={}){this.initializeSync();let t=[],s=[];e.habitId&&(t.push("habit_id = ?"),s.push(e.habitId)),e.habitCategory&&(t.push("habit_category = ?"),s.push(e.habitCategory)),e.engineer&&(t.push("engineer = ?"),s.push(e.engineer)),e.dateFrom&&(t.push("timestamp >= ?"),s.push(e.dateFrom)),e.dateTo&&(t.push("timestamp <= ?"),s.push(e.dateTo));let n=t.length>0?`WHERE ${t.join(" AND ")}`:"",i=this.db.exec(`SELECT result, COUNT(*) as count
|
|
356
|
+
FROM practice_events ${n}
|
|
357
|
+
GROUP BY result`,s),r=0,a=0,o=0;if(i.length>0)for(let d of i[0].values){let m=d[0],g=d[1];m==="followed"?r=g:m==="skipped"?a=g:m==="partial"&&(o=g);}let c=r+a+o,l=c>0?(r+o*.5)/c*100:100;return {total:c,followed:r,skipped:a,partial:o,rate:Math.round(l)}}rowToPracticeEvent(e,t){let s={};return e.forEach((n,i)=>{s[n]=t[i];}),{id:s.id,timestamp:s.timestamp,habitId:s.habit_id,habitCategory:s.habit_category,result:s.result,engineer:s.engineer,sessionId:s.session_id,loreEntryId:s.lore_entry_id||void 0,taskDescription:s.task_description||void 0,symbolsTouched:JSON.parse(s.symbols_touched||"[]"),filesModified:JSON.parse(s.files_modified||"[]"),relatedIncidentId:s.related_incident_id||void 0,notes:s.notes||void 0}}inferSymbolType(e){return e.startsWith("#")?"component":e.startsWith("^")?"gate":e.startsWith("!")?"signal":e.startsWith("$")?"flow":e.startsWith("~")?"aspect":"raw"}insertLog(e){this.initializeSync();let t=e.id||v4(),s=e.timestamp||new Date().toISOString(),n=e.symbolType||this.inferSymbolType(e.symbol);return this.db.run(`INSERT INTO logs (
|
|
1498
358
|
id, timestamp, level, symbol, symbol_type, message, data_json,
|
|
1499
359
|
service, session_id, correlation_id, duration_ms, environment
|
|
1500
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1501
|
-
[
|
|
1502
|
-
id,
|
|
1503
|
-
timestamp,
|
|
1504
|
-
input.level,
|
|
1505
|
-
input.symbol,
|
|
1506
|
-
symbolType,
|
|
1507
|
-
input.message,
|
|
1508
|
-
input.data ? JSON.stringify(input.data) : null,
|
|
1509
|
-
input.service,
|
|
1510
|
-
input.sessionId || null,
|
|
1511
|
-
input.correlationId || null,
|
|
1512
|
-
input.durationMs ?? null,
|
|
1513
|
-
input.environment || null
|
|
1514
|
-
]
|
|
1515
|
-
);
|
|
1516
|
-
this.save();
|
|
1517
|
-
return id;
|
|
1518
|
-
}
|
|
1519
|
-
insertLogBatch(entries) {
|
|
1520
|
-
this.initializeSync();
|
|
1521
|
-
let accepted = 0;
|
|
1522
|
-
const errors = [];
|
|
1523
|
-
for (const input of entries) {
|
|
1524
|
-
try {
|
|
1525
|
-
const id = input.id || uuidv4();
|
|
1526
|
-
const timestamp = input.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
1527
|
-
const symbolType = input.symbolType || this.inferSymbolType(input.symbol);
|
|
1528
|
-
this.db.run(
|
|
1529
|
-
`INSERT INTO logs (
|
|
360
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[t,s,e.level,e.symbol,n,e.message,e.data?JSON.stringify(e.data):null,e.service,e.sessionId||null,e.correlationId||null,e.durationMs??null,e.environment||null]),this.save(),t}insertLogBatch(e){this.initializeSync();let t=0,s=[];for(let n of e)try{let i=n.id||v4(),r=n.timestamp||new Date().toISOString(),a=n.symbolType||this.inferSymbolType(n.symbol);this.db.run(`INSERT INTO logs (
|
|
1530
361
|
id, timestamp, level, symbol, symbol_type, message, data_json,
|
|
1531
362
|
service, session_id, correlation_id, duration_ms, environment
|
|
1532
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1533
|
-
[
|
|
1534
|
-
id,
|
|
1535
|
-
timestamp,
|
|
1536
|
-
input.level,
|
|
1537
|
-
input.symbol,
|
|
1538
|
-
symbolType,
|
|
1539
|
-
input.message,
|
|
1540
|
-
input.data ? JSON.stringify(input.data) : null,
|
|
1541
|
-
input.service,
|
|
1542
|
-
input.sessionId || null,
|
|
1543
|
-
input.correlationId || null,
|
|
1544
|
-
input.durationMs ?? null,
|
|
1545
|
-
input.environment || null
|
|
1546
|
-
]
|
|
1547
|
-
);
|
|
1548
|
-
accepted++;
|
|
1549
|
-
} catch (err) {
|
|
1550
|
-
errors.push(err instanceof Error ? err.message : String(err));
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
this.save();
|
|
1554
|
-
return { accepted, errors };
|
|
1555
|
-
}
|
|
1556
|
-
queryLogs(options = {}) {
|
|
1557
|
-
this.initializeSync();
|
|
1558
|
-
const { limit = 100, offset = 0 } = options;
|
|
1559
|
-
const conditions = [];
|
|
1560
|
-
const params = [];
|
|
1561
|
-
if (options.level) {
|
|
1562
|
-
conditions.push("level = ?");
|
|
1563
|
-
params.push(options.level);
|
|
1564
|
-
}
|
|
1565
|
-
if (options.symbol) {
|
|
1566
|
-
conditions.push("symbol LIKE ?");
|
|
1567
|
-
params.push(`%${options.symbol}%`);
|
|
1568
|
-
}
|
|
1569
|
-
if (options.service) {
|
|
1570
|
-
conditions.push("service = ?");
|
|
1571
|
-
params.push(options.service);
|
|
1572
|
-
}
|
|
1573
|
-
if (options.sessionId) {
|
|
1574
|
-
conditions.push("session_id = ?");
|
|
1575
|
-
params.push(options.sessionId);
|
|
1576
|
-
}
|
|
1577
|
-
if (options.correlationId) {
|
|
1578
|
-
conditions.push("correlation_id = ?");
|
|
1579
|
-
params.push(options.correlationId);
|
|
1580
|
-
}
|
|
1581
|
-
if (options.search) {
|
|
1582
|
-
conditions.push("message LIKE ?");
|
|
1583
|
-
params.push(`%${options.search}%`);
|
|
1584
|
-
}
|
|
1585
|
-
if (options.since) {
|
|
1586
|
-
conditions.push("timestamp >= ?");
|
|
1587
|
-
params.push(options.since);
|
|
1588
|
-
}
|
|
1589
|
-
if (options.until) {
|
|
1590
|
-
conditions.push("timestamp <= ?");
|
|
1591
|
-
params.push(options.until);
|
|
1592
|
-
}
|
|
1593
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1594
|
-
const result = this.db.exec(
|
|
1595
|
-
`SELECT * FROM logs ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
|
|
1596
|
-
[...params, limit, offset]
|
|
1597
|
-
);
|
|
1598
|
-
if (result.length === 0) return [];
|
|
1599
|
-
return result[0].values.map(
|
|
1600
|
-
(row) => this.rowToLogEntry(result[0].columns, row)
|
|
1601
|
-
);
|
|
1602
|
-
}
|
|
1603
|
-
getLogCount(options = {}) {
|
|
1604
|
-
this.initializeSync();
|
|
1605
|
-
const conditions = [];
|
|
1606
|
-
const params = [];
|
|
1607
|
-
if (options.level) {
|
|
1608
|
-
conditions.push("level = ?");
|
|
1609
|
-
params.push(options.level);
|
|
1610
|
-
}
|
|
1611
|
-
if (options.symbol) {
|
|
1612
|
-
conditions.push("symbol LIKE ?");
|
|
1613
|
-
params.push(`%${options.symbol}%`);
|
|
1614
|
-
}
|
|
1615
|
-
if (options.service) {
|
|
1616
|
-
conditions.push("service = ?");
|
|
1617
|
-
params.push(options.service);
|
|
1618
|
-
}
|
|
1619
|
-
if (options.since) {
|
|
1620
|
-
conditions.push("timestamp >= ?");
|
|
1621
|
-
params.push(options.since);
|
|
1622
|
-
}
|
|
1623
|
-
if (options.until) {
|
|
1624
|
-
conditions.push("timestamp <= ?");
|
|
1625
|
-
params.push(options.until);
|
|
1626
|
-
}
|
|
1627
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1628
|
-
const result = this.db.exec(
|
|
1629
|
-
`SELECT COUNT(*) as count FROM logs ${whereClause}`,
|
|
1630
|
-
params
|
|
1631
|
-
);
|
|
1632
|
-
if (result.length === 0 || result[0].values.length === 0) return 0;
|
|
1633
|
-
return result[0].values[0][0];
|
|
1634
|
-
}
|
|
1635
|
-
pruneLogs(maxCount) {
|
|
1636
|
-
this.initializeSync();
|
|
1637
|
-
if (maxCount <= 0) return 0;
|
|
1638
|
-
const currentCount = this.getLogCount();
|
|
1639
|
-
if (currentCount <= maxCount) return 0;
|
|
1640
|
-
const deleteCount = currentCount - maxCount;
|
|
1641
|
-
this.db.run(
|
|
1642
|
-
`DELETE FROM logs WHERE id IN (
|
|
363
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[i,r,n.level,n.symbol,a,n.message,n.data?JSON.stringify(n.data):null,n.service,n.sessionId||null,n.correlationId||null,n.durationMs??null,n.environment||null]),t++;}catch(i){s.push(i instanceof Error?i.message:String(i));}return this.save(),{accepted:t,errors:s}}queryLogs(e={}){this.initializeSync();let{limit:t=100,offset:s=0}=e,n=[],i=[];e.level&&(n.push("level = ?"),i.push(e.level)),e.symbol&&(n.push("symbol LIKE ?"),i.push(`%${e.symbol}%`)),e.service&&(n.push("service = ?"),i.push(e.service)),e.sessionId&&(n.push("session_id = ?"),i.push(e.sessionId)),e.correlationId&&(n.push("correlation_id = ?"),i.push(e.correlationId)),e.search&&(n.push("message LIKE ?"),i.push(`%${e.search}%`)),e.since&&(n.push("timestamp >= ?"),i.push(e.since)),e.until&&(n.push("timestamp <= ?"),i.push(e.until));let r=n.length>0?`WHERE ${n.join(" AND ")}`:"",a=this.db.exec(`SELECT * FROM logs ${r} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,[...i,t,s]);return a.length===0?[]:a[0].values.map(o=>this.rowToLogEntry(a[0].columns,o))}getLogCount(e={}){this.initializeSync();let t=[],s=[];e.level&&(t.push("level = ?"),s.push(e.level)),e.symbol&&(t.push("symbol LIKE ?"),s.push(`%${e.symbol}%`)),e.service&&(t.push("service = ?"),s.push(e.service)),e.since&&(t.push("timestamp >= ?"),s.push(e.since)),e.until&&(t.push("timestamp <= ?"),s.push(e.until));let n=t.length>0?`WHERE ${t.join(" AND ")}`:"",i=this.db.exec(`SELECT COUNT(*) as count FROM logs ${n}`,s);return i.length===0||i[0].values.length===0?0:i[0].values[0][0]}pruneLogs(e){if(this.initializeSync(),e<=0)return 0;let t=this.getLogCount();if(t<=e)return 0;let s=t-e;return this.db.run(`DELETE FROM logs WHERE id IN (
|
|
1643
364
|
SELECT id FROM logs ORDER BY timestamp ASC LIMIT ?
|
|
1644
|
-
)`,
|
|
1645
|
-
[deleteCount]
|
|
1646
|
-
);
|
|
1647
|
-
this.save();
|
|
1648
|
-
return deleteCount;
|
|
1649
|
-
}
|
|
1650
|
-
rowToLogEntry(columns, row) {
|
|
1651
|
-
const obj = {};
|
|
1652
|
-
columns.forEach((col, i) => {
|
|
1653
|
-
obj[col] = row[i];
|
|
1654
|
-
});
|
|
1655
|
-
return {
|
|
1656
|
-
id: obj.id,
|
|
1657
|
-
timestamp: obj.timestamp,
|
|
1658
|
-
level: obj.level,
|
|
1659
|
-
symbol: obj.symbol,
|
|
1660
|
-
symbolType: obj.symbol_type || "raw",
|
|
1661
|
-
message: obj.message,
|
|
1662
|
-
data: obj.data_json ? JSON.parse(obj.data_json) : void 0,
|
|
1663
|
-
service: obj.service,
|
|
1664
|
-
sessionId: obj.session_id || void 0,
|
|
1665
|
-
correlationId: obj.correlation_id || void 0,
|
|
1666
|
-
durationMs: obj.duration_ms || void 0,
|
|
1667
|
-
environment: obj.environment || void 0
|
|
1668
|
-
};
|
|
1669
|
-
}
|
|
1670
|
-
// ─── Service Registry ──────────────────────────────────────────
|
|
1671
|
-
registerService(reg) {
|
|
1672
|
-
this.initializeSync();
|
|
1673
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1674
|
-
this.db.run(
|
|
1675
|
-
`INSERT INTO services (name, version, pid, started_at, last_seen_at, environment, metadata_json)
|
|
365
|
+
)`,[s]),this.save(),s}rowToLogEntry(e,t){let s={};return e.forEach((n,i)=>{s[n]=t[i];}),{id:s.id,timestamp:s.timestamp,level:s.level,symbol:s.symbol,symbolType:s.symbol_type||"raw",message:s.message,data:s.data_json?JSON.parse(s.data_json):void 0,service:s.service,sessionId:s.session_id||void 0,correlationId:s.correlation_id||void 0,durationMs:s.duration_ms||void 0,environment:s.environment||void 0}}registerService(e){this.initializeSync();let t=new Date().toISOString();this.db.run(`INSERT INTO services (name, version, pid, started_at, last_seen_at, environment, metadata_json)
|
|
1676
366
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1677
367
|
ON CONFLICT(name) DO UPDATE SET
|
|
1678
368
|
version = excluded.version,
|
|
1679
369
|
pid = excluded.pid,
|
|
1680
370
|
last_seen_at = excluded.last_seen_at,
|
|
1681
371
|
environment = excluded.environment,
|
|
1682
|
-
metadata_json = excluded.metadata_json`,
|
|
1683
|
-
[
|
|
1684
|
-
reg.name,
|
|
1685
|
-
reg.version || null,
|
|
1686
|
-
reg.pid ?? null,
|
|
1687
|
-
now,
|
|
1688
|
-
now,
|
|
1689
|
-
reg.environment || null,
|
|
1690
|
-
reg.metadata ? JSON.stringify(reg.metadata) : null
|
|
1691
|
-
]
|
|
1692
|
-
);
|
|
1693
|
-
this.save();
|
|
1694
|
-
}
|
|
1695
|
-
updateServiceLastSeen(name) {
|
|
1696
|
-
this.initializeSync();
|
|
1697
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1698
|
-
this.db.run(
|
|
1699
|
-
"UPDATE services SET last_seen_at = ? WHERE name = ?",
|
|
1700
|
-
[now, name]
|
|
1701
|
-
);
|
|
1702
|
-
this.save();
|
|
1703
|
-
}
|
|
1704
|
-
getServices() {
|
|
1705
|
-
this.initializeSync();
|
|
1706
|
-
const result = this.db.exec(
|
|
1707
|
-
"SELECT * FROM services ORDER BY last_seen_at DESC"
|
|
1708
|
-
);
|
|
1709
|
-
if (result.length === 0) return [];
|
|
1710
|
-
return result[0].values.map((row) => {
|
|
1711
|
-
const obj = {};
|
|
1712
|
-
result[0].columns.forEach((col, i) => {
|
|
1713
|
-
obj[col] = row[i];
|
|
1714
|
-
});
|
|
1715
|
-
return {
|
|
1716
|
-
name: obj.name,
|
|
1717
|
-
version: obj.version || void 0,
|
|
1718
|
-
pid: obj.pid || void 0,
|
|
1719
|
-
startedAt: obj.started_at,
|
|
1720
|
-
lastSeenAt: obj.last_seen_at,
|
|
1721
|
-
environment: obj.environment || void 0,
|
|
1722
|
-
metadata: obj.metadata_json ? JSON.parse(obj.metadata_json) : void 0
|
|
1723
|
-
};
|
|
1724
|
-
});
|
|
1725
|
-
}
|
|
1726
|
-
// ─── App State ──────────────────────────────────────────────────
|
|
1727
|
-
upsertAppState(state) {
|
|
1728
|
-
this.initializeSync();
|
|
1729
|
-
this.db.run(
|
|
1730
|
-
`INSERT INTO app_state (service, session_id, timestamp, state_json, active_flows_json, active_gates_json)
|
|
372
|
+
metadata_json = excluded.metadata_json`,[e.name,e.version||null,e.pid??null,t,t,e.environment||null,e.metadata?JSON.stringify(e.metadata):null]),this.save();}updateServiceLastSeen(e){this.initializeSync();let t=new Date().toISOString();this.db.run("UPDATE services SET last_seen_at = ? WHERE name = ?",[t,e]),this.save();}getServices(){this.initializeSync();let e=this.db.exec("SELECT * FROM services ORDER BY last_seen_at DESC");return e.length===0?[]:e[0].values.map(t=>{let s={};return e[0].columns.forEach((n,i)=>{s[n]=t[i];}),{name:s.name,version:s.version||void 0,pid:s.pid||void 0,startedAt:s.started_at,lastSeenAt:s.last_seen_at,environment:s.environment||void 0,metadata:s.metadata_json?JSON.parse(s.metadata_json):void 0}})}upsertAppState(e){this.initializeSync(),this.db.run(`INSERT INTO app_state (service, session_id, timestamp, state_json, active_flows_json, active_gates_json)
|
|
1731
373
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
1732
374
|
ON CONFLICT(service, session_id) DO UPDATE SET
|
|
1733
375
|
timestamp = excluded.timestamp,
|
|
1734
376
|
state_json = excluded.state_json,
|
|
1735
377
|
active_flows_json = excluded.active_flows_json,
|
|
1736
|
-
active_gates_json = excluded.active_gates_json`,
|
|
1737
|
-
[
|
|
1738
|
-
state.service,
|
|
1739
|
-
state.sessionId,
|
|
1740
|
-
state.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1741
|
-
JSON.stringify(state.state),
|
|
1742
|
-
state.activeFlows ? JSON.stringify(state.activeFlows) : null,
|
|
1743
|
-
state.activeGates ? JSON.stringify(state.activeGates) : null
|
|
1744
|
-
]
|
|
1745
|
-
);
|
|
1746
|
-
this.save();
|
|
1747
|
-
}
|
|
1748
|
-
getAppState(service, sessionId) {
|
|
1749
|
-
this.initializeSync();
|
|
1750
|
-
let query = "SELECT * FROM app_state WHERE service = ?";
|
|
1751
|
-
const params = [service];
|
|
1752
|
-
if (sessionId) {
|
|
1753
|
-
query += " AND session_id = ?";
|
|
1754
|
-
params.push(sessionId);
|
|
1755
|
-
}
|
|
1756
|
-
query += " ORDER BY timestamp DESC";
|
|
1757
|
-
const result = this.db.exec(query, params);
|
|
1758
|
-
if (result.length === 0) return [];
|
|
1759
|
-
return result[0].values.map((row) => this.rowToAppState(result[0].columns, row));
|
|
1760
|
-
}
|
|
1761
|
-
getAllAppStates() {
|
|
1762
|
-
this.initializeSync();
|
|
1763
|
-
const result = this.db.exec(
|
|
1764
|
-
"SELECT * FROM app_state ORDER BY timestamp DESC"
|
|
1765
|
-
);
|
|
1766
|
-
if (result.length === 0) return [];
|
|
1767
|
-
return result[0].values.map((row) => this.rowToAppState(result[0].columns, row));
|
|
1768
|
-
}
|
|
1769
|
-
rowToAppState(columns, row) {
|
|
1770
|
-
const obj = {};
|
|
1771
|
-
columns.forEach((col, i) => {
|
|
1772
|
-
obj[col] = row[i];
|
|
1773
|
-
});
|
|
1774
|
-
return {
|
|
1775
|
-
service: obj.service,
|
|
1776
|
-
sessionId: obj.session_id,
|
|
1777
|
-
timestamp: obj.timestamp,
|
|
1778
|
-
state: JSON.parse(obj.state_json),
|
|
1779
|
-
activeFlows: obj.active_flows_json ? JSON.parse(obj.active_flows_json) : void 0,
|
|
1780
|
-
activeGates: obj.active_gates_json ? JSON.parse(obj.active_gates_json) : void 0
|
|
1781
|
-
};
|
|
1782
|
-
}
|
|
1783
|
-
// ─── Metrics ───────────────────────────────────────────────────
|
|
1784
|
-
insertMetric(input) {
|
|
1785
|
-
this.initializeSync();
|
|
1786
|
-
const id = uuidv4();
|
|
1787
|
-
const timestamp = input.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
1788
|
-
this.db.run(
|
|
1789
|
-
`INSERT INTO metrics (
|
|
378
|
+
active_gates_json = excluded.active_gates_json`,[e.service,e.sessionId,e.timestamp||new Date().toISOString(),JSON.stringify(e.state),e.activeFlows?JSON.stringify(e.activeFlows):null,e.activeGates?JSON.stringify(e.activeGates):null]),this.save();}getAppState(e,t){this.initializeSync();let s="SELECT * FROM app_state WHERE service = ?",n=[e];t&&(s+=" AND session_id = ?",n.push(t)),s+=" ORDER BY timestamp DESC";let i=this.db.exec(s,n);return i.length===0?[]:i[0].values.map(r=>this.rowToAppState(i[0].columns,r))}getAllAppStates(){this.initializeSync();let e=this.db.exec("SELECT * FROM app_state ORDER BY timestamp DESC");return e.length===0?[]:e[0].values.map(t=>this.rowToAppState(e[0].columns,t))}rowToAppState(e,t){let s={};return e.forEach((n,i)=>{s[n]=t[i];}),{service:s.service,sessionId:s.session_id,timestamp:s.timestamp,state:JSON.parse(s.state_json),activeFlows:s.active_flows_json?JSON.parse(s.active_flows_json):void 0,activeGates:s.active_gates_json?JSON.parse(s.active_gates_json):void 0}}insertMetric(e){this.initializeSync();let t=v4(),s=e.timestamp||new Date().toISOString();return this.db.run(`INSERT INTO metrics (
|
|
1790
379
|
id, timestamp, name, type, value, tags_json, service, environment
|
|
1791
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1792
|
-
[
|
|
1793
|
-
id,
|
|
1794
|
-
timestamp,
|
|
1795
|
-
input.name,
|
|
1796
|
-
input.type,
|
|
1797
|
-
input.value,
|
|
1798
|
-
JSON.stringify(input.tags || {}),
|
|
1799
|
-
input.service,
|
|
1800
|
-
input.environment || null
|
|
1801
|
-
]
|
|
1802
|
-
);
|
|
1803
|
-
this.save();
|
|
1804
|
-
return id;
|
|
1805
|
-
}
|
|
1806
|
-
insertMetricBatch(entries) {
|
|
1807
|
-
this.initializeSync();
|
|
1808
|
-
let accepted = 0;
|
|
1809
|
-
const errors = [];
|
|
1810
|
-
for (const input of entries) {
|
|
1811
|
-
try {
|
|
1812
|
-
const id = uuidv4();
|
|
1813
|
-
const timestamp = input.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
1814
|
-
this.db.run(
|
|
1815
|
-
`INSERT INTO metrics (
|
|
380
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,[t,s,e.name,e.type,e.value,JSON.stringify(e.tags||{}),e.service,e.environment||null]),this.save(),t}insertMetricBatch(e){this.initializeSync();let t=0,s=[];for(let n of e)try{let i=v4(),r=n.timestamp||new Date().toISOString();this.db.run(`INSERT INTO metrics (
|
|
1816
381
|
id, timestamp, name, type, value, tags_json, service, environment
|
|
1817
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1818
|
-
|
|
1819
|
-
id,
|
|
1820
|
-
timestamp,
|
|
1821
|
-
input.name,
|
|
1822
|
-
input.type,
|
|
1823
|
-
input.value,
|
|
1824
|
-
JSON.stringify(input.tags || {}),
|
|
1825
|
-
input.service,
|
|
1826
|
-
input.environment || null
|
|
1827
|
-
]
|
|
1828
|
-
);
|
|
1829
|
-
accepted++;
|
|
1830
|
-
} catch (err) {
|
|
1831
|
-
errors.push(err instanceof Error ? err.message : String(err));
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
this.save();
|
|
1835
|
-
return { accepted, errors };
|
|
1836
|
-
}
|
|
1837
|
-
queryMetrics(options = {}) {
|
|
1838
|
-
this.initializeSync();
|
|
1839
|
-
const { limit = 100, offset = 0 } = options;
|
|
1840
|
-
const conditions = [];
|
|
1841
|
-
const params = [];
|
|
1842
|
-
if (options.name) {
|
|
1843
|
-
conditions.push("name = ?");
|
|
1844
|
-
params.push(options.name);
|
|
1845
|
-
}
|
|
1846
|
-
if (options.type) {
|
|
1847
|
-
conditions.push("type = ?");
|
|
1848
|
-
params.push(options.type);
|
|
1849
|
-
}
|
|
1850
|
-
if (options.service) {
|
|
1851
|
-
conditions.push("service = ?");
|
|
1852
|
-
params.push(options.service);
|
|
1853
|
-
}
|
|
1854
|
-
if (options.tag) {
|
|
1855
|
-
const eqIdx = options.tag.indexOf("=");
|
|
1856
|
-
if (eqIdx > 0) {
|
|
1857
|
-
const tagKey = options.tag.substring(0, eqIdx);
|
|
1858
|
-
const tagValue = options.tag.substring(eqIdx + 1);
|
|
1859
|
-
conditions.push("tags_json LIKE ?");
|
|
1860
|
-
params.push(`%"${tagKey}":"${tagValue}"%`);
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
if (options.since) {
|
|
1864
|
-
conditions.push("timestamp >= ?");
|
|
1865
|
-
params.push(options.since);
|
|
1866
|
-
}
|
|
1867
|
-
if (options.until) {
|
|
1868
|
-
conditions.push("timestamp <= ?");
|
|
1869
|
-
params.push(options.until);
|
|
1870
|
-
}
|
|
1871
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1872
|
-
const result = this.db.exec(
|
|
1873
|
-
`SELECT * FROM metrics ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
|
|
1874
|
-
[...params, limit, offset]
|
|
1875
|
-
);
|
|
1876
|
-
if (result.length === 0) return [];
|
|
1877
|
-
return result[0].values.map(
|
|
1878
|
-
(row) => this.rowToMetricEntry(result[0].columns, row)
|
|
1879
|
-
);
|
|
1880
|
-
}
|
|
1881
|
-
getMetricCount(options = {}) {
|
|
1882
|
-
this.initializeSync();
|
|
1883
|
-
const conditions = [];
|
|
1884
|
-
const params = [];
|
|
1885
|
-
if (options.name) {
|
|
1886
|
-
conditions.push("name = ?");
|
|
1887
|
-
params.push(options.name);
|
|
1888
|
-
}
|
|
1889
|
-
if (options.type) {
|
|
1890
|
-
conditions.push("type = ?");
|
|
1891
|
-
params.push(options.type);
|
|
1892
|
-
}
|
|
1893
|
-
if (options.service) {
|
|
1894
|
-
conditions.push("service = ?");
|
|
1895
|
-
params.push(options.service);
|
|
1896
|
-
}
|
|
1897
|
-
if (options.tag) {
|
|
1898
|
-
const eqIdx = options.tag.indexOf("=");
|
|
1899
|
-
if (eqIdx > 0) {
|
|
1900
|
-
const tagKey = options.tag.substring(0, eqIdx);
|
|
1901
|
-
const tagValue = options.tag.substring(eqIdx + 1);
|
|
1902
|
-
conditions.push("tags_json LIKE ?");
|
|
1903
|
-
params.push(`%"${tagKey}":"${tagValue}"%`);
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
if (options.since) {
|
|
1907
|
-
conditions.push("timestamp >= ?");
|
|
1908
|
-
params.push(options.since);
|
|
1909
|
-
}
|
|
1910
|
-
if (options.until) {
|
|
1911
|
-
conditions.push("timestamp <= ?");
|
|
1912
|
-
params.push(options.until);
|
|
1913
|
-
}
|
|
1914
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1915
|
-
const result = this.db.exec(
|
|
1916
|
-
`SELECT COUNT(*) as count FROM metrics ${whereClause}`,
|
|
1917
|
-
params
|
|
1918
|
-
);
|
|
1919
|
-
if (result.length === 0 || result[0].values.length === 0) return 0;
|
|
1920
|
-
return result[0].values[0][0];
|
|
1921
|
-
}
|
|
1922
|
-
aggregateMetric(name, options) {
|
|
1923
|
-
this.initializeSync();
|
|
1924
|
-
const conditions = ["name = ?"];
|
|
1925
|
-
const params = [name];
|
|
1926
|
-
if (options?.service) {
|
|
1927
|
-
conditions.push("service = ?");
|
|
1928
|
-
params.push(options.service);
|
|
1929
|
-
}
|
|
1930
|
-
if (options?.since) {
|
|
1931
|
-
conditions.push("timestamp >= ?");
|
|
1932
|
-
params.push(options.since);
|
|
1933
|
-
}
|
|
1934
|
-
if (options?.until) {
|
|
1935
|
-
conditions.push("timestamp <= ?");
|
|
1936
|
-
params.push(options.until);
|
|
1937
|
-
}
|
|
1938
|
-
const whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
1939
|
-
const result = this.db.exec(
|
|
1940
|
-
`SELECT COUNT(*) as count, SUM(value) as sum, MIN(value) as min, MAX(value) as max, AVG(value) as avg
|
|
1941
|
-
FROM metrics ${whereClause}`,
|
|
1942
|
-
params
|
|
1943
|
-
);
|
|
1944
|
-
if (result.length === 0 || result[0].values.length === 0) {
|
|
1945
|
-
return { name, count: 0, sum: 0, min: 0, max: 0, avg: 0 };
|
|
1946
|
-
}
|
|
1947
|
-
const row = result[0].values[0];
|
|
1948
|
-
return {
|
|
1949
|
-
name,
|
|
1950
|
-
count: row[0] || 0,
|
|
1951
|
-
sum: row[1] || 0,
|
|
1952
|
-
min: row[2] || 0,
|
|
1953
|
-
max: row[3] || 0,
|
|
1954
|
-
avg: row[4] || 0
|
|
1955
|
-
};
|
|
1956
|
-
}
|
|
1957
|
-
pruneMetrics(maxCount) {
|
|
1958
|
-
this.initializeSync();
|
|
1959
|
-
if (maxCount <= 0) return 0;
|
|
1960
|
-
const currentCount = this.getMetricCount();
|
|
1961
|
-
if (currentCount <= maxCount) return 0;
|
|
1962
|
-
const deleteCount = currentCount - maxCount;
|
|
1963
|
-
this.db.run(
|
|
1964
|
-
`DELETE FROM metrics WHERE id IN (
|
|
382
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,[i,r,n.name,n.type,n.value,JSON.stringify(n.tags||{}),n.service,n.environment||null]),t++;}catch(i){s.push(i instanceof Error?i.message:String(i));}return this.save(),{accepted:t,errors:s}}queryMetrics(e={}){this.initializeSync();let{limit:t=100,offset:s=0}=e,n=[],i=[];if(e.name&&(n.push("name = ?"),i.push(e.name)),e.type&&(n.push("type = ?"),i.push(e.type)),e.service&&(n.push("service = ?"),i.push(e.service)),e.tag){let o=e.tag.indexOf("=");if(o>0){let c=e.tag.substring(0,o),l=e.tag.substring(o+1);n.push("tags_json LIKE ?"),i.push(`%"${c}":"${l}"%`);}}e.since&&(n.push("timestamp >= ?"),i.push(e.since)),e.until&&(n.push("timestamp <= ?"),i.push(e.until));let r=n.length>0?`WHERE ${n.join(" AND ")}`:"",a=this.db.exec(`SELECT * FROM metrics ${r} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,[...i,t,s]);return a.length===0?[]:a[0].values.map(o=>this.rowToMetricEntry(a[0].columns,o))}getMetricCount(e={}){this.initializeSync();let t=[],s=[];if(e.name&&(t.push("name = ?"),s.push(e.name)),e.type&&(t.push("type = ?"),s.push(e.type)),e.service&&(t.push("service = ?"),s.push(e.service)),e.tag){let r=e.tag.indexOf("=");if(r>0){let a=e.tag.substring(0,r),o=e.tag.substring(r+1);t.push("tags_json LIKE ?"),s.push(`%"${a}":"${o}"%`);}}e.since&&(t.push("timestamp >= ?"),s.push(e.since)),e.until&&(t.push("timestamp <= ?"),s.push(e.until));let n=t.length>0?`WHERE ${t.join(" AND ")}`:"",i=this.db.exec(`SELECT COUNT(*) as count FROM metrics ${n}`,s);return i.length===0||i[0].values.length===0?0:i[0].values[0][0]}aggregateMetric(e,t){this.initializeSync();let s=["name = ?"],n=[e];t?.service&&(s.push("service = ?"),n.push(t.service)),t?.since&&(s.push("timestamp >= ?"),n.push(t.since)),t?.until&&(s.push("timestamp <= ?"),n.push(t.until));let i=`WHERE ${s.join(" AND ")}`,r=this.db.exec(`SELECT COUNT(*) as count, SUM(value) as sum, MIN(value) as min, MAX(value) as max, AVG(value) as avg
|
|
383
|
+
FROM metrics ${i}`,n);if(r.length===0||r[0].values.length===0)return {name:e,count:0,sum:0,min:0,max:0,avg:0};let a=r[0].values[0];return {name:e,count:a[0]||0,sum:a[1]||0,min:a[2]||0,max:a[3]||0,avg:a[4]||0}}pruneMetrics(e){if(this.initializeSync(),e<=0)return 0;let t=this.getMetricCount();if(t<=e)return 0;let s=t-e;return this.db.run(`DELETE FROM metrics WHERE id IN (
|
|
1965
384
|
SELECT id FROM metrics ORDER BY timestamp ASC LIMIT ?
|
|
1966
|
-
)`,
|
|
1967
|
-
[deleteCount]
|
|
1968
|
-
);
|
|
1969
|
-
this.save();
|
|
1970
|
-
return deleteCount;
|
|
1971
|
-
}
|
|
1972
|
-
rowToMetricEntry(columns, row) {
|
|
1973
|
-
const obj = {};
|
|
1974
|
-
columns.forEach((col, i) => {
|
|
1975
|
-
obj[col] = row[i];
|
|
1976
|
-
});
|
|
1977
|
-
return {
|
|
1978
|
-
id: obj.id,
|
|
1979
|
-
timestamp: obj.timestamp,
|
|
1980
|
-
name: obj.name,
|
|
1981
|
-
type: obj.type,
|
|
1982
|
-
value: obj.value,
|
|
1983
|
-
tags: obj.tags_json ? JSON.parse(obj.tags_json) : {},
|
|
1984
|
-
service: obj.service,
|
|
1985
|
-
environment: obj.environment || void 0
|
|
1986
|
-
};
|
|
1987
|
-
}
|
|
1988
|
-
// ─── Traces ───────────────────────────────────────────────────
|
|
1989
|
-
insertSpan(input) {
|
|
1990
|
-
this.initializeSync();
|
|
1991
|
-
const spanId = input.spanId || uuidv4();
|
|
1992
|
-
const startTime = input.startTime || (/* @__PURE__ */ new Date()).toISOString();
|
|
1993
|
-
this.db.run(
|
|
1994
|
-
`INSERT INTO traces (
|
|
385
|
+
)`,[s]),this.save(),s}rowToMetricEntry(e,t){let s={};return e.forEach((n,i)=>{s[n]=t[i];}),{id:s.id,timestamp:s.timestamp,name:s.name,type:s.type,value:s.value,tags:s.tags_json?JSON.parse(s.tags_json):{},service:s.service,environment:s.environment||void 0}}insertSpan(e){this.initializeSync();let t=e.spanId||v4(),s=e.startTime||new Date().toISOString();return this.db.run(`INSERT INTO traces (
|
|
1995
386
|
trace_id, span_id, parent_span_id, service, symbol, operation,
|
|
1996
387
|
start_time, end_time, duration_ms, status, tags_json, log_ids_json
|
|
1997
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1998
|
-
[
|
|
1999
|
-
input.traceId,
|
|
2000
|
-
spanId,
|
|
2001
|
-
input.parentSpanId || null,
|
|
2002
|
-
input.service,
|
|
2003
|
-
input.symbol,
|
|
2004
|
-
input.operation,
|
|
2005
|
-
startTime,
|
|
2006
|
-
input.endTime || null,
|
|
2007
|
-
input.durationMs ?? null,
|
|
2008
|
-
input.status || "ok",
|
|
2009
|
-
JSON.stringify(input.tags || {}),
|
|
2010
|
-
JSON.stringify(input.logIds || [])
|
|
2011
|
-
]
|
|
2012
|
-
);
|
|
2013
|
-
this.save();
|
|
2014
|
-
return spanId;
|
|
2015
|
-
}
|
|
2016
|
-
getTrace(traceId) {
|
|
2017
|
-
this.initializeSync();
|
|
2018
|
-
const result = this.db.exec(
|
|
2019
|
-
"SELECT * FROM traces WHERE trace_id = ? ORDER BY start_time ASC",
|
|
2020
|
-
[traceId]
|
|
2021
|
-
);
|
|
2022
|
-
if (result.length === 0 || result[0].values.length === 0) return null;
|
|
2023
|
-
const spans = result[0].values.map(
|
|
2024
|
-
(row) => this.rowToTraceSpan(result[0].columns, row)
|
|
2025
|
-
);
|
|
2026
|
-
const services = [...new Set(spans.map((s) => s.service))];
|
|
2027
|
-
const startTimes = spans.map((s) => s.startTime);
|
|
2028
|
-
const endTimes = spans.filter((s) => s.endTime).map((s) => s.endTime);
|
|
2029
|
-
const startTime = startTimes.sort()[0];
|
|
2030
|
-
const endTime = endTimes.length > 0 ? endTimes.sort().reverse()[0] : startTime;
|
|
2031
|
-
const startMs = new Date(startTime).getTime();
|
|
2032
|
-
const endMs = new Date(endTime).getTime();
|
|
2033
|
-
const totalDurationMs = endMs - startMs;
|
|
2034
|
-
return {
|
|
2035
|
-
traceId,
|
|
2036
|
-
spans,
|
|
2037
|
-
services,
|
|
2038
|
-
totalDurationMs: totalDurationMs > 0 ? totalDurationMs : 0,
|
|
2039
|
-
startTime,
|
|
2040
|
-
endTime
|
|
2041
|
-
};
|
|
2042
|
-
}
|
|
2043
|
-
queryTraces(options = {}) {
|
|
2044
|
-
this.initializeSync();
|
|
2045
|
-
const conditions = [];
|
|
2046
|
-
const params = [];
|
|
2047
|
-
if (options.service) {
|
|
2048
|
-
conditions.push("service = ?");
|
|
2049
|
-
params.push(options.service);
|
|
2050
|
-
}
|
|
2051
|
-
if (options.symbol) {
|
|
2052
|
-
conditions.push("symbol = ?");
|
|
2053
|
-
params.push(options.symbol);
|
|
2054
|
-
}
|
|
2055
|
-
if (options.since) {
|
|
2056
|
-
conditions.push("start_time >= ?");
|
|
2057
|
-
params.push(options.since);
|
|
2058
|
-
}
|
|
2059
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2060
|
-
const traceLimit = Math.min(options.limit || 20, 20);
|
|
2061
|
-
const result = this.db.exec(
|
|
2062
|
-
`SELECT DISTINCT trace_id FROM traces ${whereClause} ORDER BY start_time DESC LIMIT ?`,
|
|
2063
|
-
[...params, traceLimit]
|
|
2064
|
-
);
|
|
2065
|
-
if (result.length === 0) return [];
|
|
2066
|
-
const traces = [];
|
|
2067
|
-
for (const row of result[0].values) {
|
|
2068
|
-
const traceId = row[0];
|
|
2069
|
-
const trace = this.getTrace(traceId);
|
|
2070
|
-
if (trace) {
|
|
2071
|
-
traces.push(trace);
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
return traces;
|
|
2075
|
-
}
|
|
2076
|
-
rowToTraceSpan(columns, row) {
|
|
2077
|
-
const obj = {};
|
|
2078
|
-
columns.forEach((col, i) => {
|
|
2079
|
-
obj[col] = row[i];
|
|
2080
|
-
});
|
|
2081
|
-
return {
|
|
2082
|
-
traceId: obj.trace_id,
|
|
2083
|
-
spanId: obj.span_id,
|
|
2084
|
-
parentSpanId: obj.parent_span_id || void 0,
|
|
2085
|
-
service: obj.service,
|
|
2086
|
-
symbol: obj.symbol,
|
|
2087
|
-
operation: obj.operation,
|
|
2088
|
-
startTime: obj.start_time,
|
|
2089
|
-
endTime: obj.end_time || void 0,
|
|
2090
|
-
durationMs: obj.duration_ms || void 0,
|
|
2091
|
-
status: obj.status || "ok",
|
|
2092
|
-
tags: obj.tags_json ? JSON.parse(obj.tags_json) : {},
|
|
2093
|
-
logs: obj.log_ids_json ? JSON.parse(obj.log_ids_json) : []
|
|
2094
|
-
};
|
|
2095
|
-
}
|
|
2096
|
-
// ─── Schema Registry ─────────────────────────────────────────────
|
|
2097
|
-
registerSchema(schema) {
|
|
2098
|
-
this.initializeSync();
|
|
2099
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2100
|
-
this.db.run(
|
|
2101
|
-
`INSERT INTO schemas (
|
|
388
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.traceId,t,e.parentSpanId||null,e.service,e.symbol,e.operation,s,e.endTime||null,e.durationMs??null,e.status||"ok",JSON.stringify(e.tags||{}),JSON.stringify(e.logIds||[])]),this.save(),t}getTrace(e){this.initializeSync();let t=this.db.exec("SELECT * FROM traces WHERE trace_id = ? ORDER BY start_time ASC",[e]);if(t.length===0||t[0].values.length===0)return null;let s=t[0].values.map(m=>this.rowToTraceSpan(t[0].columns,m)),n=[...new Set(s.map(m=>m.service))],i=s.map(m=>m.startTime),r=s.filter(m=>m.endTime).map(m=>m.endTime),a=i.sort()[0],o=r.length>0?r.sort().reverse()[0]:a,c=new Date(a).getTime(),d=new Date(o).getTime()-c;return {traceId:e,spans:s,services:n,totalDurationMs:d>0?d:0,startTime:a,endTime:o}}queryTraces(e={}){this.initializeSync();let t=[],s=[];e.service&&(t.push("service = ?"),s.push(e.service)),e.symbol&&(t.push("symbol = ?"),s.push(e.symbol)),e.since&&(t.push("start_time >= ?"),s.push(e.since));let n=t.length>0?`WHERE ${t.join(" AND ")}`:"",i=Math.min(e.limit||20,20),r=this.db.exec(`SELECT DISTINCT trace_id FROM traces ${n} ORDER BY start_time DESC LIMIT ?`,[...s,i]);if(r.length===0)return [];let a=[];for(let o of r[0].values){let c=o[0],l=this.getTrace(c);l&&a.push(l);}return a}rowToTraceSpan(e,t){let s={};return e.forEach((n,i)=>{s[n]=t[i];}),{traceId:s.trace_id,spanId:s.span_id,parentSpanId:s.parent_span_id||void 0,service:s.service,symbol:s.symbol,operation:s.operation,startTime:s.start_time,endTime:s.end_time||void 0,durationMs:s.duration_ms||void 0,status:s.status||"ok",tags:s.tags_json?JSON.parse(s.tags_json):{},logs:s.log_ids_json?JSON.parse(s.log_ids_json):[]}}registerSchema(e){this.initializeSync();let t=new Date().toISOString();return this.db.run(`INSERT INTO schemas (
|
|
2102
389
|
id, version, name, description, scope_json, event_types_json,
|
|
2103
390
|
causality_json, visualization_json, tags_json, registered_at, updated_at
|
|
2104
391
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -2111,2084 +398,27 @@ var SentinelStorage = class {
|
|
|
2111
398
|
causality_json = excluded.causality_json,
|
|
2112
399
|
visualization_json = excluded.visualization_json,
|
|
2113
400
|
tags_json = excluded.tags_json,
|
|
2114
|
-
updated_at = excluded.updated_at`,
|
|
2115
|
-
[
|
|
2116
|
-
schema.id,
|
|
2117
|
-
schema.version,
|
|
2118
|
-
schema.name,
|
|
2119
|
-
schema.description || null,
|
|
2120
|
-
JSON.stringify(schema.scope),
|
|
2121
|
-
JSON.stringify(schema.eventTypes),
|
|
2122
|
-
schema.causality ? JSON.stringify(schema.causality) : null,
|
|
2123
|
-
schema.visualization ? JSON.stringify(schema.visualization) : null,
|
|
2124
|
-
JSON.stringify(schema.tags || []),
|
|
2125
|
-
now,
|
|
2126
|
-
now
|
|
2127
|
-
]
|
|
2128
|
-
);
|
|
2129
|
-
this.save();
|
|
2130
|
-
return {
|
|
2131
|
-
id: schema.id,
|
|
2132
|
-
version: schema.version,
|
|
2133
|
-
name: schema.name,
|
|
2134
|
-
description: schema.description,
|
|
2135
|
-
scope: schema.scope,
|
|
2136
|
-
eventTypes: schema.eventTypes,
|
|
2137
|
-
causality: schema.causality,
|
|
2138
|
-
visualization: schema.visualization,
|
|
2139
|
-
tags: schema.tags || [],
|
|
2140
|
-
registeredAt: now,
|
|
2141
|
-
updatedAt: now
|
|
2142
|
-
};
|
|
2143
|
-
}
|
|
2144
|
-
getSchema(id) {
|
|
2145
|
-
this.initializeSync();
|
|
2146
|
-
const result = this.db.exec("SELECT * FROM schemas WHERE id = ?", [id]);
|
|
2147
|
-
if (result.length === 0 || result[0].values.length === 0) return null;
|
|
2148
|
-
return this.rowToSchema(result[0].columns, result[0].values[0]);
|
|
2149
|
-
}
|
|
2150
|
-
listSchemas() {
|
|
2151
|
-
this.initializeSync();
|
|
2152
|
-
const result = this.db.exec("SELECT * FROM schemas ORDER BY name ASC");
|
|
2153
|
-
if (result.length === 0) return [];
|
|
2154
|
-
return result[0].values.map(
|
|
2155
|
-
(row) => this.rowToSchema(result[0].columns, row)
|
|
2156
|
-
);
|
|
2157
|
-
}
|
|
2158
|
-
rowToSchema(columns, row) {
|
|
2159
|
-
const obj = {};
|
|
2160
|
-
columns.forEach((col, i) => {
|
|
2161
|
-
obj[col] = row[i];
|
|
2162
|
-
});
|
|
2163
|
-
return {
|
|
2164
|
-
id: obj.id,
|
|
2165
|
-
version: obj.version,
|
|
2166
|
-
name: obj.name,
|
|
2167
|
-
description: obj.description || void 0,
|
|
2168
|
-
scope: JSON.parse(obj.scope_json),
|
|
2169
|
-
eventTypes: JSON.parse(obj.event_types_json),
|
|
2170
|
-
causality: obj.causality_json ? JSON.parse(obj.causality_json) : void 0,
|
|
2171
|
-
visualization: obj.visualization_json ? JSON.parse(obj.visualization_json) : void 0,
|
|
2172
|
-
tags: JSON.parse(obj.tags_json || "[]"),
|
|
2173
|
-
registeredAt: obj.registered_at,
|
|
2174
|
-
updatedAt: obj.updated_at
|
|
2175
|
-
};
|
|
2176
|
-
}
|
|
2177
|
-
// ─── Generic Events ────────────────────────────────────────────
|
|
2178
|
-
insertEventBatch(schemaId, service, inputs) {
|
|
2179
|
-
this.initializeSync();
|
|
2180
|
-
const schema = this.getSchema(schemaId);
|
|
2181
|
-
const typeMap = /* @__PURE__ */ new Map();
|
|
2182
|
-
if (schema) {
|
|
2183
|
-
for (const et of schema.eventTypes) {
|
|
2184
|
-
typeMap.set(et.type, {
|
|
2185
|
-
category: et.category,
|
|
2186
|
-
severity: et.severity || "info"
|
|
2187
|
-
});
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
let accepted = 0;
|
|
2191
|
-
const errors = [];
|
|
2192
|
-
for (const input of inputs) {
|
|
2193
|
-
try {
|
|
2194
|
-
const id = input.id || uuidv4();
|
|
2195
|
-
const timestamp = input.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
2196
|
-
const resolved = typeMap.get(input.type);
|
|
2197
|
-
const category = resolved?.category || "unknown";
|
|
2198
|
-
const severity = input.severity || resolved?.severity || "info";
|
|
2199
|
-
const scopeValue = input.scopeValue != null ? String(input.scopeValue) : null;
|
|
2200
|
-
const scopeOrdinal = typeof input.scopeValue === "number" ? input.scopeValue : null;
|
|
2201
|
-
this.db.run(
|
|
2202
|
-
`INSERT INTO events (
|
|
401
|
+
updated_at = excluded.updated_at`,[e.id,e.version,e.name,e.description||null,JSON.stringify(e.scope),JSON.stringify(e.eventTypes),e.causality?JSON.stringify(e.causality):null,e.visualization?JSON.stringify(e.visualization):null,JSON.stringify(e.tags||[]),t,t]),this.save(),{id:e.id,version:e.version,name:e.name,description:e.description,scope:e.scope,eventTypes:e.eventTypes,causality:e.causality,visualization:e.visualization,tags:e.tags||[],registeredAt:t,updatedAt:t}}getSchema(e){this.initializeSync();let t=this.db.exec("SELECT * FROM schemas WHERE id = ?",[e]);return t.length===0||t[0].values.length===0?null:this.rowToSchema(t[0].columns,t[0].values[0])}listSchemas(){this.initializeSync();let e=this.db.exec("SELECT * FROM schemas ORDER BY name ASC");return e.length===0?[]:e[0].values.map(t=>this.rowToSchema(e[0].columns,t))}rowToSchema(e,t){let s={};return e.forEach((n,i)=>{s[n]=t[i];}),{id:s.id,version:s.version,name:s.name,description:s.description||void 0,scope:JSON.parse(s.scope_json),eventTypes:JSON.parse(s.event_types_json),causality:s.causality_json?JSON.parse(s.causality_json):void 0,visualization:s.visualization_json?JSON.parse(s.visualization_json):void 0,tags:JSON.parse(s.tags_json||"[]"),registeredAt:s.registered_at,updatedAt:s.updated_at}}insertEventBatch(e,t,s){this.initializeSync();let n=this.getSchema(e),i=new Map;if(n)for(let o of n.eventTypes)i.set(o.type,{category:o.category,severity:o.severity||"info"});let r=0,a=[];for(let o of s)try{let c=o.id||v4(),l=o.timestamp||new Date().toISOString(),d=i.get(o.type),m=d?.category||"unknown",g=o.severity||d?.severity||"info",p=o.scopeValue!=null?String(o.scopeValue):null,E=typeof o.scopeValue=="number"?o.scopeValue:null;this.db.run(`INSERT INTO events (
|
|
2203
402
|
id, schema_id, event_type, category, timestamp, scope_value,
|
|
2204
403
|
scope_ordinal, session_id, service, data_json, severity,
|
|
2205
404
|
parent_event_id, depth
|
|
2206
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2207
|
-
[
|
|
2208
|
-
id,
|
|
2209
|
-
schemaId,
|
|
2210
|
-
input.type,
|
|
2211
|
-
category,
|
|
2212
|
-
timestamp,
|
|
2213
|
-
scopeValue,
|
|
2214
|
-
scopeOrdinal,
|
|
2215
|
-
input.sessionId || null,
|
|
2216
|
-
service,
|
|
2217
|
-
input.data ? JSON.stringify(input.data) : null,
|
|
2218
|
-
severity,
|
|
2219
|
-
input.parentEventId || null,
|
|
2220
|
-
input.depth ?? 0
|
|
2221
|
-
]
|
|
2222
|
-
);
|
|
2223
|
-
accepted++;
|
|
2224
|
-
} catch (err) {
|
|
2225
|
-
errors.push(err instanceof Error ? err.message : String(err));
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
this.save();
|
|
2229
|
-
return { accepted, errors };
|
|
2230
|
-
}
|
|
2231
|
-
queryEvents(options = {}) {
|
|
2232
|
-
this.initializeSync();
|
|
2233
|
-
const { limit = 100, offset = 0 } = options;
|
|
2234
|
-
const conditions = [];
|
|
2235
|
-
const params = [];
|
|
2236
|
-
if (options.schemaId) {
|
|
2237
|
-
conditions.push("schema_id = ?");
|
|
2238
|
-
params.push(options.schemaId);
|
|
2239
|
-
}
|
|
2240
|
-
if (options.eventType) {
|
|
2241
|
-
conditions.push("event_type = ?");
|
|
2242
|
-
params.push(options.eventType);
|
|
2243
|
-
}
|
|
2244
|
-
if (options.category) {
|
|
2245
|
-
conditions.push("category = ?");
|
|
2246
|
-
params.push(options.category);
|
|
2247
|
-
}
|
|
2248
|
-
if (options.service) {
|
|
2249
|
-
conditions.push("service = ?");
|
|
2250
|
-
params.push(options.service);
|
|
2251
|
-
}
|
|
2252
|
-
if (options.sessionId) {
|
|
2253
|
-
conditions.push("session_id = ?");
|
|
2254
|
-
params.push(options.sessionId);
|
|
2255
|
-
}
|
|
2256
|
-
if (options.scopeValue) {
|
|
2257
|
-
conditions.push("scope_value = ?");
|
|
2258
|
-
params.push(options.scopeValue);
|
|
2259
|
-
}
|
|
2260
|
-
if (options.scopeFrom) {
|
|
2261
|
-
conditions.push("scope_value >= ?");
|
|
2262
|
-
params.push(options.scopeFrom);
|
|
2263
|
-
}
|
|
2264
|
-
if (options.scopeTo) {
|
|
2265
|
-
conditions.push("scope_value <= ?");
|
|
2266
|
-
params.push(options.scopeTo);
|
|
2267
|
-
}
|
|
2268
|
-
if (options.severity) {
|
|
2269
|
-
conditions.push("severity = ?");
|
|
2270
|
-
params.push(options.severity);
|
|
2271
|
-
}
|
|
2272
|
-
if (options.since) {
|
|
2273
|
-
conditions.push("timestamp >= ?");
|
|
2274
|
-
params.push(options.since);
|
|
2275
|
-
}
|
|
2276
|
-
if (options.until) {
|
|
2277
|
-
conditions.push("timestamp <= ?");
|
|
2278
|
-
params.push(options.until);
|
|
2279
|
-
}
|
|
2280
|
-
if (options.search) {
|
|
2281
|
-
conditions.push("data_json LIKE ?");
|
|
2282
|
-
params.push(`%${options.search}%`);
|
|
2283
|
-
}
|
|
2284
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2285
|
-
const result = this.db.exec(
|
|
2286
|
-
`SELECT * FROM events ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
|
|
2287
|
-
[...params, limit, offset]
|
|
2288
|
-
);
|
|
2289
|
-
if (result.length === 0) return [];
|
|
2290
|
-
return result[0].values.map(
|
|
2291
|
-
(row) => this.rowToGenericEvent(result[0].columns, row)
|
|
2292
|
-
);
|
|
2293
|
-
}
|
|
2294
|
-
queryEventsByScope(schemaId, scopeValue) {
|
|
2295
|
-
this.initializeSync();
|
|
2296
|
-
const result = this.db.exec(
|
|
2297
|
-
`SELECT * FROM events
|
|
405
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[c,e,o.type,m,l,p,E,o.sessionId||null,t,o.data?JSON.stringify(o.data):null,g,o.parentEventId||null,o.depth??0]),r++;}catch(c){a.push(c instanceof Error?c.message:String(c));}return this.save(),{accepted:r,errors:a}}queryEvents(e={}){this.initializeSync();let{limit:t=100,offset:s=0}=e,n=[],i=[];e.schemaId&&(n.push("schema_id = ?"),i.push(e.schemaId)),e.eventType&&(n.push("event_type = ?"),i.push(e.eventType)),e.category&&(n.push("category = ?"),i.push(e.category)),e.service&&(n.push("service = ?"),i.push(e.service)),e.sessionId&&(n.push("session_id = ?"),i.push(e.sessionId)),e.scopeValue&&(n.push("scope_value = ?"),i.push(e.scopeValue)),e.scopeFrom&&(n.push("scope_value >= ?"),i.push(e.scopeFrom)),e.scopeTo&&(n.push("scope_value <= ?"),i.push(e.scopeTo)),e.severity&&(n.push("severity = ?"),i.push(e.severity)),e.since&&(n.push("timestamp >= ?"),i.push(e.since)),e.until&&(n.push("timestamp <= ?"),i.push(e.until)),e.search&&(n.push("data_json LIKE ?"),i.push(`%${e.search}%`));let r=n.length>0?`WHERE ${n.join(" AND ")}`:"",a=this.db.exec(`SELECT * FROM events ${r} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,[...i,t,s]);return a.length===0?[]:a[0].values.map(o=>this.rowToGenericEvent(a[0].columns,o))}queryEventsByScope(e,t){this.initializeSync();let s=this.db.exec(`SELECT * FROM events
|
|
2298
406
|
WHERE schema_id = ? AND scope_value = ?
|
|
2299
|
-
ORDER BY timestamp ASC`,
|
|
2300
|
-
[schemaId, scopeValue]
|
|
2301
|
-
);
|
|
2302
|
-
if (result.length === 0) return [];
|
|
2303
|
-
return result[0].values.map(
|
|
2304
|
-
(row) => this.rowToGenericEvent(result[0].columns, row)
|
|
2305
|
-
);
|
|
2306
|
-
}
|
|
2307
|
-
getEventScopes(schemaId, options = {}) {
|
|
2308
|
-
this.initializeSync();
|
|
2309
|
-
const { limit = 100, offset = 0 } = options;
|
|
2310
|
-
const conditions = ["schema_id = ?"];
|
|
2311
|
-
const params = [schemaId];
|
|
2312
|
-
if (options.sessionId) {
|
|
2313
|
-
conditions.push("session_id = ?");
|
|
2314
|
-
params.push(options.sessionId);
|
|
2315
|
-
}
|
|
2316
|
-
const whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
2317
|
-
const result = this.db.exec(
|
|
2318
|
-
`SELECT
|
|
407
|
+
ORDER BY timestamp ASC`,[e,t]);return s.length===0?[]:s[0].values.map(n=>this.rowToGenericEvent(s[0].columns,n))}getEventScopes(e,t={}){this.initializeSync();let{limit:s=100,offset:n=0}=t,i=["schema_id = ?"],r=[e];t.sessionId&&(i.push("session_id = ?"),r.push(t.sessionId));let a=`WHERE ${i.join(" AND ")}`,o=this.db.exec(`SELECT
|
|
2319
408
|
scope_value,
|
|
2320
409
|
MIN(scope_ordinal) as scope_ordinal,
|
|
2321
410
|
COUNT(*) as event_count,
|
|
2322
411
|
MIN(timestamp) as first_timestamp,
|
|
2323
412
|
MAX(timestamp) as last_timestamp
|
|
2324
413
|
FROM events
|
|
2325
|
-
${
|
|
414
|
+
${a}
|
|
2326
415
|
AND scope_value IS NOT NULL
|
|
2327
416
|
GROUP BY scope_value
|
|
2328
417
|
ORDER BY MIN(COALESCE(scope_ordinal, 0)) DESC, MIN(timestamp) DESC
|
|
2329
|
-
LIMIT ? OFFSET ?`,
|
|
2330
|
-
[...params, limit, offset]
|
|
2331
|
-
);
|
|
2332
|
-
if (result.length === 0) return [];
|
|
2333
|
-
const scopes = [];
|
|
2334
|
-
for (const row of result[0].values) {
|
|
2335
|
-
const scopeValue = row[0];
|
|
2336
|
-
const scopeOrdinal = row[1] != null ? row[1] : void 0;
|
|
2337
|
-
const eventCount = row[2];
|
|
2338
|
-
const firstTimestamp = row[3];
|
|
2339
|
-
const lastTimestamp = row[4];
|
|
2340
|
-
const catResult = this.db.exec(
|
|
2341
|
-
`SELECT category, COUNT(*) as count FROM events
|
|
418
|
+
LIMIT ? OFFSET ?`,[...r,s,n]);if(o.length===0)return [];let c=[];for(let l of o[0].values){let d=l[0],m=l[1]!=null?l[1]:void 0,g=l[2],p=l[3],E=l[4],S=this.db.exec(`SELECT category, COUNT(*) as count FROM events
|
|
2342
419
|
WHERE schema_id = ? AND scope_value = ?
|
|
2343
|
-
GROUP BY category`,
|
|
2344
|
-
[schemaId, scopeValue]
|
|
2345
|
-
);
|
|
2346
|
-
const categories = {};
|
|
2347
|
-
if (catResult.length > 0) {
|
|
2348
|
-
for (const catRow of catResult[0].values) {
|
|
2349
|
-
categories[catRow[0]] = catRow[1];
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
scopes.push({
|
|
2353
|
-
scopeValue,
|
|
2354
|
-
scopeOrdinal,
|
|
2355
|
-
eventCount,
|
|
2356
|
-
categories,
|
|
2357
|
-
firstTimestamp,
|
|
2358
|
-
lastTimestamp
|
|
2359
|
-
});
|
|
2360
|
-
}
|
|
2361
|
-
return scopes;
|
|
2362
|
-
}
|
|
2363
|
-
getEventCount(options = {}) {
|
|
2364
|
-
this.initializeSync();
|
|
2365
|
-
const conditions = [];
|
|
2366
|
-
const params = [];
|
|
2367
|
-
if (options.schemaId) {
|
|
2368
|
-
conditions.push("schema_id = ?");
|
|
2369
|
-
params.push(options.schemaId);
|
|
2370
|
-
}
|
|
2371
|
-
if (options.eventType) {
|
|
2372
|
-
conditions.push("event_type = ?");
|
|
2373
|
-
params.push(options.eventType);
|
|
2374
|
-
}
|
|
2375
|
-
if (options.service) {
|
|
2376
|
-
conditions.push("service = ?");
|
|
2377
|
-
params.push(options.service);
|
|
2378
|
-
}
|
|
2379
|
-
if (options.since) {
|
|
2380
|
-
conditions.push("timestamp >= ?");
|
|
2381
|
-
params.push(options.since);
|
|
2382
|
-
}
|
|
2383
|
-
if (options.until) {
|
|
2384
|
-
conditions.push("timestamp <= ?");
|
|
2385
|
-
params.push(options.until);
|
|
2386
|
-
}
|
|
2387
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2388
|
-
const result = this.db.exec(
|
|
2389
|
-
`SELECT COUNT(*) as count FROM events ${whereClause}`,
|
|
2390
|
-
params
|
|
2391
|
-
);
|
|
2392
|
-
if (result.length === 0 || result[0].values.length === 0) return 0;
|
|
2393
|
-
return result[0].values[0][0];
|
|
2394
|
-
}
|
|
2395
|
-
pruneEvents(maxCount) {
|
|
2396
|
-
this.initializeSync();
|
|
2397
|
-
if (maxCount <= 0) return 0;
|
|
2398
|
-
const currentCount = this.getEventCount();
|
|
2399
|
-
if (currentCount <= maxCount) return 0;
|
|
2400
|
-
const deleteCount = currentCount - maxCount;
|
|
2401
|
-
this.db.run(
|
|
2402
|
-
`DELETE FROM events WHERE id IN (
|
|
420
|
+
GROUP BY category`,[e,d]),v={};if(S.length>0)for(let A of S[0].values)v[A[0]]=A[1];c.push({scopeValue:d,scopeOrdinal:m,eventCount:g,categories:v,firstTimestamp:p,lastTimestamp:E});}return c}getEventCount(e={}){this.initializeSync();let t=[],s=[];e.schemaId&&(t.push("schema_id = ?"),s.push(e.schemaId)),e.eventType&&(t.push("event_type = ?"),s.push(e.eventType)),e.service&&(t.push("service = ?"),s.push(e.service)),e.since&&(t.push("timestamp >= ?"),s.push(e.since)),e.until&&(t.push("timestamp <= ?"),s.push(e.until));let n=t.length>0?`WHERE ${t.join(" AND ")}`:"",i=this.db.exec(`SELECT COUNT(*) as count FROM events ${n}`,s);return i.length===0||i[0].values.length===0?0:i[0].values[0][0]}pruneEvents(e){if(this.initializeSync(),e<=0)return 0;let t=this.getEventCount();if(t<=e)return 0;let s=t-e;return this.db.run(`DELETE FROM events WHERE id IN (
|
|
2403
421
|
SELECT id FROM events ORDER BY timestamp ASC LIMIT ?
|
|
2404
|
-
)`,
|
|
2405
|
-
|
|
2406
|
-
);
|
|
2407
|
-
this.save();
|
|
2408
|
-
return deleteCount;
|
|
2409
|
-
}
|
|
2410
|
-
rowToGenericEvent(columns, row) {
|
|
2411
|
-
const obj = {};
|
|
2412
|
-
columns.forEach((col, i) => {
|
|
2413
|
-
obj[col] = row[i];
|
|
2414
|
-
});
|
|
2415
|
-
return {
|
|
2416
|
-
id: obj.id,
|
|
2417
|
-
schemaId: obj.schema_id,
|
|
2418
|
-
eventType: obj.event_type,
|
|
2419
|
-
category: obj.category,
|
|
2420
|
-
timestamp: obj.timestamp,
|
|
2421
|
-
scopeValue: obj.scope_value || void 0,
|
|
2422
|
-
scopeOrdinal: obj.scope_ordinal != null ? obj.scope_ordinal : void 0,
|
|
2423
|
-
sessionId: obj.session_id || void 0,
|
|
2424
|
-
service: obj.service,
|
|
2425
|
-
data: obj.data_json ? JSON.parse(obj.data_json) : void 0,
|
|
2426
|
-
severity: obj.severity || "info",
|
|
2427
|
-
parentEventId: obj.parent_event_id || void 0,
|
|
2428
|
-
depth: obj.depth || 0
|
|
2429
|
-
};
|
|
2430
|
-
}
|
|
2431
|
-
close() {
|
|
2432
|
-
if (this.db) {
|
|
2433
|
-
this.save();
|
|
2434
|
-
this.db.close();
|
|
2435
|
-
this.db = null;
|
|
2436
|
-
}
|
|
2437
|
-
}
|
|
2438
|
-
};
|
|
2439
|
-
|
|
2440
|
-
// ../sentinel/src/matcher.ts
|
|
2441
|
-
var DEFAULT_CONFIG = {
|
|
2442
|
-
minScore: 30,
|
|
2443
|
-
maxResults: 5,
|
|
2444
|
-
boostConfidence: true
|
|
2445
|
-
};
|
|
2446
|
-
var PatternMatcher = class {
|
|
2447
|
-
constructor(storage2) {
|
|
2448
|
-
this.storage = storage2;
|
|
2449
|
-
}
|
|
2450
|
-
/**
|
|
2451
|
-
* Match an incident against all patterns and return ranked results
|
|
2452
|
-
*/
|
|
2453
|
-
match(incident, config = {}) {
|
|
2454
|
-
const { minScore, maxResults, boostConfidence } = {
|
|
2455
|
-
...DEFAULT_CONFIG,
|
|
2456
|
-
...config
|
|
2457
|
-
};
|
|
2458
|
-
const patterns = this.storage.getAllPatterns({ includePrivate: true });
|
|
2459
|
-
const matches = [];
|
|
2460
|
-
for (const pattern of patterns) {
|
|
2461
|
-
if (!this.matchEnvironment(pattern, incident)) {
|
|
2462
|
-
continue;
|
|
2463
|
-
}
|
|
2464
|
-
const { score, matchedCriteria } = this.scoreMatch(pattern, incident);
|
|
2465
|
-
if (score >= minScore) {
|
|
2466
|
-
let confidence = score;
|
|
2467
|
-
if (boostConfidence) {
|
|
2468
|
-
const confidenceFactor = pattern.confidence.score / 100;
|
|
2469
|
-
confidence = score * (0.5 + 0.5 * confidenceFactor);
|
|
2470
|
-
}
|
|
2471
|
-
matches.push({
|
|
2472
|
-
pattern,
|
|
2473
|
-
score,
|
|
2474
|
-
matchedCriteria,
|
|
2475
|
-
confidence: Math.round(confidence)
|
|
2476
|
-
});
|
|
2477
|
-
this.storage.updatePatternConfidence(pattern.id, "matched");
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
return matches.sort((a, b) => b.confidence - a.confidence).slice(0, maxResults);
|
|
2481
|
-
}
|
|
2482
|
-
/**
|
|
2483
|
-
* Test a pattern against historical incidents
|
|
2484
|
-
*/
|
|
2485
|
-
testPattern(pattern, limit = 100) {
|
|
2486
|
-
const incidents = this.storage.getRecentIncidents({ limit });
|
|
2487
|
-
const wouldMatch = [];
|
|
2488
|
-
let totalScore = 0;
|
|
2489
|
-
for (const incident of incidents) {
|
|
2490
|
-
if (!this.matchEnvironment(pattern, incident)) {
|
|
2491
|
-
continue;
|
|
2492
|
-
}
|
|
2493
|
-
const { score } = this.scoreMatch(pattern, incident);
|
|
2494
|
-
if (score >= 30) {
|
|
2495
|
-
wouldMatch.push(incident);
|
|
2496
|
-
totalScore += score;
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
return {
|
|
2500
|
-
wouldMatch,
|
|
2501
|
-
matchCount: wouldMatch.length,
|
|
2502
|
-
avgScore: wouldMatch.length > 0 ? Math.round(totalScore / wouldMatch.length) : 0
|
|
2503
|
-
};
|
|
2504
|
-
}
|
|
2505
|
-
/**
|
|
2506
|
-
* Score how well a pattern matches an incident
|
|
2507
|
-
*/
|
|
2508
|
-
scoreMatch(pattern, incident) {
|
|
2509
|
-
let score = 0;
|
|
2510
|
-
const matchedCriteria = {
|
|
2511
|
-
symbols: [],
|
|
2512
|
-
errorKeywords: [],
|
|
2513
|
-
missingSignals: []
|
|
2514
|
-
};
|
|
2515
|
-
const symbolScore = this.matchSymbols(
|
|
2516
|
-
pattern.pattern.symbols,
|
|
2517
|
-
incident.symbols,
|
|
2518
|
-
matchedCriteria.symbols
|
|
2519
|
-
);
|
|
2520
|
-
score += Math.min(symbolScore, 50);
|
|
2521
|
-
const errorScore = this.matchErrorText(
|
|
2522
|
-
pattern,
|
|
2523
|
-
incident,
|
|
2524
|
-
matchedCriteria.errorKeywords
|
|
2525
|
-
);
|
|
2526
|
-
score += Math.min(errorScore, 25);
|
|
2527
|
-
const signalScore = this.matchMissingSignals(
|
|
2528
|
-
pattern,
|
|
2529
|
-
incident,
|
|
2530
|
-
matchedCriteria.missingSignals
|
|
2531
|
-
);
|
|
2532
|
-
score += Math.min(signalScore, 25);
|
|
2533
|
-
score = Math.min(score, 100);
|
|
2534
|
-
return { score, matchedCriteria };
|
|
2535
|
-
}
|
|
2536
|
-
/**
|
|
2537
|
-
* Match symbols between pattern and incident
|
|
2538
|
-
*/
|
|
2539
|
-
matchSymbols(patternSymbols, incidentSymbols, matched) {
|
|
2540
|
-
let score = 0;
|
|
2541
|
-
const symbolTypes = [
|
|
2542
|
-
"feature",
|
|
2543
|
-
"component",
|
|
2544
|
-
"flow",
|
|
2545
|
-
"gate",
|
|
2546
|
-
"signal",
|
|
2547
|
-
"state",
|
|
2548
|
-
"integration"
|
|
2549
|
-
];
|
|
2550
|
-
for (const type of symbolTypes) {
|
|
2551
|
-
const patternValue = patternSymbols[type];
|
|
2552
|
-
const incidentValue = incidentSymbols[type];
|
|
2553
|
-
if (!patternValue || !incidentValue) {
|
|
2554
|
-
continue;
|
|
2555
|
-
}
|
|
2556
|
-
if (typeof patternValue === "string") {
|
|
2557
|
-
if (this.matchSingleSymbol(patternValue, incidentValue)) {
|
|
2558
|
-
score += patternValue.includes("*") ? 5 : 10;
|
|
2559
|
-
matched.push(type);
|
|
2560
|
-
}
|
|
2561
|
-
} else if (Array.isArray(patternValue)) {
|
|
2562
|
-
for (const pv of patternValue) {
|
|
2563
|
-
if (this.matchSingleSymbol(pv, incidentValue)) {
|
|
2564
|
-
score += 7;
|
|
2565
|
-
matched.push(type);
|
|
2566
|
-
break;
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
return score;
|
|
2572
|
-
}
|
|
2573
|
-
/**
|
|
2574
|
-
* Match a single symbol value (supports wildcards)
|
|
2575
|
-
*/
|
|
2576
|
-
matchSingleSymbol(pattern, value) {
|
|
2577
|
-
if (pattern === "*") {
|
|
2578
|
-
return true;
|
|
2579
|
-
}
|
|
2580
|
-
if (pattern.endsWith("*")) {
|
|
2581
|
-
const prefix = pattern.slice(0, -1);
|
|
2582
|
-
return value.startsWith(prefix);
|
|
2583
|
-
}
|
|
2584
|
-
if (pattern.startsWith("*")) {
|
|
2585
|
-
const suffix = pattern.slice(1);
|
|
2586
|
-
return value.endsWith(suffix);
|
|
2587
|
-
}
|
|
2588
|
-
if (pattern.includes("*")) {
|
|
2589
|
-
const regex = new RegExp(
|
|
2590
|
-
"^" + pattern.replace(/\*/g, ".*") + "$"
|
|
2591
|
-
);
|
|
2592
|
-
return regex.test(value);
|
|
2593
|
-
}
|
|
2594
|
-
return pattern === value;
|
|
2595
|
-
}
|
|
2596
|
-
/**
|
|
2597
|
-
* Match error text keywords and regex
|
|
2598
|
-
*/
|
|
2599
|
-
matchErrorText(pattern, incident, matched) {
|
|
2600
|
-
let score = 0;
|
|
2601
|
-
const errorMessage = incident.error.message.toLowerCase();
|
|
2602
|
-
const errorType = incident.error.type?.toLowerCase();
|
|
2603
|
-
if (pattern.pattern.errorContains) {
|
|
2604
|
-
for (const keyword of pattern.pattern.errorContains) {
|
|
2605
|
-
if (errorMessage.includes(keyword.toLowerCase())) {
|
|
2606
|
-
score += 5;
|
|
2607
|
-
matched.push(keyword);
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
if (pattern.pattern.errorMatches) {
|
|
2612
|
-
try {
|
|
2613
|
-
const regex = new RegExp(pattern.pattern.errorMatches, "i");
|
|
2614
|
-
if (regex.test(incident.error.message)) {
|
|
2615
|
-
score += 10;
|
|
2616
|
-
matched.push(`regex:${pattern.pattern.errorMatches}`);
|
|
2617
|
-
}
|
|
2618
|
-
} catch {
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
if (pattern.pattern.errorType && errorType) {
|
|
2622
|
-
for (const type of pattern.pattern.errorType) {
|
|
2623
|
-
if (errorType.includes(type.toLowerCase())) {
|
|
2624
|
-
score += 5;
|
|
2625
|
-
matched.push(`type:${type}`);
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
return score;
|
|
2630
|
-
}
|
|
2631
|
-
/**
|
|
2632
|
-
* Match missing signals from flow position
|
|
2633
|
-
*/
|
|
2634
|
-
matchMissingSignals(pattern, incident, matched) {
|
|
2635
|
-
if (!pattern.pattern.missingSignals || !incident.flowPosition?.missing) {
|
|
2636
|
-
return 0;
|
|
2637
|
-
}
|
|
2638
|
-
let score = 0;
|
|
2639
|
-
for (const expectedSignal of pattern.pattern.missingSignals) {
|
|
2640
|
-
for (const missingSignal of incident.flowPosition.missing) {
|
|
2641
|
-
if (this.matchSingleSymbol(expectedSignal, missingSignal)) {
|
|
2642
|
-
score += 12;
|
|
2643
|
-
matched.push(missingSignal);
|
|
2644
|
-
break;
|
|
2645
|
-
}
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
return score;
|
|
2649
|
-
}
|
|
2650
|
-
/**
|
|
2651
|
-
* Check if pattern's environment filter matches incident
|
|
2652
|
-
*/
|
|
2653
|
-
matchEnvironment(pattern, incident) {
|
|
2654
|
-
if (!pattern.pattern.environment || pattern.pattern.environment.length === 0) {
|
|
2655
|
-
return true;
|
|
2656
|
-
}
|
|
2657
|
-
return pattern.pattern.environment.includes(incident.environment);
|
|
2658
|
-
}
|
|
2659
|
-
};
|
|
2660
|
-
|
|
2661
|
-
// ../sentinel/src/timeline.ts
|
|
2662
|
-
var TimelineBuilder = class {
|
|
2663
|
-
/**
|
|
2664
|
-
* Build a timeline from an incident with flow position
|
|
2665
|
-
*/
|
|
2666
|
-
build(incident) {
|
|
2667
|
-
if (!incident.flowPosition) {
|
|
2668
|
-
return null;
|
|
2669
|
-
}
|
|
2670
|
-
const events = [];
|
|
2671
|
-
const baseTime = new Date(incident.timestamp).getTime();
|
|
2672
|
-
events.push({
|
|
2673
|
-
timestamp: new Date(baseTime - 5e3).toISOString(),
|
|
2674
|
-
symbol: incident.flowPosition.flowId,
|
|
2675
|
-
type: "flow-started"
|
|
2676
|
-
});
|
|
2677
|
-
let eventOffset = 1e3;
|
|
2678
|
-
for (const signal of incident.flowPosition.actual) {
|
|
2679
|
-
const type = this.inferEventType(signal);
|
|
2680
|
-
events.push({
|
|
2681
|
-
timestamp: new Date(baseTime - 4e3 + eventOffset).toISOString(),
|
|
2682
|
-
symbol: signal,
|
|
2683
|
-
type
|
|
2684
|
-
});
|
|
2685
|
-
eventOffset += Math.random() * 1e3 + 500;
|
|
2686
|
-
}
|
|
2687
|
-
const failedSymbol = incident.flowPosition.failedAt || incident.flowPosition.missing[0] || incident.symbols.gate || incident.symbols.signal || "unknown";
|
|
2688
|
-
events.push({
|
|
2689
|
-
timestamp: incident.timestamp,
|
|
2690
|
-
symbol: failedSymbol,
|
|
2691
|
-
type: "error",
|
|
2692
|
-
data: {
|
|
2693
|
-
message: incident.error.message,
|
|
2694
|
-
missing: incident.flowPosition.missing
|
|
2695
|
-
}
|
|
2696
|
-
});
|
|
2697
|
-
return {
|
|
2698
|
-
incidentId: incident.id,
|
|
2699
|
-
flowId: incident.flowPosition.flowId,
|
|
2700
|
-
events,
|
|
2701
|
-
failure: {
|
|
2702
|
-
at: incident.timestamp,
|
|
2703
|
-
symbol: failedSymbol,
|
|
2704
|
-
reason: incident.error.message
|
|
2705
|
-
}
|
|
2706
|
-
};
|
|
2707
|
-
}
|
|
2708
|
-
/**
|
|
2709
|
-
* Render timeline as ASCII art
|
|
2710
|
-
*/
|
|
2711
|
-
renderAscii(timeline) {
|
|
2712
|
-
const lines = [];
|
|
2713
|
-
lines.push(`${timeline.flowId} Timeline`);
|
|
2714
|
-
lines.push("\u2550".repeat(40));
|
|
2715
|
-
lines.push("");
|
|
2716
|
-
for (const event of timeline.events) {
|
|
2717
|
-
const time = this.formatTime(event.timestamp);
|
|
2718
|
-
const icon = this.getEventIcon(event.type);
|
|
2719
|
-
const status = this.getEventStatus(event.type);
|
|
2720
|
-
let line = `${time} ${icon} ${event.symbol}`;
|
|
2721
|
-
if (status) {
|
|
2722
|
-
line += ` (${status})`;
|
|
2723
|
-
}
|
|
2724
|
-
lines.push(line);
|
|
2725
|
-
if (event.type === "error" && event.data) {
|
|
2726
|
-
lines.push(` \u2514\u2500 ${event.data.message}`);
|
|
2727
|
-
if (event.data.missing && Array.isArray(event.data.missing) && event.data.missing.length > 0) {
|
|
2728
|
-
lines.push(
|
|
2729
|
-
` \u2514\u2500 Expected: ${event.data.missing.join(", ")}`
|
|
2730
|
-
);
|
|
2731
|
-
}
|
|
2732
|
-
}
|
|
2733
|
-
}
|
|
2734
|
-
const missing = timeline.events.find((e) => e.type === "error")?.data?.missing;
|
|
2735
|
-
if (missing && missing.length > 0) {
|
|
2736
|
-
lines.push("");
|
|
2737
|
-
lines.push(`Missing signals: ${missing.join(", ")}`);
|
|
2738
|
-
}
|
|
2739
|
-
return lines.join("\n");
|
|
2740
|
-
}
|
|
2741
|
-
/**
|
|
2742
|
-
* Render timeline as structured data (for MCP/JSON output)
|
|
2743
|
-
*/
|
|
2744
|
-
renderStructured(timeline) {
|
|
2745
|
-
return {
|
|
2746
|
-
incidentId: timeline.incidentId,
|
|
2747
|
-
flow: {
|
|
2748
|
-
id: timeline.flowId,
|
|
2749
|
-
eventCount: timeline.events.length
|
|
2750
|
-
},
|
|
2751
|
-
events: timeline.events.map((event) => ({
|
|
2752
|
-
time: this.formatTime(event.timestamp),
|
|
2753
|
-
symbol: event.symbol,
|
|
2754
|
-
type: event.type,
|
|
2755
|
-
status: this.getEventStatus(event.type),
|
|
2756
|
-
data: event.data
|
|
2757
|
-
})),
|
|
2758
|
-
failure: {
|
|
2759
|
-
at: this.formatTime(timeline.failure.at),
|
|
2760
|
-
symbol: timeline.failure.symbol,
|
|
2761
|
-
reason: timeline.failure.reason
|
|
2762
|
-
}
|
|
2763
|
-
};
|
|
2764
|
-
}
|
|
2765
|
-
/**
|
|
2766
|
-
* Infer event type from symbol prefix
|
|
2767
|
-
*/
|
|
2768
|
-
inferEventType(symbol) {
|
|
2769
|
-
if (symbol.startsWith("^")) {
|
|
2770
|
-
return "gate-passed";
|
|
2771
|
-
}
|
|
2772
|
-
if (symbol.startsWith("!")) {
|
|
2773
|
-
return "signal-emitted";
|
|
2774
|
-
}
|
|
2775
|
-
if (symbol.startsWith("%")) {
|
|
2776
|
-
return "state-changed";
|
|
2777
|
-
}
|
|
2778
|
-
return "signal-emitted";
|
|
2779
|
-
}
|
|
2780
|
-
/**
|
|
2781
|
-
* Get icon for event type
|
|
2782
|
-
*/
|
|
2783
|
-
getEventIcon(type) {
|
|
2784
|
-
switch (type) {
|
|
2785
|
-
case "flow-started":
|
|
2786
|
-
return "\u25B6";
|
|
2787
|
-
case "flow-ended":
|
|
2788
|
-
return "\u25A0";
|
|
2789
|
-
case "gate-passed":
|
|
2790
|
-
return "\u2713";
|
|
2791
|
-
case "gate-failed":
|
|
2792
|
-
return "\u2717";
|
|
2793
|
-
case "signal-emitted":
|
|
2794
|
-
return "\u26A1";
|
|
2795
|
-
case "state-changed":
|
|
2796
|
-
return "\u25C6";
|
|
2797
|
-
case "error":
|
|
2798
|
-
return "\u2717";
|
|
2799
|
-
default:
|
|
2800
|
-
return "\u2022";
|
|
2801
|
-
}
|
|
2802
|
-
}
|
|
2803
|
-
/**
|
|
2804
|
-
* Get status text for event type
|
|
2805
|
-
*/
|
|
2806
|
-
getEventStatus(type) {
|
|
2807
|
-
switch (type) {
|
|
2808
|
-
case "gate-passed":
|
|
2809
|
-
return "PASSED";
|
|
2810
|
-
case "gate-failed":
|
|
2811
|
-
return "FAILED";
|
|
2812
|
-
case "signal-emitted":
|
|
2813
|
-
return "EMITTED";
|
|
2814
|
-
case "state-changed":
|
|
2815
|
-
return "CHANGED";
|
|
2816
|
-
case "error":
|
|
2817
|
-
return "ERROR";
|
|
2818
|
-
default:
|
|
2819
|
-
return "";
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
|
-
/**
|
|
2823
|
-
* Format timestamp for display
|
|
2824
|
-
*/
|
|
2825
|
-
formatTime(timestamp) {
|
|
2826
|
-
const date = new Date(timestamp);
|
|
2827
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
2828
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
2829
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
2830
|
-
const millis = String(date.getMilliseconds()).padStart(3, "0");
|
|
2831
|
-
return `${hours}:${minutes}:${seconds}.${millis}`;
|
|
2832
|
-
}
|
|
2833
|
-
};
|
|
2834
|
-
|
|
2835
|
-
// ../sentinel/src/stats.ts
|
|
2836
|
-
var StatsCalculator = class {
|
|
2837
|
-
constructor(storage2) {
|
|
2838
|
-
this.storage = storage2;
|
|
2839
|
-
}
|
|
2840
|
-
/**
|
|
2841
|
-
* Get comprehensive statistics for a time period
|
|
2842
|
-
*/
|
|
2843
|
-
getStats(periodDays = 7) {
|
|
2844
|
-
const end = (/* @__PURE__ */ new Date()).toISOString();
|
|
2845
|
-
const start = new Date(
|
|
2846
|
-
Date.now() - periodDays * 24 * 60 * 60 * 1e3
|
|
2847
|
-
).toISOString();
|
|
2848
|
-
return this.storage.getStats({ start, end });
|
|
2849
|
-
}
|
|
2850
|
-
/**
|
|
2851
|
-
* Get health metrics for a specific symbol
|
|
2852
|
-
*/
|
|
2853
|
-
getSymbolHealth(symbol) {
|
|
2854
|
-
return this.storage.getSymbolHealth(symbol);
|
|
2855
|
-
}
|
|
2856
|
-
/**
|
|
2857
|
-
* Get trending issues (symbols with increasing incident rates)
|
|
2858
|
-
*/
|
|
2859
|
-
getTrendingIssues(days = 7) {
|
|
2860
|
-
const now = Date.now();
|
|
2861
|
-
const halfPeriod = days * 24 * 60 * 60 * 1e3 / 2;
|
|
2862
|
-
const firstHalfStart = new Date(now - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
2863
|
-
const midpoint = new Date(now - halfPeriod).toISOString();
|
|
2864
|
-
const secondHalfEnd = new Date(now).toISOString();
|
|
2865
|
-
const firstHalfIncidents = this.storage.getRecentIncidents({
|
|
2866
|
-
dateFrom: firstHalfStart,
|
|
2867
|
-
dateTo: midpoint,
|
|
2868
|
-
limit: 1e3
|
|
2869
|
-
});
|
|
2870
|
-
const secondHalfIncidents = this.storage.getRecentIncidents({
|
|
2871
|
-
dateFrom: midpoint,
|
|
2872
|
-
dateTo: secondHalfEnd,
|
|
2873
|
-
limit: 1e3
|
|
2874
|
-
});
|
|
2875
|
-
const firstHalfCounts = this.countSymbols(firstHalfIncidents);
|
|
2876
|
-
const secondHalfCounts = this.countSymbols(secondHalfIncidents);
|
|
2877
|
-
const trends = [];
|
|
2878
|
-
const allSymbols = /* @__PURE__ */ new Set([
|
|
2879
|
-
...firstHalfCounts.keys(),
|
|
2880
|
-
...secondHalfCounts.keys()
|
|
2881
|
-
]);
|
|
2882
|
-
for (const symbol of allSymbols) {
|
|
2883
|
-
const first = firstHalfCounts.get(symbol) || 0;
|
|
2884
|
-
const second = secondHalfCounts.get(symbol) || 0;
|
|
2885
|
-
if (first === 0 && second > 0) {
|
|
2886
|
-
trends.push({ symbol, trend: second * 100 });
|
|
2887
|
-
} else if (first > 0) {
|
|
2888
|
-
const change = (second - first) / first * 100;
|
|
2889
|
-
trends.push({ symbol, trend: change });
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
return trends.filter((t) => t.trend > 0).sort((a, b) => b.trend - a.trend).slice(0, 10);
|
|
2893
|
-
}
|
|
2894
|
-
/**
|
|
2895
|
-
* Get resolution metrics
|
|
2896
|
-
*/
|
|
2897
|
-
getResolutionMetrics() {
|
|
2898
|
-
const stats = this.getStats(30);
|
|
2899
|
-
return {
|
|
2900
|
-
avgTimeToResolve: stats.resolution.avgTimeToResolve,
|
|
2901
|
-
resolvedWithPattern: stats.resolution.resolvedWithPattern,
|
|
2902
|
-
resolvedManually: stats.resolution.resolvedManually,
|
|
2903
|
-
totalResolved: stats.incidents.resolved,
|
|
2904
|
-
resolutionRate: stats.resolution.resolutionRate
|
|
2905
|
-
};
|
|
2906
|
-
}
|
|
2907
|
-
/**
|
|
2908
|
-
* Get pattern effectiveness metrics
|
|
2909
|
-
*/
|
|
2910
|
-
getPatternEffectiveness() {
|
|
2911
|
-
const patterns = this.storage.getAllPatterns({ includePrivate: true });
|
|
2912
|
-
return patterns.filter((p) => p.confidence.timesMatched > 0).map((p) => ({
|
|
2913
|
-
patternId: p.id,
|
|
2914
|
-
name: p.name,
|
|
2915
|
-
matches: p.confidence.timesMatched,
|
|
2916
|
-
resolutions: p.confidence.timesResolved,
|
|
2917
|
-
recurrences: p.confidence.timesRecurred,
|
|
2918
|
-
effectiveness: p.confidence.timesMatched > 0 ? Math.round(
|
|
2919
|
-
(p.confidence.timesResolved - p.confidence.timesRecurred) / p.confidence.timesMatched * 100
|
|
2920
|
-
) : 0
|
|
2921
|
-
})).sort((a, b) => b.effectiveness - a.effectiveness);
|
|
2922
|
-
}
|
|
2923
|
-
/**
|
|
2924
|
-
* Get incident rate by hour of day
|
|
2925
|
-
*/
|
|
2926
|
-
getIncidentsByHour(days = 7) {
|
|
2927
|
-
const start = new Date(
|
|
2928
|
-
Date.now() - days * 24 * 60 * 60 * 1e3
|
|
2929
|
-
).toISOString();
|
|
2930
|
-
const incidents = this.storage.getRecentIncidents({
|
|
2931
|
-
dateFrom: start,
|
|
2932
|
-
limit: 1e4
|
|
2933
|
-
});
|
|
2934
|
-
const hourCounts = /* @__PURE__ */ new Map();
|
|
2935
|
-
for (let i = 0; i < 24; i++) {
|
|
2936
|
-
hourCounts.set(i, 0);
|
|
2937
|
-
}
|
|
2938
|
-
for (const incident of incidents) {
|
|
2939
|
-
const hour = new Date(incident.timestamp).getHours();
|
|
2940
|
-
hourCounts.set(hour, (hourCounts.get(hour) || 0) + 1);
|
|
2941
|
-
}
|
|
2942
|
-
return Array.from(hourCounts.entries()).map(([hour, count]) => ({
|
|
2943
|
-
hour,
|
|
2944
|
-
count
|
|
2945
|
-
}));
|
|
2946
|
-
}
|
|
2947
|
-
/**
|
|
2948
|
-
* Get incident rate by environment
|
|
2949
|
-
*/
|
|
2950
|
-
getIncidentsByEnvironment() {
|
|
2951
|
-
const stats = this.getStats(30);
|
|
2952
|
-
const total = stats.incidents.total;
|
|
2953
|
-
return Object.entries(stats.incidents.byEnvironment).map(([environment, count]) => ({
|
|
2954
|
-
environment,
|
|
2955
|
-
count,
|
|
2956
|
-
percentage: total > 0 ? Math.round(count / total * 100) : 0
|
|
2957
|
-
})).sort((a, b) => b.count - a.count);
|
|
2958
|
-
}
|
|
2959
|
-
/**
|
|
2960
|
-
* Get symbol correlation matrix (which symbols fail together)
|
|
2961
|
-
*/
|
|
2962
|
-
getSymbolCorrelation() {
|
|
2963
|
-
const incidents = this.storage.getRecentIncidents({ limit: 1e3 });
|
|
2964
|
-
const correlations = /* @__PURE__ */ new Map();
|
|
2965
|
-
const symbolCounts = /* @__PURE__ */ new Map();
|
|
2966
|
-
for (const incident of incidents) {
|
|
2967
|
-
const symbols = this.getSymbolsFromIncident(incident);
|
|
2968
|
-
for (const symbol of symbols) {
|
|
2969
|
-
symbolCounts.set(symbol, (symbolCounts.get(symbol) || 0) + 1);
|
|
2970
|
-
}
|
|
2971
|
-
for (let i = 0; i < symbols.length; i++) {
|
|
2972
|
-
for (let j = i + 1; j < symbols.length; j++) {
|
|
2973
|
-
const key = [symbols[i], symbols[j]].sort().join("|");
|
|
2974
|
-
correlations.set(key, (correlations.get(key) || 0) + 1);
|
|
2975
|
-
}
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
const results = [];
|
|
2979
|
-
for (const [key, count] of correlations) {
|
|
2980
|
-
const [symbol1, symbol2] = key.split("|");
|
|
2981
|
-
const count1 = symbolCounts.get(symbol1) || 1;
|
|
2982
|
-
const count2 = symbolCounts.get(symbol2) || 1;
|
|
2983
|
-
const correlation = count / Math.max(count1, count2);
|
|
2984
|
-
if (correlation > 0.3) {
|
|
2985
|
-
results.push({
|
|
2986
|
-
symbol1,
|
|
2987
|
-
symbol2,
|
|
2988
|
-
correlation: Math.round(correlation * 100) / 100
|
|
2989
|
-
});
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
return results.sort((a, b) => b.correlation - a.correlation).slice(0, 20);
|
|
2993
|
-
}
|
|
2994
|
-
/**
|
|
2995
|
-
* Generate a summary dashboard string
|
|
2996
|
-
*/
|
|
2997
|
-
generateDashboard(periodDays = 7) {
|
|
2998
|
-
const stats = this.getStats(periodDays);
|
|
2999
|
-
const lines = [];
|
|
3000
|
-
lines.push("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
3001
|
-
lines.push("\u2551 PARADIGM SENTINEL DASHBOARD \u2551");
|
|
3002
|
-
lines.push("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
|
|
3003
|
-
const todayCount = stats.incidents.byDay[stats.incidents.byDay.length - 1]?.count || 0;
|
|
3004
|
-
lines.push(
|
|
3005
|
-
`\u2551 Open: ${String(stats.incidents.open).padEnd(4)} \u2502 Investigating: ${String(stats.incidents.total - stats.incidents.open - stats.incidents.resolved).padEnd(3)} \u2502 Resolved: ${String(stats.incidents.resolved).padEnd(4)} \u2502 Today: +${todayCount} \u2551`
|
|
3006
|
-
);
|
|
3007
|
-
lines.push("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
3008
|
-
lines.push("");
|
|
3009
|
-
lines.push("Incidents by Day (last 7 days):");
|
|
3010
|
-
lines.push("\u2500".repeat(50));
|
|
3011
|
-
const maxDayCount = Math.max(...stats.incidents.byDay.map((d) => d.count), 1);
|
|
3012
|
-
for (const day of stats.incidents.byDay.slice(-7)) {
|
|
3013
|
-
const barLength = Math.round(day.count / maxDayCount * 30);
|
|
3014
|
-
const bar = "\u2588".repeat(barLength);
|
|
3015
|
-
lines.push(`${day.date.substring(5)} ${bar} ${day.count}`);
|
|
3016
|
-
}
|
|
3017
|
-
lines.push("");
|
|
3018
|
-
lines.push("Most Affected Symbols:");
|
|
3019
|
-
lines.push("\u2500".repeat(50));
|
|
3020
|
-
for (const { symbol, count } of stats.symbols.mostIncidents.slice(0, 5)) {
|
|
3021
|
-
lines.push(` ${symbol.padEnd(25)} ${count} incidents`);
|
|
3022
|
-
}
|
|
3023
|
-
lines.push("");
|
|
3024
|
-
lines.push("Top Patterns:");
|
|
3025
|
-
lines.push("\u2500".repeat(50));
|
|
3026
|
-
for (const { patternId, resolvedCount } of stats.patterns.mostEffective.slice(0, 5)) {
|
|
3027
|
-
lines.push(` ${patternId.padEnd(25)} ${resolvedCount} resolved`);
|
|
3028
|
-
}
|
|
3029
|
-
lines.push("");
|
|
3030
|
-
lines.push("Resolution Stats:");
|
|
3031
|
-
lines.push("\u2500".repeat(50));
|
|
3032
|
-
lines.push(` Resolution rate: ${Math.round(stats.resolution.resolutionRate)}%`);
|
|
3033
|
-
lines.push(` With pattern: ${stats.resolution.resolvedWithPattern}`);
|
|
3034
|
-
lines.push(` Manual: ${stats.resolution.resolvedManually}`);
|
|
3035
|
-
return lines.join("\n");
|
|
3036
|
-
}
|
|
3037
|
-
/**
|
|
3038
|
-
* Helper: Count symbols across incidents
|
|
3039
|
-
*/
|
|
3040
|
-
countSymbols(incidents) {
|
|
3041
|
-
const counts = /* @__PURE__ */ new Map();
|
|
3042
|
-
for (const incident of incidents) {
|
|
3043
|
-
for (const [, value] of Object.entries(incident.symbols)) {
|
|
3044
|
-
if (value) {
|
|
3045
|
-
counts.set(value, (counts.get(value) || 0) + 1);
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
}
|
|
3049
|
-
return counts;
|
|
3050
|
-
}
|
|
3051
|
-
/**
|
|
3052
|
-
* Helper: Get all symbols from incident
|
|
3053
|
-
*/
|
|
3054
|
-
getSymbolsFromIncident(incident) {
|
|
3055
|
-
const symbols = [];
|
|
3056
|
-
for (const [, value] of Object.entries(incident.symbols)) {
|
|
3057
|
-
if (value) {
|
|
3058
|
-
symbols.push(value);
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3061
|
-
return symbols;
|
|
3062
|
-
}
|
|
3063
|
-
};
|
|
3064
|
-
|
|
3065
|
-
// ../sentinel/src/suggester.ts
|
|
3066
|
-
var PatternSuggester = class {
|
|
3067
|
-
constructor(storage2) {
|
|
3068
|
-
this.storage = storage2;
|
|
3069
|
-
}
|
|
3070
|
-
/**
|
|
3071
|
-
* Suggest a pattern from a resolved incident
|
|
3072
|
-
*/
|
|
3073
|
-
suggestFromIncident(incident) {
|
|
3074
|
-
const baseId = this.generatePatternId(incident);
|
|
3075
|
-
const symbols = this.buildSymbolCriteria(incident.symbols);
|
|
3076
|
-
const errorKeywords = this.extractErrorKeywords(incident.error.message);
|
|
3077
|
-
const pattern = {
|
|
3078
|
-
id: baseId,
|
|
3079
|
-
name: this.generatePatternName(incident),
|
|
3080
|
-
description: `Auto-suggested pattern from incident ${incident.id}`,
|
|
3081
|
-
pattern: {
|
|
3082
|
-
symbols,
|
|
3083
|
-
errorContains: errorKeywords.length > 0 ? errorKeywords : void 0,
|
|
3084
|
-
missingSignals: incident.flowPosition?.missing
|
|
3085
|
-
},
|
|
3086
|
-
resolution: {
|
|
3087
|
-
description: incident.resolution?.notes || "Resolution approach TBD",
|
|
3088
|
-
strategy: this.inferStrategy([incident]),
|
|
3089
|
-
priority: "medium"
|
|
3090
|
-
},
|
|
3091
|
-
source: "suggested",
|
|
3092
|
-
private: false,
|
|
3093
|
-
tags: this.generateTags(incident)
|
|
3094
|
-
};
|
|
3095
|
-
return pattern;
|
|
3096
|
-
}
|
|
3097
|
-
/**
|
|
3098
|
-
* Suggest a pattern from an incident group
|
|
3099
|
-
*/
|
|
3100
|
-
suggestFromGroup(group) {
|
|
3101
|
-
const baseId = `group-${group.id.toLowerCase().replace(/[^a-z0-9]/g, "-")}`;
|
|
3102
|
-
const symbols = this.buildSymbolCriteria(group.commonSymbols);
|
|
3103
|
-
const groupIncidents = group.incidents.slice(0, 20).map((id) => this.storage.getIncident(id)).filter((i) => i != null);
|
|
3104
|
-
const pattern = {
|
|
3105
|
-
id: baseId,
|
|
3106
|
-
name: group.name || `Pattern from group ${group.id}`,
|
|
3107
|
-
description: `Auto-suggested pattern from incident group with ${group.count} incidents`,
|
|
3108
|
-
pattern: {
|
|
3109
|
-
symbols,
|
|
3110
|
-
errorContains: group.commonErrorPatterns.length > 0 ? group.commonErrorPatterns : void 0
|
|
3111
|
-
},
|
|
3112
|
-
resolution: {
|
|
3113
|
-
description: "Resolution approach TBD based on grouped incidents",
|
|
3114
|
-
strategy: groupIncidents.length > 0 ? this.inferStrategy(groupIncidents) : "fix-code",
|
|
3115
|
-
priority: this.getPriorityFromCount(group.count)
|
|
3116
|
-
},
|
|
3117
|
-
source: "suggested",
|
|
3118
|
-
private: false,
|
|
3119
|
-
tags: this.generateTagsFromGroup(group)
|
|
3120
|
-
};
|
|
3121
|
-
return pattern;
|
|
3122
|
-
}
|
|
3123
|
-
/**
|
|
3124
|
-
* Find incidents that could become patterns
|
|
3125
|
-
*/
|
|
3126
|
-
findPatternCandidates(minOccurrences = 3) {
|
|
3127
|
-
const incidents = this.storage.getRecentIncidents({
|
|
3128
|
-
limit: 1e3,
|
|
3129
|
-
status: "resolved"
|
|
3130
|
-
});
|
|
3131
|
-
const signatureGroups = /* @__PURE__ */ new Map();
|
|
3132
|
-
for (const incident of incidents) {
|
|
3133
|
-
const signature = this.getSymbolSignature(incident.symbols);
|
|
3134
|
-
const existing = signatureGroups.get(signature) || [];
|
|
3135
|
-
existing.push(incident);
|
|
3136
|
-
signatureGroups.set(signature, existing);
|
|
3137
|
-
}
|
|
3138
|
-
const candidates = [];
|
|
3139
|
-
for (const [, groupIncidents] of signatureGroups) {
|
|
3140
|
-
if (groupIncidents.length >= minOccurrences) {
|
|
3141
|
-
const hasPattern = this.hasMatchingPattern(groupIncidents[0]);
|
|
3142
|
-
if (hasPattern) continue;
|
|
3143
|
-
const suggestedPattern = this.suggestFromIncidents(groupIncidents);
|
|
3144
|
-
candidates.push({
|
|
3145
|
-
incidents: groupIncidents,
|
|
3146
|
-
suggestedPattern,
|
|
3147
|
-
occurrenceCount: groupIncidents.length
|
|
3148
|
-
});
|
|
3149
|
-
}
|
|
3150
|
-
}
|
|
3151
|
-
return candidates.sort((a, b) => b.occurrenceCount - a.occurrenceCount);
|
|
3152
|
-
}
|
|
3153
|
-
/**
|
|
3154
|
-
* Generate pattern from multiple similar incidents
|
|
3155
|
-
*/
|
|
3156
|
-
suggestFromIncidents(incidents) {
|
|
3157
|
-
const commonSymbols = this.extractCommonSymbols(incidents);
|
|
3158
|
-
const symbols = this.buildSymbolCriteria(commonSymbols);
|
|
3159
|
-
const errorKeywords = this.extractCommonErrorKeywords(incidents);
|
|
3160
|
-
const missingSignals = this.extractCommonMissingSignals(incidents);
|
|
3161
|
-
const baseId = this.generatePatternId(incidents[0]);
|
|
3162
|
-
return {
|
|
3163
|
-
id: baseId,
|
|
3164
|
-
name: this.generatePatternName(incidents[0]),
|
|
3165
|
-
description: `Auto-suggested pattern from ${incidents.length} similar incidents`,
|
|
3166
|
-
pattern: {
|
|
3167
|
-
symbols,
|
|
3168
|
-
errorContains: errorKeywords.length > 0 ? errorKeywords : void 0,
|
|
3169
|
-
missingSignals: missingSignals.length > 0 ? missingSignals : void 0
|
|
3170
|
-
},
|
|
3171
|
-
resolution: {
|
|
3172
|
-
description: "Resolution approach based on previous resolutions",
|
|
3173
|
-
strategy: this.inferStrategy(incidents),
|
|
3174
|
-
priority: this.getPriorityFromCount(incidents.length)
|
|
3175
|
-
},
|
|
3176
|
-
source: "suggested",
|
|
3177
|
-
private: false,
|
|
3178
|
-
tags: this.generateTagsFromIncidents(incidents)
|
|
3179
|
-
};
|
|
3180
|
-
}
|
|
3181
|
-
/**
|
|
3182
|
-
* Build symbol criteria for pattern, adding wildcards where appropriate
|
|
3183
|
-
*/
|
|
3184
|
-
buildSymbolCriteria(symbols) {
|
|
3185
|
-
const criteria = {};
|
|
3186
|
-
for (const [key, value] of Object.entries(symbols)) {
|
|
3187
|
-
if (value) {
|
|
3188
|
-
criteria[key] = value;
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
return criteria;
|
|
3192
|
-
}
|
|
3193
|
-
/**
|
|
3194
|
-
* Extract keywords from error message
|
|
3195
|
-
*/
|
|
3196
|
-
extractErrorKeywords(message) {
|
|
3197
|
-
const stopWords = /* @__PURE__ */ new Set([
|
|
3198
|
-
"the",
|
|
3199
|
-
"a",
|
|
3200
|
-
"an",
|
|
3201
|
-
"is",
|
|
3202
|
-
"are",
|
|
3203
|
-
"was",
|
|
3204
|
-
"were",
|
|
3205
|
-
"in",
|
|
3206
|
-
"on",
|
|
3207
|
-
"at",
|
|
3208
|
-
"to",
|
|
3209
|
-
"for",
|
|
3210
|
-
"of",
|
|
3211
|
-
"with",
|
|
3212
|
-
"and",
|
|
3213
|
-
"or",
|
|
3214
|
-
"but",
|
|
3215
|
-
"not",
|
|
3216
|
-
"no",
|
|
3217
|
-
"be",
|
|
3218
|
-
"been",
|
|
3219
|
-
"have",
|
|
3220
|
-
"has",
|
|
3221
|
-
"had",
|
|
3222
|
-
"do",
|
|
3223
|
-
"does",
|
|
3224
|
-
"did"
|
|
3225
|
-
]);
|
|
3226
|
-
const words = message.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
|
|
3227
|
-
const unique = [...new Set(words)];
|
|
3228
|
-
return unique.slice(0, 5);
|
|
3229
|
-
}
|
|
3230
|
-
/**
|
|
3231
|
-
* Extract common error keywords from multiple incidents
|
|
3232
|
-
*/
|
|
3233
|
-
extractCommonErrorKeywords(incidents) {
|
|
3234
|
-
const wordCounts = /* @__PURE__ */ new Map();
|
|
3235
|
-
for (const incident of incidents) {
|
|
3236
|
-
const keywords = this.extractErrorKeywords(incident.error.message);
|
|
3237
|
-
for (const keyword of keywords) {
|
|
3238
|
-
wordCounts.set(keyword, (wordCounts.get(keyword) || 0) + 1);
|
|
3239
|
-
}
|
|
3240
|
-
}
|
|
3241
|
-
const threshold = Math.ceil(incidents.length * 0.5);
|
|
3242
|
-
return Array.from(wordCounts.entries()).filter(([, count]) => count >= threshold).map(([word]) => word).slice(0, 5);
|
|
3243
|
-
}
|
|
3244
|
-
/**
|
|
3245
|
-
* Extract symbols common to all incidents
|
|
3246
|
-
*/
|
|
3247
|
-
extractCommonSymbols(incidents) {
|
|
3248
|
-
if (incidents.length === 0) return {};
|
|
3249
|
-
const first = incidents[0].symbols;
|
|
3250
|
-
const common = {};
|
|
3251
|
-
for (const [key, value] of Object.entries(first)) {
|
|
3252
|
-
if (!value) continue;
|
|
3253
|
-
const allMatch = incidents.every(
|
|
3254
|
-
(i) => i.symbols[key] === value
|
|
3255
|
-
);
|
|
3256
|
-
if (allMatch) {
|
|
3257
|
-
common[key] = value;
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
return common;
|
|
3261
|
-
}
|
|
3262
|
-
/**
|
|
3263
|
-
* Extract missing signals common to multiple incidents
|
|
3264
|
-
*/
|
|
3265
|
-
extractCommonMissingSignals(incidents) {
|
|
3266
|
-
const signalCounts = /* @__PURE__ */ new Map();
|
|
3267
|
-
for (const incident of incidents) {
|
|
3268
|
-
if (!incident.flowPosition?.missing) continue;
|
|
3269
|
-
for (const signal of incident.flowPosition.missing) {
|
|
3270
|
-
signalCounts.set(signal, (signalCounts.get(signal) || 0) + 1);
|
|
3271
|
-
}
|
|
3272
|
-
}
|
|
3273
|
-
const threshold = Math.ceil(incidents.length * 0.5);
|
|
3274
|
-
return Array.from(signalCounts.entries()).filter(([, count]) => count >= threshold).map(([signal]) => signal);
|
|
3275
|
-
}
|
|
3276
|
-
/**
|
|
3277
|
-
* Generate a pattern ID from incident
|
|
3278
|
-
*/
|
|
3279
|
-
generatePatternId(incident) {
|
|
3280
|
-
const parts = [];
|
|
3281
|
-
if (incident.symbols.gate) {
|
|
3282
|
-
parts.push(incident.symbols.gate.replace(/[^a-z0-9]/gi, ""));
|
|
3283
|
-
} else if (incident.symbols.feature) {
|
|
3284
|
-
parts.push(incident.symbols.feature.replace(/[^a-z0-9]/gi, ""));
|
|
3285
|
-
} else if (incident.symbols.component) {
|
|
3286
|
-
parts.push(incident.symbols.component.replace(/[^a-z0-9]/gi, ""));
|
|
3287
|
-
} else if (incident.symbols.integration) {
|
|
3288
|
-
parts.push(incident.symbols.integration.replace(/[^a-z0-9]/gi, ""));
|
|
3289
|
-
} else {
|
|
3290
|
-
parts.push("unknown");
|
|
3291
|
-
}
|
|
3292
|
-
const errorType = incident.error.type?.toLowerCase() || "error";
|
|
3293
|
-
parts.push(errorType.replace(/[^a-z0-9]/gi, ""));
|
|
3294
|
-
parts.push(String(Date.now() % 1e3).padStart(3, "0"));
|
|
3295
|
-
return parts.join("-");
|
|
3296
|
-
}
|
|
3297
|
-
/**
|
|
3298
|
-
* Generate a human-readable pattern name
|
|
3299
|
-
*/
|
|
3300
|
-
generatePatternName(incident) {
|
|
3301
|
-
const parts = [];
|
|
3302
|
-
if (incident.symbols.feature) {
|
|
3303
|
-
parts.push(
|
|
3304
|
-
incident.symbols.feature.replace("@", "").replace(/-/g, " ")
|
|
3305
|
-
);
|
|
3306
|
-
}
|
|
3307
|
-
if (incident.symbols.gate) {
|
|
3308
|
-
parts.push("gate " + incident.symbols.gate.replace("^", ""));
|
|
3309
|
-
}
|
|
3310
|
-
if (incident.error.type) {
|
|
3311
|
-
parts.push(incident.error.type);
|
|
3312
|
-
}
|
|
3313
|
-
if (parts.length === 0) {
|
|
3314
|
-
return "Unnamed Pattern";
|
|
3315
|
-
}
|
|
3316
|
-
const name = parts.join(" - ");
|
|
3317
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
3318
|
-
}
|
|
3319
|
-
/**
|
|
3320
|
-
* Generate tags from incident
|
|
3321
|
-
*/
|
|
3322
|
-
generateTags(incident) {
|
|
3323
|
-
const tags = [];
|
|
3324
|
-
if (incident.symbols.feature) {
|
|
3325
|
-
tags.push("feature");
|
|
3326
|
-
}
|
|
3327
|
-
if (incident.symbols.gate) {
|
|
3328
|
-
tags.push("gate");
|
|
3329
|
-
}
|
|
3330
|
-
if (incident.symbols.integration) {
|
|
3331
|
-
tags.push("integration");
|
|
3332
|
-
tags.push(incident.symbols.integration.replace("&", ""));
|
|
3333
|
-
}
|
|
3334
|
-
if (incident.error.type) {
|
|
3335
|
-
tags.push(incident.error.type.toLowerCase());
|
|
3336
|
-
}
|
|
3337
|
-
tags.push(incident.environment);
|
|
3338
|
-
return [...new Set(tags)].slice(0, 5);
|
|
3339
|
-
}
|
|
3340
|
-
/**
|
|
3341
|
-
* Generate tags from incident group
|
|
3342
|
-
*/
|
|
3343
|
-
generateTagsFromGroup(group) {
|
|
3344
|
-
const tags = ["grouped"];
|
|
3345
|
-
if (group.commonSymbols.feature) {
|
|
3346
|
-
tags.push("feature");
|
|
3347
|
-
}
|
|
3348
|
-
if (group.commonSymbols.gate) {
|
|
3349
|
-
tags.push("gate");
|
|
3350
|
-
}
|
|
3351
|
-
if (group.commonSymbols.integration) {
|
|
3352
|
-
tags.push("integration");
|
|
3353
|
-
}
|
|
3354
|
-
for (const env of group.environments) {
|
|
3355
|
-
tags.push(env);
|
|
3356
|
-
}
|
|
3357
|
-
return [...new Set(tags)].slice(0, 5);
|
|
3358
|
-
}
|
|
3359
|
-
/**
|
|
3360
|
-
* Generate tags from multiple incidents
|
|
3361
|
-
*/
|
|
3362
|
-
generateTagsFromIncidents(incidents) {
|
|
3363
|
-
const tagCounts = /* @__PURE__ */ new Map();
|
|
3364
|
-
for (const incident of incidents) {
|
|
3365
|
-
const tags = this.generateTags(incident);
|
|
3366
|
-
for (const tag of tags) {
|
|
3367
|
-
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
|
|
3368
|
-
}
|
|
3369
|
-
}
|
|
3370
|
-
return Array.from(tagCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([tag]) => tag);
|
|
3371
|
-
}
|
|
3372
|
-
/**
|
|
3373
|
-
* Get symbol signature for grouping
|
|
3374
|
-
*/
|
|
3375
|
-
getSymbolSignature(symbols) {
|
|
3376
|
-
const parts = [];
|
|
3377
|
-
if (symbols.feature) parts.push(`f:${symbols.feature}`);
|
|
3378
|
-
if (symbols.component) parts.push(`c:${symbols.component}`);
|
|
3379
|
-
if (symbols.flow) parts.push(`fl:${symbols.flow}`);
|
|
3380
|
-
if (symbols.gate) parts.push(`g:${symbols.gate}`);
|
|
3381
|
-
if (symbols.integration) parts.push(`i:${symbols.integration}`);
|
|
3382
|
-
return parts.sort().join("|");
|
|
3383
|
-
}
|
|
3384
|
-
/**
|
|
3385
|
-
* Check if there's already a pattern matching this incident
|
|
3386
|
-
*/
|
|
3387
|
-
hasMatchingPattern(incident) {
|
|
3388
|
-
const patterns = this.storage.getAllPatterns({ includePrivate: true });
|
|
3389
|
-
for (const pattern of patterns) {
|
|
3390
|
-
let matchCount = 0;
|
|
3391
|
-
const symbolTypes = [
|
|
3392
|
-
"feature",
|
|
3393
|
-
"component",
|
|
3394
|
-
"flow",
|
|
3395
|
-
"gate",
|
|
3396
|
-
"signal",
|
|
3397
|
-
"integration"
|
|
3398
|
-
];
|
|
3399
|
-
for (const type of symbolTypes) {
|
|
3400
|
-
const patternValue = pattern.pattern.symbols[type];
|
|
3401
|
-
const incidentValue = incident.symbols[type];
|
|
3402
|
-
if (patternValue && incidentValue && patternValue === incidentValue) {
|
|
3403
|
-
matchCount++;
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
|
-
if (matchCount >= 2) {
|
|
3407
|
-
return true;
|
|
3408
|
-
}
|
|
3409
|
-
}
|
|
3410
|
-
return false;
|
|
3411
|
-
}
|
|
3412
|
-
/**
|
|
3413
|
-
* Infer resolution strategy from incident error patterns and context.
|
|
3414
|
-
* Uses keyword heuristics across all incident messages to pick the
|
|
3415
|
-
* most likely resolution approach.
|
|
3416
|
-
*/
|
|
3417
|
-
inferStrategy(incidents) {
|
|
3418
|
-
const messages = incidents.map((i) => i.error.message.toLowerCase());
|
|
3419
|
-
const hasKeyword = (keywords) => messages.some((m) => keywords.some((k) => m.includes(k)));
|
|
3420
|
-
if (hasKeyword(["revert", "rollback", "regression", "broke after deploy", "since deploy"])) {
|
|
3421
|
-
return "rollback";
|
|
3422
|
-
}
|
|
3423
|
-
if (hasKeyword(["config", "environment variable", "env var", "missing key", "secret", "credential"])) {
|
|
3424
|
-
return "config-change";
|
|
3425
|
-
}
|
|
3426
|
-
if (hasKeyword(["out of memory", "oom", "heap", "memory limit", "capacity", "too many connections", "pool exhausted"])) {
|
|
3427
|
-
return "scale-up";
|
|
3428
|
-
}
|
|
3429
|
-
if (hasKeyword(["timeout", "network", "econnrefused", "econnreset", "dns", "socket hang up"])) {
|
|
3430
|
-
return "retry";
|
|
3431
|
-
}
|
|
3432
|
-
if (hasKeyword(["unavailable", "service down", "circuit breaker", "fallback", "503", "502"])) {
|
|
3433
|
-
return "fallback";
|
|
3434
|
-
}
|
|
3435
|
-
if (hasKeyword(["validation", "invalid", "required", "constraint", "duplicate", "not found", "404"])) {
|
|
3436
|
-
return "fix-data";
|
|
3437
|
-
}
|
|
3438
|
-
if (hasKeyword(["permission", "forbidden", "403", "401", "unauthorized", "access denied"])) {
|
|
3439
|
-
return "escalate";
|
|
3440
|
-
}
|
|
3441
|
-
const uniqueTypes = new Set(incidents.map((i) => i.error.type).filter(Boolean));
|
|
3442
|
-
if (uniqueTypes.size > 2) {
|
|
3443
|
-
return "investigate";
|
|
3444
|
-
}
|
|
3445
|
-
return "fix-code";
|
|
3446
|
-
}
|
|
3447
|
-
/**
|
|
3448
|
-
* Get priority based on occurrence count
|
|
3449
|
-
*/
|
|
3450
|
-
getPriorityFromCount(count) {
|
|
3451
|
-
if (count >= 20) return "critical";
|
|
3452
|
-
if (count >= 10) return "high";
|
|
3453
|
-
if (count >= 5) return "medium";
|
|
3454
|
-
return "low";
|
|
3455
|
-
}
|
|
3456
|
-
};
|
|
3457
|
-
|
|
3458
|
-
// ../sentinel/src/seeds/loader.ts
|
|
3459
|
-
import * as path2 from "path";
|
|
3460
|
-
import * as fs2 from "fs";
|
|
3461
|
-
import { fileURLToPath } from "url";
|
|
3462
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
3463
|
-
var __dirname = path2.dirname(__filename);
|
|
3464
|
-
function loadUniversalPatterns() {
|
|
3465
|
-
const filePath = path2.join(__dirname, "universal-patterns.json");
|
|
3466
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
3467
|
-
return JSON.parse(content);
|
|
3468
|
-
}
|
|
3469
|
-
function loadParadigmPatterns() {
|
|
3470
|
-
const filePath = path2.join(__dirname, "paradigm-patterns.json");
|
|
3471
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
3472
|
-
return JSON.parse(content);
|
|
3473
|
-
}
|
|
3474
|
-
function loadAllSeedPatterns() {
|
|
3475
|
-
const universal = loadUniversalPatterns();
|
|
3476
|
-
const paradigm = loadParadigmPatterns();
|
|
3477
|
-
return {
|
|
3478
|
-
version: "1.0.0",
|
|
3479
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3480
|
-
patterns: [...universal.patterns, ...paradigm.patterns]
|
|
3481
|
-
};
|
|
3482
|
-
}
|
|
3483
|
-
|
|
3484
|
-
// ../sentinel/src/mcp.ts
|
|
3485
|
-
var storage = null;
|
|
3486
|
-
var initialized = false;
|
|
3487
|
-
function getStorage() {
|
|
3488
|
-
if (!storage) {
|
|
3489
|
-
storage = new SentinelStorage();
|
|
3490
|
-
}
|
|
3491
|
-
if (!initialized) {
|
|
3492
|
-
try {
|
|
3493
|
-
const { patterns } = loadAllSeedPatterns();
|
|
3494
|
-
for (const pattern of patterns) {
|
|
3495
|
-
try {
|
|
3496
|
-
storage.addPattern(pattern);
|
|
3497
|
-
} catch {
|
|
3498
|
-
}
|
|
3499
|
-
}
|
|
3500
|
-
} catch {
|
|
3501
|
-
}
|
|
3502
|
-
initialized = true;
|
|
3503
|
-
}
|
|
3504
|
-
return storage;
|
|
3505
|
-
}
|
|
3506
|
-
function getToolsList() {
|
|
3507
|
-
return [
|
|
3508
|
-
{
|
|
3509
|
-
name: "sentinel_triage",
|
|
3510
|
-
description: "View and filter incidents with pattern matches. Returns recent errors with symbolic context and resolution suggestions.",
|
|
3511
|
-
annotations: {
|
|
3512
|
-
readOnlyHint: true,
|
|
3513
|
-
destructiveHint: false
|
|
3514
|
-
},
|
|
3515
|
-
inputSchema: {
|
|
3516
|
-
type: "object",
|
|
3517
|
-
properties: {
|
|
3518
|
-
symbol: { type: "string", description: "Filter by symbol (e.g., #checkout, ^auth)" },
|
|
3519
|
-
status: {
|
|
3520
|
-
type: "string",
|
|
3521
|
-
enum: ["open", "investigating", "resolved", "wont-fix", "all"],
|
|
3522
|
-
description: "Filter by status (default: all)"
|
|
3523
|
-
},
|
|
3524
|
-
environment: { type: "string", description: "Filter by environment" },
|
|
3525
|
-
search: { type: "string", description: "Search in error messages" },
|
|
3526
|
-
limit: { type: "number", description: "Max results (default: 10)" }
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
},
|
|
3530
|
-
{
|
|
3531
|
-
name: "sentinel_show",
|
|
3532
|
-
description: "Get full details of a specific incident including timeline and matched patterns.",
|
|
3533
|
-
annotations: {
|
|
3534
|
-
readOnlyHint: true,
|
|
3535
|
-
destructiveHint: false
|
|
3536
|
-
},
|
|
3537
|
-
inputSchema: {
|
|
3538
|
-
type: "object",
|
|
3539
|
-
properties: {
|
|
3540
|
-
incidentId: { type: "string", description: "Incident ID (e.g., INC-001)" },
|
|
3541
|
-
includeTimeline: { type: "boolean", description: "Include flow timeline" },
|
|
3542
|
-
includeSimilar: { type: "boolean", description: "Include similar incidents" }
|
|
3543
|
-
},
|
|
3544
|
-
required: ["incidentId"]
|
|
3545
|
-
}
|
|
3546
|
-
},
|
|
3547
|
-
{
|
|
3548
|
-
name: "sentinel_resolve",
|
|
3549
|
-
description: "Mark an incident as resolved with optional pattern and commit reference.",
|
|
3550
|
-
annotations: {
|
|
3551
|
-
readOnlyHint: false,
|
|
3552
|
-
destructiveHint: false
|
|
3553
|
-
},
|
|
3554
|
-
inputSchema: {
|
|
3555
|
-
type: "object",
|
|
3556
|
-
properties: {
|
|
3557
|
-
incidentId: { type: "string", description: "Incident ID" },
|
|
3558
|
-
patternId: { type: "string", description: "Pattern that led to resolution" },
|
|
3559
|
-
commitHash: { type: "string", description: "Fix commit hash" },
|
|
3560
|
-
prUrl: { type: "string", description: "PR URL" },
|
|
3561
|
-
notes: { type: "string", description: "Resolution notes" },
|
|
3562
|
-
wontFix: { type: "boolean", description: "Mark as wont-fix instead of resolved" }
|
|
3563
|
-
},
|
|
3564
|
-
required: ["incidentId"]
|
|
3565
|
-
}
|
|
3566
|
-
},
|
|
3567
|
-
{
|
|
3568
|
-
name: "sentinel_patterns",
|
|
3569
|
-
description: "List and filter failure patterns with confidence scores.",
|
|
3570
|
-
annotations: {
|
|
3571
|
-
readOnlyHint: true,
|
|
3572
|
-
destructiveHint: false
|
|
3573
|
-
},
|
|
3574
|
-
inputSchema: {
|
|
3575
|
-
type: "object",
|
|
3576
|
-
properties: {
|
|
3577
|
-
symbol: { type: "string", description: "Filter patterns for this symbol" },
|
|
3578
|
-
minConfidence: { type: "number", description: "Minimum confidence score" },
|
|
3579
|
-
source: {
|
|
3580
|
-
type: "string",
|
|
3581
|
-
enum: ["manual", "suggested", "imported", "community"],
|
|
3582
|
-
description: "Filter by source"
|
|
3583
|
-
}
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
},
|
|
3587
|
-
{
|
|
3588
|
-
name: "sentinel_add_pattern",
|
|
3589
|
-
description: "Create a new failure pattern.",
|
|
3590
|
-
annotations: {
|
|
3591
|
-
readOnlyHint: false,
|
|
3592
|
-
destructiveHint: false
|
|
3593
|
-
},
|
|
3594
|
-
inputSchema: {
|
|
3595
|
-
type: "object",
|
|
3596
|
-
properties: {
|
|
3597
|
-
id: { type: "string", description: "Pattern ID (kebab-case)" },
|
|
3598
|
-
name: { type: "string", description: "Human-readable name" },
|
|
3599
|
-
description: { type: "string", description: "What this pattern matches" },
|
|
3600
|
-
pattern: {
|
|
3601
|
-
type: "object",
|
|
3602
|
-
properties: {
|
|
3603
|
-
symbols: { type: "object", description: "Symbol criteria" },
|
|
3604
|
-
errorContains: { type: "array", items: { type: "string" }, description: "Error keywords" },
|
|
3605
|
-
missingSignals: { type: "array", items: { type: "string" }, description: "Expected missing signals" }
|
|
3606
|
-
}
|
|
3607
|
-
},
|
|
3608
|
-
resolution: {
|
|
3609
|
-
type: "object",
|
|
3610
|
-
properties: {
|
|
3611
|
-
description: { type: "string", description: "Resolution steps" },
|
|
3612
|
-
strategy: {
|
|
3613
|
-
type: "string",
|
|
3614
|
-
enum: ["retry", "fallback", "fix-data", "fix-code", "ignore", "escalate"]
|
|
3615
|
-
},
|
|
3616
|
-
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
3617
|
-
codeHint: { type: "string", description: "Code fix hint" }
|
|
3618
|
-
},
|
|
3619
|
-
required: ["description", "strategy"]
|
|
3620
|
-
},
|
|
3621
|
-
tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" }
|
|
3622
|
-
},
|
|
3623
|
-
required: ["id", "name", "pattern", "resolution"]
|
|
3624
|
-
}
|
|
3625
|
-
},
|
|
3626
|
-
{
|
|
3627
|
-
name: "sentinel_record",
|
|
3628
|
-
description: "Manually record a new incident.",
|
|
3629
|
-
annotations: {
|
|
3630
|
-
readOnlyHint: false,
|
|
3631
|
-
destructiveHint: false
|
|
3632
|
-
},
|
|
3633
|
-
inputSchema: {
|
|
3634
|
-
type: "object",
|
|
3635
|
-
properties: {
|
|
3636
|
-
error: {
|
|
3637
|
-
type: "object",
|
|
3638
|
-
properties: {
|
|
3639
|
-
message: { type: "string", description: "Error message" },
|
|
3640
|
-
stack: { type: "string", description: "Stack trace" },
|
|
3641
|
-
code: { type: "string", description: "Error code" },
|
|
3642
|
-
type: { type: "string", description: "Error type" }
|
|
3643
|
-
},
|
|
3644
|
-
required: ["message"]
|
|
3645
|
-
},
|
|
3646
|
-
symbols: {
|
|
3647
|
-
type: "object",
|
|
3648
|
-
properties: {
|
|
3649
|
-
component: { type: "string" },
|
|
3650
|
-
flow: { type: "string" },
|
|
3651
|
-
gate: { type: "string" },
|
|
3652
|
-
signal: { type: "string" }
|
|
3653
|
-
}
|
|
3654
|
-
},
|
|
3655
|
-
environment: { type: "string", description: "Environment (required)" },
|
|
3656
|
-
service: { type: "string", description: "Service name" },
|
|
3657
|
-
version: { type: "string", description: "App version" },
|
|
3658
|
-
flowPosition: {
|
|
3659
|
-
type: "object",
|
|
3660
|
-
properties: {
|
|
3661
|
-
flowId: { type: "string" },
|
|
3662
|
-
expected: { type: "array", items: { type: "string" } },
|
|
3663
|
-
actual: { type: "array", items: { type: "string" } },
|
|
3664
|
-
missing: { type: "array", items: { type: "string" } },
|
|
3665
|
-
failedAt: { type: "string" }
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
},
|
|
3669
|
-
required: ["error", "symbols", "environment"]
|
|
3670
|
-
}
|
|
3671
|
-
},
|
|
3672
|
-
{
|
|
3673
|
-
name: "sentinel_stats",
|
|
3674
|
-
description: "Get statistics and health metrics.",
|
|
3675
|
-
annotations: {
|
|
3676
|
-
readOnlyHint: true,
|
|
3677
|
-
destructiveHint: false
|
|
3678
|
-
},
|
|
3679
|
-
inputSchema: {
|
|
3680
|
-
type: "object",
|
|
3681
|
-
properties: {
|
|
3682
|
-
period: { type: "string", description: "Period: 1d, 7d, 30d, 90d (default: 7d)" },
|
|
3683
|
-
symbol: { type: "string", description: "Get health for specific symbol" }
|
|
3684
|
-
}
|
|
3685
|
-
}
|
|
3686
|
-
},
|
|
3687
|
-
{
|
|
3688
|
-
name: "sentinel_suggest_pattern",
|
|
3689
|
-
description: "Get AI suggestions for patterns based on incidents.",
|
|
3690
|
-
annotations: {
|
|
3691
|
-
readOnlyHint: true,
|
|
3692
|
-
destructiveHint: false
|
|
3693
|
-
},
|
|
3694
|
-
inputSchema: {
|
|
3695
|
-
type: "object",
|
|
3696
|
-
properties: {
|
|
3697
|
-
incidentId: { type: "string", description: "Suggest from specific incident" },
|
|
3698
|
-
minOccurrences: { type: "number", description: "Min similar incidents for suggestion" }
|
|
3699
|
-
}
|
|
3700
|
-
}
|
|
3701
|
-
},
|
|
3702
|
-
// ─── Observability Tools ──────────────────────────────────────
|
|
3703
|
-
{
|
|
3704
|
-
name: "sentinel_logs",
|
|
3705
|
-
description: "Query structured logs from connected apps. Filters by level, symbol, service, search text, time range.",
|
|
3706
|
-
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
3707
|
-
inputSchema: {
|
|
3708
|
-
type: "object",
|
|
3709
|
-
properties: {
|
|
3710
|
-
level: { type: "string", enum: ["debug", "info", "warn", "error"], description: "Filter by log level" },
|
|
3711
|
-
symbol: { type: "string", description: "Filter by symbol (partial match)" },
|
|
3712
|
-
service: { type: "string", description: "Filter by service name" },
|
|
3713
|
-
search: { type: "string", description: "Search in log messages" },
|
|
3714
|
-
since: { type: "string", description: "ISO timestamp \u2014 logs after this time" },
|
|
3715
|
-
sessionId: { type: "string", description: "Filter by session ID" },
|
|
3716
|
-
correlationId: { type: "string", description: "Filter by correlation ID" },
|
|
3717
|
-
limit: { type: "number", description: "Max results (default: 50)" }
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
},
|
|
3721
|
-
{
|
|
3722
|
-
name: "sentinel_services",
|
|
3723
|
-
description: "List all registered services with version, environment, and last-seen time.",
|
|
3724
|
-
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
3725
|
-
inputSchema: {
|
|
3726
|
-
type: "object",
|
|
3727
|
-
properties: {}
|
|
3728
|
-
}
|
|
3729
|
-
},
|
|
3730
|
-
{
|
|
3731
|
-
name: "sentinel_app_state",
|
|
3732
|
-
description: "Get live app state snapshots. Shows current state, active flows, and held gates for connected services.",
|
|
3733
|
-
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
3734
|
-
inputSchema: {
|
|
3735
|
-
type: "object",
|
|
3736
|
-
properties: {
|
|
3737
|
-
service: { type: "string", description: "Filter by service name" }
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
3740
|
-
},
|
|
3741
|
-
{
|
|
3742
|
-
name: "sentinel_validate_symbol",
|
|
3743
|
-
description: "Check if a symbol has been used in logs. Returns usage count and suggestions.",
|
|
3744
|
-
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
3745
|
-
inputSchema: {
|
|
3746
|
-
type: "object",
|
|
3747
|
-
properties: {
|
|
3748
|
-
symbol: { type: "string", description: "Symbol to validate (e.g., #checkout, ^auth)" }
|
|
3749
|
-
},
|
|
3750
|
-
required: ["symbol"]
|
|
3751
|
-
}
|
|
3752
|
-
},
|
|
3753
|
-
{
|
|
3754
|
-
name: "sentinel_flow_activity",
|
|
3755
|
-
description: "Get recent flow events \u2014 which flow nodes were hit, in what order, by which service.",
|
|
3756
|
-
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
3757
|
-
inputSchema: {
|
|
3758
|
-
type: "object",
|
|
3759
|
-
properties: {
|
|
3760
|
-
flowId: { type: "string", description: "Filter by flow ID (e.g., $checkout-flow)" },
|
|
3761
|
-
service: { type: "string", description: "Filter by service name" },
|
|
3762
|
-
since: { type: "string", description: "ISO timestamp \u2014 events after this time" }
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
},
|
|
3766
|
-
{
|
|
3767
|
-
name: "sentinel_metrics",
|
|
3768
|
-
description: "Query metrics (counters, gauges, histograms) from connected apps. Supports filtering and aggregation.",
|
|
3769
|
-
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
3770
|
-
inputSchema: {
|
|
3771
|
-
type: "object",
|
|
3772
|
-
properties: {
|
|
3773
|
-
name: { type: "string", description: "Metric name filter" },
|
|
3774
|
-
type: { type: "string", enum: ["counter", "gauge", "histogram"], description: "Metric type filter" },
|
|
3775
|
-
service: { type: "string", description: "Service name filter" },
|
|
3776
|
-
since: { type: "string", description: "ISO timestamp \u2014 metrics after this time" },
|
|
3777
|
-
aggregate: { type: "boolean", description: "If true and name is provided, return aggregation instead of raw data" },
|
|
3778
|
-
limit: { type: "number", description: "Max results (default: 50)" }
|
|
3779
|
-
}
|
|
3780
|
-
}
|
|
3781
|
-
},
|
|
3782
|
-
{
|
|
3783
|
-
name: "sentinel_traces",
|
|
3784
|
-
description: "Query distributed traces across services. Shows span trees with timing, status, and service hops.",
|
|
3785
|
-
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
3786
|
-
inputSchema: {
|
|
3787
|
-
type: "object",
|
|
3788
|
-
properties: {
|
|
3789
|
-
traceId: { type: "string", description: "Get a specific trace by ID" },
|
|
3790
|
-
service: { type: "string", description: "Filter by service name" },
|
|
3791
|
-
symbol: { type: "string", description: "Filter by symbol" },
|
|
3792
|
-
since: { type: "string", description: "ISO timestamp \u2014 traces after this time" },
|
|
3793
|
-
limit: { type: "number", description: "Max traces (default: 10, max: 20)" }
|
|
3794
|
-
}
|
|
3795
|
-
}
|
|
3796
|
-
}
|
|
3797
|
-
];
|
|
3798
|
-
}
|
|
3799
|
-
async function handleTool(name, args) {
|
|
3800
|
-
const store = getStorage();
|
|
3801
|
-
const matcher = new PatternMatcher(store);
|
|
3802
|
-
switch (name) {
|
|
3803
|
-
case "sentinel_triage": {
|
|
3804
|
-
const { symbol, status = "all", environment, search, limit = 10 } = args;
|
|
3805
|
-
const incidents = store.getRecentIncidents({
|
|
3806
|
-
limit,
|
|
3807
|
-
status,
|
|
3808
|
-
symbol,
|
|
3809
|
-
environment,
|
|
3810
|
-
search
|
|
3811
|
-
});
|
|
3812
|
-
if (incidents.length === 0) {
|
|
3813
|
-
return JSON.stringify({
|
|
3814
|
-
count: 0,
|
|
3815
|
-
incidents: [],
|
|
3816
|
-
tip: "No incidents recorded yet. Use sentinel_record to create incidents or integrate the SDK."
|
|
3817
|
-
}, null, 2);
|
|
3818
|
-
}
|
|
3819
|
-
const results = incidents.map((incident) => {
|
|
3820
|
-
const matches = matcher.match(incident, { maxResults: 3 });
|
|
3821
|
-
return {
|
|
3822
|
-
id: incident.id,
|
|
3823
|
-
timestamp: incident.timestamp,
|
|
3824
|
-
status: incident.status,
|
|
3825
|
-
error: incident.error.message,
|
|
3826
|
-
symbols: incident.symbols,
|
|
3827
|
-
environment: incident.environment,
|
|
3828
|
-
matches: matches.map((m) => ({
|
|
3829
|
-
patternId: m.pattern.id,
|
|
3830
|
-
name: m.pattern.name,
|
|
3831
|
-
confidence: m.confidence,
|
|
3832
|
-
strategy: m.pattern.resolution.strategy,
|
|
3833
|
-
description: m.pattern.resolution.description
|
|
3834
|
-
}))
|
|
3835
|
-
};
|
|
3836
|
-
});
|
|
3837
|
-
return JSON.stringify({ count: results.length, incidents: results }, null, 2);
|
|
3838
|
-
}
|
|
3839
|
-
case "sentinel_show": {
|
|
3840
|
-
const { incidentId, includeTimeline, includeSimilar } = args;
|
|
3841
|
-
const incident = store.getIncident(incidentId);
|
|
3842
|
-
if (!incident) {
|
|
3843
|
-
return JSON.stringify({ error: `Incident ${incidentId} not found` });
|
|
3844
|
-
}
|
|
3845
|
-
const matches = matcher.match(incident, { maxResults: 5 });
|
|
3846
|
-
const result = {
|
|
3847
|
-
incident,
|
|
3848
|
-
matches: matches.map((m) => ({
|
|
3849
|
-
patternId: m.pattern.id,
|
|
3850
|
-
name: m.pattern.name,
|
|
3851
|
-
confidence: m.confidence,
|
|
3852
|
-
matchedCriteria: m.matchedCriteria,
|
|
3853
|
-
resolution: m.pattern.resolution
|
|
3854
|
-
}))
|
|
3855
|
-
};
|
|
3856
|
-
if (includeTimeline && incident.flowPosition) {
|
|
3857
|
-
const timeline = new TimelineBuilder().build(incident);
|
|
3858
|
-
if (timeline) {
|
|
3859
|
-
result.timeline = new TimelineBuilder().renderStructured(timeline);
|
|
3860
|
-
}
|
|
3861
|
-
}
|
|
3862
|
-
if (includeSimilar) {
|
|
3863
|
-
const similar = store.getRecentIncidents({ symbol: Object.values(incident.symbols)[0], limit: 5 }).filter((i) => i.id !== incidentId);
|
|
3864
|
-
result.similar = similar.map((i) => ({
|
|
3865
|
-
id: i.id,
|
|
3866
|
-
error: i.error.message,
|
|
3867
|
-
status: i.status
|
|
3868
|
-
}));
|
|
3869
|
-
}
|
|
3870
|
-
return JSON.stringify(result, null, 2);
|
|
3871
|
-
}
|
|
3872
|
-
case "sentinel_resolve": {
|
|
3873
|
-
const { incidentId, patternId, commitHash, prUrl, notes, wontFix } = args;
|
|
3874
|
-
const incident = store.getIncident(incidentId);
|
|
3875
|
-
if (!incident) {
|
|
3876
|
-
return JSON.stringify({ error: `Incident ${incidentId} not found` });
|
|
3877
|
-
}
|
|
3878
|
-
if (wontFix) {
|
|
3879
|
-
store.updateIncident(incidentId, {
|
|
3880
|
-
status: "wont-fix",
|
|
3881
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3882
|
-
resolvedBy: "manual",
|
|
3883
|
-
resolution: { notes }
|
|
3884
|
-
});
|
|
3885
|
-
return JSON.stringify({
|
|
3886
|
-
success: true,
|
|
3887
|
-
message: `Incident ${incidentId} marked as won't fix`
|
|
3888
|
-
});
|
|
3889
|
-
}
|
|
3890
|
-
store.recordResolution({ incidentId, patternId, commitHash, prUrl, notes });
|
|
3891
|
-
return JSON.stringify({
|
|
3892
|
-
success: true,
|
|
3893
|
-
message: `Incident ${incidentId} resolved`,
|
|
3894
|
-
patternId,
|
|
3895
|
-
commitHash,
|
|
3896
|
-
prUrl
|
|
3897
|
-
});
|
|
3898
|
-
}
|
|
3899
|
-
case "sentinel_patterns": {
|
|
3900
|
-
const { symbol, minConfidence, source } = args;
|
|
3901
|
-
const patterns = store.getAllPatterns({
|
|
3902
|
-
source,
|
|
3903
|
-
minConfidence,
|
|
3904
|
-
includePrivate: false
|
|
3905
|
-
});
|
|
3906
|
-
let filtered = patterns;
|
|
3907
|
-
if (symbol) {
|
|
3908
|
-
filtered = patterns.filter((p) => {
|
|
3909
|
-
const symbols = p.pattern.symbols;
|
|
3910
|
-
return Object.values(symbols).some((v) => {
|
|
3911
|
-
if (!v) return false;
|
|
3912
|
-
if (Array.isArray(v)) return v.includes(symbol) || v.some((s) => symbol.match(s.replace("*", ".*")));
|
|
3913
|
-
return v === symbol || symbol.match(v.replace("*", ".*"));
|
|
3914
|
-
});
|
|
3915
|
-
});
|
|
3916
|
-
}
|
|
3917
|
-
return JSON.stringify(
|
|
3918
|
-
{
|
|
3919
|
-
count: filtered.length,
|
|
3920
|
-
patterns: filtered.map((p) => ({
|
|
3921
|
-
id: p.id,
|
|
3922
|
-
name: p.name,
|
|
3923
|
-
description: p.description,
|
|
3924
|
-
confidence: p.confidence.score,
|
|
3925
|
-
resolution: p.resolution,
|
|
3926
|
-
tags: p.tags
|
|
3927
|
-
}))
|
|
3928
|
-
},
|
|
3929
|
-
null,
|
|
3930
|
-
2
|
|
3931
|
-
);
|
|
3932
|
-
}
|
|
3933
|
-
case "sentinel_add_pattern": {
|
|
3934
|
-
const { id, name: name2, description, pattern, resolution, tags } = args;
|
|
3935
|
-
const input = {
|
|
3936
|
-
id,
|
|
3937
|
-
name: name2,
|
|
3938
|
-
description: description || "",
|
|
3939
|
-
pattern: {
|
|
3940
|
-
symbols: pattern.symbols || {},
|
|
3941
|
-
errorContains: pattern.errorContains,
|
|
3942
|
-
missingSignals: pattern.missingSignals
|
|
3943
|
-
},
|
|
3944
|
-
resolution: {
|
|
3945
|
-
description: resolution.description,
|
|
3946
|
-
strategy: resolution.strategy,
|
|
3947
|
-
priority: resolution.priority || "medium",
|
|
3948
|
-
codeHint: resolution.codeHint
|
|
3949
|
-
},
|
|
3950
|
-
source: "manual",
|
|
3951
|
-
private: false,
|
|
3952
|
-
tags: tags || []
|
|
3953
|
-
};
|
|
3954
|
-
store.addPattern(input);
|
|
3955
|
-
return JSON.stringify({
|
|
3956
|
-
success: true,
|
|
3957
|
-
message: `Pattern ${id} created`,
|
|
3958
|
-
pattern: input
|
|
3959
|
-
});
|
|
3960
|
-
}
|
|
3961
|
-
case "sentinel_record": {
|
|
3962
|
-
const { error, symbols, environment, service, version, flowPosition } = args;
|
|
3963
|
-
const incidentId = store.recordIncident({
|
|
3964
|
-
error,
|
|
3965
|
-
symbols,
|
|
3966
|
-
environment,
|
|
3967
|
-
service,
|
|
3968
|
-
version,
|
|
3969
|
-
flowPosition
|
|
3970
|
-
});
|
|
3971
|
-
const incident = store.getIncident(incidentId);
|
|
3972
|
-
const matches = incident ? matcher.match(incident, { maxResults: 3 }) : [];
|
|
3973
|
-
return JSON.stringify(
|
|
3974
|
-
{
|
|
3975
|
-
success: true,
|
|
3976
|
-
incidentId,
|
|
3977
|
-
matches: matches.map((m) => ({
|
|
3978
|
-
patternId: m.pattern.id,
|
|
3979
|
-
confidence: m.confidence,
|
|
3980
|
-
resolution: m.pattern.resolution.description
|
|
3981
|
-
}))
|
|
3982
|
-
},
|
|
3983
|
-
null,
|
|
3984
|
-
2
|
|
3985
|
-
);
|
|
3986
|
-
}
|
|
3987
|
-
case "sentinel_stats": {
|
|
3988
|
-
const { period = "7d", symbol } = args;
|
|
3989
|
-
const calculator = new StatsCalculator(store);
|
|
3990
|
-
if (symbol) {
|
|
3991
|
-
const health = calculator.getSymbolHealth(symbol);
|
|
3992
|
-
return JSON.stringify({ symbol, health }, null, 2);
|
|
3993
|
-
}
|
|
3994
|
-
const match = period.match(/^(\d+)d$/);
|
|
3995
|
-
const periodDays = match ? parseInt(match[1], 10) : 7;
|
|
3996
|
-
const stats = calculator.getStats(periodDays);
|
|
3997
|
-
return JSON.stringify({ period: `${periodDays}d`, stats }, null, 2);
|
|
3998
|
-
}
|
|
3999
|
-
case "sentinel_suggest_pattern": {
|
|
4000
|
-
const { incidentId, minOccurrences } = args;
|
|
4001
|
-
const suggester = new PatternSuggester(store);
|
|
4002
|
-
if (incidentId) {
|
|
4003
|
-
const incident = store.getIncident(incidentId);
|
|
4004
|
-
if (!incident) {
|
|
4005
|
-
return JSON.stringify({ error: `Incident ${incidentId} not found` });
|
|
4006
|
-
}
|
|
4007
|
-
const suggestion = suggester.suggestFromIncident(incident);
|
|
4008
|
-
return JSON.stringify({ source: "incident", incidentId, suggestion }, null, 2);
|
|
4009
|
-
}
|
|
4010
|
-
const candidates = suggester.findPatternCandidates(minOccurrences || 3);
|
|
4011
|
-
return JSON.stringify(
|
|
4012
|
-
{
|
|
4013
|
-
source: "analysis",
|
|
4014
|
-
candidates: candidates.slice(0, 5).map((c) => ({
|
|
4015
|
-
occurrences: c.occurrenceCount,
|
|
4016
|
-
sampleIncidents: c.incidents.slice(0, 3).map((i) => i.id),
|
|
4017
|
-
suggestion: c.suggestedPattern
|
|
4018
|
-
}))
|
|
4019
|
-
},
|
|
4020
|
-
null,
|
|
4021
|
-
2
|
|
4022
|
-
);
|
|
4023
|
-
}
|
|
4024
|
-
// ─── Observability Tools ──────────────────────────────────────
|
|
4025
|
-
case "sentinel_logs": {
|
|
4026
|
-
const { level, symbol, service, search, since, sessionId, correlationId, limit = 50 } = args;
|
|
4027
|
-
const logs = store.queryLogs({
|
|
4028
|
-
level,
|
|
4029
|
-
symbol,
|
|
4030
|
-
service,
|
|
4031
|
-
search,
|
|
4032
|
-
since,
|
|
4033
|
-
sessionId,
|
|
4034
|
-
correlationId,
|
|
4035
|
-
limit
|
|
4036
|
-
});
|
|
4037
|
-
const total = store.getLogCount({ level, symbol, service, since });
|
|
4038
|
-
return JSON.stringify({
|
|
4039
|
-
count: logs.length,
|
|
4040
|
-
total,
|
|
4041
|
-
logs: logs.map((l) => ({
|
|
4042
|
-
timestamp: l.timestamp,
|
|
4043
|
-
level: l.level,
|
|
4044
|
-
symbol: l.symbol,
|
|
4045
|
-
service: l.service,
|
|
4046
|
-
message: l.message,
|
|
4047
|
-
data: l.data,
|
|
4048
|
-
sessionId: l.sessionId,
|
|
4049
|
-
correlationId: l.correlationId,
|
|
4050
|
-
durationMs: l.durationMs
|
|
4051
|
-
}))
|
|
4052
|
-
}, null, 2);
|
|
4053
|
-
}
|
|
4054
|
-
case "sentinel_services": {
|
|
4055
|
-
const services = store.getServices();
|
|
4056
|
-
return JSON.stringify({
|
|
4057
|
-
count: services.length,
|
|
4058
|
-
services: services.map((s) => ({
|
|
4059
|
-
name: s.name,
|
|
4060
|
-
version: s.version,
|
|
4061
|
-
environment: s.environment,
|
|
4062
|
-
lastSeen: s.lastSeenAt,
|
|
4063
|
-
startedAt: s.startedAt,
|
|
4064
|
-
pid: s.pid
|
|
4065
|
-
}))
|
|
4066
|
-
}, null, 2);
|
|
4067
|
-
}
|
|
4068
|
-
case "sentinel_app_state": {
|
|
4069
|
-
const { service: svc } = args;
|
|
4070
|
-
const states = svc ? store.getAppState(svc) : store.getAllAppStates();
|
|
4071
|
-
return JSON.stringify({
|
|
4072
|
-
states: states.map((s) => ({
|
|
4073
|
-
service: s.service,
|
|
4074
|
-
sessionId: s.sessionId,
|
|
4075
|
-
state: s.state,
|
|
4076
|
-
activeFlows: s.activeFlows,
|
|
4077
|
-
activeGates: s.activeGates,
|
|
4078
|
-
timestamp: s.timestamp
|
|
4079
|
-
}))
|
|
4080
|
-
}, null, 2);
|
|
4081
|
-
}
|
|
4082
|
-
case "sentinel_validate_symbol": {
|
|
4083
|
-
const { symbol: sym } = args;
|
|
4084
|
-
const logCount = store.getLogCount({ symbol: sym });
|
|
4085
|
-
return JSON.stringify({
|
|
4086
|
-
symbol: sym,
|
|
4087
|
-
usedInLogs: logCount > 0,
|
|
4088
|
-
logCount,
|
|
4089
|
-
tip: logCount === 0 ? "This symbol has not appeared in any logs. It may be a typo or unused." : `This symbol has been used in ${logCount} log entries.`
|
|
4090
|
-
}, null, 2);
|
|
4091
|
-
}
|
|
4092
|
-
case "sentinel_flow_activity": {
|
|
4093
|
-
const { flowId, service: flowSvc, since: flowSince } = args;
|
|
4094
|
-
const flowLogs = store.queryLogs({ symbol: flowId, service: flowSvc, since: flowSince, limit: 100 });
|
|
4095
|
-
const flowEvents = flowLogs.filter((l) => ["flow", "signal", "gate"].includes(l.symbolType)).map((l) => ({
|
|
4096
|
-
timestamp: l.timestamp,
|
|
4097
|
-
symbol: l.symbol,
|
|
4098
|
-
symbolType: l.symbolType,
|
|
4099
|
-
service: l.service,
|
|
4100
|
-
message: l.message,
|
|
4101
|
-
level: l.level
|
|
4102
|
-
}));
|
|
4103
|
-
return JSON.stringify({ count: flowEvents.length, events: flowEvents }, null, 2);
|
|
4104
|
-
}
|
|
4105
|
-
case "sentinel_metrics": {
|
|
4106
|
-
const { name: metricName, type: metricType, service: metricSvc, since: metricSince, aggregate, limit: metricLimit } = args;
|
|
4107
|
-
if (aggregate && metricName) {
|
|
4108
|
-
const agg = store.aggregateMetric(metricName, { service: metricSvc, since: metricSince });
|
|
4109
|
-
return JSON.stringify(agg, null, 2);
|
|
4110
|
-
}
|
|
4111
|
-
const metrics = store.queryMetrics({
|
|
4112
|
-
name: metricName,
|
|
4113
|
-
type: metricType,
|
|
4114
|
-
service: metricSvc,
|
|
4115
|
-
since: metricSince,
|
|
4116
|
-
limit: Math.min(metricLimit || 50, 100)
|
|
4117
|
-
});
|
|
4118
|
-
return JSON.stringify({
|
|
4119
|
-
count: metrics.length,
|
|
4120
|
-
metrics: metrics.map((m) => ({
|
|
4121
|
-
timestamp: m.timestamp,
|
|
4122
|
-
name: m.name,
|
|
4123
|
-
type: m.type,
|
|
4124
|
-
value: m.value,
|
|
4125
|
-
tags: m.tags,
|
|
4126
|
-
service: m.service
|
|
4127
|
-
}))
|
|
4128
|
-
}, null, 2);
|
|
4129
|
-
}
|
|
4130
|
-
case "sentinel_traces": {
|
|
4131
|
-
const { traceId: tid, service: traceSvc, symbol: traceSym, since: traceSince, limit: traceLimit } = args;
|
|
4132
|
-
if (tid) {
|
|
4133
|
-
const trace = store.getTrace(tid);
|
|
4134
|
-
if (!trace) return JSON.stringify({ error: "Trace not found" });
|
|
4135
|
-
return JSON.stringify(trace, null, 2);
|
|
4136
|
-
}
|
|
4137
|
-
const traces = store.queryTraces({
|
|
4138
|
-
service: traceSvc,
|
|
4139
|
-
symbol: traceSym,
|
|
4140
|
-
since: traceSince,
|
|
4141
|
-
limit: Math.min(traceLimit || 10, 20)
|
|
4142
|
-
});
|
|
4143
|
-
return JSON.stringify({
|
|
4144
|
-
count: traces.length,
|
|
4145
|
-
traces: traces.map((t) => ({
|
|
4146
|
-
traceId: t.traceId,
|
|
4147
|
-
services: t.services,
|
|
4148
|
-
spanCount: t.spans.length,
|
|
4149
|
-
totalDurationMs: t.totalDurationMs,
|
|
4150
|
-
startTime: t.startTime,
|
|
4151
|
-
endTime: t.endTime
|
|
4152
|
-
}))
|
|
4153
|
-
}, null, 2);
|
|
4154
|
-
}
|
|
4155
|
-
default:
|
|
4156
|
-
return JSON.stringify({ error: `Unknown tool: ${name}` });
|
|
4157
|
-
}
|
|
4158
|
-
}
|
|
4159
|
-
async function main() {
|
|
4160
|
-
console.error("[sentinel-mcp] Starting...");
|
|
4161
|
-
const store = getStorage();
|
|
4162
|
-
await store.ensureReady();
|
|
4163
|
-
console.error("[sentinel-mcp] Storage initialized");
|
|
4164
|
-
const server = new Server(
|
|
4165
|
-
{ name: "sentinel", version: "0.2.0" },
|
|
4166
|
-
{ capabilities: { tools: {} } }
|
|
4167
|
-
);
|
|
4168
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
4169
|
-
tools: getToolsList()
|
|
4170
|
-
}));
|
|
4171
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
4172
|
-
const { name, arguments: args } = request.params;
|
|
4173
|
-
try {
|
|
4174
|
-
const text = await handleTool(name, args || {});
|
|
4175
|
-
return { content: [{ type: "text", text }] };
|
|
4176
|
-
} catch (error) {
|
|
4177
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
4178
|
-
return {
|
|
4179
|
-
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
4180
|
-
isError: true
|
|
4181
|
-
};
|
|
4182
|
-
}
|
|
4183
|
-
});
|
|
4184
|
-
server.onerror = (error) => {
|
|
4185
|
-
console.error("[sentinel-mcp] Server error:", error);
|
|
4186
|
-
};
|
|
4187
|
-
const transport = new StdioServerTransport();
|
|
4188
|
-
await server.connect(transport);
|
|
4189
|
-
console.error("[sentinel-mcp] Running on stdio");
|
|
4190
|
-
}
|
|
4191
|
-
main().catch((err) => {
|
|
4192
|
-
console.error("[sentinel-mcp] Fatal:", err);
|
|
4193
|
-
process.exit(1);
|
|
4194
|
-
});
|
|
422
|
+
)`,[s]),this.save(),s}rowToGenericEvent(e,t){let s={};return e.forEach((n,i)=>{s[n]=t[i];}),{id:s.id,schemaId:s.schema_id,eventType:s.event_type,category:s.category,timestamp:s.timestamp,scopeValue:s.scope_value||void 0,scopeOrdinal:s.scope_ordinal!=null?s.scope_ordinal:void 0,sessionId:s.session_id||void 0,service:s.service,data:s.data_json?JSON.parse(s.data_json):void 0,severity:s.severity||"info",parentEventId:s.parent_event_id||void 0,depth:s.depth||0}}close(){this.db&&(this.save(),this.db.close(),this.db=null);}};var k={minScore:30,maxResults:5,boostConfidence:true},R=class{constructor(e){this.storage=e;}match(e,t={}){let{minScore:s,maxResults:n,boostConfidence:i}={...k,...t},r=this.storage.getAllPatterns({includePrivate:true}),a=[];for(let o of r){if(!this.matchEnvironment(o,e))continue;let{score:c,matchedCriteria:l}=this.scoreMatch(o,e);if(c>=s){let d=c;if(i){let m=o.confidence.score/100;d=c*(.5+.5*m);}a.push({pattern:o,score:c,matchedCriteria:l,confidence:Math.round(d)}),this.storage.updatePatternConfidence(o.id,"matched");}}return a.sort((o,c)=>c.confidence-o.confidence).slice(0,n)}testPattern(e,t=100){let s=this.storage.getRecentIncidents({limit:t}),n=[],i=0;for(let r of s){if(!this.matchEnvironment(e,r))continue;let{score:a}=this.scoreMatch(e,r);a>=30&&(n.push(r),i+=a);}return {wouldMatch:n,matchCount:n.length,avgScore:n.length>0?Math.round(i/n.length):0}}scoreMatch(e,t){let s=0,n={symbols:[],errorKeywords:[],missingSignals:[]},i=this.matchSymbols(e.pattern.symbols,t.symbols,n.symbols);s+=Math.min(i,50);let r=this.matchErrorText(e,t,n.errorKeywords);s+=Math.min(r,25);let a=this.matchMissingSignals(e,t,n.missingSignals);return s+=Math.min(a,25),s=Math.min(s,100),{score:s,matchedCriteria:n}}matchSymbols(e,t,s){let n=0,i=["feature","component","flow","gate","signal","state","integration"];for(let r of i){let a=e[r],o=t[r];if(!(!a||!o)){if(typeof a=="string")this.matchSingleSymbol(a,o)&&(n+=a.includes("*")?5:10,s.push(r));else if(Array.isArray(a)){for(let c of a)if(this.matchSingleSymbol(c,o)){n+=7,s.push(r);break}}}}return n}matchSingleSymbol(e,t){if(e==="*")return true;if(e.endsWith("*")){let s=e.slice(0,-1);return t.startsWith(s)}if(e.startsWith("*")){let s=e.slice(1);return t.endsWith(s)}return e.includes("*")?new RegExp("^"+e.replace(/\*/g,".*")+"$").test(t):e===t}matchErrorText(e,t,s){let n=0,i=t.error.message.toLowerCase(),r=t.error.type?.toLowerCase();if(e.pattern.errorContains)for(let a of e.pattern.errorContains)i.includes(a.toLowerCase())&&(n+=5,s.push(a));if(e.pattern.errorMatches)try{new RegExp(e.pattern.errorMatches,"i").test(t.error.message)&&(n+=10,s.push(`regex:${e.pattern.errorMatches}`));}catch{}if(e.pattern.errorType&&r)for(let a of e.pattern.errorType)r.includes(a.toLowerCase())&&(n+=5,s.push(`type:${a}`));return n}matchMissingSignals(e,t,s){if(!e.pattern.missingSignals||!t.flowPosition?.missing)return 0;let n=0;for(let i of e.pattern.missingSignals)for(let r of t.flowPosition.missing)if(this.matchSingleSymbol(i,r)){n+=12,s.push(r);break}return n}matchEnvironment(e,t){return !e.pattern.environment||e.pattern.environment.length===0?true:e.pattern.environment.includes(t.environment)}};var _=class{build(e){if(!e.flowPosition)return null;let t=[],s=new Date(e.timestamp).getTime();t.push({timestamp:new Date(s-5e3).toISOString(),symbol:e.flowPosition.flowId,type:"flow-started"});let n=1e3;for(let r of e.flowPosition.actual){let a=this.inferEventType(r);t.push({timestamp:new Date(s-4e3+n).toISOString(),symbol:r,type:a}),n+=Math.random()*1e3+500;}let i=e.flowPosition.failedAt||e.flowPosition.missing[0]||e.symbols.gate||e.symbols.signal||"unknown";return t.push({timestamp:e.timestamp,symbol:i,type:"error",data:{message:e.error.message,missing:e.flowPosition.missing}}),{incidentId:e.id,flowId:e.flowPosition.flowId,events:t,failure:{at:e.timestamp,symbol:i,reason:e.error.message}}}renderAscii(e){let t=[];t.push(`${e.flowId} Timeline`),t.push("\u2550".repeat(40)),t.push("");for(let n of e.events){let i=this.formatTime(n.timestamp),r=this.getEventIcon(n.type),a=this.getEventStatus(n.type),o=`${i} ${r} ${n.symbol}`;a&&(o+=` (${a})`),t.push(o),n.type==="error"&&n.data&&(t.push(` \u2514\u2500 ${n.data.message}`),n.data.missing&&Array.isArray(n.data.missing)&&n.data.missing.length>0&&t.push(` \u2514\u2500 Expected: ${n.data.missing.join(", ")}`));}let s=e.events.find(n=>n.type==="error")?.data?.missing;return s&&s.length>0&&(t.push(""),t.push(`Missing signals: ${s.join(", ")}`)),t.join(`
|
|
423
|
+
`)}renderStructured(e){return {incidentId:e.incidentId,flow:{id:e.flowId,eventCount:e.events.length},events:e.events.map(t=>({time:this.formatTime(t.timestamp),symbol:t.symbol,type:t.type,status:this.getEventStatus(t.type),data:t.data})),failure:{at:this.formatTime(e.failure.at),symbol:e.failure.symbol,reason:e.failure.reason}}}inferEventType(e){return e.startsWith("^")?"gate-passed":e.startsWith("!")?"signal-emitted":e.startsWith("%")?"state-changed":"signal-emitted"}getEventIcon(e){switch(e){case "flow-started":return "\u25B6";case "flow-ended":return "\u25A0";case "gate-passed":return "\u2713";case "gate-failed":return "\u2717";case "signal-emitted":return "\u26A1";case "state-changed":return "\u25C6";case "error":return "\u2717";default:return "\u2022"}}getEventStatus(e){switch(e){case "gate-passed":return "PASSED";case "gate-failed":return "FAILED";case "signal-emitted":return "EMITTED";case "state-changed":return "CHANGED";case "error":return "ERROR";default:return ""}}formatTime(e){let t=new Date(e),s=String(t.getHours()).padStart(2,"0"),n=String(t.getMinutes()).padStart(2,"0"),i=String(t.getSeconds()).padStart(2,"0"),r=String(t.getMilliseconds()).padStart(3,"0");return `${s}:${n}:${i}.${r}`}};var L=class{constructor(e){this.storage=e;}getStats(e=7){let t=new Date().toISOString(),s=new Date(Date.now()-e*24*60*60*1e3).toISOString();return this.storage.getStats({start:s,end:t})}getSymbolHealth(e){return this.storage.getSymbolHealth(e)}getTrendingIssues(e=7){let t=Date.now(),s=e*24*60*60*1e3/2,n=new Date(t-e*24*60*60*1e3).toISOString(),i=new Date(t-s).toISOString(),r=new Date(t).toISOString(),a=this.storage.getRecentIncidents({dateFrom:n,dateTo:i,limit:1e3}),o=this.storage.getRecentIncidents({dateFrom:i,dateTo:r,limit:1e3}),c=this.countSymbols(a),l=this.countSymbols(o),d=[],m=new Set([...c.keys(),...l.keys()]);for(let g of m){let p=c.get(g)||0,E=l.get(g)||0;if(p===0&&E>0)d.push({symbol:g,trend:E*100});else if(p>0){let S=(E-p)/p*100;d.push({symbol:g,trend:S});}}return d.filter(g=>g.trend>0).sort((g,p)=>p.trend-g.trend).slice(0,10)}getResolutionMetrics(){let e=this.getStats(30);return {avgTimeToResolve:e.resolution.avgTimeToResolve,resolvedWithPattern:e.resolution.resolvedWithPattern,resolvedManually:e.resolution.resolvedManually,totalResolved:e.incidents.resolved,resolutionRate:e.resolution.resolutionRate}}getPatternEffectiveness(){return this.storage.getAllPatterns({includePrivate:true}).filter(t=>t.confidence.timesMatched>0).map(t=>({patternId:t.id,name:t.name,matches:t.confidence.timesMatched,resolutions:t.confidence.timesResolved,recurrences:t.confidence.timesRecurred,effectiveness:t.confidence.timesMatched>0?Math.round((t.confidence.timesResolved-t.confidence.timesRecurred)/t.confidence.timesMatched*100):0})).sort((t,s)=>s.effectiveness-t.effectiveness)}getIncidentsByHour(e=7){let t=new Date(Date.now()-e*24*60*60*1e3).toISOString(),s=this.storage.getRecentIncidents({dateFrom:t,limit:1e4}),n=new Map;for(let i=0;i<24;i++)n.set(i,0);for(let i of s){let r=new Date(i.timestamp).getHours();n.set(r,(n.get(r)||0)+1);}return Array.from(n.entries()).map(([i,r])=>({hour:i,count:r}))}getIncidentsByEnvironment(){let e=this.getStats(30),t=e.incidents.total;return Object.entries(e.incidents.byEnvironment).map(([s,n])=>({environment:s,count:n,percentage:t>0?Math.round(n/t*100):0})).sort((s,n)=>n.count-s.count)}getSymbolCorrelation(){let e=this.storage.getRecentIncidents({limit:1e3}),t=new Map,s=new Map;for(let i of e){let r=this.getSymbolsFromIncident(i);for(let a of r)s.set(a,(s.get(a)||0)+1);for(let a=0;a<r.length;a++)for(let o=a+1;o<r.length;o++){let c=[r[a],r[o]].sort().join("|");t.set(c,(t.get(c)||0)+1);}}let n=[];for(let[i,r]of t){let[a,o]=i.split("|"),c=s.get(a)||1,l=s.get(o)||1,d=r/Math.max(c,l);d>.3&&n.push({symbol1:a,symbol2:o,correlation:Math.round(d*100)/100});}return n.sort((i,r)=>r.correlation-i.correlation).slice(0,20)}generateDashboard(e=7){let t=this.getStats(e),s=[];s.push("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),s.push("\u2551 PARADIGM SENTINEL DASHBOARD \u2551"),s.push("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");let n=t.incidents.byDay[t.incidents.byDay.length-1]?.count||0;s.push(`\u2551 Open: ${String(t.incidents.open).padEnd(4)} \u2502 Investigating: ${String(t.incidents.total-t.incidents.open-t.incidents.resolved).padEnd(3)} \u2502 Resolved: ${String(t.incidents.resolved).padEnd(4)} \u2502 Today: +${n} \u2551`),s.push("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),s.push(""),s.push("Incidents by Day (last 7 days):"),s.push("\u2500".repeat(50));let i=Math.max(...t.incidents.byDay.map(r=>r.count),1);for(let r of t.incidents.byDay.slice(-7)){let a=Math.round(r.count/i*30),o="\u2588".repeat(a);s.push(`${r.date.substring(5)} ${o} ${r.count}`);}s.push(""),s.push("Most Affected Symbols:"),s.push("\u2500".repeat(50));for(let{symbol:r,count:a}of t.symbols.mostIncidents.slice(0,5))s.push(` ${r.padEnd(25)} ${a} incidents`);s.push(""),s.push("Top Patterns:"),s.push("\u2500".repeat(50));for(let{patternId:r,resolvedCount:a}of t.patterns.mostEffective.slice(0,5))s.push(` ${r.padEnd(25)} ${a} resolved`);return s.push(""),s.push("Resolution Stats:"),s.push("\u2500".repeat(50)),s.push(` Resolution rate: ${Math.round(t.resolution.resolutionRate)}%`),s.push(` With pattern: ${t.resolution.resolvedWithPattern}`),s.push(` Manual: ${t.resolution.resolvedManually}`),s.join(`
|
|
424
|
+
`)}countSymbols(e){let t=new Map;for(let s of e)for(let[,n]of Object.entries(s.symbols))n&&t.set(n,(t.get(n)||0)+1);return t}getSymbolsFromIncident(e){let t=[];for(let[,s]of Object.entries(e.symbols))s&&t.push(s);return t}};var C=class{constructor(e){this.storage=e;}suggestFromIncident(e){let t=this.generatePatternId(e),s=this.buildSymbolCriteria(e.symbols),n=this.extractErrorKeywords(e.error.message);return {id:t,name:this.generatePatternName(e),description:`Auto-suggested pattern from incident ${e.id}`,pattern:{symbols:s,errorContains:n.length>0?n:void 0,missingSignals:e.flowPosition?.missing},resolution:{description:e.resolution?.notes||"Resolution approach TBD",strategy:this.inferStrategy([e]),priority:"medium"},source:"suggested",private:false,tags:this.generateTags(e)}}suggestFromGroup(e){let t=`group-${e.id.toLowerCase().replace(/[^a-z0-9]/g,"-")}`,s=this.buildSymbolCriteria(e.commonSymbols),n=e.incidents.slice(0,20).map(r=>this.storage.getIncident(r)).filter(r=>r!=null);return {id:t,name:e.name||`Pattern from group ${e.id}`,description:`Auto-suggested pattern from incident group with ${e.count} incidents`,pattern:{symbols:s,errorContains:e.commonErrorPatterns.length>0?e.commonErrorPatterns:void 0},resolution:{description:"Resolution approach TBD based on grouped incidents",strategy:n.length>0?this.inferStrategy(n):"fix-code",priority:this.getPriorityFromCount(e.count)},source:"suggested",private:false,tags:this.generateTagsFromGroup(e)}}findPatternCandidates(e=3){let t=this.storage.getRecentIncidents({limit:1e3,status:"resolved"}),s=new Map;for(let i of t){let r=this.getSymbolSignature(i.symbols),a=s.get(r)||[];a.push(i),s.set(r,a);}let n=[];for(let[,i]of s)if(i.length>=e){if(this.hasMatchingPattern(i[0]))continue;let a=this.suggestFromIncidents(i);n.push({incidents:i,suggestedPattern:a,occurrenceCount:i.length});}return n.sort((i,r)=>r.occurrenceCount-i.occurrenceCount)}suggestFromIncidents(e){let t=this.extractCommonSymbols(e),s=this.buildSymbolCriteria(t),n=this.extractCommonErrorKeywords(e),i=this.extractCommonMissingSignals(e);return {id:this.generatePatternId(e[0]),name:this.generatePatternName(e[0]),description:`Auto-suggested pattern from ${e.length} similar incidents`,pattern:{symbols:s,errorContains:n.length>0?n:void 0,missingSignals:i.length>0?i:void 0},resolution:{description:"Resolution approach based on previous resolutions",strategy:this.inferStrategy(e),priority:this.getPriorityFromCount(e.length)},source:"suggested",private:false,tags:this.generateTagsFromIncidents(e)}}buildSymbolCriteria(e){let t={};for(let[s,n]of Object.entries(e))n&&(t[s]=n);return t}extractErrorKeywords(e){let t=new Set(["the","a","an","is","are","was","were","in","on","at","to","for","of","with","and","or","but","not","no","be","been","have","has","had","do","does","did"]),s=e.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(i=>i.length>2&&!t.has(i));return [...new Set(s)].slice(0,5)}extractCommonErrorKeywords(e){let t=new Map;for(let n of e){let i=this.extractErrorKeywords(n.error.message);for(let r of i)t.set(r,(t.get(r)||0)+1);}let s=Math.ceil(e.length*.5);return Array.from(t.entries()).filter(([,n])=>n>=s).map(([n])=>n).slice(0,5)}extractCommonSymbols(e){if(e.length===0)return {};let t=e[0].symbols,s={};for(let[n,i]of Object.entries(t)){if(!i)continue;e.every(a=>a.symbols[n]===i)&&(s[n]=i);}return s}extractCommonMissingSignals(e){let t=new Map;for(let n of e)if(n.flowPosition?.missing)for(let i of n.flowPosition.missing)t.set(i,(t.get(i)||0)+1);let s=Math.ceil(e.length*.5);return Array.from(t.entries()).filter(([,n])=>n>=s).map(([n])=>n)}generatePatternId(e){let t=[];e.symbols.gate?t.push(e.symbols.gate.replace(/[^a-z0-9]/gi,"")):e.symbols.feature?t.push(e.symbols.feature.replace(/[^a-z0-9]/gi,"")):e.symbols.component?t.push(e.symbols.component.replace(/[^a-z0-9]/gi,"")):e.symbols.integration?t.push(e.symbols.integration.replace(/[^a-z0-9]/gi,"")):t.push("unknown");let s=e.error.type?.toLowerCase()||"error";return t.push(s.replace(/[^a-z0-9]/gi,"")),t.push(String(Date.now()%1e3).padStart(3,"0")),t.join("-")}generatePatternName(e){let t=[];if(e.symbols.feature&&t.push(e.symbols.feature.replace("@","").replace(/-/g," ")),e.symbols.gate&&t.push("gate "+e.symbols.gate.replace("^","")),e.error.type&&t.push(e.error.type),t.length===0)return "Unnamed Pattern";let s=t.join(" - ");return s.charAt(0).toUpperCase()+s.slice(1)}generateTags(e){let t=[];return e.symbols.feature&&t.push("feature"),e.symbols.gate&&t.push("gate"),e.symbols.integration&&(t.push("integration"),t.push(e.symbols.integration.replace("&",""))),e.error.type&&t.push(e.error.type.toLowerCase()),t.push(e.environment),[...new Set(t)].slice(0,5)}generateTagsFromGroup(e){let t=["grouped"];e.commonSymbols.feature&&t.push("feature"),e.commonSymbols.gate&&t.push("gate"),e.commonSymbols.integration&&t.push("integration");for(let s of e.environments)t.push(s);return [...new Set(t)].slice(0,5)}generateTagsFromIncidents(e){let t=new Map;for(let s of e){let n=this.generateTags(s);for(let i of n)t.set(i,(t.get(i)||0)+1);}return Array.from(t.entries()).sort((s,n)=>n[1]-s[1]).slice(0,5).map(([s])=>s)}getSymbolSignature(e){let t=[];return e.feature&&t.push(`f:${e.feature}`),e.component&&t.push(`c:${e.component}`),e.flow&&t.push(`fl:${e.flow}`),e.gate&&t.push(`g:${e.gate}`),e.integration&&t.push(`i:${e.integration}`),t.sort().join("|")}hasMatchingPattern(e){let t=this.storage.getAllPatterns({includePrivate:true});for(let s of t){let n=0,i=["feature","component","flow","gate","signal","integration"];for(let r of i){let a=s.pattern.symbols[r],o=e.symbols[r];a&&o&&a===o&&n++;}if(n>=2)return true}return false}inferStrategy(e){let t=e.map(i=>i.error.message.toLowerCase()),s=i=>t.some(r=>i.some(a=>r.includes(a)));return s(["revert","rollback","regression","broke after deploy","since deploy"])?"rollback":s(["config","environment variable","env var","missing key","secret","credential"])?"config-change":s(["out of memory","oom","heap","memory limit","capacity","too many connections","pool exhausted"])?"scale-up":s(["timeout","network","econnrefused","econnreset","dns","socket hang up"])?"retry":s(["unavailable","service down","circuit breaker","fallback","503","502"])?"fallback":s(["validation","invalid","required","constraint","duplicate","not found","404"])?"fix-data":s(["permission","forbidden","403","401","unauthorized","access denied"])?"escalate":new Set(e.map(i=>i.error.type).filter(Boolean)).size>2?"investigate":"fix-code"}getPriorityFromCount(e){return e>=20?"critical":e>=10?"high":e>=5?"medium":"low"}};var B=fileURLToPath(import.meta.url),F=I.dirname(B);function G(){let h=I.join(F,"universal-patterns.json"),e=y.readFileSync(h,"utf-8");return JSON.parse(e)}function W(){let h=I.join(F,"paradigm-patterns.json"),e=y.readFileSync(h,"utf-8");return JSON.parse(e)}function M(){let h=G(),e=W();return {version:"1.0.0",exportedAt:new Date().toISOString(),patterns:[...h.patterns,...e.patterns]}}var w=null,x=false;function U(){if(w||(w=new O),!x){try{let{patterns:h}=M();for(let e of h)try{w.addPattern(e);}catch{}}catch{}x=true;}return w}function Q(){return [{name:"sentinel_triage",description:"View and filter incidents with pattern matches. Returns recent errors with symbolic context and resolution suggestions.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{symbol:{type:"string",description:"Filter by symbol (e.g., #checkout, ^auth)"},status:{type:"string",enum:["open","investigating","resolved","wont-fix","all"],description:"Filter by status (default: all)"},environment:{type:"string",description:"Filter by environment"},search:{type:"string",description:"Search in error messages"},limit:{type:"number",description:"Max results (default: 10)"}}}},{name:"sentinel_show",description:"Get full details of a specific incident including timeline and matched patterns.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{incidentId:{type:"string",description:"Incident ID (e.g., INC-001)"},includeTimeline:{type:"boolean",description:"Include flow timeline"},includeSimilar:{type:"boolean",description:"Include similar incidents"}},required:["incidentId"]}},{name:"sentinel_resolve",description:"Mark an incident as resolved with optional pattern and commit reference.",annotations:{readOnlyHint:false,destructiveHint:false},inputSchema:{type:"object",properties:{incidentId:{type:"string",description:"Incident ID"},patternId:{type:"string",description:"Pattern that led to resolution"},commitHash:{type:"string",description:"Fix commit hash"},prUrl:{type:"string",description:"PR URL"},notes:{type:"string",description:"Resolution notes"},wontFix:{type:"boolean",description:"Mark as wont-fix instead of resolved"}},required:["incidentId"]}},{name:"sentinel_patterns",description:"List and filter failure patterns with confidence scores.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{symbol:{type:"string",description:"Filter patterns for this symbol"},minConfidence:{type:"number",description:"Minimum confidence score"},source:{type:"string",enum:["manual","suggested","imported","community"],description:"Filter by source"}}}},{name:"sentinel_add_pattern",description:"Create a new failure pattern.",annotations:{readOnlyHint:false,destructiveHint:false},inputSchema:{type:"object",properties:{id:{type:"string",description:"Pattern ID (kebab-case)"},name:{type:"string",description:"Human-readable name"},description:{type:"string",description:"What this pattern matches"},pattern:{type:"object",properties:{symbols:{type:"object",description:"Symbol criteria"},errorContains:{type:"array",items:{type:"string"},description:"Error keywords"},missingSignals:{type:"array",items:{type:"string"},description:"Expected missing signals"}}},resolution:{type:"object",properties:{description:{type:"string",description:"Resolution steps"},strategy:{type:"string",enum:["retry","fallback","fix-data","fix-code","ignore","escalate"]},priority:{type:"string",enum:["low","medium","high","critical"]},codeHint:{type:"string",description:"Code fix hint"}},required:["description","strategy"]},tags:{type:"array",items:{type:"string"},description:"Tags for categorization"}},required:["id","name","pattern","resolution"]}},{name:"sentinel_record",description:"Manually record a new incident.",annotations:{readOnlyHint:false,destructiveHint:false},inputSchema:{type:"object",properties:{error:{type:"object",properties:{message:{type:"string",description:"Error message"},stack:{type:"string",description:"Stack trace"},code:{type:"string",description:"Error code"},type:{type:"string",description:"Error type"}},required:["message"]},symbols:{type:"object",properties:{component:{type:"string"},flow:{type:"string"},gate:{type:"string"},signal:{type:"string"}}},environment:{type:"string",description:"Environment (required)"},service:{type:"string",description:"Service name"},version:{type:"string",description:"App version"},flowPosition:{type:"object",properties:{flowId:{type:"string"},expected:{type:"array",items:{type:"string"}},actual:{type:"array",items:{type:"string"}},missing:{type:"array",items:{type:"string"}},failedAt:{type:"string"}}}},required:["error","symbols","environment"]}},{name:"sentinel_stats",description:"Get statistics and health metrics.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{period:{type:"string",description:"Period: 1d, 7d, 30d, 90d (default: 7d)"},symbol:{type:"string",description:"Get health for specific symbol"}}}},{name:"sentinel_suggest_pattern",description:"Get AI suggestions for patterns based on incidents.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{incidentId:{type:"string",description:"Suggest from specific incident"},minOccurrences:{type:"number",description:"Min similar incidents for suggestion"}}}},{name:"sentinel_logs",description:"Query structured logs from connected apps. Filters by level, symbol, service, search text, time range.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{level:{type:"string",enum:["debug","info","warn","error"],description:"Filter by log level"},symbol:{type:"string",description:"Filter by symbol (partial match)"},service:{type:"string",description:"Filter by service name"},search:{type:"string",description:"Search in log messages"},since:{type:"string",description:"ISO timestamp \u2014 logs after this time"},sessionId:{type:"string",description:"Filter by session ID"},correlationId:{type:"string",description:"Filter by correlation ID"},limit:{type:"number",description:"Max results (default: 50)"}}}},{name:"sentinel_services",description:"List all registered services with version, environment, and last-seen time.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{}}},{name:"sentinel_app_state",description:"Get live app state snapshots. Shows current state, active flows, and held gates for connected services.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{service:{type:"string",description:"Filter by service name"}}}},{name:"sentinel_validate_symbol",description:"Check if a symbol has been used in logs. Returns usage count and suggestions.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{symbol:{type:"string",description:"Symbol to validate (e.g., #checkout, ^auth)"}},required:["symbol"]}},{name:"sentinel_flow_activity",description:"Get recent flow events \u2014 which flow nodes were hit, in what order, by which service.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{flowId:{type:"string",description:"Filter by flow ID (e.g., $checkout-flow)"},service:{type:"string",description:"Filter by service name"},since:{type:"string",description:"ISO timestamp \u2014 events after this time"}}}},{name:"sentinel_metrics",description:"Query metrics (counters, gauges, histograms) from connected apps. Supports filtering and aggregation.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{name:{type:"string",description:"Metric name filter"},type:{type:"string",enum:["counter","gauge","histogram"],description:"Metric type filter"},service:{type:"string",description:"Service name filter"},since:{type:"string",description:"ISO timestamp \u2014 metrics after this time"},aggregate:{type:"boolean",description:"If true and name is provided, return aggregation instead of raw data"},limit:{type:"number",description:"Max results (default: 50)"}}}},{name:"sentinel_traces",description:"Query distributed traces across services. Shows span trees with timing, status, and service hops.",annotations:{readOnlyHint:true,destructiveHint:false},inputSchema:{type:"object",properties:{traceId:{type:"string",description:"Get a specific trace by ID"},service:{type:"string",description:"Filter by service name"},symbol:{type:"string",description:"Filter by symbol"},since:{type:"string",description:"ISO timestamp \u2014 traces after this time"},limit:{type:"number",description:"Max traces (default: 10, max: 20)"}}}}]}async function Z(h,e){let t=U(),s=new R(t);switch(h){case "sentinel_triage":{let{symbol:n,status:i="all",environment:r,search:a,limit:o=10}=e,c=t.getRecentIncidents({limit:o,status:i,symbol:n,environment:r,search:a});if(c.length===0)return JSON.stringify({count:0,incidents:[],tip:"No incidents recorded yet. Use sentinel_record to create incidents or integrate the SDK."},null,2);let l=c.map(d=>{let m=s.match(d,{maxResults:3});return {id:d.id,timestamp:d.timestamp,status:d.status,error:d.error.message,symbols:d.symbols,environment:d.environment,matches:m.map(g=>({patternId:g.pattern.id,name:g.pattern.name,confidence:g.confidence,strategy:g.pattern.resolution.strategy,description:g.pattern.resolution.description}))}});return JSON.stringify({count:l.length,incidents:l},null,2)}case "sentinel_show":{let{incidentId:n,includeTimeline:i,includeSimilar:r}=e,a=t.getIncident(n);if(!a)return JSON.stringify({error:`Incident ${n} not found`});let o=s.match(a,{maxResults:5}),c={incident:a,matches:o.map(l=>({patternId:l.pattern.id,name:l.pattern.name,confidence:l.confidence,matchedCriteria:l.matchedCriteria,resolution:l.pattern.resolution}))};if(i&&a.flowPosition){let l=new _().build(a);l&&(c.timeline=new _().renderStructured(l));}if(r){let l=t.getRecentIncidents({symbol:Object.values(a.symbols)[0],limit:5}).filter(d=>d.id!==n);c.similar=l.map(d=>({id:d.id,error:d.error.message,status:d.status}));}return JSON.stringify(c,null,2)}case "sentinel_resolve":{let{incidentId:n,patternId:i,commitHash:r,prUrl:a,notes:o,wontFix:c}=e;return t.getIncident(n)?c?(t.updateIncident(n,{status:"wont-fix",resolvedAt:new Date().toISOString(),resolvedBy:"manual",resolution:{notes:o}}),JSON.stringify({success:true,message:`Incident ${n} marked as won't fix`})):(t.recordResolution({incidentId:n,patternId:i,commitHash:r,prUrl:a,notes:o}),JSON.stringify({success:true,message:`Incident ${n} resolved`,patternId:i,commitHash:r,prUrl:a})):JSON.stringify({error:`Incident ${n} not found`})}case "sentinel_patterns":{let{symbol:n,minConfidence:i,source:r}=e,a=t.getAllPatterns({source:r,minConfidence:i,includePrivate:false}),o=a;return n&&(o=a.filter(c=>{let l=c.pattern.symbols;return Object.values(l).some(d=>d?Array.isArray(d)?d.includes(n)||d.some(m=>n.match(m.replace("*",".*"))):d===n||n.match(d.replace("*",".*")):false)})),JSON.stringify({count:o.length,patterns:o.map(c=>({id:c.id,name:c.name,description:c.description,confidence:c.confidence.score,resolution:c.resolution,tags:c.tags}))},null,2)}case "sentinel_add_pattern":{let{id:n,name:i,description:r,pattern:a,resolution:o,tags:c}=e,l={id:n,name:i,description:r||"",pattern:{symbols:a.symbols||{},errorContains:a.errorContains,missingSignals:a.missingSignals},resolution:{description:o.description,strategy:o.strategy,priority:o.priority||"medium",codeHint:o.codeHint},source:"manual",private:false,tags:c||[]};return t.addPattern(l),JSON.stringify({success:true,message:`Pattern ${n} created`,pattern:l})}case "sentinel_record":{let{error:n,symbols:i,environment:r,service:a,version:o,flowPosition:c}=e,l=t.recordIncident({error:n,symbols:i,environment:r,service:a,version:o,flowPosition:c}),d=t.getIncident(l),m=d?s.match(d,{maxResults:3}):[];return JSON.stringify({success:true,incidentId:l,matches:m.map(g=>({patternId:g.pattern.id,confidence:g.confidence,resolution:g.pattern.resolution.description}))},null,2)}case "sentinel_stats":{let{period:n="7d",symbol:i}=e,r=new L(t);if(i){let l=r.getSymbolHealth(i);return JSON.stringify({symbol:i,health:l},null,2)}let a=n.match(/^(\d+)d$/),o=a?parseInt(a[1],10):7,c=r.getStats(o);return JSON.stringify({period:`${o}d`,stats:c},null,2)}case "sentinel_suggest_pattern":{let{incidentId:n,minOccurrences:i}=e,r=new C(t);if(n){let o=t.getIncident(n);if(!o)return JSON.stringify({error:`Incident ${n} not found`});let c=r.suggestFromIncident(o);return JSON.stringify({source:"incident",incidentId:n,suggestion:c},null,2)}let a=r.findPatternCandidates(i||3);return JSON.stringify({source:"analysis",candidates:a.slice(0,5).map(o=>({occurrences:o.occurrenceCount,sampleIncidents:o.incidents.slice(0,3).map(c=>c.id),suggestion:o.suggestedPattern}))},null,2)}case "sentinel_logs":{let{level:n,symbol:i,service:r,search:a,since:o,sessionId:c,correlationId:l,limit:d=50}=e,m=t.queryLogs({level:n,symbol:i,service:r,search:a,since:o,sessionId:c,correlationId:l,limit:d}),g=t.getLogCount({level:n,symbol:i,service:r,since:o});return JSON.stringify({count:m.length,total:g,logs:m.map(p=>({timestamp:p.timestamp,level:p.level,symbol:p.symbol,service:p.service,message:p.message,data:p.data,sessionId:p.sessionId,correlationId:p.correlationId,durationMs:p.durationMs}))},null,2)}case "sentinel_services":{let n=t.getServices();return JSON.stringify({count:n.length,services:n.map(i=>({name:i.name,version:i.version,environment:i.environment,lastSeen:i.lastSeenAt,startedAt:i.startedAt,pid:i.pid}))},null,2)}case "sentinel_app_state":{let{service:n}=e,i=n?t.getAppState(n):t.getAllAppStates();return JSON.stringify({states:i.map(r=>({service:r.service,sessionId:r.sessionId,state:r.state,activeFlows:r.activeFlows,activeGates:r.activeGates,timestamp:r.timestamp}))},null,2)}case "sentinel_validate_symbol":{let{symbol:n}=e,i=t.getLogCount({symbol:n});return JSON.stringify({symbol:n,usedInLogs:i>0,logCount:i,tip:i===0?"This symbol has not appeared in any logs. It may be a typo or unused.":`This symbol has been used in ${i} log entries.`},null,2)}case "sentinel_flow_activity":{let{flowId:n,service:i,since:r}=e,o=t.queryLogs({symbol:n,service:i,since:r,limit:100}).filter(c=>["flow","signal","gate"].includes(c.symbolType)).map(c=>({timestamp:c.timestamp,symbol:c.symbol,symbolType:c.symbolType,service:c.service,message:c.message,level:c.level}));return JSON.stringify({count:o.length,events:o},null,2)}case "sentinel_metrics":{let{name:n,type:i,service:r,since:a,aggregate:o,limit:c}=e;if(o&&n){let d=t.aggregateMetric(n,{service:r,since:a});return JSON.stringify(d,null,2)}let l=t.queryMetrics({name:n,type:i,service:r,since:a,limit:Math.min(c||50,100)});return JSON.stringify({count:l.length,metrics:l.map(d=>({timestamp:d.timestamp,name:d.name,type:d.type,value:d.value,tags:d.tags,service:d.service}))},null,2)}case "sentinel_traces":{let{traceId:n,service:i,symbol:r,since:a,limit:o}=e;if(n){let l=t.getTrace(n);return l?JSON.stringify(l,null,2):JSON.stringify({error:"Trace not found"})}let c=t.queryTraces({service:i,symbol:r,since:a,limit:Math.min(o||10,20)});return JSON.stringify({count:c.length,traces:c.map(l=>({traceId:l.traceId,services:l.services,spanCount:l.spans.length,totalDurationMs:l.totalDurationMs,startTime:l.startTime,endTime:l.endTime}))},null,2)}default:return JSON.stringify({error:`Unknown tool: ${h}`})}}async function ee(){console.error("[sentinel-mcp] Starting..."),await U().ensureReady(),console.error("[sentinel-mcp] Storage initialized");let e=new Server({name:"sentinel",version:"0.2.0"},{capabilities:{tools:{}}});e.setRequestHandler(ListToolsRequestSchema,async()=>({tools:Q()})),e.setRequestHandler(CallToolRequestSchema,async s=>{let{name:n,arguments:i}=s.params;try{return {content:[{type:"text",text:await Z(n,i||{})}]}}catch(r){let a=r instanceof Error?r.message:String(r);return {content:[{type:"text",text:JSON.stringify({error:a})}],isError:true}}}),e.onerror=s=>{console.error("[sentinel-mcp] Server error:",s);};let t=new StdioServerTransport;await e.connect(t),console.error("[sentinel-mcp] Running on stdio");}ee().catch(h=>{console.error("[sentinel-mcp] Fatal:",h),process.exit(1);});
|