@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/chunk-ZSYVKSY6.js
DELETED
|
@@ -1,2602 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
SentinelStorage
|
|
4
|
-
} from "./chunk-FKJUBQU3.js";
|
|
5
|
-
|
|
6
|
-
// ../sentinel/dist/chunk-VQ3SIN7S.js
|
|
7
|
-
import { v4 as uuidv4 } from "uuid";
|
|
8
|
-
var DEFAULTS = {
|
|
9
|
-
url: "http://localhost:3838",
|
|
10
|
-
batchSize: 50,
|
|
11
|
-
flushIntervalMs: 5e3,
|
|
12
|
-
maxBufferSize: 1e3,
|
|
13
|
-
maxRetries: 3,
|
|
14
|
-
retryBackoffMs: 1e3
|
|
15
|
-
};
|
|
16
|
-
var SentinelClient = class {
|
|
17
|
-
url;
|
|
18
|
-
service;
|
|
19
|
-
version;
|
|
20
|
-
environment;
|
|
21
|
-
token;
|
|
22
|
-
batchSize;
|
|
23
|
-
maxBufferSize;
|
|
24
|
-
maxRetries;
|
|
25
|
-
retryBackoffMs;
|
|
26
|
-
onDrop;
|
|
27
|
-
onError;
|
|
28
|
-
sessionId;
|
|
29
|
-
logBuffer = [];
|
|
30
|
-
metricsBuffer = [];
|
|
31
|
-
flushTimer = null;
|
|
32
|
-
closed = false;
|
|
33
|
-
beforeUnloadHandler = null;
|
|
34
|
-
constructor(options) {
|
|
35
|
-
this.url = (options.url ?? DEFAULTS.url).replace(/\/+$/, "");
|
|
36
|
-
this.service = options.service;
|
|
37
|
-
this.version = options.version;
|
|
38
|
-
this.environment = options.environment;
|
|
39
|
-
this.token = options.token;
|
|
40
|
-
this.batchSize = options.batchSize ?? DEFAULTS.batchSize;
|
|
41
|
-
this.maxBufferSize = options.maxBufferSize ?? DEFAULTS.maxBufferSize;
|
|
42
|
-
this.maxRetries = options.maxRetries ?? DEFAULTS.maxRetries;
|
|
43
|
-
this.retryBackoffMs = options.retryBackoffMs ?? DEFAULTS.retryBackoffMs;
|
|
44
|
-
this.onDrop = options.onDrop;
|
|
45
|
-
this.onError = options.onError;
|
|
46
|
-
this.sessionId = uuidv4();
|
|
47
|
-
const intervalMs = options.flushIntervalMs ?? DEFAULTS.flushIntervalMs;
|
|
48
|
-
this.flushTimer = setInterval(() => {
|
|
49
|
-
this.flush().catch((err) => {
|
|
50
|
-
this.handleError(err);
|
|
51
|
-
});
|
|
52
|
-
}, intervalMs);
|
|
53
|
-
if (this.flushTimer && typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
|
|
54
|
-
this.flushTimer.unref();
|
|
55
|
-
}
|
|
56
|
-
if (typeof globalThis !== "undefined" && typeof globalThis.addEventListener === "function") {
|
|
57
|
-
this.beforeUnloadHandler = () => {
|
|
58
|
-
this.flushSync();
|
|
59
|
-
};
|
|
60
|
-
globalThis.addEventListener("beforeunload", this.beforeUnloadHandler);
|
|
61
|
-
}
|
|
62
|
-
this.registerService();
|
|
63
|
-
}
|
|
64
|
-
// ── Logging Methods ──────────────────────────────────────────────
|
|
65
|
-
/** Log a debug-level message */
|
|
66
|
-
debug(symbol, message, data) {
|
|
67
|
-
this.log("debug", symbol, message, data);
|
|
68
|
-
}
|
|
69
|
-
/** Log an info-level message */
|
|
70
|
-
info(symbol, message, data) {
|
|
71
|
-
this.log("info", symbol, message, data);
|
|
72
|
-
}
|
|
73
|
-
/** Log a warn-level message */
|
|
74
|
-
warn(symbol, message, data) {
|
|
75
|
-
this.log("warn", symbol, message, data);
|
|
76
|
-
}
|
|
77
|
-
/** Log an error-level message */
|
|
78
|
-
error(symbol, message, data) {
|
|
79
|
-
this.log("error", symbol, message, data);
|
|
80
|
-
}
|
|
81
|
-
/** Log a message at the specified level */
|
|
82
|
-
log(level, symbol, message, data) {
|
|
83
|
-
if (this.closed) return;
|
|
84
|
-
const entry = {
|
|
85
|
-
level,
|
|
86
|
-
symbol,
|
|
87
|
-
message,
|
|
88
|
-
service: this.service,
|
|
89
|
-
sessionId: this.sessionId,
|
|
90
|
-
environment: this.environment,
|
|
91
|
-
data
|
|
92
|
-
};
|
|
93
|
-
this.pushToLogBuffer(entry);
|
|
94
|
-
}
|
|
95
|
-
// ── Metrics Methods ──────────────────────────────────────────────
|
|
96
|
-
/** Record a counter metric (increments) */
|
|
97
|
-
counter(name, value, tags) {
|
|
98
|
-
this.metric({ name, type: "counter", value: value ?? 1, tags });
|
|
99
|
-
}
|
|
100
|
-
/** Record a gauge metric (current value) */
|
|
101
|
-
gauge(name, value, tags) {
|
|
102
|
-
this.metric({ name, type: "gauge", value, tags });
|
|
103
|
-
}
|
|
104
|
-
/** Record a histogram metric (distribution) */
|
|
105
|
-
histogram(name, value, tags) {
|
|
106
|
-
this.metric({ name, type: "histogram", value, tags });
|
|
107
|
-
}
|
|
108
|
-
/** Record a metric of any type */
|
|
109
|
-
metric(input) {
|
|
110
|
-
if (this.closed) return;
|
|
111
|
-
const entry = {
|
|
112
|
-
...input,
|
|
113
|
-
service: this.service,
|
|
114
|
-
environment: this.environment
|
|
115
|
-
};
|
|
116
|
-
this.pushToMetricsBuffer(entry);
|
|
117
|
-
}
|
|
118
|
-
// ── State Push ───────────────────────────────────────────────────
|
|
119
|
-
/** Push an application state snapshot to the server */
|
|
120
|
-
async pushState(state, activeFlows, activeGates) {
|
|
121
|
-
if (this.closed) return;
|
|
122
|
-
await this.post("/api/state", {
|
|
123
|
-
service: this.service,
|
|
124
|
-
sessionId: this.sessionId,
|
|
125
|
-
state,
|
|
126
|
-
activeFlows,
|
|
127
|
-
activeGates
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
// ── Tracing ──────────────────────────────────────────────────────
|
|
131
|
-
/** Start a trace span. Call end() on the returned SpanContext when the operation completes. */
|
|
132
|
-
startSpan(symbol, operation, parentSpanId) {
|
|
133
|
-
const traceId = parentSpanId ? parentSpanId.split("-")[0] || uuidv4() : uuidv4();
|
|
134
|
-
const spanId = uuidv4();
|
|
135
|
-
const startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
136
|
-
const startMs = Date.now();
|
|
137
|
-
const self = this;
|
|
138
|
-
return {
|
|
139
|
-
traceId,
|
|
140
|
-
spanId,
|
|
141
|
-
async end(status = "ok") {
|
|
142
|
-
const endTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
143
|
-
const durationMs = Date.now() - startMs;
|
|
144
|
-
const span = {
|
|
145
|
-
traceId,
|
|
146
|
-
spanId,
|
|
147
|
-
parentSpanId,
|
|
148
|
-
service: self.service,
|
|
149
|
-
symbol,
|
|
150
|
-
operation,
|
|
151
|
-
startTime,
|
|
152
|
-
endTime,
|
|
153
|
-
durationMs,
|
|
154
|
-
status
|
|
155
|
-
};
|
|
156
|
-
await self.post("/api/traces", span);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
// ── Buffer Management ────────────────────────────────────────────
|
|
161
|
-
/** Flush all buffered logs and metrics to the server */
|
|
162
|
-
async flush() {
|
|
163
|
-
const logEntries = this.drainLogBuffer();
|
|
164
|
-
const metricEntries = this.drainMetricsBuffer();
|
|
165
|
-
const promises = [];
|
|
166
|
-
if (logEntries.length > 0) {
|
|
167
|
-
promises.push(this.sendLogs(logEntries));
|
|
168
|
-
}
|
|
169
|
-
if (metricEntries.length > 0) {
|
|
170
|
-
promises.push(this.sendMetrics(metricEntries));
|
|
171
|
-
}
|
|
172
|
-
await Promise.allSettled(promises);
|
|
173
|
-
}
|
|
174
|
-
// ── Lifecycle ────────────────────────────────────────────────────
|
|
175
|
-
/** Flush remaining entries and shut down the client */
|
|
176
|
-
async close() {
|
|
177
|
-
if (this.closed) return;
|
|
178
|
-
this.closed = true;
|
|
179
|
-
if (this.flushTimer !== null) {
|
|
180
|
-
clearInterval(this.flushTimer);
|
|
181
|
-
this.flushTimer = null;
|
|
182
|
-
}
|
|
183
|
-
if (this.beforeUnloadHandler && typeof globalThis !== "undefined" && typeof globalThis.removeEventListener === "function") {
|
|
184
|
-
globalThis.removeEventListener("beforeunload", this.beforeUnloadHandler);
|
|
185
|
-
this.beforeUnloadHandler = null;
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
await this.flush();
|
|
189
|
-
} catch {
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/** Get the session ID assigned to this client instance */
|
|
193
|
-
getSessionId() {
|
|
194
|
-
return this.sessionId;
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Synchronous best-effort flush using sendBeacon (browser) or sync XHR fallback.
|
|
198
|
-
* Used in beforeunload where async is unreliable.
|
|
199
|
-
*/
|
|
200
|
-
flushSync() {
|
|
201
|
-
const logEntries = this.drainLogBuffer();
|
|
202
|
-
const metricEntries = this.drainMetricsBuffer();
|
|
203
|
-
const sendBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" ? navigator.sendBeacon.bind(navigator) : void 0;
|
|
204
|
-
if (sendBeacon) {
|
|
205
|
-
if (logEntries.length > 0) {
|
|
206
|
-
sendBeacon(`${this.url}/api/logs`, JSON.stringify({ entries: logEntries }));
|
|
207
|
-
}
|
|
208
|
-
if (metricEntries.length > 0) {
|
|
209
|
-
sendBeacon(`${this.url}/api/metrics`, JSON.stringify({ entries: metricEntries }));
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
214
|
-
// PRIVATE METHODS
|
|
215
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
216
|
-
/** Register this service with the Sentinel server (fire-and-forget) */
|
|
217
|
-
registerService() {
|
|
218
|
-
const registration = {
|
|
219
|
-
name: this.service,
|
|
220
|
-
version: this.version,
|
|
221
|
-
pid: typeof process !== "undefined" ? process.pid : void 0,
|
|
222
|
-
environment: this.environment
|
|
223
|
-
};
|
|
224
|
-
this.post("/api/services", registration).catch(() => {
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
/** Push a log entry into the ring buffer, enforcing maxBufferSize */
|
|
228
|
-
pushToLogBuffer(entry) {
|
|
229
|
-
if (this.logBuffer.length >= this.maxBufferSize) {
|
|
230
|
-
const dropCount = Math.max(1, Math.floor(this.maxBufferSize * 0.1));
|
|
231
|
-
this.logBuffer.splice(0, dropCount);
|
|
232
|
-
if (this.onDrop) {
|
|
233
|
-
this.onDrop(dropCount);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
this.logBuffer.push(entry);
|
|
237
|
-
if (this.logBuffer.length >= this.batchSize) {
|
|
238
|
-
this.flush().catch((err) => {
|
|
239
|
-
this.handleError(err);
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
/** Push a metric entry into the ring buffer, enforcing maxBufferSize */
|
|
244
|
-
pushToMetricsBuffer(entry) {
|
|
245
|
-
if (this.metricsBuffer.length >= this.maxBufferSize) {
|
|
246
|
-
const dropCount = Math.max(1, Math.floor(this.maxBufferSize * 0.1));
|
|
247
|
-
this.metricsBuffer.splice(0, dropCount);
|
|
248
|
-
if (this.onDrop) {
|
|
249
|
-
this.onDrop(dropCount);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
this.metricsBuffer.push(entry);
|
|
253
|
-
if (this.metricsBuffer.length >= this.batchSize) {
|
|
254
|
-
this.flush().catch((err) => {
|
|
255
|
-
this.handleError(err);
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
/** Drain and return all entries from the log buffer */
|
|
260
|
-
drainLogBuffer() {
|
|
261
|
-
const entries = this.logBuffer;
|
|
262
|
-
this.logBuffer = [];
|
|
263
|
-
return entries;
|
|
264
|
-
}
|
|
265
|
-
/** Drain and return all entries from the metrics buffer */
|
|
266
|
-
drainMetricsBuffer() {
|
|
267
|
-
const entries = this.metricsBuffer;
|
|
268
|
-
this.metricsBuffer = [];
|
|
269
|
-
return entries;
|
|
270
|
-
}
|
|
271
|
-
/** Send log entries to the server with retry */
|
|
272
|
-
async sendLogs(entries) {
|
|
273
|
-
try {
|
|
274
|
-
await this.post("/api/logs", { entries });
|
|
275
|
-
} catch (err) {
|
|
276
|
-
const capacity = this.maxBufferSize - this.logBuffer.length;
|
|
277
|
-
if (capacity > 0) {
|
|
278
|
-
const toRestore = entries.slice(0, capacity);
|
|
279
|
-
this.logBuffer.unshift(...toRestore);
|
|
280
|
-
const dropped = entries.length - toRestore.length;
|
|
281
|
-
if (dropped > 0 && this.onDrop) {
|
|
282
|
-
this.onDrop(dropped);
|
|
283
|
-
}
|
|
284
|
-
} else if (this.onDrop) {
|
|
285
|
-
this.onDrop(entries.length);
|
|
286
|
-
}
|
|
287
|
-
throw err;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
/** Send metric entries to the server with retry */
|
|
291
|
-
async sendMetrics(entries) {
|
|
292
|
-
try {
|
|
293
|
-
await this.post("/api/metrics", { entries });
|
|
294
|
-
} catch (err) {
|
|
295
|
-
const capacity = this.maxBufferSize - this.metricsBuffer.length;
|
|
296
|
-
if (capacity > 0) {
|
|
297
|
-
const toRestore = entries.slice(0, capacity);
|
|
298
|
-
this.metricsBuffer.unshift(...toRestore);
|
|
299
|
-
const dropped = entries.length - toRestore.length;
|
|
300
|
-
if (dropped > 0 && this.onDrop) {
|
|
301
|
-
this.onDrop(dropped);
|
|
302
|
-
}
|
|
303
|
-
} else if (this.onDrop) {
|
|
304
|
-
this.onDrop(entries.length);
|
|
305
|
-
}
|
|
306
|
-
throw err;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* POST JSON to the Sentinel server with exponential backoff retry.
|
|
311
|
-
* Retries on network errors and 5xx responses. Does NOT retry on 4xx.
|
|
312
|
-
*/
|
|
313
|
-
async post(path4, body) {
|
|
314
|
-
const fetchFn = this.getFetch();
|
|
315
|
-
if (!fetchFn) {
|
|
316
|
-
return void 0;
|
|
317
|
-
}
|
|
318
|
-
const url = `${this.url}${path4}`;
|
|
319
|
-
const headers = {
|
|
320
|
-
"Content-Type": "application/json"
|
|
321
|
-
};
|
|
322
|
-
if (this.token) {
|
|
323
|
-
headers["Authorization"] = `Bearer ${this.token}`;
|
|
324
|
-
}
|
|
325
|
-
let lastError;
|
|
326
|
-
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
327
|
-
try {
|
|
328
|
-
const response = await fetchFn(url, {
|
|
329
|
-
method: "POST",
|
|
330
|
-
headers,
|
|
331
|
-
body: JSON.stringify(body)
|
|
332
|
-
});
|
|
333
|
-
if (response.status >= 400 && response.status < 500) {
|
|
334
|
-
const text = await response.text().catch(() => "");
|
|
335
|
-
const err = new Error(`Sentinel server returned ${response.status}: ${text}`);
|
|
336
|
-
this.handleError(err);
|
|
337
|
-
return void 0;
|
|
338
|
-
}
|
|
339
|
-
if (response.status >= 500) {
|
|
340
|
-
const text = await response.text().catch(() => "");
|
|
341
|
-
lastError = new Error(`Sentinel server returned ${response.status}: ${text}`);
|
|
342
|
-
if (attempt < this.maxRetries) {
|
|
343
|
-
await this.backoff(attempt);
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
this.handleError(lastError);
|
|
347
|
-
throw lastError;
|
|
348
|
-
}
|
|
349
|
-
const contentType = response.headers.get("content-type") || "";
|
|
350
|
-
if (contentType.includes("application/json")) {
|
|
351
|
-
return await response.json();
|
|
352
|
-
}
|
|
353
|
-
return void 0;
|
|
354
|
-
} catch (err) {
|
|
355
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
356
|
-
if (attempt < this.maxRetries) {
|
|
357
|
-
await this.backoff(attempt);
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
this.handleError(lastError);
|
|
361
|
-
throw lastError;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (lastError) throw lastError;
|
|
365
|
-
return void 0;
|
|
366
|
-
}
|
|
367
|
-
/** Get the fetch function, falling back to globalThis.fetch */
|
|
368
|
-
getFetch() {
|
|
369
|
-
if (typeof globalThis !== "undefined" && typeof globalThis.fetch === "function") {
|
|
370
|
-
return globalThis.fetch;
|
|
371
|
-
}
|
|
372
|
-
return void 0;
|
|
373
|
-
}
|
|
374
|
-
/** Wait with exponential backoff */
|
|
375
|
-
backoff(attempt) {
|
|
376
|
-
const delayMs = this.retryBackoffMs * Math.pow(2, attempt);
|
|
377
|
-
const jitter = Math.floor(Math.random() * delayMs * 0.25);
|
|
378
|
-
return new Promise((resolve) => setTimeout(resolve, delayMs + jitter));
|
|
379
|
-
}
|
|
380
|
-
/** Handle an error, calling the onError callback if provided */
|
|
381
|
-
handleError(err) {
|
|
382
|
-
if (this.onError) {
|
|
383
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
384
|
-
this.onError(error);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
function createSentinelClient(options) {
|
|
389
|
-
return new SentinelClient(options);
|
|
390
|
-
}
|
|
391
|
-
var SentinelTransport = class {
|
|
392
|
-
client;
|
|
393
|
-
constructor(client) {
|
|
394
|
-
this.client = client;
|
|
395
|
-
}
|
|
396
|
-
send(entry) {
|
|
397
|
-
this.client.log(
|
|
398
|
-
entry.level,
|
|
399
|
-
entry.symbol,
|
|
400
|
-
entry.message,
|
|
401
|
-
{
|
|
402
|
-
...entry.data,
|
|
403
|
-
symbolType: entry.symbolType,
|
|
404
|
-
correlationId: entry.correlationId
|
|
405
|
-
}
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
function createSentinelTransport(clientOrOptions) {
|
|
410
|
-
const client = clientOrOptions instanceof SentinelClient ? clientOrOptions : new SentinelClient(clientOrOptions);
|
|
411
|
-
return new SentinelTransport(client);
|
|
412
|
-
}
|
|
413
|
-
function enableSentinel(logger, clientOrOptions) {
|
|
414
|
-
const transport = createSentinelTransport(clientOrOptions);
|
|
415
|
-
logger.addTransport(transport);
|
|
416
|
-
return transport;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// ../sentinel/dist/index.js
|
|
420
|
-
import * as path from "path";
|
|
421
|
-
import * as fs from "fs";
|
|
422
|
-
import { fileURLToPath } from "url";
|
|
423
|
-
import * as fs2 from "fs";
|
|
424
|
-
import * as path2 from "path";
|
|
425
|
-
import * as fs3 from "fs";
|
|
426
|
-
import * as path3 from "path";
|
|
427
|
-
import * as fs4 from "fs";
|
|
428
|
-
var DEFAULT_CONFIG = {
|
|
429
|
-
minScore: 30,
|
|
430
|
-
maxResults: 5,
|
|
431
|
-
boostConfidence: true
|
|
432
|
-
};
|
|
433
|
-
var PatternMatcher = class {
|
|
434
|
-
constructor(storage) {
|
|
435
|
-
this.storage = storage;
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* Match an incident against all patterns and return ranked results
|
|
439
|
-
*/
|
|
440
|
-
match(incident, config = {}) {
|
|
441
|
-
const { minScore, maxResults, boostConfidence } = {
|
|
442
|
-
...DEFAULT_CONFIG,
|
|
443
|
-
...config
|
|
444
|
-
};
|
|
445
|
-
const patterns = this.storage.getAllPatterns({ includePrivate: true });
|
|
446
|
-
const matches = [];
|
|
447
|
-
for (const pattern of patterns) {
|
|
448
|
-
if (!this.matchEnvironment(pattern, incident)) {
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
const { score, matchedCriteria } = this.scoreMatch(pattern, incident);
|
|
452
|
-
if (score >= minScore) {
|
|
453
|
-
let confidence = score;
|
|
454
|
-
if (boostConfidence) {
|
|
455
|
-
const confidenceFactor = pattern.confidence.score / 100;
|
|
456
|
-
confidence = score * (0.5 + 0.5 * confidenceFactor);
|
|
457
|
-
}
|
|
458
|
-
matches.push({
|
|
459
|
-
pattern,
|
|
460
|
-
score,
|
|
461
|
-
matchedCriteria,
|
|
462
|
-
confidence: Math.round(confidence)
|
|
463
|
-
});
|
|
464
|
-
this.storage.updatePatternConfidence(pattern.id, "matched");
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return matches.sort((a, b) => b.confidence - a.confidence).slice(0, maxResults);
|
|
468
|
-
}
|
|
469
|
-
/**
|
|
470
|
-
* Test a pattern against historical incidents
|
|
471
|
-
*/
|
|
472
|
-
testPattern(pattern, limit = 100) {
|
|
473
|
-
const incidents = this.storage.getRecentIncidents({ limit });
|
|
474
|
-
const wouldMatch = [];
|
|
475
|
-
let totalScore = 0;
|
|
476
|
-
for (const incident of incidents) {
|
|
477
|
-
if (!this.matchEnvironment(pattern, incident)) {
|
|
478
|
-
continue;
|
|
479
|
-
}
|
|
480
|
-
const { score } = this.scoreMatch(pattern, incident);
|
|
481
|
-
if (score >= 30) {
|
|
482
|
-
wouldMatch.push(incident);
|
|
483
|
-
totalScore += score;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
return {
|
|
487
|
-
wouldMatch,
|
|
488
|
-
matchCount: wouldMatch.length,
|
|
489
|
-
avgScore: wouldMatch.length > 0 ? Math.round(totalScore / wouldMatch.length) : 0
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Score how well a pattern matches an incident
|
|
494
|
-
*/
|
|
495
|
-
scoreMatch(pattern, incident) {
|
|
496
|
-
let score = 0;
|
|
497
|
-
const matchedCriteria = {
|
|
498
|
-
symbols: [],
|
|
499
|
-
errorKeywords: [],
|
|
500
|
-
missingSignals: []
|
|
501
|
-
};
|
|
502
|
-
const symbolScore = this.matchSymbols(
|
|
503
|
-
pattern.pattern.symbols,
|
|
504
|
-
incident.symbols,
|
|
505
|
-
matchedCriteria.symbols
|
|
506
|
-
);
|
|
507
|
-
score += Math.min(symbolScore, 50);
|
|
508
|
-
const errorScore = this.matchErrorText(
|
|
509
|
-
pattern,
|
|
510
|
-
incident,
|
|
511
|
-
matchedCriteria.errorKeywords
|
|
512
|
-
);
|
|
513
|
-
score += Math.min(errorScore, 25);
|
|
514
|
-
const signalScore = this.matchMissingSignals(
|
|
515
|
-
pattern,
|
|
516
|
-
incident,
|
|
517
|
-
matchedCriteria.missingSignals
|
|
518
|
-
);
|
|
519
|
-
score += Math.min(signalScore, 25);
|
|
520
|
-
score = Math.min(score, 100);
|
|
521
|
-
return { score, matchedCriteria };
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Match symbols between pattern and incident
|
|
525
|
-
*/
|
|
526
|
-
matchSymbols(patternSymbols, incidentSymbols, matched) {
|
|
527
|
-
let score = 0;
|
|
528
|
-
const symbolTypes = [
|
|
529
|
-
"feature",
|
|
530
|
-
"component",
|
|
531
|
-
"flow",
|
|
532
|
-
"gate",
|
|
533
|
-
"signal",
|
|
534
|
-
"state",
|
|
535
|
-
"integration"
|
|
536
|
-
];
|
|
537
|
-
for (const type of symbolTypes) {
|
|
538
|
-
const patternValue = patternSymbols[type];
|
|
539
|
-
const incidentValue = incidentSymbols[type];
|
|
540
|
-
if (!patternValue || !incidentValue) {
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
if (typeof patternValue === "string") {
|
|
544
|
-
if (this.matchSingleSymbol(patternValue, incidentValue)) {
|
|
545
|
-
score += patternValue.includes("*") ? 5 : 10;
|
|
546
|
-
matched.push(type);
|
|
547
|
-
}
|
|
548
|
-
} else if (Array.isArray(patternValue)) {
|
|
549
|
-
for (const pv of patternValue) {
|
|
550
|
-
if (this.matchSingleSymbol(pv, incidentValue)) {
|
|
551
|
-
score += 7;
|
|
552
|
-
matched.push(type);
|
|
553
|
-
break;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
return score;
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* Match a single symbol value (supports wildcards)
|
|
562
|
-
*/
|
|
563
|
-
matchSingleSymbol(pattern, value) {
|
|
564
|
-
if (pattern === "*") {
|
|
565
|
-
return true;
|
|
566
|
-
}
|
|
567
|
-
if (pattern.endsWith("*")) {
|
|
568
|
-
const prefix = pattern.slice(0, -1);
|
|
569
|
-
return value.startsWith(prefix);
|
|
570
|
-
}
|
|
571
|
-
if (pattern.startsWith("*")) {
|
|
572
|
-
const suffix = pattern.slice(1);
|
|
573
|
-
return value.endsWith(suffix);
|
|
574
|
-
}
|
|
575
|
-
if (pattern.includes("*")) {
|
|
576
|
-
const regex = new RegExp(
|
|
577
|
-
"^" + pattern.replace(/\*/g, ".*") + "$"
|
|
578
|
-
);
|
|
579
|
-
return regex.test(value);
|
|
580
|
-
}
|
|
581
|
-
return pattern === value;
|
|
582
|
-
}
|
|
583
|
-
/**
|
|
584
|
-
* Match error text keywords and regex
|
|
585
|
-
*/
|
|
586
|
-
matchErrorText(pattern, incident, matched) {
|
|
587
|
-
let score = 0;
|
|
588
|
-
const errorMessage = incident.error.message.toLowerCase();
|
|
589
|
-
const errorType = incident.error.type?.toLowerCase();
|
|
590
|
-
if (pattern.pattern.errorContains) {
|
|
591
|
-
for (const keyword of pattern.pattern.errorContains) {
|
|
592
|
-
if (errorMessage.includes(keyword.toLowerCase())) {
|
|
593
|
-
score += 5;
|
|
594
|
-
matched.push(keyword);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
if (pattern.pattern.errorMatches) {
|
|
599
|
-
try {
|
|
600
|
-
const regex = new RegExp(pattern.pattern.errorMatches, "i");
|
|
601
|
-
if (regex.test(incident.error.message)) {
|
|
602
|
-
score += 10;
|
|
603
|
-
matched.push(`regex:${pattern.pattern.errorMatches}`);
|
|
604
|
-
}
|
|
605
|
-
} catch {
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
if (pattern.pattern.errorType && errorType) {
|
|
609
|
-
for (const type of pattern.pattern.errorType) {
|
|
610
|
-
if (errorType.includes(type.toLowerCase())) {
|
|
611
|
-
score += 5;
|
|
612
|
-
matched.push(`type:${type}`);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
return score;
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Match missing signals from flow position
|
|
620
|
-
*/
|
|
621
|
-
matchMissingSignals(pattern, incident, matched) {
|
|
622
|
-
if (!pattern.pattern.missingSignals || !incident.flowPosition?.missing) {
|
|
623
|
-
return 0;
|
|
624
|
-
}
|
|
625
|
-
let score = 0;
|
|
626
|
-
for (const expectedSignal of pattern.pattern.missingSignals) {
|
|
627
|
-
for (const missingSignal of incident.flowPosition.missing) {
|
|
628
|
-
if (this.matchSingleSymbol(expectedSignal, missingSignal)) {
|
|
629
|
-
score += 12;
|
|
630
|
-
matched.push(missingSignal);
|
|
631
|
-
break;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return score;
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Check if pattern's environment filter matches incident
|
|
639
|
-
*/
|
|
640
|
-
matchEnvironment(pattern, incident) {
|
|
641
|
-
if (!pattern.pattern.environment || pattern.pattern.environment.length === 0) {
|
|
642
|
-
return true;
|
|
643
|
-
}
|
|
644
|
-
return pattern.pattern.environment.includes(incident.environment);
|
|
645
|
-
}
|
|
646
|
-
};
|
|
647
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
648
|
-
var __dirname = path.dirname(__filename);
|
|
649
|
-
function loadUniversalPatterns() {
|
|
650
|
-
const filePath = path.join(__dirname, "universal-patterns.json");
|
|
651
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
652
|
-
return JSON.parse(content);
|
|
653
|
-
}
|
|
654
|
-
function loadParadigmPatterns() {
|
|
655
|
-
const filePath = path.join(__dirname, "paradigm-patterns.json");
|
|
656
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
657
|
-
return JSON.parse(content);
|
|
658
|
-
}
|
|
659
|
-
function loadAllSeedPatterns() {
|
|
660
|
-
const universal = loadUniversalPatterns();
|
|
661
|
-
const paradigm = loadParadigmPatterns();
|
|
662
|
-
return {
|
|
663
|
-
version: "1.0.0",
|
|
664
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
665
|
-
patterns: [...universal.patterns, ...paradigm.patterns]
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
function ensurePrefix(id, prefix) {
|
|
669
|
-
return id.startsWith(prefix) ? id : `${prefix}${id}`;
|
|
670
|
-
}
|
|
671
|
-
var FlowTracker = class {
|
|
672
|
-
flowId;
|
|
673
|
-
sentinel;
|
|
674
|
-
actual = [];
|
|
675
|
-
expected = [];
|
|
676
|
-
completed = false;
|
|
677
|
-
constructor(flowId, sentinel) {
|
|
678
|
-
this.flowId = ensurePrefix(flowId, "$");
|
|
679
|
-
this.sentinel = sentinel;
|
|
680
|
-
}
|
|
681
|
-
/** Declare which signals/gates are expected in this flow */
|
|
682
|
-
expect(...symbols) {
|
|
683
|
-
this.expected.push(...symbols);
|
|
684
|
-
return this;
|
|
685
|
-
}
|
|
686
|
-
/** Record a generic step in the flow */
|
|
687
|
-
step(symbol) {
|
|
688
|
-
this.actual.push(symbol);
|
|
689
|
-
return this;
|
|
690
|
-
}
|
|
691
|
-
/** Record a gate check result */
|
|
692
|
-
gate(id, passed) {
|
|
693
|
-
const gateId = ensurePrefix(id, "^");
|
|
694
|
-
this.actual.push(gateId);
|
|
695
|
-
if (!passed) {
|
|
696
|
-
this.fail(new Error(`Gate ${gateId} failed`));
|
|
697
|
-
}
|
|
698
|
-
return this;
|
|
699
|
-
}
|
|
700
|
-
/** Record a signal emission */
|
|
701
|
-
signal(id, _data) {
|
|
702
|
-
this.actual.push(ensurePrefix(id, "!"));
|
|
703
|
-
return this;
|
|
704
|
-
}
|
|
705
|
-
/** Mark the flow as successfully completed */
|
|
706
|
-
complete() {
|
|
707
|
-
this.completed = true;
|
|
708
|
-
}
|
|
709
|
-
/** Capture an error with full flow position context */
|
|
710
|
-
fail(error) {
|
|
711
|
-
if (this.completed) return;
|
|
712
|
-
this.completed = true;
|
|
713
|
-
const missing = this.expected.filter((s) => !this.actual.includes(s));
|
|
714
|
-
const failedAt = this.actual.length > 0 ? this.actual[this.actual.length - 1] : void 0;
|
|
715
|
-
const flowPosition = {
|
|
716
|
-
flowId: this.flowId,
|
|
717
|
-
expected: this.expected,
|
|
718
|
-
actual: this.actual,
|
|
719
|
-
missing,
|
|
720
|
-
failedAt
|
|
721
|
-
};
|
|
722
|
-
this.sentinel.capture(error, { flow: this.flowId }, flowPosition);
|
|
723
|
-
}
|
|
724
|
-
};
|
|
725
|
-
var Sentinel = class {
|
|
726
|
-
storage;
|
|
727
|
-
matcher;
|
|
728
|
-
config;
|
|
729
|
-
ready = false;
|
|
730
|
-
readyPromise = null;
|
|
731
|
-
seeded = false;
|
|
732
|
-
constructor(config) {
|
|
733
|
-
this.config = config;
|
|
734
|
-
this.storage = new SentinelStorage(config.dbPath);
|
|
735
|
-
this.matcher = new PatternMatcher(this.storage);
|
|
736
|
-
}
|
|
737
|
-
/** Explicitly initialize storage. Optional — auto-called on first capture. */
|
|
738
|
-
async init() {
|
|
739
|
-
if (this.ready) return;
|
|
740
|
-
if (this.readyPromise) return this.readyPromise;
|
|
741
|
-
this.readyPromise = this.doInit();
|
|
742
|
-
return this.readyPromise;
|
|
743
|
-
}
|
|
744
|
-
async doInit() {
|
|
745
|
-
await this.storage.ensureReady();
|
|
746
|
-
if (!this.seeded) {
|
|
747
|
-
try {
|
|
748
|
-
const { patterns } = loadAllSeedPatterns();
|
|
749
|
-
for (const pattern of patterns) {
|
|
750
|
-
try {
|
|
751
|
-
this.storage.addPattern(pattern);
|
|
752
|
-
} catch {
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
} catch {
|
|
756
|
-
}
|
|
757
|
-
this.seeded = true;
|
|
758
|
-
}
|
|
759
|
-
this.ready = true;
|
|
760
|
-
}
|
|
761
|
-
ensureReady() {
|
|
762
|
-
if (!this.ready) {
|
|
763
|
-
if (!this.readyPromise) {
|
|
764
|
-
this.readyPromise = this.doInit();
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
// ── Symbol Context ──────────────────────────────────────────────
|
|
769
|
-
/**
|
|
770
|
-
* Create a component context for scoped error capture.
|
|
771
|
-
*
|
|
772
|
-
* @param id - Component symbol (e.g. '#checkout' or 'checkout')
|
|
773
|
-
* @returns ComponentContext with capture() and wrap() methods
|
|
774
|
-
*/
|
|
775
|
-
component(id) {
|
|
776
|
-
const componentId = ensurePrefix(id, "#");
|
|
777
|
-
const self = this;
|
|
778
|
-
return {
|
|
779
|
-
id: componentId,
|
|
780
|
-
capture(error, extra) {
|
|
781
|
-
return self.capture(error, { component: componentId, ...extra });
|
|
782
|
-
},
|
|
783
|
-
wrap(fn) {
|
|
784
|
-
const wrapped = ((...args) => {
|
|
785
|
-
try {
|
|
786
|
-
const result = fn(...args);
|
|
787
|
-
if (result && typeof result.catch === "function") {
|
|
788
|
-
return result.catch((err) => {
|
|
789
|
-
self.capture(err, { component: componentId });
|
|
790
|
-
throw err;
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
return result;
|
|
794
|
-
} catch (err) {
|
|
795
|
-
if (err instanceof Error) {
|
|
796
|
-
self.capture(err, { component: componentId });
|
|
797
|
-
}
|
|
798
|
-
throw err;
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
|
-
return wrapped;
|
|
802
|
-
}
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Record a gate check result.
|
|
807
|
-
* If the gate fails, auto-captures an incident.
|
|
808
|
-
*
|
|
809
|
-
* @param id - Gate symbol (e.g. '^authenticated' or 'authenticated')
|
|
810
|
-
* @param passed - Whether the gate passed
|
|
811
|
-
*/
|
|
812
|
-
gate(id, passed) {
|
|
813
|
-
if (!passed) {
|
|
814
|
-
const gateId = ensurePrefix(id, "^");
|
|
815
|
-
this.capture(new Error(`Gate ${gateId} failed`), { gate: gateId });
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
/**
|
|
819
|
-
* Record a signal emission. Primarily for flow tracking context.
|
|
820
|
-
*
|
|
821
|
-
* @param id - Signal symbol (e.g. '!payment-authorized' or 'payment-authorized')
|
|
822
|
-
*/
|
|
823
|
-
signal(id, _data) {
|
|
824
|
-
void ensurePrefix(id, "!");
|
|
825
|
-
}
|
|
826
|
-
// ── Flow Tracking ───────────────────────────────────────────────
|
|
827
|
-
/**
|
|
828
|
-
* Create a flow tracker for monitoring multi-step operations.
|
|
829
|
-
*
|
|
830
|
-
* @param id - Flow symbol (e.g. '$checkout-flow' or 'checkout-flow')
|
|
831
|
-
* @returns FlowTracker instance
|
|
832
|
-
*/
|
|
833
|
-
flow(id) {
|
|
834
|
-
return new FlowTracker(id, this);
|
|
835
|
-
}
|
|
836
|
-
// ── Error Capture ───────────────────────────────────────────────
|
|
837
|
-
/**
|
|
838
|
-
* Capture an error with symbolic context.
|
|
839
|
-
*
|
|
840
|
-
* @param error - The error to capture
|
|
841
|
-
* @param context - Symbolic context (component, gate, flow, signal)
|
|
842
|
-
* @param flowPosition - Optional flow position data
|
|
843
|
-
* @returns Incident ID (e.g. 'INC-001')
|
|
844
|
-
*/
|
|
845
|
-
capture(error, context, flowPosition) {
|
|
846
|
-
this.ensureReady();
|
|
847
|
-
const input = {
|
|
848
|
-
error: {
|
|
849
|
-
message: error.message,
|
|
850
|
-
stack: error.stack,
|
|
851
|
-
type: error.constructor.name !== "Error" ? error.constructor.name : void 0
|
|
852
|
-
},
|
|
853
|
-
symbols: context || {},
|
|
854
|
-
environment: this.config.environment || "development",
|
|
855
|
-
service: this.config.service,
|
|
856
|
-
version: this.config.version,
|
|
857
|
-
flowPosition
|
|
858
|
-
};
|
|
859
|
-
const incidentId = this.storage.recordIncident(input);
|
|
860
|
-
const incident = this.storage.getIncident(incidentId);
|
|
861
|
-
if (incident && this.config.onCapture) {
|
|
862
|
-
this.config.onCapture(incident);
|
|
863
|
-
}
|
|
864
|
-
return incidentId;
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* Get pattern matches for a captured incident.
|
|
868
|
-
*
|
|
869
|
-
* @param incidentId - The incident ID to match
|
|
870
|
-
* @returns Array of pattern matches sorted by confidence
|
|
871
|
-
*/
|
|
872
|
-
match(incidentId) {
|
|
873
|
-
const incident = this.storage.getIncident(incidentId);
|
|
874
|
-
if (!incident) return [];
|
|
875
|
-
return this.matcher.match(incident);
|
|
876
|
-
}
|
|
877
|
-
// ── Framework Integration ───────────────────────────────────────
|
|
878
|
-
/**
|
|
879
|
-
* Create Express error-handling middleware.
|
|
880
|
-
*
|
|
881
|
-
* Usage:
|
|
882
|
-
* app.use(sentinel.express());
|
|
883
|
-
*/
|
|
884
|
-
express() {
|
|
885
|
-
const self = this;
|
|
886
|
-
return (err, req, res, next) => {
|
|
887
|
-
const context = {};
|
|
888
|
-
const routeParts = (req.path || req.url || "").split("/").filter(Boolean);
|
|
889
|
-
if (routeParts.length >= 2) {
|
|
890
|
-
context.component = `#${routeParts[1]}`;
|
|
891
|
-
}
|
|
892
|
-
const incidentId = self.capture(err, context);
|
|
893
|
-
if (res.setHeader) {
|
|
894
|
-
res.setHeader("X-Sentinel-Incident", incidentId);
|
|
895
|
-
}
|
|
896
|
-
next(err);
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
// ── Lifecycle ───────────────────────────────────────────────────
|
|
900
|
-
/** Close the database connection. Call when shutting down. */
|
|
901
|
-
close() {
|
|
902
|
-
this.storage.close();
|
|
903
|
-
this.ready = false;
|
|
904
|
-
this.readyPromise = null;
|
|
905
|
-
}
|
|
906
|
-
/** Get the underlying storage instance (for advanced usage). */
|
|
907
|
-
getStorage() {
|
|
908
|
-
return this.storage;
|
|
909
|
-
}
|
|
910
|
-
/** Get the underlying pattern matcher (for advanced usage). */
|
|
911
|
-
getMatcher() {
|
|
912
|
-
return this.matcher;
|
|
913
|
-
}
|
|
914
|
-
};
|
|
915
|
-
var DIR_PATTERNS = [
|
|
916
|
-
{ dirs: ["services", "src/services"], prefix: "#", type: "components" },
|
|
917
|
-
{ dirs: ["routes", "src/routes", "api", "src/api"], prefix: "#", type: "components" },
|
|
918
|
-
{ dirs: ["handlers", "src/handlers"], prefix: "#", type: "components" },
|
|
919
|
-
{ dirs: ["controllers", "src/controllers"], prefix: "#", type: "components" },
|
|
920
|
-
{ dirs: ["components", "src/components"], prefix: "#", type: "components" },
|
|
921
|
-
{ dirs: ["lib", "src/lib"], prefix: "#", type: "components" },
|
|
922
|
-
{ dirs: ["middleware", "src/middleware"], prefix: "^", type: "gates" },
|
|
923
|
-
{ dirs: ["guards", "src/guards"], prefix: "^", type: "gates" },
|
|
924
|
-
{ dirs: ["auth", "src/auth"], prefix: "^", type: "gates" },
|
|
925
|
-
{ dirs: ["events", "src/events"], prefix: "!", type: "signals" },
|
|
926
|
-
{ dirs: ["listeners", "src/listeners"], prefix: "!", type: "signals" },
|
|
927
|
-
{ dirs: ["flows", "src/flows"], prefix: "$", type: "flows" },
|
|
928
|
-
{ dirs: ["workflows", "src/workflows"], prefix: "$", type: "flows" },
|
|
929
|
-
{ dirs: ["pipelines", "src/pipelines"], prefix: "$", type: "flows" }
|
|
930
|
-
];
|
|
931
|
-
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".js", ".tsx", ".jsx", ".mjs", ".mts"]);
|
|
932
|
-
function detectSymbols(projectDir) {
|
|
933
|
-
const result = {
|
|
934
|
-
components: [],
|
|
935
|
-
gates: [],
|
|
936
|
-
flows: [],
|
|
937
|
-
signals: [],
|
|
938
|
-
routes: {}
|
|
939
|
-
};
|
|
940
|
-
const purposeSymbols = readPurposeFiles(projectDir);
|
|
941
|
-
if (purposeSymbols) {
|
|
942
|
-
result.components.push(...purposeSymbols.components);
|
|
943
|
-
result.gates.push(...purposeSymbols.gates);
|
|
944
|
-
result.flows.push(...purposeSymbols.flows);
|
|
945
|
-
result.signals.push(...purposeSymbols.signals);
|
|
946
|
-
}
|
|
947
|
-
for (const pattern of DIR_PATTERNS) {
|
|
948
|
-
for (const dir of pattern.dirs) {
|
|
949
|
-
const fullPath = path2.join(projectDir, dir);
|
|
950
|
-
if (!fs2.existsSync(fullPath)) continue;
|
|
951
|
-
const files = safeReaddir(fullPath);
|
|
952
|
-
for (const file of files) {
|
|
953
|
-
const ext = path2.extname(file);
|
|
954
|
-
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
955
|
-
const name = path2.basename(file, ext);
|
|
956
|
-
if (name === "index" || name.endsWith(".test") || name.endsWith(".spec")) continue;
|
|
957
|
-
const symbol = `${pattern.prefix}${toKebabCase(name)}`;
|
|
958
|
-
if (!result[pattern.type].includes(symbol)) {
|
|
959
|
-
result[pattern.type].push(symbol);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
scanRoutes(projectDir, result);
|
|
965
|
-
return result;
|
|
966
|
-
}
|
|
967
|
-
function generateConfig(projectDir) {
|
|
968
|
-
const detected = detectSymbols(projectDir);
|
|
969
|
-
return {
|
|
970
|
-
version: "1.0",
|
|
971
|
-
project: path2.basename(projectDir),
|
|
972
|
-
symbols: {
|
|
973
|
-
components: detected.components.length > 0 ? detected.components : void 0,
|
|
974
|
-
gates: detected.gates.length > 0 ? detected.gates : void 0,
|
|
975
|
-
flows: detected.flows.length > 0 ? detected.flows : void 0,
|
|
976
|
-
signals: detected.signals.length > 0 ? detected.signals : void 0
|
|
977
|
-
},
|
|
978
|
-
routes: Object.keys(detected.routes).length > 0 ? detected.routes : void 0
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
function readPurposeFiles(projectDir) {
|
|
982
|
-
const paradigmDir = path2.join(projectDir, ".paradigm");
|
|
983
|
-
if (!fs2.existsSync(paradigmDir)) return null;
|
|
984
|
-
const result = {
|
|
985
|
-
components: [],
|
|
986
|
-
gates: [],
|
|
987
|
-
flows: [],
|
|
988
|
-
signals: [],
|
|
989
|
-
routes: {}
|
|
990
|
-
};
|
|
991
|
-
const purposeFiles = findFiles(projectDir, ".purpose");
|
|
992
|
-
for (const file of purposeFiles) {
|
|
993
|
-
try {
|
|
994
|
-
const content = fs2.readFileSync(file, "utf-8");
|
|
995
|
-
extractPurposeSymbols(content, result);
|
|
996
|
-
} catch {
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
const hasAny = result.components.length > 0 || result.gates.length > 0 || result.flows.length > 0 || result.signals.length > 0;
|
|
1000
|
-
return hasAny ? result : null;
|
|
1001
|
-
}
|
|
1002
|
-
function extractPurposeSymbols(content, result) {
|
|
1003
|
-
const lines = content.split("\n");
|
|
1004
|
-
let currentSection = "";
|
|
1005
|
-
for (const line of lines) {
|
|
1006
|
-
const trimmed = line.trim();
|
|
1007
|
-
if (trimmed === "components:" || trimmed === "features:") {
|
|
1008
|
-
currentSection = "components";
|
|
1009
|
-
continue;
|
|
1010
|
-
}
|
|
1011
|
-
if (trimmed === "gates:") {
|
|
1012
|
-
currentSection = "gates";
|
|
1013
|
-
continue;
|
|
1014
|
-
}
|
|
1015
|
-
if (trimmed === "flows:") {
|
|
1016
|
-
currentSection = "flows";
|
|
1017
|
-
continue;
|
|
1018
|
-
}
|
|
1019
|
-
if (trimmed === "signals:") {
|
|
1020
|
-
currentSection = "signals";
|
|
1021
|
-
continue;
|
|
1022
|
-
}
|
|
1023
|
-
if (currentSection && /^\s{2}\S/.test(line)) {
|
|
1024
|
-
const idMatch = trimmed.match(/^([a-zA-Z][\w-]*):$/);
|
|
1025
|
-
if (idMatch) {
|
|
1026
|
-
const prefixes = {
|
|
1027
|
-
components: "#",
|
|
1028
|
-
gates: "^",
|
|
1029
|
-
flows: "$",
|
|
1030
|
-
signals: "!"
|
|
1031
|
-
};
|
|
1032
|
-
const prefix = prefixes[currentSection] || "#";
|
|
1033
|
-
const symbol = `${prefix}${idMatch[1]}`;
|
|
1034
|
-
if (!result[currentSection]?.includes(symbol)) {
|
|
1035
|
-
result[currentSection]?.push(symbol);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
if (trimmed && !line.startsWith(" ") && !trimmed.endsWith(":")) {
|
|
1040
|
-
currentSection = "";
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
function scanRoutes(projectDir, result) {
|
|
1045
|
-
const routeDirs = ["routes", "src/routes", "api", "src/api"];
|
|
1046
|
-
for (const dir of routeDirs) {
|
|
1047
|
-
const fullPath = path2.join(projectDir, dir);
|
|
1048
|
-
if (!fs2.existsSync(fullPath)) continue;
|
|
1049
|
-
const files = safeReaddir(fullPath);
|
|
1050
|
-
for (const file of files) {
|
|
1051
|
-
const ext = path2.extname(file);
|
|
1052
|
-
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
1053
|
-
const name = path2.basename(file, ext);
|
|
1054
|
-
if (name === "index") continue;
|
|
1055
|
-
const routePrefix = `/api/${toKebabCase(name)}`;
|
|
1056
|
-
const component = `#${toKebabCase(name)}`;
|
|
1057
|
-
result.routes[routePrefix] = component;
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
function toKebabCase(str) {
|
|
1062
|
-
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/\..*$/, "").toLowerCase();
|
|
1063
|
-
}
|
|
1064
|
-
function safeReaddir(dir) {
|
|
1065
|
-
try {
|
|
1066
|
-
return fs2.readdirSync(dir).filter((f) => {
|
|
1067
|
-
const fullPath = path2.join(dir, f);
|
|
1068
|
-
try {
|
|
1069
|
-
return fs2.statSync(fullPath).isFile();
|
|
1070
|
-
} catch {
|
|
1071
|
-
return false;
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1074
|
-
} catch {
|
|
1075
|
-
return [];
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
function findFiles(dir, filename, maxDepth = 4, depth = 0) {
|
|
1079
|
-
if (depth > maxDepth) return [];
|
|
1080
|
-
const results = [];
|
|
1081
|
-
const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".next", ".nuxt"]);
|
|
1082
|
-
try {
|
|
1083
|
-
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
1084
|
-
for (const entry of entries) {
|
|
1085
|
-
if (entry.isFile() && entry.name === filename) {
|
|
1086
|
-
results.push(path2.join(dir, entry.name));
|
|
1087
|
-
} else if (entry.isDirectory() && !skipDirs.has(entry.name)) {
|
|
1088
|
-
results.push(...findFiles(path2.join(dir, entry.name), filename, maxDepth, depth + 1));
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
} catch {
|
|
1092
|
-
}
|
|
1093
|
-
return results;
|
|
1094
|
-
}
|
|
1095
|
-
var DEFAULT_SIMILARITY_THRESHOLD = 0.6;
|
|
1096
|
-
var DECAY_HALF_LIFE_DAYS = 14;
|
|
1097
|
-
var IncidentGrouper = class {
|
|
1098
|
-
constructor(storage, config) {
|
|
1099
|
-
this.storage = storage;
|
|
1100
|
-
this.similarityThreshold = config?.similarityThreshold ?? DEFAULT_SIMILARITY_THRESHOLD;
|
|
1101
|
-
this.decayHalfLifeDays = config?.decayHalfLifeDays ?? DECAY_HALF_LIFE_DAYS;
|
|
1102
|
-
this.useStackFingerprint = config?.useStackFingerprint ?? true;
|
|
1103
|
-
}
|
|
1104
|
-
similarityThreshold;
|
|
1105
|
-
decayHalfLifeDays;
|
|
1106
|
-
useStackFingerprint;
|
|
1107
|
-
/**
|
|
1108
|
-
* Try to find or create a group for an incident
|
|
1109
|
-
* Returns the group ID if grouped, null if no suitable group
|
|
1110
|
-
*/
|
|
1111
|
-
group(incident) {
|
|
1112
|
-
const groups = this.storage.getGroups({ limit: 100 });
|
|
1113
|
-
for (const group of groups) {
|
|
1114
|
-
if (this.shouldJoinGroup(incident, group)) {
|
|
1115
|
-
this.storage.addToGroup(group.id, incident.id);
|
|
1116
|
-
return group.id;
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
const similar = this.findSimilar(incident, 10);
|
|
1120
|
-
if (similar.length >= 1) {
|
|
1121
|
-
const commonSymbols = this.extractCommonSymbols([incident, ...similar]);
|
|
1122
|
-
const commonErrorPatterns = this.extractCommonErrorPatterns([
|
|
1123
|
-
incident,
|
|
1124
|
-
...similar
|
|
1125
|
-
]);
|
|
1126
|
-
const groupId = this.storage.createGroup({
|
|
1127
|
-
incidents: [incident.id, ...similar.map((i) => i.id)],
|
|
1128
|
-
commonSymbols,
|
|
1129
|
-
commonErrorPatterns,
|
|
1130
|
-
firstSeen: this.getEarliestTimestamp([incident, ...similar]),
|
|
1131
|
-
lastSeen: incident.timestamp,
|
|
1132
|
-
environments: this.getUniqueEnvironments([incident, ...similar])
|
|
1133
|
-
});
|
|
1134
|
-
return groupId;
|
|
1135
|
-
}
|
|
1136
|
-
return null;
|
|
1137
|
-
}
|
|
1138
|
-
/**
|
|
1139
|
-
* Find incidents similar to the given one
|
|
1140
|
-
*/
|
|
1141
|
-
findSimilar(incident, limit = 10) {
|
|
1142
|
-
const candidates = this.storage.getRecentIncidents({
|
|
1143
|
-
limit: 500,
|
|
1144
|
-
status: "all"
|
|
1145
|
-
});
|
|
1146
|
-
const similar = [];
|
|
1147
|
-
for (const candidate of candidates) {
|
|
1148
|
-
if (candidate.id === incident.id) {
|
|
1149
|
-
continue;
|
|
1150
|
-
}
|
|
1151
|
-
const score = this.calculateSimilarity(incident, candidate);
|
|
1152
|
-
if (score >= this.similarityThreshold) {
|
|
1153
|
-
similar.push({ incident: candidate, score });
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
return similar.sort((a, b) => b.score - a.score).slice(0, limit).map((s) => s.incident);
|
|
1157
|
-
}
|
|
1158
|
-
/**
|
|
1159
|
-
* Analyze ungrouped incidents and create groups automatically
|
|
1160
|
-
*/
|
|
1161
|
-
analyzeAndGroup(options = {}) {
|
|
1162
|
-
const minSize = options.minSize || 3;
|
|
1163
|
-
const ungrouped = this.storage.getRecentIncidents({
|
|
1164
|
-
limit: 1e3
|
|
1165
|
-
}).filter((i) => !i.groupId);
|
|
1166
|
-
const newGroups = [];
|
|
1167
|
-
const processed = /* @__PURE__ */ new Set();
|
|
1168
|
-
for (const incident of ungrouped) {
|
|
1169
|
-
if (processed.has(incident.id)) {
|
|
1170
|
-
continue;
|
|
1171
|
-
}
|
|
1172
|
-
const similar = ungrouped.filter(
|
|
1173
|
-
(other) => other.id !== incident.id && !processed.has(other.id) && this.calculateSimilarity(incident, other) >= this.similarityThreshold
|
|
1174
|
-
);
|
|
1175
|
-
if (similar.length + 1 >= minSize) {
|
|
1176
|
-
const members = [incident, ...similar];
|
|
1177
|
-
const commonSymbols = this.extractCommonSymbols(members);
|
|
1178
|
-
const commonErrorPatterns = this.extractCommonErrorPatterns(members);
|
|
1179
|
-
const groupId = this.storage.createGroup({
|
|
1180
|
-
incidents: members.map((m) => m.id),
|
|
1181
|
-
commonSymbols,
|
|
1182
|
-
commonErrorPatterns,
|
|
1183
|
-
firstSeen: this.getEarliestTimestamp(members),
|
|
1184
|
-
lastSeen: this.getLatestTimestamp(members),
|
|
1185
|
-
environments: this.getUniqueEnvironments(members)
|
|
1186
|
-
});
|
|
1187
|
-
for (const m of members) {
|
|
1188
|
-
processed.add(m.id);
|
|
1189
|
-
}
|
|
1190
|
-
const group = this.storage.getGroup(groupId);
|
|
1191
|
-
if (group) {
|
|
1192
|
-
newGroups.push(group);
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
return newGroups;
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Calculate similarity between two incidents (0-1)
|
|
1200
|
-
* Applies time-decay so older incidents contribute less, and optionally
|
|
1201
|
-
* uses stack trace fingerprinting for more accurate grouping.
|
|
1202
|
-
*/
|
|
1203
|
-
calculateSimilarity(a, b) {
|
|
1204
|
-
let score = 0;
|
|
1205
|
-
let maxScore = 0;
|
|
1206
|
-
const hasStacks = this.useStackFingerprint && a.error.stack && b.error.stack;
|
|
1207
|
-
const symbolWeight = hasStacks ? 0.45 : 0.6;
|
|
1208
|
-
const errorWeight = hasStacks ? 0.25 : 0.3;
|
|
1209
|
-
const envWeight = 0.1;
|
|
1210
|
-
const stackWeight = hasStacks ? 0.2 : 0;
|
|
1211
|
-
const symbolTypes = [
|
|
1212
|
-
"feature",
|
|
1213
|
-
"component",
|
|
1214
|
-
"flow",
|
|
1215
|
-
"gate",
|
|
1216
|
-
"signal",
|
|
1217
|
-
"state",
|
|
1218
|
-
"integration"
|
|
1219
|
-
];
|
|
1220
|
-
for (const type of symbolTypes) {
|
|
1221
|
-
const aValue = a.symbols[type];
|
|
1222
|
-
const bValue = b.symbols[type];
|
|
1223
|
-
if (aValue || bValue) {
|
|
1224
|
-
maxScore += symbolWeight / symbolTypes.length;
|
|
1225
|
-
if (aValue === bValue) {
|
|
1226
|
-
score += symbolWeight / symbolTypes.length;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
const errorSimilarity = this.stringSimilarity(
|
|
1231
|
-
a.error.message,
|
|
1232
|
-
b.error.message
|
|
1233
|
-
);
|
|
1234
|
-
score += errorWeight * errorSimilarity;
|
|
1235
|
-
maxScore += errorWeight;
|
|
1236
|
-
if (a.environment === b.environment) {
|
|
1237
|
-
score += envWeight;
|
|
1238
|
-
}
|
|
1239
|
-
maxScore += envWeight;
|
|
1240
|
-
if (hasStacks) {
|
|
1241
|
-
const aFingerprint = this.fingerprintStack(a.error.stack);
|
|
1242
|
-
const bFingerprint = this.fingerprintStack(b.error.stack);
|
|
1243
|
-
const stackSimilarity = this.compareFingerprints(aFingerprint, bFingerprint);
|
|
1244
|
-
score += stackWeight * stackSimilarity;
|
|
1245
|
-
maxScore += stackWeight;
|
|
1246
|
-
}
|
|
1247
|
-
const rawScore = maxScore > 0 ? score / maxScore : 0;
|
|
1248
|
-
const timeDelta = Math.abs(
|
|
1249
|
-
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
1250
|
-
);
|
|
1251
|
-
const daysDelta = timeDelta / (1e3 * 60 * 60 * 24);
|
|
1252
|
-
const decayFactor = Math.pow(0.5, daysDelta / this.decayHalfLifeDays);
|
|
1253
|
-
return rawScore * decayFactor;
|
|
1254
|
-
}
|
|
1255
|
-
/**
|
|
1256
|
-
* Extract a fingerprint from a stack trace by normalizing frames.
|
|
1257
|
-
* Strips line numbers, column numbers, and absolute paths to capture
|
|
1258
|
-
* the structural signature of the call stack.
|
|
1259
|
-
*/
|
|
1260
|
-
fingerprintStack(stack) {
|
|
1261
|
-
return stack.split("\n").filter((line) => line.trim().startsWith("at ")).slice(0, 10).map((frame) => {
|
|
1262
|
-
return frame.trim().replace(/:\d+:\d+\)?$/, "").replace(/\(.*[/\\]/, "(").replace(/^\s*at\s+/, "");
|
|
1263
|
-
});
|
|
1264
|
-
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Compare two stack fingerprints (0-1 similarity)
|
|
1267
|
-
*/
|
|
1268
|
-
compareFingerprints(a, b) {
|
|
1269
|
-
if (a.length === 0 && b.length === 0) return 1;
|
|
1270
|
-
if (a.length === 0 || b.length === 0) return 0;
|
|
1271
|
-
let matches = 0;
|
|
1272
|
-
const maxLen = Math.max(a.length, b.length);
|
|
1273
|
-
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
1274
|
-
if (a[i] === b[i]) {
|
|
1275
|
-
matches++;
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
return matches / maxLen;
|
|
1279
|
-
}
|
|
1280
|
-
/**
|
|
1281
|
-
* Calculate string similarity using Levenshtein distance
|
|
1282
|
-
*/
|
|
1283
|
-
stringSimilarity(a, b) {
|
|
1284
|
-
const maxLen = Math.max(a.length, b.length);
|
|
1285
|
-
if (maxLen === 0) return 1;
|
|
1286
|
-
const distance = this.levenshteinDistance(
|
|
1287
|
-
a.toLowerCase(),
|
|
1288
|
-
b.toLowerCase()
|
|
1289
|
-
);
|
|
1290
|
-
return 1 - distance / maxLen;
|
|
1291
|
-
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Levenshtein distance for string comparison
|
|
1294
|
-
*/
|
|
1295
|
-
levenshteinDistance(a, b) {
|
|
1296
|
-
if (a.length === 0) return b.length;
|
|
1297
|
-
if (b.length === 0) return a.length;
|
|
1298
|
-
const matrix = [];
|
|
1299
|
-
for (let i = 0; i <= b.length; i++) {
|
|
1300
|
-
matrix[i] = [i];
|
|
1301
|
-
}
|
|
1302
|
-
for (let j = 0; j <= a.length; j++) {
|
|
1303
|
-
matrix[0][j] = j;
|
|
1304
|
-
}
|
|
1305
|
-
for (let i = 1; i <= b.length; i++) {
|
|
1306
|
-
for (let j = 1; j <= a.length; j++) {
|
|
1307
|
-
const cost = a[j - 1] === b[i - 1] ? 0 : 1;
|
|
1308
|
-
matrix[i][j] = Math.min(
|
|
1309
|
-
matrix[i - 1][j] + 1,
|
|
1310
|
-
matrix[i][j - 1] + 1,
|
|
1311
|
-
matrix[i - 1][j - 1] + cost
|
|
1312
|
-
);
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
return matrix[b.length][a.length];
|
|
1316
|
-
}
|
|
1317
|
-
/**
|
|
1318
|
-
* Check if incident should join existing group
|
|
1319
|
-
*/
|
|
1320
|
-
shouldJoinGroup(incident, group) {
|
|
1321
|
-
let matchCount = 0;
|
|
1322
|
-
let totalCommon = 0;
|
|
1323
|
-
for (const [key, value] of Object.entries(group.commonSymbols)) {
|
|
1324
|
-
if (value) {
|
|
1325
|
-
totalCommon++;
|
|
1326
|
-
const incidentValue = incident.symbols[key];
|
|
1327
|
-
if (incidentValue === value) {
|
|
1328
|
-
matchCount++;
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
if (totalCommon === 0) {
|
|
1333
|
-
return false;
|
|
1334
|
-
}
|
|
1335
|
-
const symbolMatch = matchCount / totalCommon;
|
|
1336
|
-
const errorLower = incident.error.message.toLowerCase();
|
|
1337
|
-
const errorMatch = group.commonErrorPatterns.some(
|
|
1338
|
-
(pattern) => errorLower.includes(pattern.toLowerCase())
|
|
1339
|
-
);
|
|
1340
|
-
return symbolMatch >= 0.5 || errorMatch;
|
|
1341
|
-
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Extract symbols common to all incidents
|
|
1344
|
-
*/
|
|
1345
|
-
extractCommonSymbols(incidents) {
|
|
1346
|
-
if (incidents.length === 0) return {};
|
|
1347
|
-
const first = incidents[0].symbols;
|
|
1348
|
-
const common = {};
|
|
1349
|
-
for (const [key, value] of Object.entries(first)) {
|
|
1350
|
-
if (!value) continue;
|
|
1351
|
-
const allMatch = incidents.every(
|
|
1352
|
-
(i) => i.symbols[key] === value
|
|
1353
|
-
);
|
|
1354
|
-
if (allMatch) {
|
|
1355
|
-
common[key] = value;
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
return common;
|
|
1359
|
-
}
|
|
1360
|
-
/**
|
|
1361
|
-
* Extract common error patterns from incidents
|
|
1362
|
-
*/
|
|
1363
|
-
extractCommonErrorPatterns(incidents) {
|
|
1364
|
-
if (incidents.length === 0) return [];
|
|
1365
|
-
const wordCounts = /* @__PURE__ */ new Map();
|
|
1366
|
-
const stopWords = /* @__PURE__ */ new Set([
|
|
1367
|
-
"the",
|
|
1368
|
-
"a",
|
|
1369
|
-
"an",
|
|
1370
|
-
"is",
|
|
1371
|
-
"are",
|
|
1372
|
-
"was",
|
|
1373
|
-
"were",
|
|
1374
|
-
"in",
|
|
1375
|
-
"on",
|
|
1376
|
-
"at",
|
|
1377
|
-
"to",
|
|
1378
|
-
"for",
|
|
1379
|
-
"of",
|
|
1380
|
-
"with",
|
|
1381
|
-
"error",
|
|
1382
|
-
"failed",
|
|
1383
|
-
"cannot"
|
|
1384
|
-
]);
|
|
1385
|
-
for (const incident of incidents) {
|
|
1386
|
-
const words = incident.error.message.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
|
|
1387
|
-
const uniqueWords = new Set(words);
|
|
1388
|
-
for (const word of uniqueWords) {
|
|
1389
|
-
wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
const threshold = Math.ceil(incidents.length * 0.6);
|
|
1393
|
-
const commonPatterns = Array.from(wordCounts.entries()).filter(([, count]) => count >= threshold).map(([word]) => word).slice(0, 5);
|
|
1394
|
-
return commonPatterns;
|
|
1395
|
-
}
|
|
1396
|
-
getEarliestTimestamp(incidents) {
|
|
1397
|
-
return incidents.reduce(
|
|
1398
|
-
(earliest, i) => i.timestamp < earliest ? i.timestamp : earliest,
|
|
1399
|
-
incidents[0].timestamp
|
|
1400
|
-
);
|
|
1401
|
-
}
|
|
1402
|
-
getLatestTimestamp(incidents) {
|
|
1403
|
-
return incidents.reduce(
|
|
1404
|
-
(latest, i) => i.timestamp > latest ? i.timestamp : latest,
|
|
1405
|
-
incidents[0].timestamp
|
|
1406
|
-
);
|
|
1407
|
-
}
|
|
1408
|
-
getUniqueEnvironments(incidents) {
|
|
1409
|
-
return [...new Set(incidents.map((i) => i.environment))];
|
|
1410
|
-
}
|
|
1411
|
-
};
|
|
1412
|
-
var TimelineBuilder = class {
|
|
1413
|
-
/**
|
|
1414
|
-
* Build a timeline from an incident with flow position
|
|
1415
|
-
*/
|
|
1416
|
-
build(incident) {
|
|
1417
|
-
if (!incident.flowPosition) {
|
|
1418
|
-
return null;
|
|
1419
|
-
}
|
|
1420
|
-
const events = [];
|
|
1421
|
-
const baseTime = new Date(incident.timestamp).getTime();
|
|
1422
|
-
events.push({
|
|
1423
|
-
timestamp: new Date(baseTime - 5e3).toISOString(),
|
|
1424
|
-
symbol: incident.flowPosition.flowId,
|
|
1425
|
-
type: "flow-started"
|
|
1426
|
-
});
|
|
1427
|
-
let eventOffset = 1e3;
|
|
1428
|
-
for (const signal of incident.flowPosition.actual) {
|
|
1429
|
-
const type = this.inferEventType(signal);
|
|
1430
|
-
events.push({
|
|
1431
|
-
timestamp: new Date(baseTime - 4e3 + eventOffset).toISOString(),
|
|
1432
|
-
symbol: signal,
|
|
1433
|
-
type
|
|
1434
|
-
});
|
|
1435
|
-
eventOffset += Math.random() * 1e3 + 500;
|
|
1436
|
-
}
|
|
1437
|
-
const failedSymbol = incident.flowPosition.failedAt || incident.flowPosition.missing[0] || incident.symbols.gate || incident.symbols.signal || "unknown";
|
|
1438
|
-
events.push({
|
|
1439
|
-
timestamp: incident.timestamp,
|
|
1440
|
-
symbol: failedSymbol,
|
|
1441
|
-
type: "error",
|
|
1442
|
-
data: {
|
|
1443
|
-
message: incident.error.message,
|
|
1444
|
-
missing: incident.flowPosition.missing
|
|
1445
|
-
}
|
|
1446
|
-
});
|
|
1447
|
-
return {
|
|
1448
|
-
incidentId: incident.id,
|
|
1449
|
-
flowId: incident.flowPosition.flowId,
|
|
1450
|
-
events,
|
|
1451
|
-
failure: {
|
|
1452
|
-
at: incident.timestamp,
|
|
1453
|
-
symbol: failedSymbol,
|
|
1454
|
-
reason: incident.error.message
|
|
1455
|
-
}
|
|
1456
|
-
};
|
|
1457
|
-
}
|
|
1458
|
-
/**
|
|
1459
|
-
* Render timeline as ASCII art
|
|
1460
|
-
*/
|
|
1461
|
-
renderAscii(timeline) {
|
|
1462
|
-
const lines = [];
|
|
1463
|
-
lines.push(`${timeline.flowId} Timeline`);
|
|
1464
|
-
lines.push("\u2550".repeat(40));
|
|
1465
|
-
lines.push("");
|
|
1466
|
-
for (const event of timeline.events) {
|
|
1467
|
-
const time = this.formatTime(event.timestamp);
|
|
1468
|
-
const icon = this.getEventIcon(event.type);
|
|
1469
|
-
const status = this.getEventStatus(event.type);
|
|
1470
|
-
let line = `${time} ${icon} ${event.symbol}`;
|
|
1471
|
-
if (status) {
|
|
1472
|
-
line += ` (${status})`;
|
|
1473
|
-
}
|
|
1474
|
-
lines.push(line);
|
|
1475
|
-
if (event.type === "error" && event.data) {
|
|
1476
|
-
lines.push(` \u2514\u2500 ${event.data.message}`);
|
|
1477
|
-
if (event.data.missing && Array.isArray(event.data.missing) && event.data.missing.length > 0) {
|
|
1478
|
-
lines.push(
|
|
1479
|
-
` \u2514\u2500 Expected: ${event.data.missing.join(", ")}`
|
|
1480
|
-
);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
const missing = timeline.events.find((e) => e.type === "error")?.data?.missing;
|
|
1485
|
-
if (missing && missing.length > 0) {
|
|
1486
|
-
lines.push("");
|
|
1487
|
-
lines.push(`Missing signals: ${missing.join(", ")}`);
|
|
1488
|
-
}
|
|
1489
|
-
return lines.join("\n");
|
|
1490
|
-
}
|
|
1491
|
-
/**
|
|
1492
|
-
* Render timeline as structured data (for MCP/JSON output)
|
|
1493
|
-
*/
|
|
1494
|
-
renderStructured(timeline) {
|
|
1495
|
-
return {
|
|
1496
|
-
incidentId: timeline.incidentId,
|
|
1497
|
-
flow: {
|
|
1498
|
-
id: timeline.flowId,
|
|
1499
|
-
eventCount: timeline.events.length
|
|
1500
|
-
},
|
|
1501
|
-
events: timeline.events.map((event) => ({
|
|
1502
|
-
time: this.formatTime(event.timestamp),
|
|
1503
|
-
symbol: event.symbol,
|
|
1504
|
-
type: event.type,
|
|
1505
|
-
status: this.getEventStatus(event.type),
|
|
1506
|
-
data: event.data
|
|
1507
|
-
})),
|
|
1508
|
-
failure: {
|
|
1509
|
-
at: this.formatTime(timeline.failure.at),
|
|
1510
|
-
symbol: timeline.failure.symbol,
|
|
1511
|
-
reason: timeline.failure.reason
|
|
1512
|
-
}
|
|
1513
|
-
};
|
|
1514
|
-
}
|
|
1515
|
-
/**
|
|
1516
|
-
* Infer event type from symbol prefix
|
|
1517
|
-
*/
|
|
1518
|
-
inferEventType(symbol) {
|
|
1519
|
-
if (symbol.startsWith("^")) {
|
|
1520
|
-
return "gate-passed";
|
|
1521
|
-
}
|
|
1522
|
-
if (symbol.startsWith("!")) {
|
|
1523
|
-
return "signal-emitted";
|
|
1524
|
-
}
|
|
1525
|
-
if (symbol.startsWith("%")) {
|
|
1526
|
-
return "state-changed";
|
|
1527
|
-
}
|
|
1528
|
-
return "signal-emitted";
|
|
1529
|
-
}
|
|
1530
|
-
/**
|
|
1531
|
-
* Get icon for event type
|
|
1532
|
-
*/
|
|
1533
|
-
getEventIcon(type) {
|
|
1534
|
-
switch (type) {
|
|
1535
|
-
case "flow-started":
|
|
1536
|
-
return "\u25B6";
|
|
1537
|
-
case "flow-ended":
|
|
1538
|
-
return "\u25A0";
|
|
1539
|
-
case "gate-passed":
|
|
1540
|
-
return "\u2713";
|
|
1541
|
-
case "gate-failed":
|
|
1542
|
-
return "\u2717";
|
|
1543
|
-
case "signal-emitted":
|
|
1544
|
-
return "\u26A1";
|
|
1545
|
-
case "state-changed":
|
|
1546
|
-
return "\u25C6";
|
|
1547
|
-
case "error":
|
|
1548
|
-
return "\u2717";
|
|
1549
|
-
default:
|
|
1550
|
-
return "\u2022";
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
/**
|
|
1554
|
-
* Get status text for event type
|
|
1555
|
-
*/
|
|
1556
|
-
getEventStatus(type) {
|
|
1557
|
-
switch (type) {
|
|
1558
|
-
case "gate-passed":
|
|
1559
|
-
return "PASSED";
|
|
1560
|
-
case "gate-failed":
|
|
1561
|
-
return "FAILED";
|
|
1562
|
-
case "signal-emitted":
|
|
1563
|
-
return "EMITTED";
|
|
1564
|
-
case "state-changed":
|
|
1565
|
-
return "CHANGED";
|
|
1566
|
-
case "error":
|
|
1567
|
-
return "ERROR";
|
|
1568
|
-
default:
|
|
1569
|
-
return "";
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
/**
|
|
1573
|
-
* Format timestamp for display
|
|
1574
|
-
*/
|
|
1575
|
-
formatTime(timestamp) {
|
|
1576
|
-
const date = new Date(timestamp);
|
|
1577
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
1578
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
1579
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
1580
|
-
const millis = String(date.getMilliseconds()).padStart(3, "0");
|
|
1581
|
-
return `${hours}:${minutes}:${seconds}.${millis}`;
|
|
1582
|
-
}
|
|
1583
|
-
};
|
|
1584
|
-
var StatsCalculator = class {
|
|
1585
|
-
constructor(storage) {
|
|
1586
|
-
this.storage = storage;
|
|
1587
|
-
}
|
|
1588
|
-
/**
|
|
1589
|
-
* Get comprehensive statistics for a time period
|
|
1590
|
-
*/
|
|
1591
|
-
getStats(periodDays = 7) {
|
|
1592
|
-
const end = (/* @__PURE__ */ new Date()).toISOString();
|
|
1593
|
-
const start = new Date(
|
|
1594
|
-
Date.now() - periodDays * 24 * 60 * 60 * 1e3
|
|
1595
|
-
).toISOString();
|
|
1596
|
-
return this.storage.getStats({ start, end });
|
|
1597
|
-
}
|
|
1598
|
-
/**
|
|
1599
|
-
* Get health metrics for a specific symbol
|
|
1600
|
-
*/
|
|
1601
|
-
getSymbolHealth(symbol) {
|
|
1602
|
-
return this.storage.getSymbolHealth(symbol);
|
|
1603
|
-
}
|
|
1604
|
-
/**
|
|
1605
|
-
* Get trending issues (symbols with increasing incident rates)
|
|
1606
|
-
*/
|
|
1607
|
-
getTrendingIssues(days = 7) {
|
|
1608
|
-
const now = Date.now();
|
|
1609
|
-
const halfPeriod = days * 24 * 60 * 60 * 1e3 / 2;
|
|
1610
|
-
const firstHalfStart = new Date(now - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
1611
|
-
const midpoint = new Date(now - halfPeriod).toISOString();
|
|
1612
|
-
const secondHalfEnd = new Date(now).toISOString();
|
|
1613
|
-
const firstHalfIncidents = this.storage.getRecentIncidents({
|
|
1614
|
-
dateFrom: firstHalfStart,
|
|
1615
|
-
dateTo: midpoint,
|
|
1616
|
-
limit: 1e3
|
|
1617
|
-
});
|
|
1618
|
-
const secondHalfIncidents = this.storage.getRecentIncidents({
|
|
1619
|
-
dateFrom: midpoint,
|
|
1620
|
-
dateTo: secondHalfEnd,
|
|
1621
|
-
limit: 1e3
|
|
1622
|
-
});
|
|
1623
|
-
const firstHalfCounts = this.countSymbols(firstHalfIncidents);
|
|
1624
|
-
const secondHalfCounts = this.countSymbols(secondHalfIncidents);
|
|
1625
|
-
const trends = [];
|
|
1626
|
-
const allSymbols = /* @__PURE__ */ new Set([
|
|
1627
|
-
...firstHalfCounts.keys(),
|
|
1628
|
-
...secondHalfCounts.keys()
|
|
1629
|
-
]);
|
|
1630
|
-
for (const symbol of allSymbols) {
|
|
1631
|
-
const first = firstHalfCounts.get(symbol) || 0;
|
|
1632
|
-
const second = secondHalfCounts.get(symbol) || 0;
|
|
1633
|
-
if (first === 0 && second > 0) {
|
|
1634
|
-
trends.push({ symbol, trend: second * 100 });
|
|
1635
|
-
} else if (first > 0) {
|
|
1636
|
-
const change = (second - first) / first * 100;
|
|
1637
|
-
trends.push({ symbol, trend: change });
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
return trends.filter((t) => t.trend > 0).sort((a, b) => b.trend - a.trend).slice(0, 10);
|
|
1641
|
-
}
|
|
1642
|
-
/**
|
|
1643
|
-
* Get resolution metrics
|
|
1644
|
-
*/
|
|
1645
|
-
getResolutionMetrics() {
|
|
1646
|
-
const stats = this.getStats(30);
|
|
1647
|
-
return {
|
|
1648
|
-
avgTimeToResolve: stats.resolution.avgTimeToResolve,
|
|
1649
|
-
resolvedWithPattern: stats.resolution.resolvedWithPattern,
|
|
1650
|
-
resolvedManually: stats.resolution.resolvedManually,
|
|
1651
|
-
totalResolved: stats.incidents.resolved,
|
|
1652
|
-
resolutionRate: stats.resolution.resolutionRate
|
|
1653
|
-
};
|
|
1654
|
-
}
|
|
1655
|
-
/**
|
|
1656
|
-
* Get pattern effectiveness metrics
|
|
1657
|
-
*/
|
|
1658
|
-
getPatternEffectiveness() {
|
|
1659
|
-
const patterns = this.storage.getAllPatterns({ includePrivate: true });
|
|
1660
|
-
return patterns.filter((p) => p.confidence.timesMatched > 0).map((p) => ({
|
|
1661
|
-
patternId: p.id,
|
|
1662
|
-
name: p.name,
|
|
1663
|
-
matches: p.confidence.timesMatched,
|
|
1664
|
-
resolutions: p.confidence.timesResolved,
|
|
1665
|
-
recurrences: p.confidence.timesRecurred,
|
|
1666
|
-
effectiveness: p.confidence.timesMatched > 0 ? Math.round(
|
|
1667
|
-
(p.confidence.timesResolved - p.confidence.timesRecurred) / p.confidence.timesMatched * 100
|
|
1668
|
-
) : 0
|
|
1669
|
-
})).sort((a, b) => b.effectiveness - a.effectiveness);
|
|
1670
|
-
}
|
|
1671
|
-
/**
|
|
1672
|
-
* Get incident rate by hour of day
|
|
1673
|
-
*/
|
|
1674
|
-
getIncidentsByHour(days = 7) {
|
|
1675
|
-
const start = new Date(
|
|
1676
|
-
Date.now() - days * 24 * 60 * 60 * 1e3
|
|
1677
|
-
).toISOString();
|
|
1678
|
-
const incidents = this.storage.getRecentIncidents({
|
|
1679
|
-
dateFrom: start,
|
|
1680
|
-
limit: 1e4
|
|
1681
|
-
});
|
|
1682
|
-
const hourCounts = /* @__PURE__ */ new Map();
|
|
1683
|
-
for (let i = 0; i < 24; i++) {
|
|
1684
|
-
hourCounts.set(i, 0);
|
|
1685
|
-
}
|
|
1686
|
-
for (const incident of incidents) {
|
|
1687
|
-
const hour = new Date(incident.timestamp).getHours();
|
|
1688
|
-
hourCounts.set(hour, (hourCounts.get(hour) || 0) + 1);
|
|
1689
|
-
}
|
|
1690
|
-
return Array.from(hourCounts.entries()).map(([hour, count]) => ({
|
|
1691
|
-
hour,
|
|
1692
|
-
count
|
|
1693
|
-
}));
|
|
1694
|
-
}
|
|
1695
|
-
/**
|
|
1696
|
-
* Get incident rate by environment
|
|
1697
|
-
*/
|
|
1698
|
-
getIncidentsByEnvironment() {
|
|
1699
|
-
const stats = this.getStats(30);
|
|
1700
|
-
const total = stats.incidents.total;
|
|
1701
|
-
return Object.entries(stats.incidents.byEnvironment).map(([environment, count]) => ({
|
|
1702
|
-
environment,
|
|
1703
|
-
count,
|
|
1704
|
-
percentage: total > 0 ? Math.round(count / total * 100) : 0
|
|
1705
|
-
})).sort((a, b) => b.count - a.count);
|
|
1706
|
-
}
|
|
1707
|
-
/**
|
|
1708
|
-
* Get symbol correlation matrix (which symbols fail together)
|
|
1709
|
-
*/
|
|
1710
|
-
getSymbolCorrelation() {
|
|
1711
|
-
const incidents = this.storage.getRecentIncidents({ limit: 1e3 });
|
|
1712
|
-
const correlations = /* @__PURE__ */ new Map();
|
|
1713
|
-
const symbolCounts = /* @__PURE__ */ new Map();
|
|
1714
|
-
for (const incident of incidents) {
|
|
1715
|
-
const symbols = this.getSymbolsFromIncident(incident);
|
|
1716
|
-
for (const symbol of symbols) {
|
|
1717
|
-
symbolCounts.set(symbol, (symbolCounts.get(symbol) || 0) + 1);
|
|
1718
|
-
}
|
|
1719
|
-
for (let i = 0; i < symbols.length; i++) {
|
|
1720
|
-
for (let j = i + 1; j < symbols.length; j++) {
|
|
1721
|
-
const key = [symbols[i], symbols[j]].sort().join("|");
|
|
1722
|
-
correlations.set(key, (correlations.get(key) || 0) + 1);
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
const results = [];
|
|
1727
|
-
for (const [key, count] of correlations) {
|
|
1728
|
-
const [symbol1, symbol2] = key.split("|");
|
|
1729
|
-
const count1 = symbolCounts.get(symbol1) || 1;
|
|
1730
|
-
const count2 = symbolCounts.get(symbol2) || 1;
|
|
1731
|
-
const correlation = count / Math.max(count1, count2);
|
|
1732
|
-
if (correlation > 0.3) {
|
|
1733
|
-
results.push({
|
|
1734
|
-
symbol1,
|
|
1735
|
-
symbol2,
|
|
1736
|
-
correlation: Math.round(correlation * 100) / 100
|
|
1737
|
-
});
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
return results.sort((a, b) => b.correlation - a.correlation).slice(0, 20);
|
|
1741
|
-
}
|
|
1742
|
-
/**
|
|
1743
|
-
* Generate a summary dashboard string
|
|
1744
|
-
*/
|
|
1745
|
-
generateDashboard(periodDays = 7) {
|
|
1746
|
-
const stats = this.getStats(periodDays);
|
|
1747
|
-
const lines = [];
|
|
1748
|
-
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");
|
|
1749
|
-
lines.push("\u2551 PARADIGM SENTINEL DASHBOARD \u2551");
|
|
1750
|
-
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");
|
|
1751
|
-
const todayCount = stats.incidents.byDay[stats.incidents.byDay.length - 1]?.count || 0;
|
|
1752
|
-
lines.push(
|
|
1753
|
-
`\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`
|
|
1754
|
-
);
|
|
1755
|
-
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");
|
|
1756
|
-
lines.push("");
|
|
1757
|
-
lines.push("Incidents by Day (last 7 days):");
|
|
1758
|
-
lines.push("\u2500".repeat(50));
|
|
1759
|
-
const maxDayCount = Math.max(...stats.incidents.byDay.map((d) => d.count), 1);
|
|
1760
|
-
for (const day of stats.incidents.byDay.slice(-7)) {
|
|
1761
|
-
const barLength = Math.round(day.count / maxDayCount * 30);
|
|
1762
|
-
const bar = "\u2588".repeat(barLength);
|
|
1763
|
-
lines.push(`${day.date.substring(5)} ${bar} ${day.count}`);
|
|
1764
|
-
}
|
|
1765
|
-
lines.push("");
|
|
1766
|
-
lines.push("Most Affected Symbols:");
|
|
1767
|
-
lines.push("\u2500".repeat(50));
|
|
1768
|
-
for (const { symbol, count } of stats.symbols.mostIncidents.slice(0, 5)) {
|
|
1769
|
-
lines.push(` ${symbol.padEnd(25)} ${count} incidents`);
|
|
1770
|
-
}
|
|
1771
|
-
lines.push("");
|
|
1772
|
-
lines.push("Top Patterns:");
|
|
1773
|
-
lines.push("\u2500".repeat(50));
|
|
1774
|
-
for (const { patternId, resolvedCount } of stats.patterns.mostEffective.slice(0, 5)) {
|
|
1775
|
-
lines.push(` ${patternId.padEnd(25)} ${resolvedCount} resolved`);
|
|
1776
|
-
}
|
|
1777
|
-
lines.push("");
|
|
1778
|
-
lines.push("Resolution Stats:");
|
|
1779
|
-
lines.push("\u2500".repeat(50));
|
|
1780
|
-
lines.push(` Resolution rate: ${Math.round(stats.resolution.resolutionRate)}%`);
|
|
1781
|
-
lines.push(` With pattern: ${stats.resolution.resolvedWithPattern}`);
|
|
1782
|
-
lines.push(` Manual: ${stats.resolution.resolvedManually}`);
|
|
1783
|
-
return lines.join("\n");
|
|
1784
|
-
}
|
|
1785
|
-
/**
|
|
1786
|
-
* Helper: Count symbols across incidents
|
|
1787
|
-
*/
|
|
1788
|
-
countSymbols(incidents) {
|
|
1789
|
-
const counts = /* @__PURE__ */ new Map();
|
|
1790
|
-
for (const incident of incidents) {
|
|
1791
|
-
for (const [, value] of Object.entries(incident.symbols)) {
|
|
1792
|
-
if (value) {
|
|
1793
|
-
counts.set(value, (counts.get(value) || 0) + 1);
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
return counts;
|
|
1798
|
-
}
|
|
1799
|
-
/**
|
|
1800
|
-
* Helper: Get all symbols from incident
|
|
1801
|
-
*/
|
|
1802
|
-
getSymbolsFromIncident(incident) {
|
|
1803
|
-
const symbols = [];
|
|
1804
|
-
for (const [, value] of Object.entries(incident.symbols)) {
|
|
1805
|
-
if (value) {
|
|
1806
|
-
symbols.push(value);
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
return symbols;
|
|
1810
|
-
}
|
|
1811
|
-
};
|
|
1812
|
-
var ContextEnricher = class {
|
|
1813
|
-
constructor(projectRoot = process.cwd()) {
|
|
1814
|
-
this.projectRoot = projectRoot;
|
|
1815
|
-
}
|
|
1816
|
-
symbolCache = /* @__PURE__ */ new Map();
|
|
1817
|
-
purposeCache = /* @__PURE__ */ new Map();
|
|
1818
|
-
/**
|
|
1819
|
-
* Enrich an incident with symbol context
|
|
1820
|
-
*/
|
|
1821
|
-
enrich(incident) {
|
|
1822
|
-
const symbolEnrichments = {};
|
|
1823
|
-
for (const [, value] of Object.entries(incident.symbols)) {
|
|
1824
|
-
if (value) {
|
|
1825
|
-
const enrichment = this.getSymbolContext(value);
|
|
1826
|
-
if (enrichment && Object.keys(enrichment).length > 0) {
|
|
1827
|
-
symbolEnrichments[value] = enrichment;
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
let flowDescription;
|
|
1832
|
-
if (incident.symbols.flow) {
|
|
1833
|
-
const flowContext = this.getSymbolContext(incident.symbols.flow);
|
|
1834
|
-
flowDescription = flowContext?.description;
|
|
1835
|
-
}
|
|
1836
|
-
return {
|
|
1837
|
-
...incident,
|
|
1838
|
-
enriched: {
|
|
1839
|
-
symbols: symbolEnrichments,
|
|
1840
|
-
flowDescription
|
|
1841
|
-
}
|
|
1842
|
-
};
|
|
1843
|
-
}
|
|
1844
|
-
/**
|
|
1845
|
-
* Get symbol metadata from index or .purpose files
|
|
1846
|
-
*/
|
|
1847
|
-
getSymbolContext(symbol) {
|
|
1848
|
-
const cached = this.symbolCache.get(symbol);
|
|
1849
|
-
if (cached) {
|
|
1850
|
-
return cached;
|
|
1851
|
-
}
|
|
1852
|
-
const enrichment = {};
|
|
1853
|
-
const indexEntry = this.findInSymbolIndex(symbol);
|
|
1854
|
-
if (indexEntry) {
|
|
1855
|
-
enrichment.description = indexEntry.description;
|
|
1856
|
-
enrichment.definedIn = indexEntry.file;
|
|
1857
|
-
enrichment.references = indexEntry.references;
|
|
1858
|
-
enrichment.referencedBy = indexEntry.referencedBy;
|
|
1859
|
-
}
|
|
1860
|
-
const purposeEntry = this.findInPurposeFiles(symbol);
|
|
1861
|
-
if (purposeEntry) {
|
|
1862
|
-
if (!enrichment.description && purposeEntry.description) {
|
|
1863
|
-
enrichment.description = purposeEntry.description;
|
|
1864
|
-
}
|
|
1865
|
-
if (purposeEntry.references) {
|
|
1866
|
-
enrichment.references = [
|
|
1867
|
-
.../* @__PURE__ */ new Set([...enrichment.references || [], ...purposeEntry.references])
|
|
1868
|
-
];
|
|
1869
|
-
}
|
|
1870
|
-
if (purposeEntry.referencedBy) {
|
|
1871
|
-
enrichment.referencedBy = [
|
|
1872
|
-
.../* @__PURE__ */ new Set([...enrichment.referencedBy || [], ...purposeEntry.referencedBy])
|
|
1873
|
-
];
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
this.symbolCache.set(symbol, enrichment);
|
|
1877
|
-
return enrichment;
|
|
1878
|
-
}
|
|
1879
|
-
/**
|
|
1880
|
-
* Find symbol in premise index
|
|
1881
|
-
*/
|
|
1882
|
-
findInSymbolIndex(symbol) {
|
|
1883
|
-
const indexPath = path3.join(this.projectRoot, ".paradigm", "index.json");
|
|
1884
|
-
if (!fs3.existsSync(indexPath)) {
|
|
1885
|
-
return null;
|
|
1886
|
-
}
|
|
1887
|
-
try {
|
|
1888
|
-
const indexContent = fs3.readFileSync(indexPath, "utf-8");
|
|
1889
|
-
const index = JSON.parse(indexContent);
|
|
1890
|
-
if (index.symbols && Array.isArray(index.symbols)) {
|
|
1891
|
-
return index.symbols.find(
|
|
1892
|
-
(s) => s.id === symbol
|
|
1893
|
-
) || null;
|
|
1894
|
-
}
|
|
1895
|
-
return null;
|
|
1896
|
-
} catch {
|
|
1897
|
-
return null;
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
/**
|
|
1901
|
-
* Find symbol in .purpose files
|
|
1902
|
-
*/
|
|
1903
|
-
findInPurposeFiles(symbol) {
|
|
1904
|
-
const searchPaths = this.getSearchPathsForSymbol(symbol);
|
|
1905
|
-
for (const searchPath of searchPaths) {
|
|
1906
|
-
const fullPath = path3.join(this.projectRoot, searchPath);
|
|
1907
|
-
if (!fs3.existsSync(fullPath)) {
|
|
1908
|
-
continue;
|
|
1909
|
-
}
|
|
1910
|
-
const cached = this.purposeCache.get(fullPath);
|
|
1911
|
-
if (cached) {
|
|
1912
|
-
if (cached.symbol === symbol) {
|
|
1913
|
-
return cached;
|
|
1914
|
-
}
|
|
1915
|
-
continue;
|
|
1916
|
-
}
|
|
1917
|
-
try {
|
|
1918
|
-
const content = fs3.readFileSync(fullPath, "utf-8");
|
|
1919
|
-
const purpose = this.parsePurposeFile(content);
|
|
1920
|
-
this.purposeCache.set(fullPath, purpose);
|
|
1921
|
-
if (purpose.symbol === symbol) {
|
|
1922
|
-
return purpose;
|
|
1923
|
-
}
|
|
1924
|
-
} catch {
|
|
1925
|
-
continue;
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
return null;
|
|
1929
|
-
}
|
|
1930
|
-
/**
|
|
1931
|
-
* Get potential file paths for a symbol
|
|
1932
|
-
*/
|
|
1933
|
-
getSearchPathsForSymbol(symbol) {
|
|
1934
|
-
const paths = [];
|
|
1935
|
-
const cleanSymbol = symbol.replace(/^[@#$%^!&~?]/, "");
|
|
1936
|
-
const prefixDirs = {
|
|
1937
|
-
"@": ["features", "src/features"],
|
|
1938
|
-
"#": ["components", "src/components"],
|
|
1939
|
-
"$": ["flows", "src/flows"],
|
|
1940
|
-
"^": ["middleware", "gates", "src/middleware"],
|
|
1941
|
-
"!": ["signals", "events", "src/signals"],
|
|
1942
|
-
"%": ["state", "store", "src/state"],
|
|
1943
|
-
"&": ["integrations", "services", "src/integrations"]
|
|
1944
|
-
};
|
|
1945
|
-
const prefix = symbol[0];
|
|
1946
|
-
const dirs = prefixDirs[prefix] || [];
|
|
1947
|
-
for (const dir of dirs) {
|
|
1948
|
-
paths.push(path3.join(dir, cleanSymbol, ".purpose"));
|
|
1949
|
-
paths.push(path3.join(dir, `${cleanSymbol}.purpose`));
|
|
1950
|
-
}
|
|
1951
|
-
paths.push(path3.join(".paradigm", "purposes", `${cleanSymbol}.yaml`));
|
|
1952
|
-
paths.push(path3.join(".paradigm", "purposes", `${cleanSymbol}.json`));
|
|
1953
|
-
return paths;
|
|
1954
|
-
}
|
|
1955
|
-
/**
|
|
1956
|
-
* Parse a .purpose file
|
|
1957
|
-
*/
|
|
1958
|
-
parsePurposeFile(content) {
|
|
1959
|
-
const result = {};
|
|
1960
|
-
const lines = content.split("\n");
|
|
1961
|
-
for (const line of lines) {
|
|
1962
|
-
const trimmed = line.trim();
|
|
1963
|
-
if (trimmed.startsWith("symbol:")) {
|
|
1964
|
-
result.symbol = trimmed.substring(7).trim();
|
|
1965
|
-
} else if (trimmed.startsWith("description:")) {
|
|
1966
|
-
result.description = trimmed.substring(12).trim();
|
|
1967
|
-
} else if (trimmed.startsWith("purpose:")) {
|
|
1968
|
-
result.description = trimmed.substring(8).trim();
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
if (!result.description) {
|
|
1972
|
-
const firstLine = lines.find((l) => l.trim() && !l.startsWith("#"));
|
|
1973
|
-
if (firstLine) {
|
|
1974
|
-
result.description = firstLine.trim();
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
return result;
|
|
1978
|
-
}
|
|
1979
|
-
/**
|
|
1980
|
-
* Clear caches
|
|
1981
|
-
*/
|
|
1982
|
-
clearCache() {
|
|
1983
|
-
this.symbolCache.clear();
|
|
1984
|
-
this.purposeCache.clear();
|
|
1985
|
-
}
|
|
1986
|
-
/**
|
|
1987
|
-
* Batch enrich multiple incidents
|
|
1988
|
-
*/
|
|
1989
|
-
enrichBatch(incidents) {
|
|
1990
|
-
return incidents.map((i) => this.enrich(i));
|
|
1991
|
-
}
|
|
1992
|
-
};
|
|
1993
|
-
var PatternSuggester = class {
|
|
1994
|
-
constructor(storage) {
|
|
1995
|
-
this.storage = storage;
|
|
1996
|
-
}
|
|
1997
|
-
/**
|
|
1998
|
-
* Suggest a pattern from a resolved incident
|
|
1999
|
-
*/
|
|
2000
|
-
suggestFromIncident(incident) {
|
|
2001
|
-
const baseId = this.generatePatternId(incident);
|
|
2002
|
-
const symbols = this.buildSymbolCriteria(incident.symbols);
|
|
2003
|
-
const errorKeywords = this.extractErrorKeywords(incident.error.message);
|
|
2004
|
-
const pattern = {
|
|
2005
|
-
id: baseId,
|
|
2006
|
-
name: this.generatePatternName(incident),
|
|
2007
|
-
description: `Auto-suggested pattern from incident ${incident.id}`,
|
|
2008
|
-
pattern: {
|
|
2009
|
-
symbols,
|
|
2010
|
-
errorContains: errorKeywords.length > 0 ? errorKeywords : void 0,
|
|
2011
|
-
missingSignals: incident.flowPosition?.missing
|
|
2012
|
-
},
|
|
2013
|
-
resolution: {
|
|
2014
|
-
description: incident.resolution?.notes || "Resolution approach TBD",
|
|
2015
|
-
strategy: this.inferStrategy([incident]),
|
|
2016
|
-
priority: "medium"
|
|
2017
|
-
},
|
|
2018
|
-
source: "suggested",
|
|
2019
|
-
private: false,
|
|
2020
|
-
tags: this.generateTags(incident)
|
|
2021
|
-
};
|
|
2022
|
-
return pattern;
|
|
2023
|
-
}
|
|
2024
|
-
/**
|
|
2025
|
-
* Suggest a pattern from an incident group
|
|
2026
|
-
*/
|
|
2027
|
-
suggestFromGroup(group) {
|
|
2028
|
-
const baseId = `group-${group.id.toLowerCase().replace(/[^a-z0-9]/g, "-")}`;
|
|
2029
|
-
const symbols = this.buildSymbolCriteria(group.commonSymbols);
|
|
2030
|
-
const groupIncidents = group.incidents.slice(0, 20).map((id) => this.storage.getIncident(id)).filter((i) => i != null);
|
|
2031
|
-
const pattern = {
|
|
2032
|
-
id: baseId,
|
|
2033
|
-
name: group.name || `Pattern from group ${group.id}`,
|
|
2034
|
-
description: `Auto-suggested pattern from incident group with ${group.count} incidents`,
|
|
2035
|
-
pattern: {
|
|
2036
|
-
symbols,
|
|
2037
|
-
errorContains: group.commonErrorPatterns.length > 0 ? group.commonErrorPatterns : void 0
|
|
2038
|
-
},
|
|
2039
|
-
resolution: {
|
|
2040
|
-
description: "Resolution approach TBD based on grouped incidents",
|
|
2041
|
-
strategy: groupIncidents.length > 0 ? this.inferStrategy(groupIncidents) : "fix-code",
|
|
2042
|
-
priority: this.getPriorityFromCount(group.count)
|
|
2043
|
-
},
|
|
2044
|
-
source: "suggested",
|
|
2045
|
-
private: false,
|
|
2046
|
-
tags: this.generateTagsFromGroup(group)
|
|
2047
|
-
};
|
|
2048
|
-
return pattern;
|
|
2049
|
-
}
|
|
2050
|
-
/**
|
|
2051
|
-
* Find incidents that could become patterns
|
|
2052
|
-
*/
|
|
2053
|
-
findPatternCandidates(minOccurrences = 3) {
|
|
2054
|
-
const incidents = this.storage.getRecentIncidents({
|
|
2055
|
-
limit: 1e3,
|
|
2056
|
-
status: "resolved"
|
|
2057
|
-
});
|
|
2058
|
-
const signatureGroups = /* @__PURE__ */ new Map();
|
|
2059
|
-
for (const incident of incidents) {
|
|
2060
|
-
const signature = this.getSymbolSignature(incident.symbols);
|
|
2061
|
-
const existing = signatureGroups.get(signature) || [];
|
|
2062
|
-
existing.push(incident);
|
|
2063
|
-
signatureGroups.set(signature, existing);
|
|
2064
|
-
}
|
|
2065
|
-
const candidates = [];
|
|
2066
|
-
for (const [, groupIncidents] of signatureGroups) {
|
|
2067
|
-
if (groupIncidents.length >= minOccurrences) {
|
|
2068
|
-
const hasPattern = this.hasMatchingPattern(groupIncidents[0]);
|
|
2069
|
-
if (hasPattern) continue;
|
|
2070
|
-
const suggestedPattern = this.suggestFromIncidents(groupIncidents);
|
|
2071
|
-
candidates.push({
|
|
2072
|
-
incidents: groupIncidents,
|
|
2073
|
-
suggestedPattern,
|
|
2074
|
-
occurrenceCount: groupIncidents.length
|
|
2075
|
-
});
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
return candidates.sort((a, b) => b.occurrenceCount - a.occurrenceCount);
|
|
2079
|
-
}
|
|
2080
|
-
/**
|
|
2081
|
-
* Generate pattern from multiple similar incidents
|
|
2082
|
-
*/
|
|
2083
|
-
suggestFromIncidents(incidents) {
|
|
2084
|
-
const commonSymbols = this.extractCommonSymbols(incidents);
|
|
2085
|
-
const symbols = this.buildSymbolCriteria(commonSymbols);
|
|
2086
|
-
const errorKeywords = this.extractCommonErrorKeywords(incidents);
|
|
2087
|
-
const missingSignals = this.extractCommonMissingSignals(incidents);
|
|
2088
|
-
const baseId = this.generatePatternId(incidents[0]);
|
|
2089
|
-
return {
|
|
2090
|
-
id: baseId,
|
|
2091
|
-
name: this.generatePatternName(incidents[0]),
|
|
2092
|
-
description: `Auto-suggested pattern from ${incidents.length} similar incidents`,
|
|
2093
|
-
pattern: {
|
|
2094
|
-
symbols,
|
|
2095
|
-
errorContains: errorKeywords.length > 0 ? errorKeywords : void 0,
|
|
2096
|
-
missingSignals: missingSignals.length > 0 ? missingSignals : void 0
|
|
2097
|
-
},
|
|
2098
|
-
resolution: {
|
|
2099
|
-
description: "Resolution approach based on previous resolutions",
|
|
2100
|
-
strategy: this.inferStrategy(incidents),
|
|
2101
|
-
priority: this.getPriorityFromCount(incidents.length)
|
|
2102
|
-
},
|
|
2103
|
-
source: "suggested",
|
|
2104
|
-
private: false,
|
|
2105
|
-
tags: this.generateTagsFromIncidents(incidents)
|
|
2106
|
-
};
|
|
2107
|
-
}
|
|
2108
|
-
/**
|
|
2109
|
-
* Build symbol criteria for pattern, adding wildcards where appropriate
|
|
2110
|
-
*/
|
|
2111
|
-
buildSymbolCriteria(symbols) {
|
|
2112
|
-
const criteria = {};
|
|
2113
|
-
for (const [key, value] of Object.entries(symbols)) {
|
|
2114
|
-
if (value) {
|
|
2115
|
-
criteria[key] = value;
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
return criteria;
|
|
2119
|
-
}
|
|
2120
|
-
/**
|
|
2121
|
-
* Extract keywords from error message
|
|
2122
|
-
*/
|
|
2123
|
-
extractErrorKeywords(message) {
|
|
2124
|
-
const stopWords = /* @__PURE__ */ new Set([
|
|
2125
|
-
"the",
|
|
2126
|
-
"a",
|
|
2127
|
-
"an",
|
|
2128
|
-
"is",
|
|
2129
|
-
"are",
|
|
2130
|
-
"was",
|
|
2131
|
-
"were",
|
|
2132
|
-
"in",
|
|
2133
|
-
"on",
|
|
2134
|
-
"at",
|
|
2135
|
-
"to",
|
|
2136
|
-
"for",
|
|
2137
|
-
"of",
|
|
2138
|
-
"with",
|
|
2139
|
-
"and",
|
|
2140
|
-
"or",
|
|
2141
|
-
"but",
|
|
2142
|
-
"not",
|
|
2143
|
-
"no",
|
|
2144
|
-
"be",
|
|
2145
|
-
"been",
|
|
2146
|
-
"have",
|
|
2147
|
-
"has",
|
|
2148
|
-
"had",
|
|
2149
|
-
"do",
|
|
2150
|
-
"does",
|
|
2151
|
-
"did"
|
|
2152
|
-
]);
|
|
2153
|
-
const words = message.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
|
|
2154
|
-
const unique = [...new Set(words)];
|
|
2155
|
-
return unique.slice(0, 5);
|
|
2156
|
-
}
|
|
2157
|
-
/**
|
|
2158
|
-
* Extract common error keywords from multiple incidents
|
|
2159
|
-
*/
|
|
2160
|
-
extractCommonErrorKeywords(incidents) {
|
|
2161
|
-
const wordCounts = /* @__PURE__ */ new Map();
|
|
2162
|
-
for (const incident of incidents) {
|
|
2163
|
-
const keywords = this.extractErrorKeywords(incident.error.message);
|
|
2164
|
-
for (const keyword of keywords) {
|
|
2165
|
-
wordCounts.set(keyword, (wordCounts.get(keyword) || 0) + 1);
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
const threshold = Math.ceil(incidents.length * 0.5);
|
|
2169
|
-
return Array.from(wordCounts.entries()).filter(([, count]) => count >= threshold).map(([word]) => word).slice(0, 5);
|
|
2170
|
-
}
|
|
2171
|
-
/**
|
|
2172
|
-
* Extract symbols common to all incidents
|
|
2173
|
-
*/
|
|
2174
|
-
extractCommonSymbols(incidents) {
|
|
2175
|
-
if (incidents.length === 0) return {};
|
|
2176
|
-
const first = incidents[0].symbols;
|
|
2177
|
-
const common = {};
|
|
2178
|
-
for (const [key, value] of Object.entries(first)) {
|
|
2179
|
-
if (!value) continue;
|
|
2180
|
-
const allMatch = incidents.every(
|
|
2181
|
-
(i) => i.symbols[key] === value
|
|
2182
|
-
);
|
|
2183
|
-
if (allMatch) {
|
|
2184
|
-
common[key] = value;
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
return common;
|
|
2188
|
-
}
|
|
2189
|
-
/**
|
|
2190
|
-
* Extract missing signals common to multiple incidents
|
|
2191
|
-
*/
|
|
2192
|
-
extractCommonMissingSignals(incidents) {
|
|
2193
|
-
const signalCounts = /* @__PURE__ */ new Map();
|
|
2194
|
-
for (const incident of incidents) {
|
|
2195
|
-
if (!incident.flowPosition?.missing) continue;
|
|
2196
|
-
for (const signal of incident.flowPosition.missing) {
|
|
2197
|
-
signalCounts.set(signal, (signalCounts.get(signal) || 0) + 1);
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
const threshold = Math.ceil(incidents.length * 0.5);
|
|
2201
|
-
return Array.from(signalCounts.entries()).filter(([, count]) => count >= threshold).map(([signal]) => signal);
|
|
2202
|
-
}
|
|
2203
|
-
/**
|
|
2204
|
-
* Generate a pattern ID from incident
|
|
2205
|
-
*/
|
|
2206
|
-
generatePatternId(incident) {
|
|
2207
|
-
const parts = [];
|
|
2208
|
-
if (incident.symbols.gate) {
|
|
2209
|
-
parts.push(incident.symbols.gate.replace(/[^a-z0-9]/gi, ""));
|
|
2210
|
-
} else if (incident.symbols.feature) {
|
|
2211
|
-
parts.push(incident.symbols.feature.replace(/[^a-z0-9]/gi, ""));
|
|
2212
|
-
} else if (incident.symbols.component) {
|
|
2213
|
-
parts.push(incident.symbols.component.replace(/[^a-z0-9]/gi, ""));
|
|
2214
|
-
} else if (incident.symbols.integration) {
|
|
2215
|
-
parts.push(incident.symbols.integration.replace(/[^a-z0-9]/gi, ""));
|
|
2216
|
-
} else {
|
|
2217
|
-
parts.push("unknown");
|
|
2218
|
-
}
|
|
2219
|
-
const errorType = incident.error.type?.toLowerCase() || "error";
|
|
2220
|
-
parts.push(errorType.replace(/[^a-z0-9]/gi, ""));
|
|
2221
|
-
parts.push(String(Date.now() % 1e3).padStart(3, "0"));
|
|
2222
|
-
return parts.join("-");
|
|
2223
|
-
}
|
|
2224
|
-
/**
|
|
2225
|
-
* Generate a human-readable pattern name
|
|
2226
|
-
*/
|
|
2227
|
-
generatePatternName(incident) {
|
|
2228
|
-
const parts = [];
|
|
2229
|
-
if (incident.symbols.feature) {
|
|
2230
|
-
parts.push(
|
|
2231
|
-
incident.symbols.feature.replace("@", "").replace(/-/g, " ")
|
|
2232
|
-
);
|
|
2233
|
-
}
|
|
2234
|
-
if (incident.symbols.gate) {
|
|
2235
|
-
parts.push("gate " + incident.symbols.gate.replace("^", ""));
|
|
2236
|
-
}
|
|
2237
|
-
if (incident.error.type) {
|
|
2238
|
-
parts.push(incident.error.type);
|
|
2239
|
-
}
|
|
2240
|
-
if (parts.length === 0) {
|
|
2241
|
-
return "Unnamed Pattern";
|
|
2242
|
-
}
|
|
2243
|
-
const name = parts.join(" - ");
|
|
2244
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
2245
|
-
}
|
|
2246
|
-
/**
|
|
2247
|
-
* Generate tags from incident
|
|
2248
|
-
*/
|
|
2249
|
-
generateTags(incident) {
|
|
2250
|
-
const tags = [];
|
|
2251
|
-
if (incident.symbols.feature) {
|
|
2252
|
-
tags.push("feature");
|
|
2253
|
-
}
|
|
2254
|
-
if (incident.symbols.gate) {
|
|
2255
|
-
tags.push("gate");
|
|
2256
|
-
}
|
|
2257
|
-
if (incident.symbols.integration) {
|
|
2258
|
-
tags.push("integration");
|
|
2259
|
-
tags.push(incident.symbols.integration.replace("&", ""));
|
|
2260
|
-
}
|
|
2261
|
-
if (incident.error.type) {
|
|
2262
|
-
tags.push(incident.error.type.toLowerCase());
|
|
2263
|
-
}
|
|
2264
|
-
tags.push(incident.environment);
|
|
2265
|
-
return [...new Set(tags)].slice(0, 5);
|
|
2266
|
-
}
|
|
2267
|
-
/**
|
|
2268
|
-
* Generate tags from incident group
|
|
2269
|
-
*/
|
|
2270
|
-
generateTagsFromGroup(group) {
|
|
2271
|
-
const tags = ["grouped"];
|
|
2272
|
-
if (group.commonSymbols.feature) {
|
|
2273
|
-
tags.push("feature");
|
|
2274
|
-
}
|
|
2275
|
-
if (group.commonSymbols.gate) {
|
|
2276
|
-
tags.push("gate");
|
|
2277
|
-
}
|
|
2278
|
-
if (group.commonSymbols.integration) {
|
|
2279
|
-
tags.push("integration");
|
|
2280
|
-
}
|
|
2281
|
-
for (const env of group.environments) {
|
|
2282
|
-
tags.push(env);
|
|
2283
|
-
}
|
|
2284
|
-
return [...new Set(tags)].slice(0, 5);
|
|
2285
|
-
}
|
|
2286
|
-
/**
|
|
2287
|
-
* Generate tags from multiple incidents
|
|
2288
|
-
*/
|
|
2289
|
-
generateTagsFromIncidents(incidents) {
|
|
2290
|
-
const tagCounts = /* @__PURE__ */ new Map();
|
|
2291
|
-
for (const incident of incidents) {
|
|
2292
|
-
const tags = this.generateTags(incident);
|
|
2293
|
-
for (const tag of tags) {
|
|
2294
|
-
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
|
|
2295
|
-
}
|
|
2296
|
-
}
|
|
2297
|
-
return Array.from(tagCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([tag]) => tag);
|
|
2298
|
-
}
|
|
2299
|
-
/**
|
|
2300
|
-
* Get symbol signature for grouping
|
|
2301
|
-
*/
|
|
2302
|
-
getSymbolSignature(symbols) {
|
|
2303
|
-
const parts = [];
|
|
2304
|
-
if (symbols.feature) parts.push(`f:${symbols.feature}`);
|
|
2305
|
-
if (symbols.component) parts.push(`c:${symbols.component}`);
|
|
2306
|
-
if (symbols.flow) parts.push(`fl:${symbols.flow}`);
|
|
2307
|
-
if (symbols.gate) parts.push(`g:${symbols.gate}`);
|
|
2308
|
-
if (symbols.integration) parts.push(`i:${symbols.integration}`);
|
|
2309
|
-
return parts.sort().join("|");
|
|
2310
|
-
}
|
|
2311
|
-
/**
|
|
2312
|
-
* Check if there's already a pattern matching this incident
|
|
2313
|
-
*/
|
|
2314
|
-
hasMatchingPattern(incident) {
|
|
2315
|
-
const patterns = this.storage.getAllPatterns({ includePrivate: true });
|
|
2316
|
-
for (const pattern of patterns) {
|
|
2317
|
-
let matchCount = 0;
|
|
2318
|
-
const symbolTypes = [
|
|
2319
|
-
"feature",
|
|
2320
|
-
"component",
|
|
2321
|
-
"flow",
|
|
2322
|
-
"gate",
|
|
2323
|
-
"signal",
|
|
2324
|
-
"integration"
|
|
2325
|
-
];
|
|
2326
|
-
for (const type of symbolTypes) {
|
|
2327
|
-
const patternValue = pattern.pattern.symbols[type];
|
|
2328
|
-
const incidentValue = incident.symbols[type];
|
|
2329
|
-
if (patternValue && incidentValue && patternValue === incidentValue) {
|
|
2330
|
-
matchCount++;
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
if (matchCount >= 2) {
|
|
2334
|
-
return true;
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
return false;
|
|
2338
|
-
}
|
|
2339
|
-
/**
|
|
2340
|
-
* Infer resolution strategy from incident error patterns and context.
|
|
2341
|
-
* Uses keyword heuristics across all incident messages to pick the
|
|
2342
|
-
* most likely resolution approach.
|
|
2343
|
-
*/
|
|
2344
|
-
inferStrategy(incidents) {
|
|
2345
|
-
const messages = incidents.map((i) => i.error.message.toLowerCase());
|
|
2346
|
-
const hasKeyword = (keywords) => messages.some((m) => keywords.some((k) => m.includes(k)));
|
|
2347
|
-
if (hasKeyword(["revert", "rollback", "regression", "broke after deploy", "since deploy"])) {
|
|
2348
|
-
return "rollback";
|
|
2349
|
-
}
|
|
2350
|
-
if (hasKeyword(["config", "environment variable", "env var", "missing key", "secret", "credential"])) {
|
|
2351
|
-
return "config-change";
|
|
2352
|
-
}
|
|
2353
|
-
if (hasKeyword(["out of memory", "oom", "heap", "memory limit", "capacity", "too many connections", "pool exhausted"])) {
|
|
2354
|
-
return "scale-up";
|
|
2355
|
-
}
|
|
2356
|
-
if (hasKeyword(["timeout", "network", "econnrefused", "econnreset", "dns", "socket hang up"])) {
|
|
2357
|
-
return "retry";
|
|
2358
|
-
}
|
|
2359
|
-
if (hasKeyword(["unavailable", "service down", "circuit breaker", "fallback", "503", "502"])) {
|
|
2360
|
-
return "fallback";
|
|
2361
|
-
}
|
|
2362
|
-
if (hasKeyword(["validation", "invalid", "required", "constraint", "duplicate", "not found", "404"])) {
|
|
2363
|
-
return "fix-data";
|
|
2364
|
-
}
|
|
2365
|
-
if (hasKeyword(["permission", "forbidden", "403", "401", "unauthorized", "access denied"])) {
|
|
2366
|
-
return "escalate";
|
|
2367
|
-
}
|
|
2368
|
-
const uniqueTypes = new Set(incidents.map((i) => i.error.type).filter(Boolean));
|
|
2369
|
-
if (uniqueTypes.size > 2) {
|
|
2370
|
-
return "investigate";
|
|
2371
|
-
}
|
|
2372
|
-
return "fix-code";
|
|
2373
|
-
}
|
|
2374
|
-
/**
|
|
2375
|
-
* Get priority based on occurrence count
|
|
2376
|
-
*/
|
|
2377
|
-
getPriorityFromCount(count) {
|
|
2378
|
-
if (count >= 20) return "critical";
|
|
2379
|
-
if (count >= 10) return "high";
|
|
2380
|
-
if (count >= 5) return "medium";
|
|
2381
|
-
return "low";
|
|
2382
|
-
}
|
|
2383
|
-
};
|
|
2384
|
-
var PatternImporter = class {
|
|
2385
|
-
/**
|
|
2386
|
-
* Validate a pattern export file
|
|
2387
|
-
*/
|
|
2388
|
-
validate(data) {
|
|
2389
|
-
const errors = [];
|
|
2390
|
-
const warnings = [];
|
|
2391
|
-
if (!data || typeof data !== "object") {
|
|
2392
|
-
return { valid: false, errors: ["Invalid data: expected object"], warnings: [] };
|
|
2393
|
-
}
|
|
2394
|
-
const obj = data;
|
|
2395
|
-
if (!obj.version) {
|
|
2396
|
-
errors.push("Missing version field");
|
|
2397
|
-
}
|
|
2398
|
-
if (!Array.isArray(obj.patterns)) {
|
|
2399
|
-
errors.push("Missing or invalid patterns array");
|
|
2400
|
-
return { valid: false, errors, warnings };
|
|
2401
|
-
}
|
|
2402
|
-
for (let i = 0; i < obj.patterns.length; i++) {
|
|
2403
|
-
const pattern = obj.patterns[i];
|
|
2404
|
-
const patternErrors = this.validatePattern(pattern, i);
|
|
2405
|
-
errors.push(...patternErrors.errors);
|
|
2406
|
-
warnings.push(...patternErrors.warnings);
|
|
2407
|
-
}
|
|
2408
|
-
return {
|
|
2409
|
-
valid: errors.length === 0,
|
|
2410
|
-
errors,
|
|
2411
|
-
warnings
|
|
2412
|
-
};
|
|
2413
|
-
}
|
|
2414
|
-
/**
|
|
2415
|
-
* Validate a single pattern
|
|
2416
|
-
*/
|
|
2417
|
-
validatePattern(pattern, index) {
|
|
2418
|
-
const errors = [];
|
|
2419
|
-
const warnings = [];
|
|
2420
|
-
const prefix = `Pattern[${index}]`;
|
|
2421
|
-
if (!pattern.id || typeof pattern.id !== "string") {
|
|
2422
|
-
errors.push(`${prefix}: Missing or invalid id`);
|
|
2423
|
-
} else if (!/^[a-z0-9-]+$/.test(pattern.id)) {
|
|
2424
|
-
warnings.push(`${prefix}: ID "${pattern.id}" should be kebab-case`);
|
|
2425
|
-
}
|
|
2426
|
-
if (!pattern.name || typeof pattern.name !== "string") {
|
|
2427
|
-
errors.push(`${prefix}: Missing or invalid name`);
|
|
2428
|
-
}
|
|
2429
|
-
if (!pattern.pattern || typeof pattern.pattern !== "object") {
|
|
2430
|
-
errors.push(`${prefix}: Missing or invalid pattern criteria`);
|
|
2431
|
-
} else {
|
|
2432
|
-
const criteria = pattern.pattern;
|
|
2433
|
-
const hasSymbols = criteria.symbols && typeof criteria.symbols === "object" && Object.keys(criteria.symbols).length > 0;
|
|
2434
|
-
const hasErrorContains = Array.isArray(criteria.errorContains) && criteria.errorContains.length > 0;
|
|
2435
|
-
const hasErrorMatches = criteria.errorMatches && typeof criteria.errorMatches === "string";
|
|
2436
|
-
const hasMissingSignals = Array.isArray(criteria.missingSignals) && criteria.missingSignals.length > 0;
|
|
2437
|
-
if (!hasSymbols && !hasErrorContains && !hasErrorMatches && !hasMissingSignals) {
|
|
2438
|
-
errors.push(`${prefix}: Pattern must have at least one matching criteria`);
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
if (!pattern.resolution || typeof pattern.resolution !== "object") {
|
|
2442
|
-
errors.push(`${prefix}: Missing or invalid resolution`);
|
|
2443
|
-
} else {
|
|
2444
|
-
const resolution = pattern.resolution;
|
|
2445
|
-
if (!resolution.description || typeof resolution.description !== "string") {
|
|
2446
|
-
errors.push(`${prefix}: Missing resolution description`);
|
|
2447
|
-
}
|
|
2448
|
-
if (!resolution.strategy || typeof resolution.strategy !== "string") {
|
|
2449
|
-
errors.push(`${prefix}: Missing resolution strategy`);
|
|
2450
|
-
} else {
|
|
2451
|
-
const validStrategies = [
|
|
2452
|
-
"retry",
|
|
2453
|
-
"fallback",
|
|
2454
|
-
"fix-data",
|
|
2455
|
-
"fix-code",
|
|
2456
|
-
"ignore",
|
|
2457
|
-
"escalate"
|
|
2458
|
-
];
|
|
2459
|
-
if (!validStrategies.includes(resolution.strategy)) {
|
|
2460
|
-
errors.push(`${prefix}: Invalid strategy "${resolution.strategy}"`);
|
|
2461
|
-
}
|
|
2462
|
-
}
|
|
2463
|
-
if (!resolution.priority || typeof resolution.priority !== "string") {
|
|
2464
|
-
warnings.push(`${prefix}: Missing priority, will default to medium`);
|
|
2465
|
-
} else {
|
|
2466
|
-
const validPriorities = ["low", "medium", "high", "critical"];
|
|
2467
|
-
if (!validPriorities.includes(resolution.priority)) {
|
|
2468
|
-
warnings.push(`${prefix}: Invalid priority "${resolution.priority}"`);
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
return { errors, warnings };
|
|
2473
|
-
}
|
|
2474
|
-
/**
|
|
2475
|
-
* Load patterns from a JSON file
|
|
2476
|
-
*/
|
|
2477
|
-
loadFromFile(filePath) {
|
|
2478
|
-
if (!fs4.existsSync(filePath)) {
|
|
2479
|
-
throw new Error(`File not found: ${filePath}`);
|
|
2480
|
-
}
|
|
2481
|
-
const content = fs4.readFileSync(filePath, "utf-8");
|
|
2482
|
-
const data = JSON.parse(content);
|
|
2483
|
-
const validation = this.validate(data);
|
|
2484
|
-
if (!validation.valid) {
|
|
2485
|
-
throw new Error(`Invalid pattern file: ${validation.errors.join(", ")}`);
|
|
2486
|
-
}
|
|
2487
|
-
return this.normalizeExport(data);
|
|
2488
|
-
}
|
|
2489
|
-
/**
|
|
2490
|
-
* Load patterns from a URL
|
|
2491
|
-
*/
|
|
2492
|
-
async loadFromUrl(url) {
|
|
2493
|
-
const response = await fetch(url);
|
|
2494
|
-
if (!response.ok) {
|
|
2495
|
-
throw new Error(`Failed to fetch patterns: ${response.statusText}`);
|
|
2496
|
-
}
|
|
2497
|
-
const data = await response.json();
|
|
2498
|
-
const validation = this.validate(data);
|
|
2499
|
-
if (!validation.valid) {
|
|
2500
|
-
throw new Error(`Invalid pattern data: ${validation.errors.join(", ")}`);
|
|
2501
|
-
}
|
|
2502
|
-
return this.normalizeExport(data);
|
|
2503
|
-
}
|
|
2504
|
-
/**
|
|
2505
|
-
* Normalize raw data to PatternExport
|
|
2506
|
-
*/
|
|
2507
|
-
normalizeExport(data) {
|
|
2508
|
-
const patterns = data.patterns.map(
|
|
2509
|
-
(p) => this.normalizePattern(p)
|
|
2510
|
-
);
|
|
2511
|
-
return {
|
|
2512
|
-
version: data.version || "1.0.0",
|
|
2513
|
-
exportedAt: data.exportedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2514
|
-
patterns
|
|
2515
|
-
};
|
|
2516
|
-
}
|
|
2517
|
-
/**
|
|
2518
|
-
* Normalize a raw pattern object
|
|
2519
|
-
*/
|
|
2520
|
-
normalizePattern(data) {
|
|
2521
|
-
const pattern = data.pattern;
|
|
2522
|
-
const resolution = data.resolution;
|
|
2523
|
-
const confidence = data.confidence || {};
|
|
2524
|
-
return {
|
|
2525
|
-
id: data.id,
|
|
2526
|
-
name: data.name,
|
|
2527
|
-
description: data.description || "",
|
|
2528
|
-
pattern: {
|
|
2529
|
-
symbols: pattern.symbols || {},
|
|
2530
|
-
errorContains: pattern.errorContains,
|
|
2531
|
-
errorMatches: pattern.errorMatches,
|
|
2532
|
-
errorType: pattern.errorType,
|
|
2533
|
-
missingSignals: pattern.missingSignals,
|
|
2534
|
-
environment: pattern.environment
|
|
2535
|
-
},
|
|
2536
|
-
resolution: {
|
|
2537
|
-
description: resolution.description,
|
|
2538
|
-
strategy: resolution.strategy,
|
|
2539
|
-
priority: resolution.priority || "medium",
|
|
2540
|
-
codeHint: resolution.codeHint,
|
|
2541
|
-
codeSnippet: resolution.codeSnippet,
|
|
2542
|
-
symbolsToModify: resolution.symbolsToModify,
|
|
2543
|
-
filesLikelyInvolved: resolution.filesLikelyInvolved,
|
|
2544
|
-
commitRef: resolution.commitRef,
|
|
2545
|
-
prRef: resolution.prRef,
|
|
2546
|
-
docsRef: resolution.docsRef
|
|
2547
|
-
},
|
|
2548
|
-
confidence: {
|
|
2549
|
-
score: confidence.score || 50,
|
|
2550
|
-
timesMatched: confidence.timesMatched || 0,
|
|
2551
|
-
timesResolved: confidence.timesResolved || 0,
|
|
2552
|
-
timesRecurred: confidence.timesRecurred || 0,
|
|
2553
|
-
avgTimeToResolve: confidence.avgTimeToResolve,
|
|
2554
|
-
lastMatched: confidence.lastMatched,
|
|
2555
|
-
lastResolved: confidence.lastResolved
|
|
2556
|
-
},
|
|
2557
|
-
source: data.source || "imported",
|
|
2558
|
-
private: Boolean(data.private),
|
|
2559
|
-
tags: data.tags || [],
|
|
2560
|
-
createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2561
|
-
updatedAt: data.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
2562
|
-
};
|
|
2563
|
-
}
|
|
2564
|
-
/**
|
|
2565
|
-
* Merge patterns from multiple sources
|
|
2566
|
-
*/
|
|
2567
|
-
mergePatterns(...exports) {
|
|
2568
|
-
const patternMap = /* @__PURE__ */ new Map();
|
|
2569
|
-
for (const exp of exports) {
|
|
2570
|
-
for (const pattern of exp.patterns) {
|
|
2571
|
-
patternMap.set(pattern.id, pattern);
|
|
2572
|
-
}
|
|
2573
|
-
}
|
|
2574
|
-
return {
|
|
2575
|
-
version: "1.0.0",
|
|
2576
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2577
|
-
patterns: Array.from(patternMap.values())
|
|
2578
|
-
};
|
|
2579
|
-
}
|
|
2580
|
-
};
|
|
2581
|
-
|
|
2582
|
-
export {
|
|
2583
|
-
SentinelClient,
|
|
2584
|
-
createSentinelClient,
|
|
2585
|
-
SentinelTransport,
|
|
2586
|
-
createSentinelTransport,
|
|
2587
|
-
enableSentinel,
|
|
2588
|
-
PatternMatcher,
|
|
2589
|
-
loadUniversalPatterns,
|
|
2590
|
-
loadParadigmPatterns,
|
|
2591
|
-
loadAllSeedPatterns,
|
|
2592
|
-
FlowTracker,
|
|
2593
|
-
Sentinel,
|
|
2594
|
-
detectSymbols,
|
|
2595
|
-
generateConfig,
|
|
2596
|
-
IncidentGrouper,
|
|
2597
|
-
TimelineBuilder,
|
|
2598
|
-
StatsCalculator,
|
|
2599
|
-
ContextEnricher,
|
|
2600
|
-
PatternSuggester,
|
|
2601
|
-
PatternImporter
|
|
2602
|
-
};
|