From 273adb83edf8a549dca57164d989a4cbfc4021e3 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:19:00 +0000 Subject: [PATCH] CodeRabbit Generated Unit Tests: Add unit tests for PR changes --- coverage/base.css | 224 +++ coverage/block-navigation.js | 87 ++ coverage/clover.xml | 1121 +++++++++++++++ coverage/coverage-final.json | 19 + coverage/favicon.png | 0 coverage/index.html | 161 +++ coverage/prettify.css | 1 + coverage/prettify.js | 2 + coverage/sort-arrow-sprite.png | 0 coverage/sorter.js | 210 +++ coverage/src/commands/config.js.html | 1138 +++++++++++++++ coverage/src/commands/index.html | 146 ++ coverage/src/commands/ping.js.html | 139 ++ coverage/src/commands/status.js.html | 544 +++++++ coverage/src/db.js.html | 502 +++++++ coverage/src/index.html | 146 ++ coverage/src/index.js.html | 1075 ++++++++++++++ coverage/src/logger.js.html | 808 +++++++++++ coverage/src/modules/ai.js.html | 520 +++++++ coverage/src/modules/chimeIn.js.html | 1000 +++++++++++++ coverage/src/modules/config.js.html | 1411 +++++++++++++++++++ coverage/src/modules/events.js.html | 544 +++++++ coverage/src/modules/index.html | 191 +++ coverage/src/modules/spam.js.html | 268 ++++ coverage/src/modules/welcome.js.html | 1333 ++++++++++++++++++ coverage/src/utils/errors.js.html | 757 ++++++++++ coverage/src/utils/health.js.html | 562 ++++++++ coverage/src/utils/index.html | 191 +++ coverage/src/utils/permissions.js.html | 316 +++++ coverage/src/utils/registerCommands.js.html | 256 ++++ coverage/src/utils/retry.js.html | 475 +++++++ coverage/src/utils/splitMessage.js.html | 268 ++++ tests/ai.test.js | 377 +++++ tests/errors.test.js | 346 +++++ tests/logger.test.js | 231 +++ tests/ping.test.js | 143 ++ tests/spam.test.js | 262 ++++ tests/status.test.js | 301 ++++ tests/welcome.test.js | 413 ++++++ 39 files changed, 16488 insertions(+) create mode 100644 coverage/base.css create mode 100644 coverage/block-navigation.js create mode 100644 coverage/clover.xml create mode 100644 coverage/coverage-final.json create mode 100644 coverage/favicon.png create mode 100644 coverage/index.html create mode 100644 coverage/prettify.css create mode 100644 coverage/prettify.js create mode 100644 coverage/sort-arrow-sprite.png create mode 100644 coverage/sorter.js create mode 100644 coverage/src/commands/config.js.html create mode 100644 coverage/src/commands/index.html create mode 100644 coverage/src/commands/ping.js.html create mode 100644 coverage/src/commands/status.js.html create mode 100644 coverage/src/db.js.html create mode 100644 coverage/src/index.html create mode 100644 coverage/src/index.js.html create mode 100644 coverage/src/logger.js.html create mode 100644 coverage/src/modules/ai.js.html create mode 100644 coverage/src/modules/chimeIn.js.html create mode 100644 coverage/src/modules/config.js.html create mode 100644 coverage/src/modules/events.js.html create mode 100644 coverage/src/modules/index.html create mode 100644 coverage/src/modules/spam.js.html create mode 100644 coverage/src/modules/welcome.js.html create mode 100644 coverage/src/utils/errors.js.html create mode 100644 coverage/src/utils/health.js.html create mode 100644 coverage/src/utils/index.html create mode 100644 coverage/src/utils/permissions.js.html create mode 100644 coverage/src/utils/registerCommands.js.html create mode 100644 coverage/src/utils/retry.js.html create mode 100644 coverage/src/utils/splitMessage.js.html create mode 100644 tests/ai.test.js create mode 100644 tests/errors.test.js create mode 100644 tests/logger.test.js create mode 100644 tests/ping.test.js create mode 100644 tests/spam.test.js create mode 100644 tests/status.test.js create mode 100644 tests/welcome.test.js diff --git a/coverage/base.css b/coverage/base.css new file mode 100644 index 00000000..b4d67560 --- /dev/null +++ b/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} \ No newline at end of file diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js new file mode 100644 index 00000000..832f51d3 --- /dev/null +++ b/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); \ No newline at end of file diff --git a/coverage/clover.xml b/coverage/clover.xml new file mode 100644 index 00000000..a4410ebc --- /dev/null +++ b/coverage/clover.xml @@ -0,0 +1,1121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json new file mode 100644 index 00000000..8e476d01 --- /dev/null +++ b/coverage/coverage-final.json @@ -0,0 +1,19 @@ +{"/home/jailuser/git/src/db.js": {"path":"/home/jailuser/git/src/db.js","statementMap":{"0":{"start":{"line":9,"column":17},"end":{"line":9,"column":19}},"1":{"start":{"line":12,"column":11},"end":{"line":12,"column":15}},"2":{"start":{"line":15,"column":19},"end":{"line":15,"column":24}},"3":{"start":{"line":32,"column":2},"end":{"line":34,"column":null}},"4":{"start":{"line":33,"column":4},"end":{"line":33,"column":null}},"5":{"start":{"line":36,"column":17},"end":{"line":36,"column":70}},"6":{"start":{"line":38,"column":2},"end":{"line":40,"column":null}},"7":{"start":{"line":39,"column":4},"end":{"line":39,"column":null}},"8":{"start":{"line":42,"column":2},"end":{"line":44,"column":null}},"9":{"start":{"line":43,"column":4},"end":{"line":43,"column":null}},"10":{"start":{"line":47,"column":2},"end":{"line":47,"column":null}},"11":{"start":{"line":55,"column":2},"end":{"line":55,"column":null}},"12":{"start":{"line":55,"column":12},"end":{"line":55,"column":null}},"13":{"start":{"line":56,"column":2},"end":{"line":58,"column":null}},"14":{"start":{"line":57,"column":4},"end":{"line":57,"column":null}},"15":{"start":{"line":60,"column":2},"end":{"line":60,"column":null}},"16":{"start":{"line":61,"column":2},"end":{"line":110,"column":null}},"17":{"start":{"line":62,"column":29},"end":{"line":62,"column":53}},"18":{"start":{"line":63,"column":4},"end":{"line":65,"column":null}},"19":{"start":{"line":64,"column":6},"end":{"line":64,"column":null}},"20":{"start":{"line":67,"column":4},"end":{"line":73,"column":null}},"21":{"start":{"line":76,"column":4},"end":{"line":78,"column":null}},"22":{"start":{"line":77,"column":5},"end":{"line":77,"column":null}},"23":{"start":{"line":80,"column":4},"end":{"line":105,"column":null}},"24":{"start":{"line":82,"column":21},"end":{"line":82,"column":41}},"25":{"start":{"line":83,"column":6},"end":{"line":88,"column":null}},"26":{"start":{"line":84,"column":8},"end":{"line":84,"column":null}},"27":{"start":{"line":85,"column":7},"end":{"line":85,"column":null}},"28":{"start":{"line":87,"column":8},"end":{"line":87,"column":null}},"29":{"start":{"line":91,"column":6},"end":{"line":97,"column":null}},"30":{"start":{"line":99,"column":5},"end":{"line":99,"column":null}},"31":{"start":{"line":102,"column":6},"end":{"line":102,"column":null}},"32":{"start":{"line":103,"column":6},"end":{"line":103,"column":null}},"33":{"start":{"line":104,"column":6},"end":{"line":104,"column":null}},"34":{"start":{"line":107,"column":4},"end":{"line":107,"column":null}},"35":{"start":{"line":109,"column":4},"end":{"line":109,"column":null}},"36":{"start":{"line":119,"column":2},"end":{"line":121,"column":null}},"37":{"start":{"line":120,"column":4},"end":{"line":120,"column":null}},"38":{"start":{"line":122,"column":2},"end":{"line":122,"column":null}},"39":{"start":{"line":129,"column":2},"end":{"line":138,"column":null}},"40":{"start":{"line":130,"column":4},"end":{"line":137,"column":null}},"41":{"start":{"line":131,"column":6},"end":{"line":131,"column":null}},"42":{"start":{"line":132,"column":5},"end":{"line":132,"column":null}},"43":{"start":{"line":134,"column":5},"end":{"line":134,"column":null}},"44":{"start":{"line":136,"column":6},"end":{"line":136,"column":null}}},"fnMap":{"0":{"name":"getSslConfig","decl":{"start":{"line":30,"column":9},"end":{"line":30,"column":21}},"loc":{"start":{"line":30,"column":40},"end":{"line":48,"column":null}},"line":30},"1":{"name":"initDb","decl":{"start":{"line":54,"column":22},"end":{"line":54,"column":28}},"loc":{"start":{"line":54,"column":31},"end":{"line":111,"column":null}},"line":54},"2":{"name":"(anonymous_2)","decl":{"start":{"line":76,"column":21},"end":{"line":76,"column":22}},"loc":{"start":{"line":76,"column":30},"end":{"line":78,"column":5}},"line":76},"3":{"name":"(anonymous_3)","decl":{"start":{"line":102,"column":29},"end":{"line":102,"column":30}},"loc":{"start":{"line":102,"column":35},"end":{"line":102,"column":37}},"line":102},"4":{"name":"getPool","decl":{"start":{"line":118,"column":16},"end":{"line":118,"column":23}},"loc":{"start":{"line":118,"column":26},"end":{"line":123,"column":null}},"line":118},"5":{"name":"closeDb","decl":{"start":{"line":128,"column":22},"end":{"line":128,"column":29}},"loc":{"start":{"line":128,"column":32},"end":{"line":139,"column":null}},"line":128}},"branchMap":{"0":{"loc":{"start":{"line":32,"column":2},"end":{"line":34,"column":null}},"type":"if","locations":[{"start":{"line":32,"column":2},"end":{"line":34,"column":null}},{"start":{},"end":{}}],"line":32},"1":{"loc":{"start":{"line":36,"column":18},"end":{"line":36,"column":48}},"type":"binary-expr","locations":[{"start":{"line":36,"column":18},"end":{"line":36,"column":42}},{"start":{"line":36,"column":46},"end":{"line":36,"column":48}}],"line":36},"2":{"loc":{"start":{"line":38,"column":2},"end":{"line":40,"column":null}},"type":"if","locations":[{"start":{"line":38,"column":2},"end":{"line":40,"column":null}},{"start":{},"end":{}}],"line":38},"3":{"loc":{"start":{"line":38,"column":6},"end":{"line":38,"column":44}},"type":"binary-expr","locations":[{"start":{"line":38,"column":6},"end":{"line":38,"column":24}},{"start":{"line":38,"column":28},"end":{"line":38,"column":44}}],"line":38},"4":{"loc":{"start":{"line":42,"column":2},"end":{"line":44,"column":null}},"type":"if","locations":[{"start":{"line":42,"column":2},"end":{"line":44,"column":null}},{"start":{},"end":{}}],"line":42},"5":{"loc":{"start":{"line":55,"column":2},"end":{"line":55,"column":null}},"type":"if","locations":[{"start":{"line":55,"column":2},"end":{"line":55,"column":null}},{"start":{},"end":{}}],"line":55},"6":{"loc":{"start":{"line":56,"column":2},"end":{"line":58,"column":null}},"type":"if","locations":[{"start":{"line":56,"column":2},"end":{"line":58,"column":null}},{"start":{},"end":{}}],"line":56},"7":{"loc":{"start":{"line":63,"column":4},"end":{"line":65,"column":null}},"type":"if","locations":[{"start":{"line":63,"column":4},"end":{"line":65,"column":null}},{"start":{},"end":{}}],"line":63},"8":{"loc":{"start":{"line":119,"column":2},"end":{"line":121,"column":null}},"type":"if","locations":[{"start":{"line":119,"column":2},"end":{"line":121,"column":null}},{"start":{},"end":{}}],"line":119},"9":{"loc":{"start":{"line":129,"column":2},"end":{"line":138,"column":null}},"type":"if","locations":[{"start":{"line":129,"column":2},"end":{"line":138,"column":null}},{"start":{},"end":{}}],"line":129}},"s":{"0":1,"1":1,"2":1,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0]},"meta":{"lastBranch":10,"lastFunction":6,"lastStatement":45,"seen":{"s:9:17:9:19":0,"s:12:11:12:15":1,"s:15:19:15:24":2,"f:30:9:30:21":0,"b:32:2:34:Infinity:undefined:undefined:undefined:undefined":0,"s:32:2:34:Infinity":3,"s:33:4:33:Infinity":4,"s:36:17:36:70":5,"b:36:18:36:42:36:46:36:48":1,"b:38:2:40:Infinity:undefined:undefined:undefined:undefined":2,"s:38:2:40:Infinity":6,"b:38:6:38:24:38:28:38:44":3,"s:39:4:39:Infinity":7,"b:42:2:44:Infinity:undefined:undefined:undefined:undefined":4,"s:42:2:44:Infinity":8,"s:43:4:43:Infinity":9,"s:47:2:47:Infinity":10,"f:54:22:54:28":1,"b:55:2:55:Infinity:undefined:undefined:undefined:undefined":5,"s:55:2:55:Infinity":11,"s:55:12:55:Infinity":12,"b:56:2:58:Infinity:undefined:undefined:undefined:undefined":6,"s:56:2:58:Infinity":13,"s:57:4:57:Infinity":14,"s:60:2:60:Infinity":15,"s:61:2:110:Infinity":16,"s:62:29:62:53":17,"b:63:4:65:Infinity:undefined:undefined:undefined:undefined":7,"s:63:4:65:Infinity":18,"s:64:6:64:Infinity":19,"s:67:4:73:Infinity":20,"s:76:4:78:Infinity":21,"f:76:21:76:22":2,"s:77:5:77:Infinity":22,"s:80:4:105:Infinity":23,"s:82:21:82:41":24,"s:83:6:88:Infinity":25,"s:84:8:84:Infinity":26,"s:85:7:85:Infinity":27,"s:87:8:87:Infinity":28,"s:91:6:97:Infinity":29,"s:99:5:99:Infinity":30,"s:102:6:102:Infinity":31,"f:102:29:102:30":3,"s:103:6:103:Infinity":32,"s:104:6:104:Infinity":33,"s:107:4:107:Infinity":34,"s:109:4:109:Infinity":35,"f:118:16:118:23":4,"b:119:2:121:Infinity:undefined:undefined:undefined:undefined":8,"s:119:2:121:Infinity":36,"s:120:4:120:Infinity":37,"s:122:2:122:Infinity":38,"f:128:22:128:29":5,"b:129:2:138:Infinity:undefined:undefined:undefined:undefined":9,"s:129:2:138:Infinity":39,"s:130:4:137:Infinity":40,"s:131:6:131:Infinity":41,"s:132:5:132:Infinity":42,"s:134:5:134:Infinity":43,"s:136:6:136:Infinity":44}}} +,"/home/jailuser/git/src/index.js": {"path":"/home/jailuser/git/src/index.js","statementMap":{"0":{"start":{"line":29,"column":18},"end":{"line":29,"column":49}},"1":{"start":{"line":30,"column":17},"end":{"line":30,"column":37}},"2":{"start":{"line":33,"column":15},"end":{"line":33,"column":45}},"3":{"start":{"line":34,"column":17},"end":{"line":34,"column":45}},"4":{"start":{"line":37,"column":0},"end":{"line":37,"column":null}},"5":{"start":{"line":43,"column":13},"end":{"line":43,"column":15}},"6":{"start":{"line":46,"column":15},"end":{"line":54,"column":2}},"7":{"start":{"line":57,"column":0},"end":{"line":57,"column":null}},"8":{"start":{"line":60,"column":22},"end":{"line":60,"column":49}},"9":{"start":{"line":63,"column":24},"end":{"line":63,"column":33}},"10":{"start":{"line":70,"column":20},"end":{"line":70,"column":37}},"11":{"start":{"line":71,"column":2},"end":{"line":71,"column":null}},"12":{"start":{"line":72,"column":2},"end":{"line":72,"column":null}},"13":{"start":{"line":80,"column":2},"end":{"line":80,"column":null}},"14":{"start":{"line":87,"column":2},"end":{"line":102,"column":null}},"15":{"start":{"line":89,"column":4},"end":{"line":91,"column":null}},"16":{"start":{"line":90,"column":5},"end":{"line":90,"column":null}},"17":{"start":{"line":93,"column":31},"end":{"line":93,"column":56}},"18":{"start":{"line":94,"column":22},"end":{"line":97,"column":5}},"19":{"start":{"line":98,"column":3},"end":{"line":98,"column":null}},"20":{"start":{"line":99,"column":3},"end":{"line":99,"column":null}},"21":{"start":{"line":101,"column":3},"end":{"line":101,"column":null}},"22":{"start":{"line":109,"column":2},"end":{"line":120,"column":null}},"23":{"start":{"line":110,"column":4},"end":{"line":112,"column":null}},"24":{"start":{"line":111,"column":6},"end":{"line":111,"column":null}},"25":{"start":{"line":113,"column":22},"end":{"line":113,"column":66}},"26":{"start":{"line":114,"column":4},"end":{"line":117,"column":null}},"27":{"start":{"line":115,"column":5},"end":{"line":115,"column":null}},"28":{"start":{"line":116,"column":5},"end":{"line":116,"column":null}},"29":{"start":{"line":119,"column":3},"end":{"line":119,"column":null}},"30":{"start":{"line":127,"column":22},"end":{"line":127,"column":50}},"31":{"start":{"line":128,"column":22},"end":{"line":128,"column":87}},"32":{"start":{"line":128,"column":66},"end":{"line":128,"column":86}},"33":{"start":{"line":130,"column":2},"end":{"line":143,"column":null}},"34":{"start":{"line":131,"column":20},"end":{"line":131,"column":45}},"35":{"start":{"line":132,"column":4},"end":{"line":142,"column":null}},"36":{"start":{"line":133,"column":22},"end":{"line":133,"column":44}},"37":{"start":{"line":134,"column":6},"end":{"line":139,"column":null}},"38":{"start":{"line":135,"column":8},"end":{"line":135,"column":null}},"39":{"start":{"line":136,"column":7},"end":{"line":136,"column":null}},"40":{"start":{"line":138,"column":7},"end":{"line":138,"column":null}},"41":{"start":{"line":141,"column":5},"end":{"line":141,"column":null}},"42":{"start":{"line":149,"column":0},"end":{"line":159,"column":null}},"43":{"start":{"line":151,"column":2},"end":{"line":158,"column":null}},"44":{"start":{"line":152,"column":21},"end":{"line":152,"column":57}},"45":{"start":{"line":153,"column":20},"end":{"line":153,"column":48}},"46":{"start":{"line":155,"column":4},"end":{"line":155,"column":null}},"47":{"start":{"line":157,"column":3},"end":{"line":157,"column":null}},"48":{"start":{"line":162,"column":0},"end":{"line":219,"column":null}},"49":{"start":{"line":164,"column":2},"end":{"line":174,"column":null}},"50":{"start":{"line":165,"column":20},"end":{"line":165,"column":64}},"51":{"start":{"line":166,"column":4},"end":{"line":172,"column":null}},"52":{"start":{"line":167,"column":6},"end":{"line":171,"column":null}},"53":{"start":{"line":168,"column":8},"end":{"line":168,"column":null}},"54":{"start":{"line":170,"column":7},"end":{"line":170,"column":null}},"55":{"start":{"line":173,"column":4},"end":{"line":173,"column":null}},"56":{"start":{"line":176,"column":2},"end":{"line":176,"column":null}},"57":{"start":{"line":176,"column":41},"end":{"line":176,"column":null}},"58":{"start":{"line":178,"column":34},"end":{"line":178,"column":45}},"59":{"start":{"line":180,"column":2},"end":{"line":218,"column":null}},"60":{"start":{"line":181,"column":3},"end":{"line":181,"column":null}},"61":{"start":{"line":184,"column":4},"end":{"line":191,"column":null}},"62":{"start":{"line":185,"column":6},"end":{"line":188,"column":null}},"63":{"start":{"line":189,"column":5},"end":{"line":189,"column":null}},"64":{"start":{"line":190,"column":6},"end":{"line":190,"column":null}},"65":{"start":{"line":194,"column":20},"end":{"line":194,"column":52}},"66":{"start":{"line":195,"column":4},"end":{"line":201,"column":null}},"67":{"start":{"line":196,"column":6},"end":{"line":199,"column":null}},"68":{"start":{"line":200,"column":6},"end":{"line":200,"column":null}},"69":{"start":{"line":203,"column":4},"end":{"line":203,"column":null}},"70":{"start":{"line":204,"column":3},"end":{"line":204,"column":null}},"71":{"start":{"line":206,"column":3},"end":{"line":206,"column":null}},"72":{"start":{"line":208,"column":25},"end":{"line":211,"column":5}},"73":{"start":{"line":213,"column":4},"end":{"line":217,"column":null}},"74":{"start":{"line":214,"column":6},"end":{"line":214,"column":null}},"75":{"start":{"line":216,"column":6},"end":{"line":216,"column":null}},"76":{"start":{"line":226,"column":1},"end":{"line":226,"column":null}},"77":{"start":{"line":229,"column":27},"end":{"line":229,"column":32}},"78":{"start":{"line":230,"column":2},"end":{"line":243,"column":null}},"79":{"start":{"line":231,"column":3},"end":{"line":231,"column":null}},"80":{"start":{"line":232,"column":22},"end":{"line":232,"column":32}},"81":{"start":{"line":234,"column":4},"end":{"line":236,"column":null}},"82":{"start":{"line":235,"column":6},"end":{"line":235,"column":null}},"83":{"start":{"line":235,"column":37},"end":{"line":235,"column":61}},"84":{"start":{"line":238,"column":4},"end":{"line":242,"column":null}},"85":{"start":{"line":239,"column":5},"end":{"line":239,"column":null}},"86":{"start":{"line":241,"column":5},"end":{"line":241,"column":null}},"87":{"start":{"line":246,"column":1},"end":{"line":246,"column":null}},"88":{"start":{"line":247,"column":2},"end":{"line":247,"column":null}},"89":{"start":{"line":250,"column":1},"end":{"line":250,"column":null}},"90":{"start":{"line":251,"column":2},"end":{"line":255,"column":null}},"91":{"start":{"line":252,"column":4},"end":{"line":252,"column":null}},"92":{"start":{"line":254,"column":3},"end":{"line":254,"column":null}},"93":{"start":{"line":258,"column":1},"end":{"line":258,"column":null}},"94":{"start":{"line":259,"column":2},"end":{"line":259,"column":null}},"95":{"start":{"line":262,"column":1},"end":{"line":262,"column":null}},"96":{"start":{"line":263,"column":2},"end":{"line":263,"column":null}},"97":{"start":{"line":267,"column":0},"end":{"line":267,"column":null}},"98":{"start":{"line":267,"column":28},"end":{"line":267,"column":55}},"99":{"start":{"line":268,"column":0},"end":{"line":268,"column":null}},"100":{"start":{"line":268,"column":27},"end":{"line":268,"column":53}},"101":{"start":{"line":271,"column":0},"end":{"line":277,"column":null}},"102":{"start":{"line":272,"column":1},"end":{"line":276,"column":null}},"103":{"start":{"line":279,"column":0},"end":{"line":285,"column":null}},"104":{"start":{"line":280,"column":1},"end":{"line":284,"column":null}},"105":{"start":{"line":288,"column":14},"end":{"line":288,"column":39}},"106":{"start":{"line":289,"column":0},"end":{"line":292,"column":null}},"107":{"start":{"line":290,"column":1},"end":{"line":290,"column":null}},"108":{"start":{"line":291,"column":2},"end":{"line":291,"column":null}},"109":{"start":{"line":305,"column":2},"end":{"line":310,"column":null}},"110":{"start":{"line":306,"column":4},"end":{"line":306,"column":null}},"111":{"start":{"line":307,"column":3},"end":{"line":307,"column":null}},"112":{"start":{"line":309,"column":3},"end":{"line":309,"column":null}},"113":{"start":{"line":313,"column":2},"end":{"line":313,"column":null}},"114":{"start":{"line":314,"column":1},"end":{"line":314,"column":null}},"115":{"start":{"line":317,"column":2},"end":{"line":317,"column":null}},"116":{"start":{"line":320,"column":1},"end":{"line":320,"column":null}},"117":{"start":{"line":323,"column":2},"end":{"line":323,"column":null}},"118":{"start":{"line":324,"column":2},"end":{"line":324,"column":null}},"119":{"start":{"line":327,"column":0},"end":{"line":330,"column":null}},"120":{"start":{"line":328,"column":1},"end":{"line":328,"column":null}},"121":{"start":{"line":329,"column":2},"end":{"line":329,"column":null}}},"fnMap":{"0":{"name":"registerPendingRequest","decl":{"start":{"line":69,"column":16},"end":{"line":69,"column":38}},"loc":{"start":{"line":69,"column":41},"end":{"line":73,"column":null}},"line":69},"1":{"name":"removePendingRequest","decl":{"start":{"line":79,"column":16},"end":{"line":79,"column":36}},"loc":{"start":{"line":79,"column":48},"end":{"line":81,"column":null}},"line":79},"2":{"name":"saveState","decl":{"start":{"line":86,"column":9},"end":{"line":86,"column":18}},"loc":{"start":{"line":86,"column":21},"end":{"line":103,"column":null}},"line":86},"3":{"name":"loadState","decl":{"start":{"line":108,"column":9},"end":{"line":108,"column":18}},"loc":{"start":{"line":108,"column":21},"end":{"line":121,"column":null}},"line":108},"4":{"name":"loadCommands","decl":{"start":{"line":126,"column":15},"end":{"line":126,"column":27}},"loc":{"start":{"line":126,"column":30},"end":{"line":144,"column":null}},"line":126},"5":{"name":"(anonymous_5)","decl":{"start":{"line":128,"column":56},"end":{"line":128,"column":57}},"loc":{"start":{"line":128,"column":66},"end":{"line":128,"column":86}},"line":128},"6":{"name":"(anonymous_6)","decl":{"start":{"line":149,"column":27},"end":{"line":149,"column":32}},"loc":{"start":{"line":149,"column":39},"end":{"line":159,"column":1}},"line":149},"7":{"name":"(anonymous_7)","decl":{"start":{"line":162,"column":31},"end":{"line":162,"column":36}},"loc":{"start":{"line":162,"column":54},"end":{"line":219,"column":1}},"line":162},"8":{"name":"(anonymous_8)","decl":{"start":{"line":214,"column":53},"end":{"line":214,"column":54}},"loc":{"start":{"line":214,"column":59},"end":{"line":214,"column":61}},"line":214},"9":{"name":"(anonymous_9)","decl":{"start":{"line":216,"column":50},"end":{"line":216,"column":51}},"loc":{"start":{"line":216,"column":56},"end":{"line":216,"column":58}},"line":216},"10":{"name":"gracefulShutdown","decl":{"start":{"line":225,"column":15},"end":{"line":225,"column":31}},"loc":{"start":{"line":225,"column":40},"end":{"line":264,"column":null}},"line":225},"11":{"name":"(anonymous_11)","decl":{"start":{"line":235,"column":24},"end":{"line":235,"column":25}},"loc":{"start":{"line":235,"column":37},"end":{"line":235,"column":61}},"line":235},"12":{"name":"(anonymous_12)","decl":{"start":{"line":267,"column":22},"end":{"line":267,"column":23}},"loc":{"start":{"line":267,"column":28},"end":{"line":267,"column":55}},"line":267},"13":{"name":"(anonymous_13)","decl":{"start":{"line":268,"column":21},"end":{"line":268,"column":22}},"loc":{"start":{"line":268,"column":27},"end":{"line":268,"column":53}},"line":268},"14":{"name":"(anonymous_14)","decl":{"start":{"line":271,"column":19},"end":{"line":271,"column":20}},"loc":{"start":{"line":271,"column":28},"end":{"line":277,"column":1}},"line":271},"15":{"name":"(anonymous_15)","decl":{"start":{"line":279,"column":33},"end":{"line":279,"column":34}},"loc":{"start":{"line":279,"column":42},"end":{"line":285,"column":1}},"line":279},"16":{"name":"startup","decl":{"start":{"line":303,"column":15},"end":{"line":303,"column":22}},"loc":{"start":{"line":303,"column":25},"end":{"line":325,"column":null}},"line":303},"17":{"name":"(anonymous_17)","decl":{"start":{"line":327,"column":16},"end":{"line":327,"column":17}},"loc":{"start":{"line":327,"column":25},"end":{"line":330,"column":1}},"line":327}},"branchMap":{"0":{"loc":{"start":{"line":89,"column":4},"end":{"line":91,"column":null}},"type":"if","locations":[{"start":{"line":89,"column":4},"end":{"line":91,"column":null}},{"start":{},"end":{}}],"line":89},"1":{"loc":{"start":{"line":110,"column":4},"end":{"line":112,"column":null}},"type":"if","locations":[{"start":{"line":110,"column":4},"end":{"line":112,"column":null}},{"start":{},"end":{}}],"line":110},"2":{"loc":{"start":{"line":114,"column":4},"end":{"line":117,"column":null}},"type":"if","locations":[{"start":{"line":114,"column":4},"end":{"line":117,"column":null}},{"start":{},"end":{}}],"line":114},"3":{"loc":{"start":{"line":134,"column":6},"end":{"line":139,"column":null}},"type":"if","locations":[{"start":{"line":134,"column":6},"end":{"line":139,"column":null}},{"start":{"line":137,"column":13},"end":{"line":139,"column":null}}],"line":134},"4":{"loc":{"start":{"line":134,"column":10},"end":{"line":134,"column":41}},"type":"binary-expr","locations":[{"start":{"line":134,"column":10},"end":{"line":134,"column":22}},{"start":{"line":134,"column":26},"end":{"line":134,"column":41}}],"line":134},"5":{"loc":{"start":{"line":153,"column":20},"end":{"line":153,"column":48}},"type":"binary-expr","locations":[{"start":{"line":153,"column":20},"end":{"line":153,"column":40}},{"start":{"line":153,"column":44},"end":{"line":153,"column":48}}],"line":153},"6":{"loc":{"start":{"line":164,"column":2},"end":{"line":174,"column":null}},"type":"if","locations":[{"start":{"line":164,"column":2},"end":{"line":174,"column":null}},{"start":{},"end":{}}],"line":164},"7":{"loc":{"start":{"line":166,"column":4},"end":{"line":172,"column":null}},"type":"if","locations":[{"start":{"line":166,"column":4},"end":{"line":172,"column":null}},{"start":{},"end":{}}],"line":166},"8":{"loc":{"start":{"line":176,"column":2},"end":{"line":176,"column":null}},"type":"if","locations":[{"start":{"line":176,"column":2},"end":{"line":176,"column":null}},{"start":{},"end":{}}],"line":176},"9":{"loc":{"start":{"line":184,"column":4},"end":{"line":191,"column":null}},"type":"if","locations":[{"start":{"line":184,"column":4},"end":{"line":191,"column":null}},{"start":{},"end":{}}],"line":184},"10":{"loc":{"start":{"line":195,"column":4},"end":{"line":201,"column":null}},"type":"if","locations":[{"start":{"line":195,"column":4},"end":{"line":201,"column":null}},{"start":{},"end":{}}],"line":195},"11":{"loc":{"start":{"line":213,"column":4},"end":{"line":217,"column":null}},"type":"if","locations":[{"start":{"line":213,"column":4},"end":{"line":217,"column":null}},{"start":{"line":215,"column":11},"end":{"line":217,"column":null}}],"line":213},"12":{"loc":{"start":{"line":213,"column":8},"end":{"line":213,"column":51}},"type":"binary-expr","locations":[{"start":{"line":213,"column":8},"end":{"line":213,"column":27}},{"start":{"line":213,"column":31},"end":{"line":213,"column":51}}],"line":213},"13":{"loc":{"start":{"line":230,"column":2},"end":{"line":243,"column":null}},"type":"if","locations":[{"start":{"line":230,"column":2},"end":{"line":243,"column":null}},{"start":{},"end":{}}],"line":230},"14":{"loc":{"start":{"line":234,"column":11},"end":{"line":234,"column":80}},"type":"binary-expr","locations":[{"start":{"line":234,"column":11},"end":{"line":234,"column":35}},{"start":{"line":234,"column":39},"end":{"line":234,"column":80}}],"line":234},"15":{"loc":{"start":{"line":238,"column":4},"end":{"line":242,"column":null}},"type":"if","locations":[{"start":{"line":238,"column":4},"end":{"line":242,"column":null}},{"start":{"line":240,"column":11},"end":{"line":242,"column":null}}],"line":238},"16":{"loc":{"start":{"line":281,"column":11},"end":{"line":281,"column":38}},"type":"binary-expr","locations":[{"start":{"line":281,"column":11},"end":{"line":281,"column":23}},{"start":{"line":281,"column":27},"end":{"line":281,"column":38}}],"line":281},"17":{"loc":{"start":{"line":289,"column":0},"end":{"line":292,"column":null}},"type":"if","locations":[{"start":{"line":289,"column":0},"end":{"line":292,"column":null}},{"start":{},"end":{}}],"line":289},"18":{"loc":{"start":{"line":305,"column":2},"end":{"line":310,"column":null}},"type":"if","locations":[{"start":{"line":305,"column":2},"end":{"line":310,"column":null}},{"start":{"line":308,"column":9},"end":{"line":310,"column":null}}],"line":305}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0,"104":0,"105":0,"106":0,"107":0,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":0,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0,"121":0},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0],"18":[0,0]},"meta":{"lastBranch":19,"lastFunction":18,"lastStatement":122,"seen":{"s:29:18:29:49":0,"s:30:17:30:37":1,"s:33:15:33:45":2,"s:34:17:34:45":3,"s:37:0:37:Infinity":4,"s:43:13:43:15":5,"s:46:15:54:2":6,"s:57:0:57:Infinity":7,"s:60:22:60:49":8,"s:63:24:63:33":9,"f:69:16:69:38":0,"s:70:20:70:37":10,"s:71:2:71:Infinity":11,"s:72:2:72:Infinity":12,"f:79:16:79:36":1,"s:80:2:80:Infinity":13,"f:86:9:86:18":2,"s:87:2:102:Infinity":14,"b:89:4:91:Infinity:undefined:undefined:undefined:undefined":0,"s:89:4:91:Infinity":15,"s:90:5:90:Infinity":16,"s:93:31:93:56":17,"s:94:22:97:5":18,"s:98:3:98:Infinity":19,"s:99:3:99:Infinity":20,"s:101:3:101:Infinity":21,"f:108:9:108:18":3,"s:109:2:120:Infinity":22,"b:110:4:112:Infinity:undefined:undefined:undefined:undefined":1,"s:110:4:112:Infinity":23,"s:111:6:111:Infinity":24,"s:113:22:113:66":25,"b:114:4:117:Infinity:undefined:undefined:undefined:undefined":2,"s:114:4:117:Infinity":26,"s:115:5:115:Infinity":27,"s:116:5:116:Infinity":28,"s:119:3:119:Infinity":29,"f:126:15:126:27":4,"s:127:22:127:50":30,"s:128:22:128:87":31,"f:128:56:128:57":5,"s:128:66:128:86":32,"s:130:2:143:Infinity":33,"s:131:20:131:45":34,"s:132:4:142:Infinity":35,"s:133:22:133:44":36,"b:134:6:139:Infinity:137:13:139:Infinity":3,"s:134:6:139:Infinity":37,"b:134:10:134:22:134:26:134:41":4,"s:135:8:135:Infinity":38,"s:136:7:136:Infinity":39,"s:138:7:138:Infinity":40,"s:141:5:141:Infinity":41,"s:149:0:159:Infinity":42,"f:149:27:149:32":6,"s:151:2:158:Infinity":43,"s:152:21:152:57":44,"s:153:20:153:48":45,"b:153:20:153:40:153:44:153:48":5,"s:155:4:155:Infinity":46,"s:157:3:157:Infinity":47,"s:162:0:219:Infinity":48,"f:162:31:162:36":7,"b:164:2:174:Infinity:undefined:undefined:undefined:undefined":6,"s:164:2:174:Infinity":49,"s:165:20:165:64":50,"b:166:4:172:Infinity:undefined:undefined:undefined:undefined":7,"s:166:4:172:Infinity":51,"s:167:6:171:Infinity":52,"s:168:8:168:Infinity":53,"s:170:7:170:Infinity":54,"s:173:4:173:Infinity":55,"b:176:2:176:Infinity:undefined:undefined:undefined:undefined":8,"s:176:2:176:Infinity":56,"s:176:41:176:Infinity":57,"s:178:34:178:45":58,"s:180:2:218:Infinity":59,"s:181:3:181:Infinity":60,"b:184:4:191:Infinity:undefined:undefined:undefined:undefined":9,"s:184:4:191:Infinity":61,"s:185:6:188:Infinity":62,"s:189:5:189:Infinity":63,"s:190:6:190:Infinity":64,"s:194:20:194:52":65,"b:195:4:201:Infinity:undefined:undefined:undefined:undefined":10,"s:195:4:201:Infinity":66,"s:196:6:199:Infinity":67,"s:200:6:200:Infinity":68,"s:203:4:203:Infinity":69,"s:204:3:204:Infinity":70,"s:206:3:206:Infinity":71,"s:208:25:211:5":72,"b:213:4:217:Infinity:215:11:217:Infinity":11,"s:213:4:217:Infinity":73,"b:213:8:213:27:213:31:213:51":12,"s:214:6:214:Infinity":74,"f:214:53:214:54":8,"s:216:6:216:Infinity":75,"f:216:50:216:51":9,"f:225:15:225:31":10,"s:226:1:226:Infinity":76,"s:229:27:229:32":77,"b:230:2:243:Infinity:undefined:undefined:undefined:undefined":13,"s:230:2:243:Infinity":78,"s:231:3:231:Infinity":79,"s:232:22:232:32":80,"s:234:4:236:Infinity":81,"b:234:11:234:35:234:39:234:80":14,"s:235:6:235:Infinity":82,"f:235:24:235:25":11,"s:235:37:235:61":83,"b:238:4:242:Infinity:240:11:242:Infinity":15,"s:238:4:242:Infinity":84,"s:239:5:239:Infinity":85,"s:241:5:241:Infinity":86,"s:246:1:246:Infinity":87,"s:247:2:247:Infinity":88,"s:250:1:250:Infinity":89,"s:251:2:255:Infinity":90,"s:252:4:252:Infinity":91,"s:254:3:254:Infinity":92,"s:258:1:258:Infinity":93,"s:259:2:259:Infinity":94,"s:262:1:262:Infinity":95,"s:263:2:263:Infinity":96,"s:267:0:267:Infinity":97,"f:267:22:267:23":12,"s:267:28:267:55":98,"s:268:0:268:Infinity":99,"f:268:21:268:22":13,"s:268:27:268:53":100,"s:271:0:277:Infinity":101,"f:271:19:271:20":14,"s:272:1:276:Infinity":102,"s:279:0:285:Infinity":103,"f:279:33:279:34":15,"s:280:1:284:Infinity":104,"b:281:11:281:23:281:27:281:38":16,"s:288:14:288:39":105,"b:289:0:292:Infinity:undefined:undefined:undefined:undefined":17,"s:289:0:292:Infinity":106,"s:290:1:290:Infinity":107,"s:291:2:291:Infinity":108,"f:303:15:303:22":16,"b:305:2:310:Infinity:308:9:310:Infinity":18,"s:305:2:310:Infinity":109,"s:306:4:306:Infinity":110,"s:307:3:307:Infinity":111,"s:309:3:309:Infinity":112,"s:313:2:313:Infinity":113,"s:314:1:314:Infinity":114,"s:317:2:317:Infinity":115,"s:320:1:320:Infinity":116,"s:323:2:323:Infinity":117,"s:324:2:324:Infinity":118,"s:327:0:330:Infinity":119,"f:327:16:327:17":17,"s:328:1:328:Infinity":120,"s:329:2:329:Infinity":121}}} +,"/home/jailuser/git/src/logger.js": {"path":"/home/jailuser/git/src/logger.js","statementMap":{"0":{"start":{"line":17,"column":17},"end":{"line":17,"column":57}},"1":{"start":{"line":18,"column":18},"end":{"line":18,"column":55}},"2":{"start":{"line":19,"column":15},"end":{"line":19,"column":45}},"3":{"start":{"line":22,"column":15},"end":{"line":22,"column":21}},"4":{"start":{"line":23,"column":24},"end":{"line":23,"column":29}},"5":{"start":{"line":25,"column":0},"end":{"line":34,"column":null}},"6":{"start":{"line":26,"column":2},"end":{"line":30,"column":null}},"7":{"start":{"line":27,"column":19},"end":{"line":27,"column":64}},"8":{"start":{"line":28,"column":4},"end":{"line":28,"column":null}},"9":{"start":{"line":29,"column":4},"end":{"line":29,"column":null}},"10":{"start":{"line":33,"column":2},"end":{"line":33,"column":null}},"11":{"start":{"line":37,"column":0},"end":{"line":46,"column":null}},"12":{"start":{"line":38,"column":2},"end":{"line":45,"column":null}},"13":{"start":{"line":39,"column":4},"end":{"line":41,"column":null}},"14":{"start":{"line":40,"column":5},"end":{"line":40,"column":null}},"15":{"start":{"line":44,"column":4},"end":{"line":44,"column":null}},"16":{"start":{"line":51,"column":25},"end":{"line":59,"column":1}},"17":{"start":{"line":65,"column":2},"end":{"line":67,"column":null}},"18":{"start":{"line":66,"column":4},"end":{"line":66,"column":null}},"19":{"start":{"line":69,"column":2},"end":{"line":71,"column":null}},"20":{"start":{"line":70,"column":4},"end":{"line":70,"column":null}},"21":{"start":{"line":73,"column":2},"end":{"line":75,"column":null}},"22":{"start":{"line":74,"column":4},"end":{"line":74,"column":null}},"23":{"start":{"line":74,"column":29},"end":{"line":74,"column":54}},"24":{"start":{"line":77,"column":19},"end":{"line":77,"column":21}},"25":{"start":{"line":78,"column":2},"end":{"line":89,"column":null}},"26":{"start":{"line":80,"column":24},"end":{"line":80,"column":99}},"27":{"start":{"line":80,"column":57},"end":{"line":80,"column":98}},"28":{"start":{"line":82,"column":4},"end":{"line":88,"column":null}},"29":{"start":{"line":83,"column":6},"end":{"line":83,"column":null}},"30":{"start":{"line":84,"column":11},"end":{"line":88,"column":null}},"31":{"start":{"line":85,"column":6},"end":{"line":85,"column":null}},"32":{"start":{"line":87,"column":6},"end":{"line":87,"column":null}},"33":{"start":{"line":91,"column":2},"end":{"line":91,"column":null}},"34":{"start":{"line":97,"column":28},"end":{"line":119,"column":4}},"35":{"start":{"line":99,"column":19},"end":{"line":99,"column":61}},"36":{"start":{"line":102,"column":2},"end":{"line":116,"column":null}},"37":{"start":{"line":103,"column":4},"end":{"line":115,"column":null}},"38":{"start":{"line":105,"column":26},"end":{"line":107,"column":7}},"39":{"start":{"line":106,"column":19},"end":{"line":106,"column":60}},"40":{"start":{"line":109,"column":6},"end":{"line":114,"column":null}},"41":{"start":{"line":110,"column":8},"end":{"line":110,"column":null}},"42":{"start":{"line":111,"column":13},"end":{"line":114,"column":null}},"43":{"start":{"line":113,"column":8},"end":{"line":113,"column":null}},"44":{"start":{"line":118,"column":2},"end":{"line":118,"column":null}},"45":{"start":{"line":124,"column":18},"end":{"line":129,"column":1}},"46":{"start":{"line":134,"column":30},"end":{"line":137,"column":4}},"47":{"start":{"line":135,"column":2},"end":{"line":135,"column":null}},"48":{"start":{"line":136,"column":2},"end":{"line":136,"column":null}},"49":{"start":{"line":142,"column":22},"end":{"line":150,"column":1}},"50":{"start":{"line":145,"column":19},"end":{"line":145,"column":51}},"51":{"start":{"line":146,"column":20},"end":{"line":146,"column":82}},"52":{"start":{"line":148,"column":4},"end":{"line":148,"column":null}},"53":{"start":{"line":155,"column":19},"end":{"line":165,"column":1}},"54":{"start":{"line":168,"column":0},"end":{"line":198,"column":null}},"55":{"start":{"line":169,"column":2},"end":{"line":181,"column":null}},"56":{"start":{"line":184,"column":2},"end":{"line":197,"column":null}},"57":{"start":{"line":200,"column":15},"end":{"line":204,"column":2}},"58":{"start":{"line":210,"column":2},"end":{"line":210,"column":null}},"59":{"start":{"line":217,"column":2},"end":{"line":217,"column":null}},"60":{"start":{"line":224,"column":2},"end":{"line":224,"column":null}},"61":{"start":{"line":231,"column":2},"end":{"line":231,"column":null}}},"fnMap":{"0":{"name":"filterSensitiveData","decl":{"start":{"line":64,"column":9},"end":{"line":64,"column":28}},"loc":{"start":{"line":64,"column":34},"end":{"line":92,"column":null}},"line":64},"1":{"name":"(anonymous_1)","decl":{"start":{"line":74,"column":19},"end":{"line":74,"column":20}},"loc":{"start":{"line":74,"column":29},"end":{"line":74,"column":54}},"line":74},"2":{"name":"(anonymous_2)","decl":{"start":{"line":80,"column":46},"end":{"line":80,"column":47}},"loc":{"start":{"line":80,"column":57},"end":{"line":80,"column":98}},"line":80},"3":{"name":"(anonymous_3)","decl":{"start":{"line":97,"column":43},"end":{"line":97,"column":44}},"loc":{"start":{"line":97,"column":53},"end":{"line":119,"column":1}},"line":97},"4":{"name":"(anonymous_4)","decl":{"start":{"line":106,"column":8},"end":{"line":106,"column":9}},"loc":{"start":{"line":106,"column":19},"end":{"line":106,"column":60}},"line":106},"5":{"name":"(anonymous_5)","decl":{"start":{"line":134,"column":45},"end":{"line":134,"column":46}},"loc":{"start":{"line":134,"column":55},"end":{"line":137,"column":1}},"line":134},"6":{"name":"(anonymous_6)","decl":{"start":{"line":143,"column":2},"end":{"line":143,"column":3}},"loc":{"start":{"line":143,"column":61},"end":{"line":149,"column":3}},"line":143},"7":{"name":"debug","decl":{"start":{"line":209,"column":16},"end":{"line":209,"column":21}},"loc":{"start":{"line":209,"column":42},"end":{"line":211,"column":null}},"line":209},"8":{"name":"info","decl":{"start":{"line":216,"column":16},"end":{"line":216,"column":20}},"loc":{"start":{"line":216,"column":41},"end":{"line":218,"column":null}},"line":216},"9":{"name":"warn","decl":{"start":{"line":223,"column":16},"end":{"line":223,"column":20}},"loc":{"start":{"line":223,"column":41},"end":{"line":225,"column":null}},"line":223},"10":{"name":"error","decl":{"start":{"line":230,"column":16},"end":{"line":230,"column":21}},"loc":{"start":{"line":230,"column":42},"end":{"line":232,"column":null}},"line":230}},"branchMap":{"0":{"loc":{"start":{"line":26,"column":2},"end":{"line":30,"column":null}},"type":"if","locations":[{"start":{"line":26,"column":2},"end":{"line":30,"column":null}},{"start":{},"end":{}}],"line":26},"1":{"loc":{"start":{"line":28,"column":15},"end":{"line":28,"column":71}},"type":"binary-expr","locations":[{"start":{"line":28,"column":15},"end":{"line":28,"column":36}},{"start":{"line":28,"column":40},"end":{"line":28,"column":61}},{"start":{"line":28,"column":65},"end":{"line":28,"column":71}}],"line":28},"2":{"loc":{"start":{"line":29,"column":24},"end":{"line":29,"column":59}},"type":"binary-expr","locations":[{"start":{"line":29,"column":24},"end":{"line":29,"column":50}},{"start":{"line":29,"column":54},"end":{"line":29,"column":59}}],"line":29},"3":{"loc":{"start":{"line":33,"column":13},"end":{"line":33,"column":44}},"type":"binary-expr","locations":[{"start":{"line":33,"column":13},"end":{"line":33,"column":34}},{"start":{"line":33,"column":38},"end":{"line":33,"column":44}}],"line":33},"4":{"loc":{"start":{"line":37,"column":0},"end":{"line":46,"column":null}},"type":"if","locations":[{"start":{"line":37,"column":0},"end":{"line":46,"column":null}},{"start":{},"end":{}}],"line":37},"5":{"loc":{"start":{"line":39,"column":4},"end":{"line":41,"column":null}},"type":"if","locations":[{"start":{"line":39,"column":4},"end":{"line":41,"column":null}},{"start":{},"end":{}}],"line":39},"6":{"loc":{"start":{"line":65,"column":2},"end":{"line":67,"column":null}},"type":"if","locations":[{"start":{"line":65,"column":2},"end":{"line":67,"column":null}},{"start":{},"end":{}}],"line":65},"7":{"loc":{"start":{"line":65,"column":6},"end":{"line":65,"column":39}},"type":"binary-expr","locations":[{"start":{"line":65,"column":6},"end":{"line":65,"column":18}},{"start":{"line":65,"column":22},"end":{"line":65,"column":39}}],"line":65},"8":{"loc":{"start":{"line":69,"column":2},"end":{"line":71,"column":null}},"type":"if","locations":[{"start":{"line":69,"column":2},"end":{"line":71,"column":null}},{"start":{},"end":{}}],"line":69},"9":{"loc":{"start":{"line":73,"column":2},"end":{"line":75,"column":null}},"type":"if","locations":[{"start":{"line":73,"column":2},"end":{"line":75,"column":null}},{"start":{},"end":{}}],"line":73},"10":{"loc":{"start":{"line":82,"column":4},"end":{"line":88,"column":null}},"type":"if","locations":[{"start":{"line":82,"column":4},"end":{"line":88,"column":null}},{"start":{"line":84,"column":11},"end":{"line":88,"column":null}}],"line":82},"11":{"loc":{"start":{"line":84,"column":11},"end":{"line":88,"column":null}},"type":"if","locations":[{"start":{"line":84,"column":11},"end":{"line":88,"column":null}},{"start":{"line":86,"column":11},"end":{"line":88,"column":null}}],"line":84},"12":{"loc":{"start":{"line":84,"column":15},"end":{"line":84,"column":58}},"type":"binary-expr","locations":[{"start":{"line":84,"column":15},"end":{"line":84,"column":40}},{"start":{"line":84,"column":44},"end":{"line":84,"column":58}}],"line":84},"13":{"loc":{"start":{"line":103,"column":4},"end":{"line":115,"column":null}},"type":"if","locations":[{"start":{"line":103,"column":4},"end":{"line":115,"column":null}},{"start":{},"end":{}}],"line":103},"14":{"loc":{"start":{"line":103,"column":8},"end":{"line":103,"column":59}},"type":"binary-expr","locations":[{"start":{"line":103,"column":8},"end":{"line":103,"column":32}},{"start":{"line":103,"column":36},"end":{"line":103,"column":59}}],"line":103},"15":{"loc":{"start":{"line":109,"column":6},"end":{"line":114,"column":null}},"type":"if","locations":[{"start":{"line":109,"column":6},"end":{"line":114,"column":null}},{"start":{"line":111,"column":13},"end":{"line":114,"column":null}}],"line":109},"16":{"loc":{"start":{"line":111,"column":13},"end":{"line":114,"column":null}},"type":"if","locations":[{"start":{"line":111,"column":13},"end":{"line":114,"column":null}},{"start":{},"end":{}}],"line":111},"17":{"loc":{"start":{"line":111,"column":17},"end":{"line":111,"column":68}},"type":"binary-expr","locations":[{"start":{"line":111,"column":17},"end":{"line":111,"column":46}},{"start":{"line":111,"column":50},"end":{"line":111,"column":68}}],"line":111},"18":{"loc":{"start":{"line":145,"column":19},"end":{"line":145,"column":51}},"type":"binary-expr","locations":[{"start":{"line":145,"column":19},"end":{"line":145,"column":43}},{"start":{"line":145,"column":47},"end":{"line":145,"column":51}}],"line":145},"19":{"loc":{"start":{"line":146,"column":20},"end":{"line":146,"column":82}},"type":"cond-expr","locations":[{"start":{"line":146,"column":51},"end":{"line":146,"column":77}},{"start":{"line":146,"column":80},"end":{"line":146,"column":82}}],"line":146},"20":{"loc":{"start":{"line":168,"column":0},"end":{"line":198,"column":null}},"type":"if","locations":[{"start":{"line":168,"column":0},"end":{"line":198,"column":null}},{"start":{},"end":{}}],"line":168},"21":{"loc":{"start":{"line":209,"column":31},"end":{"line":209,"column":40}},"type":"default-arg","locations":[{"start":{"line":209,"column":38},"end":{"line":209,"column":40}}],"line":209},"22":{"loc":{"start":{"line":216,"column":30},"end":{"line":216,"column":39}},"type":"default-arg","locations":[{"start":{"line":216,"column":37},"end":{"line":216,"column":39}}],"line":216},"23":{"loc":{"start":{"line":223,"column":30},"end":{"line":223,"column":39}},"type":"default-arg","locations":[{"start":{"line":223,"column":37},"end":{"line":223,"column":39}}],"line":223},"24":{"loc":{"start":{"line":230,"column":31},"end":{"line":230,"column":40}},"type":"default-arg","locations":[{"start":{"line":230,"column":38},"end":{"line":230,"column":40}}],"line":230}},"s":{"0":5,"1":5,"2":5,"3":5,"4":5,"5":5,"6":5,"7":5,"8":5,"9":5,"10":0,"11":5,"12":5,"13":5,"14":0,"15":0,"16":5,"17":5431,"18":0,"19":5431,"20":0,"21":5431,"22":2,"23":4,"24":5429,"25":5429,"26":10856,"27":75986,"28":10856,"29":2,"30":10854,"31":5422,"32":5432,"33":6,"34":5,"35":148,"36":148,"37":514,"38":217,"39":1487,"40":217,"41":8,"42":209,"43":5,"44":147,"45":5,"46":5,"47":64,"48":64,"49":5,"50":64,"51":64,"52":64,"53":5,"54":5,"55":5,"56":5,"57":5,"58":1,"59":45,"60":1,"61":19},"f":{"0":5431,"1":4,"2":75986,"3":148,"4":1487,"5":64,"6":64,"7":1,"8":45,"9":1,"10":19},"b":{"0":[5,0],"1":[5,5,0],"2":[5,0],"3":[0,0],"4":[5,0],"5":[0,5],"6":[0,5431],"7":[5431,5431],"8":[0,5431],"9":[2,5429],"10":[2,10854],"11":[5422,5432],"12":[10854,5422],"13":[217,297],"14":[514,514],"15":[8,209],"16":[5,204],"17":[209,5],"18":[64,0],"19":[55,9],"20":[5,0],"21":[1],"22":[45],"23":[1],"24":[19]},"meta":{"lastBranch":25,"lastFunction":11,"lastStatement":62,"seen":{"s:17:17:17:57":0,"s:18:18:18:55":1,"s:19:15:19:45":2,"s:22:15:22:21":3,"s:23:24:23:29":4,"s:25:0:34:Infinity":5,"b:26:2:30:Infinity:undefined:undefined:undefined:undefined":0,"s:26:2:30:Infinity":6,"s:27:19:27:64":7,"s:28:4:28:Infinity":8,"b:28:15:28:36:28:40:28:61:28:65:28:71":1,"s:29:4:29:Infinity":9,"b:29:24:29:50:29:54:29:59":2,"s:33:2:33:Infinity":10,"b:33:13:33:34:33:38:33:44":3,"b:37:0:46:Infinity:undefined:undefined:undefined:undefined":4,"s:37:0:46:Infinity":11,"s:38:2:45:Infinity":12,"b:39:4:41:Infinity:undefined:undefined:undefined:undefined":5,"s:39:4:41:Infinity":13,"s:40:5:40:Infinity":14,"s:44:4:44:Infinity":15,"s:51:25:59:1":16,"f:64:9:64:28":0,"b:65:2:67:Infinity:undefined:undefined:undefined:undefined":6,"s:65:2:67:Infinity":17,"b:65:6:65:18:65:22:65:39":7,"s:66:4:66:Infinity":18,"b:69:2:71:Infinity:undefined:undefined:undefined:undefined":8,"s:69:2:71:Infinity":19,"s:70:4:70:Infinity":20,"b:73:2:75:Infinity:undefined:undefined:undefined:undefined":9,"s:73:2:75:Infinity":21,"s:74:4:74:Infinity":22,"f:74:19:74:20":1,"s:74:29:74:54":23,"s:77:19:77:21":24,"s:78:2:89:Infinity":25,"s:80:24:80:99":26,"f:80:46:80:47":2,"s:80:57:80:98":27,"b:82:4:88:Infinity:84:11:88:Infinity":10,"s:82:4:88:Infinity":28,"s:83:6:83:Infinity":29,"b:84:11:88:Infinity:86:11:88:Infinity":11,"s:84:11:88:Infinity":30,"b:84:15:84:40:84:44:84:58":12,"s:85:6:85:Infinity":31,"s:87:6:87:Infinity":32,"s:91:2:91:Infinity":33,"s:97:28:119:4":34,"f:97:43:97:44":3,"s:99:19:99:61":35,"s:102:2:116:Infinity":36,"b:103:4:115:Infinity:undefined:undefined:undefined:undefined":13,"s:103:4:115:Infinity":37,"b:103:8:103:32:103:36:103:59":14,"s:105:26:107:7":38,"f:106:8:106:9":4,"s:106:19:106:60":39,"b:109:6:114:Infinity:111:13:114:Infinity":15,"s:109:6:114:Infinity":40,"s:110:8:110:Infinity":41,"b:111:13:114:Infinity:undefined:undefined:undefined:undefined":16,"s:111:13:114:Infinity":42,"b:111:17:111:46:111:50:111:68":17,"s:113:8:113:Infinity":43,"s:118:2:118:Infinity":44,"s:124:18:129:1":45,"s:134:30:137:4":46,"f:134:45:134:46":5,"s:135:2:135:Infinity":47,"s:136:2:136:Infinity":48,"s:142:22:150:1":49,"f:143:2:143:3":6,"s:145:19:145:51":50,"b:145:19:145:43:145:47:145:51":18,"s:146:20:146:82":51,"b:146:51:146:77:146:80:146:82":19,"s:148:4:148:Infinity":52,"s:155:19:165:1":53,"b:168:0:198:Infinity:undefined:undefined:undefined:undefined":20,"s:168:0:198:Infinity":54,"s:169:2:181:Infinity":55,"s:184:2:197:Infinity":56,"s:200:15:204:2":57,"f:209:16:209:21":7,"b:209:38:209:40":21,"s:210:2:210:Infinity":58,"f:216:16:216:20":8,"b:216:37:216:39":22,"s:217:2:217:Infinity":59,"f:223:16:223:20":9,"b:223:37:223:39":23,"s:224:2:224:Infinity":60,"f:230:16:230:21":10,"b:230:38:230:40":24,"s:231:2:231:Infinity":61}}} +,"/home/jailuser/git/src/commands/config.js": {"path":"/home/jailuser/git/src/commands/config.js","statementMap":{"0":{"start":{"line":15,"column":2},"end":{"line":15,"column":null}},"1":{"start":{"line":18,"column":20},"end":{"line":64,"column":3}},"2":{"start":{"line":22,"column":4},"end":{"line":31,"column":7}},"3":{"start":{"line":26,"column":8},"end":{"line":30,"column":32}},"4":{"start":{"line":34,"column":4},"end":{"line":51,"column":7}},"5":{"start":{"line":38,"column":8},"end":{"line":42,"column":32}},"6":{"start":{"line":45,"column":8},"end":{"line":50,"column":28}},"7":{"start":{"line":54,"column":4},"end":{"line":63,"column":7}},"8":{"start":{"line":58,"column":8},"end":{"line":62,"column":32}},"9":{"start":{"line":66,"column":25},"end":{"line":66,"column":29}},"10":{"start":{"line":77,"column":2},"end":{"line":92,"column":null}},"11":{"start":{"line":79,"column":4},"end":{"line":82,"column":null}},"12":{"start":{"line":80,"column":6},"end":{"line":80,"column":null}},"13":{"start":{"line":81,"column":6},"end":{"line":81,"column":null}},"14":{"start":{"line":83,"column":4},"end":{"line":90,"column":null}},"15":{"start":{"line":84,"column":19},"end":{"line":84,"column":64}},"16":{"start":{"line":85,"column":6},"end":{"line":89,"column":null}},"17":{"start":{"line":86,"column":8},"end":{"line":86,"column":null}},"18":{"start":{"line":88,"column":8},"end":{"line":88,"column":null}},"19":{"start":{"line":91,"column":4},"end":{"line":91,"column":null}},"20":{"start":{"line":94,"column":2},"end":{"line":96,"column":null}},"21":{"start":{"line":95,"column":4},"end":{"line":95,"column":null}},"22":{"start":{"line":99,"column":2},"end":{"line":102,"column":null}},"23":{"start":{"line":100,"column":4},"end":{"line":100,"column":null}},"24":{"start":{"line":101,"column":4},"end":{"line":101,"column":null}},"25":{"start":{"line":104,"column":2},"end":{"line":111,"column":null}},"26":{"start":{"line":105,"column":17},"end":{"line":105,"column":50}},"27":{"start":{"line":106,"column":4},"end":{"line":110,"column":null}},"28":{"start":{"line":107,"column":6},"end":{"line":107,"column":null}},"29":{"start":{"line":109,"column":6},"end":{"line":109,"column":null}},"30":{"start":{"line":113,"column":2},"end":{"line":113,"column":null}},"31":{"start":{"line":121,"column":24},"end":{"line":121,"column":60}},"32":{"start":{"line":122,"column":23},"end":{"line":122,"column":63}},"33":{"start":{"line":123,"column":16},"end":{"line":123,"column":28}},"34":{"start":{"line":126,"column":2},"end":{"line":149,"column":null}},"35":{"start":{"line":128,"column":4},"end":{"line":131,"column":null}},"36":{"start":{"line":129,"column":21},"end":{"line":129,"column":59}},"37":{"start":{"line":131,"column":19},"end":{"line":131,"column":40}},"38":{"start":{"line":134,"column":18},"end":{"line":134,"column":44}},"39":{"start":{"line":135,"column":4},"end":{"line":148,"column":null}},"40":{"start":{"line":136,"column":21},"end":{"line":136,"column":59}},"41":{"start":{"line":138,"column":23},"end":{"line":138,"column":38}},"42":{"start":{"line":139,"column":23},"end":{"line":139,"column":38}},"43":{"start":{"line":140,"column":33},"end":{"line":140,"column":64}},"44":{"start":{"line":141,"column":33},"end":{"line":141,"column":64}},"45":{"start":{"line":142,"column":8},"end":{"line":144,"column":null}},"46":{"start":{"line":143,"column":10},"end":{"line":143,"column":null}},"47":{"start":{"line":145,"column":8},"end":{"line":145,"column":null}},"48":{"start":{"line":148,"column":19},"end":{"line":148,"column":40}},"49":{"start":{"line":151,"column":2},"end":{"line":151,"column":null}},"50":{"start":{"line":159,"column":21},"end":{"line":159,"column":56}},"51":{"start":{"line":161,"column":2},"end":{"line":177,"column":null}},"52":{"start":{"line":163,"column":6},"end":{"line":163,"column":null}},"53":{"start":{"line":164,"column":6},"end":{"line":164,"column":null}},"54":{"start":{"line":166,"column":6},"end":{"line":166,"column":null}},"55":{"start":{"line":167,"column":6},"end":{"line":167,"column":null}},"56":{"start":{"line":169,"column":6},"end":{"line":169,"column":null}},"57":{"start":{"line":170,"column":6},"end":{"line":170,"column":null}},"58":{"start":{"line":172,"column":6},"end":{"line":175,"column":null}},"59":{"start":{"line":176,"column":6},"end":{"line":176,"column":null}},"60":{"start":{"line":181,"column":25},"end":{"line":181,"column":29}},"61":{"start":{"line":187,"column":2},"end":{"line":263,"column":null}},"62":{"start":{"line":188,"column":18},"end":{"line":188,"column":30}},"63":{"start":{"line":189,"column":20},"end":{"line":189,"column":60}},"64":{"start":{"line":191,"column":18},"end":{"line":197,"column":21}},"65":{"start":{"line":199,"column":4},"end":{"line":255,"column":null}},"66":{"start":{"line":200,"column":26},"end":{"line":200,"column":41}},"67":{"start":{"line":201,"column":6},"end":{"line":207,"column":null}},"68":{"start":{"line":202,"column":28},"end":{"line":202,"column":53}},"69":{"start":{"line":203,"column":8},"end":{"line":206,"column":null}},"70":{"start":{"line":209,"column":6},"end":{"line":209,"column":null}},"71":{"start":{"line":210,"column":26},"end":{"line":210,"column":62}},"72":{"start":{"line":211,"column":6},"end":{"line":217,"column":null}},"73":{"start":{"line":219,"column":6},"end":{"line":219,"column":null}},"74":{"start":{"line":222,"column":24},"end":{"line":222,"column":95}},"75":{"start":{"line":223,"column":22},"end":{"line":223,"column":27}},"76":{"start":{"line":225,"column":6},"end":{"line":248,"column":null}},"77":{"start":{"line":226,"column":24},"end":{"line":226,"column":54}},"78":{"start":{"line":227,"column":27},"end":{"line":227,"column":115}},"79":{"start":{"line":228,"column":26},"end":{"line":228,"column":43}},"80":{"start":{"line":229,"column":28},"end":{"line":229,"column":64}},"81":{"start":{"line":231,"column":8},"end":{"line":240,"column":null}},"82":{"start":{"line":233,"column":10},"end":{"line":237,"column":null}},"83":{"start":{"line":238,"column":10},"end":{"line":238,"column":null}},"84":{"start":{"line":239,"column":10},"end":{"line":239,"column":null}},"85":{"start":{"line":242,"column":8},"end":{"line":242,"column":null}},"86":{"start":{"line":243,"column":8},"end":{"line":247,"column":null}},"87":{"start":{"line":250,"column":6},"end":{"line":254,"column":null}},"88":{"start":{"line":251,"column":8},"end":{"line":253,"column":null}},"89":{"start":{"line":257,"column":4},"end":{"line":257,"column":null}},"90":{"start":{"line":259,"column":4},"end":{"line":262,"column":null}},"91":{"start":{"line":270,"column":15},"end":{"line":270,"column":52}},"92":{"start":{"line":271,"column":16},"end":{"line":271,"column":54}},"93":{"start":{"line":274,"column":18},"end":{"line":274,"column":36}},"94":{"start":{"line":275,"column":24},"end":{"line":275,"column":48}},"95":{"start":{"line":276,"column":2},"end":{"line":282,"column":null}},"96":{"start":{"line":277,"column":24},"end":{"line":277,"column":49}},"97":{"start":{"line":278,"column":4},"end":{"line":281,"column":null}},"98":{"start":{"line":284,"column":2},"end":{"line":317,"column":null}},"99":{"start":{"line":285,"column":4},"end":{"line":285,"column":null}},"100":{"start":{"line":287,"column":27},"end":{"line":287,"column":60}},"101":{"start":{"line":290,"column":22},"end":{"line":293,"column":51}},"102":{"start":{"line":293,"column":26},"end":{"line":293,"column":34}},"103":{"start":{"line":295,"column":25},"end":{"line":295,"column":68}},"104":{"start":{"line":297,"column":6},"end":{"line":297,"column":84}},"105":{"start":{"line":299,"column":18},"end":{"line":307,"column":21}},"106":{"start":{"line":309,"column":4},"end":{"line":309,"column":null}},"107":{"start":{"line":311,"column":20},"end":{"line":311,"column":60}},"108":{"start":{"line":312,"column":4},"end":{"line":316,"column":null}},"109":{"start":{"line":313,"column":6},"end":{"line":313,"column":null}},"110":{"start":{"line":315,"column":6},"end":{"line":315,"column":null}},"111":{"start":{"line":324,"column":18},"end":{"line":324,"column":58}},"112":{"start":{"line":326,"column":2},"end":{"line":350,"column":null}},"113":{"start":{"line":327,"column":4},"end":{"line":327,"column":null}},"114":{"start":{"line":329,"column":4},"end":{"line":329,"column":null}},"115":{"start":{"line":331,"column":18},"end":{"line":340,"column":21}},"116":{"start":{"line":342,"column":4},"end":{"line":342,"column":null}},"117":{"start":{"line":344,"column":20},"end":{"line":344,"column":62}},"118":{"start":{"line":345,"column":4},"end":{"line":349,"column":null}},"119":{"start":{"line":346,"column":6},"end":{"line":346,"column":null}},"120":{"start":{"line":348,"column":6},"end":{"line":348,"column":null}}},"fnMap":{"0":{"name":"escapeInlineCode","decl":{"start":{"line":14,"column":9},"end":{"line":14,"column":25}},"loc":{"start":{"line":14,"column":31},"end":{"line":16,"column":null}},"line":14},"1":{"name":"(anonymous_1)","decl":{"start":{"line":21,"column":17},"end":{"line":21,"column":18}},"loc":{"start":{"line":22,"column":4},"end":{"line":31,"column":7}},"line":22},"2":{"name":"(anonymous_2)","decl":{"start":{"line":25,"column":23},"end":{"line":25,"column":24}},"loc":{"start":{"line":26,"column":8},"end":{"line":30,"column":32}},"line":26},"3":{"name":"(anonymous_3)","decl":{"start":{"line":33,"column":17},"end":{"line":33,"column":18}},"loc":{"start":{"line":34,"column":4},"end":{"line":51,"column":7}},"line":34},"4":{"name":"(anonymous_4)","decl":{"start":{"line":37,"column":23},"end":{"line":37,"column":24}},"loc":{"start":{"line":38,"column":8},"end":{"line":42,"column":32}},"line":38},"5":{"name":"(anonymous_5)","decl":{"start":{"line":44,"column":23},"end":{"line":44,"column":24}},"loc":{"start":{"line":45,"column":8},"end":{"line":50,"column":28}},"line":45},"6":{"name":"(anonymous_6)","decl":{"start":{"line":53,"column":17},"end":{"line":53,"column":18}},"loc":{"start":{"line":54,"column":4},"end":{"line":63,"column":7}},"line":54},"7":{"name":"(anonymous_7)","decl":{"start":{"line":57,"column":23},"end":{"line":57,"column":24}},"loc":{"start":{"line":58,"column":8},"end":{"line":62,"column":32}},"line":58},"8":{"name":"collectConfigPaths","decl":{"start":{"line":76,"column":9},"end":{"line":76,"column":27}},"loc":{"start":{"line":76,"column":61},"end":{"line":114,"column":null}},"line":76},"9":{"name":"(anonymous_9)","decl":{"start":{"line":83,"column":19},"end":{"line":83,"column":20}},"loc":{"start":{"line":83,"column":37},"end":{"line":90,"column":5}},"line":83},"10":{"name":"autocomplete","decl":{"start":{"line":120,"column":22},"end":{"line":120,"column":34}},"loc":{"start":{"line":120,"column":48},"end":{"line":152,"column":null}},"line":120},"11":{"name":"(anonymous_11)","decl":{"start":{"line":129,"column":14},"end":{"line":129,"column":15}},"loc":{"start":{"line":129,"column":21},"end":{"line":129,"column":59}},"line":129},"12":{"name":"(anonymous_12)","decl":{"start":{"line":131,"column":11},"end":{"line":131,"column":12}},"loc":{"start":{"line":131,"column":19},"end":{"line":131,"column":40}},"line":131},"13":{"name":"(anonymous_13)","decl":{"start":{"line":136,"column":14},"end":{"line":136,"column":15}},"loc":{"start":{"line":136,"column":21},"end":{"line":136,"column":59}},"line":136},"14":{"name":"(anonymous_14)","decl":{"start":{"line":137,"column":12},"end":{"line":137,"column":13}},"loc":{"start":{"line":137,"column":22},"end":{"line":146,"column":7}},"line":137},"15":{"name":"(anonymous_15)","decl":{"start":{"line":148,"column":11},"end":{"line":148,"column":12}},"loc":{"start":{"line":148,"column":19},"end":{"line":148,"column":40}},"line":148},"16":{"name":"execute","decl":{"start":{"line":158,"column":22},"end":{"line":158,"column":29}},"loc":{"start":{"line":158,"column":43},"end":{"line":178,"column":null}},"line":158},"17":{"name":"handleView","decl":{"start":{"line":186,"column":15},"end":{"line":186,"column":25}},"loc":{"start":{"line":186,"column":39},"end":{"line":264,"column":null}},"line":186},"18":{"name":"handleSet","decl":{"start":{"line":269,"column":15},"end":{"line":269,"column":24}},"loc":{"start":{"line":269,"column":38},"end":{"line":318,"column":null}},"line":269},"19":{"name":"(anonymous_19)","decl":{"start":{"line":293,"column":14},"end":{"line":293,"column":15}},"loc":{"start":{"line":293,"column":26},"end":{"line":293,"column":34}},"line":293},"20":{"name":"handleReset","decl":{"start":{"line":323,"column":15},"end":{"line":323,"column":26}},"loc":{"start":{"line":323,"column":40},"end":{"line":351,"column":null}},"line":323}},"branchMap":{"0":{"loc":{"start":{"line":76,"column":36},"end":{"line":76,"column":47}},"type":"default-arg","locations":[{"start":{"line":76,"column":45},"end":{"line":76,"column":47}}],"line":76},"1":{"loc":{"start":{"line":76,"column":49},"end":{"line":76,"column":59}},"type":"default-arg","locations":[{"start":{"line":76,"column":57},"end":{"line":76,"column":59}}],"line":76},"2":{"loc":{"start":{"line":77,"column":2},"end":{"line":92,"column":null}},"type":"if","locations":[{"start":{"line":77,"column":2},"end":{"line":92,"column":null}},{"start":{},"end":{}}],"line":77},"3":{"loc":{"start":{"line":79,"column":4},"end":{"line":82,"column":null}},"type":"if","locations":[{"start":{"line":79,"column":4},"end":{"line":82,"column":null}},{"start":{},"end":{}}],"line":79},"4":{"loc":{"start":{"line":79,"column":8},"end":{"line":79,"column":37}},"type":"binary-expr","locations":[{"start":{"line":79,"column":8},"end":{"line":79,"column":27}},{"start":{"line":79,"column":31},"end":{"line":79,"column":37}}],"line":79},"5":{"loc":{"start":{"line":84,"column":19},"end":{"line":84,"column":64}},"type":"cond-expr","locations":[{"start":{"line":84,"column":28},"end":{"line":84,"column":48}},{"start":{"line":84,"column":51},"end":{"line":84,"column":64}}],"line":84},"6":{"loc":{"start":{"line":85,"column":6},"end":{"line":89,"column":null}},"type":"if","locations":[{"start":{"line":85,"column":6},"end":{"line":89,"column":null}},{"start":{"line":87,"column":13},"end":{"line":89,"column":null}}],"line":85},"7":{"loc":{"start":{"line":85,"column":10},"end":{"line":85,"column":44}},"type":"binary-expr","locations":[{"start":{"line":85,"column":10},"end":{"line":85,"column":15}},{"start":{"line":85,"column":19},"end":{"line":85,"column":44}}],"line":85},"8":{"loc":{"start":{"line":94,"column":2},"end":{"line":96,"column":null}},"type":"if","locations":[{"start":{"line":94,"column":2},"end":{"line":96,"column":null}},{"start":{},"end":{}}],"line":94},"9":{"loc":{"start":{"line":94,"column":6},"end":{"line":94,"column":43}},"type":"binary-expr","locations":[{"start":{"line":94,"column":6},"end":{"line":94,"column":13}},{"start":{"line":94,"column":17},"end":{"line":94,"column":43}}],"line":94},"10":{"loc":{"start":{"line":99,"column":2},"end":{"line":102,"column":null}},"type":"if","locations":[{"start":{"line":99,"column":2},"end":{"line":102,"column":null}},{"start":{},"end":{}}],"line":99},"11":{"loc":{"start":{"line":99,"column":6},"end":{"line":99,"column":48}},"type":"binary-expr","locations":[{"start":{"line":99,"column":6},"end":{"line":99,"column":38}},{"start":{"line":99,"column":42},"end":{"line":99,"column":48}}],"line":99},"12":{"loc":{"start":{"line":105,"column":17},"end":{"line":105,"column":50}},"type":"cond-expr","locations":[{"start":{"line":105,"column":26},"end":{"line":105,"column":44}},{"start":{"line":105,"column":47},"end":{"line":105,"column":50}}],"line":105},"13":{"loc":{"start":{"line":106,"column":4},"end":{"line":110,"column":null}},"type":"if","locations":[{"start":{"line":106,"column":4},"end":{"line":110,"column":null}},{"start":{"line":108,"column":11},"end":{"line":110,"column":null}}],"line":106},"14":{"loc":{"start":{"line":106,"column":8},"end":{"line":106,"column":42}},"type":"binary-expr","locations":[{"start":{"line":106,"column":8},"end":{"line":106,"column":13}},{"start":{"line":106,"column":17},"end":{"line":106,"column":42}}],"line":106},"15":{"loc":{"start":{"line":126,"column":2},"end":{"line":149,"column":null}},"type":"if","locations":[{"start":{"line":126,"column":2},"end":{"line":149,"column":null}},{"start":{"line":132,"column":9},"end":{"line":149,"column":null}}],"line":126},"16":{"loc":{"start":{"line":142,"column":8},"end":{"line":144,"column":null}},"type":"if","locations":[{"start":{"line":142,"column":8},"end":{"line":144,"column":null}},{"start":{},"end":{}}],"line":142},"17":{"loc":{"start":{"line":143,"column":17},"end":{"line":143,"column":42}},"type":"cond-expr","locations":[{"start":{"line":143,"column":36},"end":{"line":143,"column":38}},{"start":{"line":143,"column":41},"end":{"line":143,"column":42}}],"line":143},"18":{"loc":{"start":{"line":161,"column":2},"end":{"line":177,"column":null}},"type":"switch","locations":[{"start":{"line":162,"column":4},"end":{"line":164,"column":null}},{"start":{"line":165,"column":4},"end":{"line":167,"column":null}},{"start":{"line":168,"column":4},"end":{"line":170,"column":null}},{"start":{"line":171,"column":4},"end":{"line":176,"column":null}}],"line":161},"19":{"loc":{"start":{"line":195,"column":17},"end":{"line":195,"column":101}},"type":"cond-expr","locations":[{"start":{"line":195,"column":44},"end":{"line":195,"column":66}},{"start":{"line":195,"column":69},"end":{"line":195,"column":101}}],"line":195},"20":{"loc":{"start":{"line":199,"column":4},"end":{"line":255,"column":null}},"type":"if","locations":[{"start":{"line":199,"column":4},"end":{"line":255,"column":null}},{"start":{"line":218,"column":11},"end":{"line":255,"column":null}}],"line":199},"21":{"loc":{"start":{"line":201,"column":6},"end":{"line":207,"column":null}},"type":"if","locations":[{"start":{"line":201,"column":6},"end":{"line":207,"column":null}},{"start":{},"end":{}}],"line":201},"22":{"loc":{"start":{"line":215,"column":11},"end":{"line":215,"column":86}},"type":"cond-expr","locations":[{"start":{"line":215,"column":39},"end":{"line":215,"column":72}},{"start":{"line":215,"column":75},"end":{"line":215,"column":86}}],"line":215},"23":{"loc":{"start":{"line":222,"column":25},"end":{"line":222,"column":54}},"type":"binary-expr","locations":[{"start":{"line":222,"column":25},"end":{"line":222,"column":49}},{"start":{"line":222,"column":53},"end":{"line":222,"column":54}}],"line":222},"24":{"loc":{"start":{"line":222,"column":59},"end":{"line":222,"column":94}},"type":"binary-expr","locations":[{"start":{"line":222,"column":59},"end":{"line":222,"column":89}},{"start":{"line":222,"column":93},"end":{"line":222,"column":94}}],"line":222},"25":{"loc":{"start":{"line":227,"column":42},"end":{"line":227,"column":105}},"type":"cond-expr","locations":[{"start":{"line":227,"column":66},"end":{"line":227,"column":95}},{"start":{"line":227,"column":98},"end":{"line":227,"column":105}}],"line":227},"26":{"loc":{"start":{"line":231,"column":8},"end":{"line":240,"column":null}},"type":"if","locations":[{"start":{"line":231,"column":8},"end":{"line":240,"column":null}},{"start":{},"end":{}}],"line":231},"27":{"loc":{"start":{"line":250,"column":6},"end":{"line":254,"column":null}},"type":"if","locations":[{"start":{"line":250,"column":6},"end":{"line":254,"column":null}},{"start":{},"end":{}}],"line":250},"28":{"loc":{"start":{"line":276,"column":2},"end":{"line":282,"column":null}},"type":"if","locations":[{"start":{"line":276,"column":2},"end":{"line":282,"column":null}},{"start":{},"end":{}}],"line":276},"29":{"loc":{"start":{"line":295,"column":25},"end":{"line":295,"column":68}},"type":"binary-expr","locations":[{"start":{"line":295,"column":25},"end":{"line":295,"column":59}},{"start":{"line":295,"column":63},"end":{"line":295,"column":68}}],"line":295},"30":{"loc":{"start":{"line":297,"column":6},"end":{"line":297,"column":84}},"type":"cond-expr","locations":[{"start":{"line":297,"column":35},"end":{"line":297,"column":69}},{"start":{"line":297,"column":72},"end":{"line":297,"column":84}}],"line":297},"31":{"loc":{"start":{"line":312,"column":4},"end":{"line":316,"column":null}},"type":"if","locations":[{"start":{"line":312,"column":4},"end":{"line":316,"column":null}},{"start":{"line":314,"column":11},"end":{"line":316,"column":null}}],"line":312},"32":{"loc":{"start":{"line":329,"column":22},"end":{"line":329,"column":42}},"type":"binary-expr","locations":[{"start":{"line":329,"column":22},"end":{"line":329,"column":29}},{"start":{"line":329,"column":33},"end":{"line":329,"column":42}}],"line":329},"33":{"loc":{"start":{"line":335,"column":8},"end":{"line":337,"column":76}},"type":"cond-expr","locations":[{"start":{"line":336,"column":12},"end":{"line":336,"column":null}},{"start":{"line":337,"column":12},"end":{"line":337,"column":76}}],"line":335},"34":{"loc":{"start":{"line":345,"column":4},"end":{"line":349,"column":null}},"type":"if","locations":[{"start":{"line":345,"column":4},"end":{"line":349,"column":null}},{"start":{"line":347,"column":11},"end":{"line":349,"column":null}}],"line":345}},"s":{"0":0,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":1,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0,"104":0,"105":0,"106":0,"107":0,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":0,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0},"f":{"0":0,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0},"b":{"0":[0],"1":[0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0],"18":[0,0,0,0],"19":[0,0],"20":[0,0],"21":[0,0],"22":[0,0],"23":[0,0],"24":[0,0],"25":[0,0],"26":[0,0],"27":[0,0],"28":[0,0],"29":[0,0],"30":[0,0],"31":[0,0],"32":[0,0],"33":[0,0],"34":[0,0]},"meta":{"lastBranch":35,"lastFunction":21,"lastStatement":121,"seen":{"f:14:9:14:25":0,"s:15:2:15:Infinity":0,"s:18:20:64:3":1,"f:21:17:21:18":1,"s:22:4:31:7":2,"f:25:23:25:24":2,"s:26:8:30:32":3,"f:33:17:33:18":3,"s:34:4:51:7":4,"f:37:23:37:24":4,"s:38:8:42:32":5,"f:44:23:44:24":5,"s:45:8:50:28":6,"f:53:17:53:18":6,"s:54:4:63:7":7,"f:57:23:57:24":7,"s:58:8:62:32":8,"s:66:25:66:29":9,"f:76:9:76:27":8,"b:76:45:76:47":0,"b:76:57:76:59":1,"b:77:2:92:Infinity:undefined:undefined:undefined:undefined":2,"s:77:2:92:Infinity":10,"b:79:4:82:Infinity:undefined:undefined:undefined:undefined":3,"s:79:4:82:Infinity":11,"b:79:8:79:27:79:31:79:37":4,"s:80:6:80:Infinity":12,"s:81:6:81:Infinity":13,"s:83:4:90:Infinity":14,"f:83:19:83:20":9,"s:84:19:84:64":15,"b:84:28:84:48:84:51:84:64":5,"b:85:6:89:Infinity:87:13:89:Infinity":6,"s:85:6:89:Infinity":16,"b:85:10:85:15:85:19:85:44":7,"s:86:8:86:Infinity":17,"s:88:8:88:Infinity":18,"s:91:4:91:Infinity":19,"b:94:2:96:Infinity:undefined:undefined:undefined:undefined":8,"s:94:2:96:Infinity":20,"b:94:6:94:13:94:17:94:43":9,"s:95:4:95:Infinity":21,"b:99:2:102:Infinity:undefined:undefined:undefined:undefined":10,"s:99:2:102:Infinity":22,"b:99:6:99:38:99:42:99:48":11,"s:100:4:100:Infinity":23,"s:101:4:101:Infinity":24,"s:104:2:111:Infinity":25,"s:105:17:105:50":26,"b:105:26:105:44:105:47:105:50":12,"b:106:4:110:Infinity:108:11:110:Infinity":13,"s:106:4:110:Infinity":27,"b:106:8:106:13:106:17:106:42":14,"s:107:6:107:Infinity":28,"s:109:6:109:Infinity":29,"s:113:2:113:Infinity":30,"f:120:22:120:34":10,"s:121:24:121:60":31,"s:122:23:122:63":32,"s:123:16:123:28":33,"b:126:2:149:Infinity:132:9:149:Infinity":15,"s:126:2:149:Infinity":34,"s:128:4:131:Infinity":35,"f:129:14:129:15":11,"s:129:21:129:59":36,"f:131:11:131:12":12,"s:131:19:131:40":37,"s:134:18:134:44":38,"s:135:4:148:Infinity":39,"f:136:14:136:15":13,"s:136:21:136:59":40,"f:137:12:137:13":14,"s:138:23:138:38":41,"s:139:23:139:38":42,"s:140:33:140:64":43,"s:141:33:141:64":44,"b:142:8:144:Infinity:undefined:undefined:undefined:undefined":16,"s:142:8:144:Infinity":45,"s:143:10:143:Infinity":46,"b:143:36:143:38:143:41:143:42":17,"s:145:8:145:Infinity":47,"f:148:11:148:12":15,"s:148:19:148:40":48,"s:151:2:151:Infinity":49,"f:158:22:158:29":16,"s:159:21:159:56":50,"b:162:4:164:Infinity:165:4:167:Infinity:168:4:170:Infinity:171:4:176:Infinity":18,"s:161:2:177:Infinity":51,"s:163:6:163:Infinity":52,"s:164:6:164:Infinity":53,"s:166:6:166:Infinity":54,"s:167:6:167:Infinity":55,"s:169:6:169:Infinity":56,"s:170:6:170:Infinity":57,"s:172:6:175:Infinity":58,"s:176:6:176:Infinity":59,"s:181:25:181:29":60,"f:186:15:186:25":17,"s:187:2:263:Infinity":61,"s:188:18:188:30":62,"s:189:20:189:60":63,"s:191:18:197:21":64,"b:195:44:195:66:195:69:195:101":19,"b:199:4:255:Infinity:218:11:255:Infinity":20,"s:199:4:255:Infinity":65,"s:200:26:200:41":66,"b:201:6:207:Infinity:undefined:undefined:undefined:undefined":21,"s:201:6:207:Infinity":67,"s:202:28:202:53":68,"s:203:8:206:Infinity":69,"s:209:6:209:Infinity":70,"s:210:26:210:62":71,"s:211:6:217:Infinity":72,"b:215:39:215:72:215:75:215:86":22,"s:219:6:219:Infinity":73,"s:222:24:222:95":74,"b:222:25:222:49:222:53:222:54":23,"b:222:59:222:89:222:93:222:94":24,"s:223:22:223:27":75,"s:225:6:248:Infinity":76,"s:226:24:226:54":77,"s:227:27:227:115":78,"b:227:66:227:95:227:98:227:105":25,"s:228:26:228:43":79,"s:229:28:229:64":80,"b:231:8:240:Infinity:undefined:undefined:undefined:undefined":26,"s:231:8:240:Infinity":81,"s:233:10:237:Infinity":82,"s:238:10:238:Infinity":83,"s:239:10:239:Infinity":84,"s:242:8:242:Infinity":85,"s:243:8:247:Infinity":86,"b:250:6:254:Infinity:undefined:undefined:undefined:undefined":27,"s:250:6:254:Infinity":87,"s:251:8:253:Infinity":88,"s:257:4:257:Infinity":89,"s:259:4:262:Infinity":90,"f:269:15:269:24":18,"s:270:15:270:52":91,"s:271:16:271:54":92,"s:274:18:274:36":93,"s:275:24:275:48":94,"b:276:2:282:Infinity:undefined:undefined:undefined:undefined":28,"s:276:2:282:Infinity":95,"s:277:24:277:49":96,"s:278:4:281:Infinity":97,"s:284:2:317:Infinity":98,"s:285:4:285:Infinity":99,"s:287:27:287:60":100,"s:290:22:293:51":101,"f:293:14:293:15":19,"s:293:26:293:34":102,"s:295:25:295:68":103,"b:295:25:295:59:295:63:295:68":29,"s:297:6:297:84":104,"b:297:35:297:69:297:72:297:84":30,"s:299:18:307:21":105,"s:309:4:309:Infinity":106,"s:311:20:311:60":107,"b:312:4:316:Infinity:314:11:316:Infinity":31,"s:312:4:316:Infinity":108,"s:313:6:313:Infinity":109,"s:315:6:315:Infinity":110,"f:323:15:323:26":20,"s:324:18:324:58":111,"s:326:2:350:Infinity":112,"s:327:4:327:Infinity":113,"s:329:4:329:Infinity":114,"b:329:22:329:29:329:33:329:42":32,"s:331:18:340:21":115,"b:336:12:336:Infinity:337:12:337:76":33,"s:342:4:342:Infinity":116,"s:344:20:344:62":117,"b:345:4:349:Infinity:347:11:349:Infinity":34,"s:345:4:349:Infinity":118,"s:346:6:346:Infinity":119,"s:348:6:348:Infinity":120}}} +,"/home/jailuser/git/src/commands/ping.js": {"path":"/home/jailuser/git/src/commands/ping.js","statementMap":{"0":{"start":{"line":3,"column":20},"end":{"line":5,"column":57}},"1":{"start":{"line":8,"column":19},"end":{"line":11,"column":4}},"2":{"start":{"line":13,"column":15},"end":{"line":13,"column":40}},"3":{"start":{"line":14,"column":18},"end":{"line":14,"column":70}},"4":{"start":{"line":15,"column":21},"end":{"line":15,"column":59}},"5":{"start":{"line":17,"column":2},"end":{"line":17,"column":null}}},"fnMap":{"0":{"name":"execute","decl":{"start":{"line":7,"column":22},"end":{"line":7,"column":29}},"loc":{"start":{"line":7,"column":43},"end":{"line":18,"column":null}},"line":7}},"branchMap":{},"s":{"0":2,"1":9,"2":9,"3":9,"4":9,"5":9},"f":{"0":9},"b":{},"meta":{"lastBranch":0,"lastFunction":1,"lastStatement":6,"seen":{"s:3:20:5:57":0,"f:7:22:7:29":0,"s:8:19:11:4":1,"s:13:15:13:40":2,"s:14:18:14:70":3,"s:15:21:15:59":4,"s:17:2:17:Infinity":5}}} +,"/home/jailuser/git/src/commands/status.js": {"path":"/home/jailuser/git/src/commands/status.js","statementMap":{"0":{"start":{"line":12,"column":20},"end":{"line":20,"column":3}},"1":{"start":{"line":16,"column":4},"end":{"line":19,"column":25}},"2":{"start":{"line":26,"column":2},"end":{"line":26,"column":null}},"3":{"start":{"line":26,"column":18},"end":{"line":26,"column":null}},"4":{"start":{"line":28,"column":14},"end":{"line":28,"column":24}},"5":{"start":{"line":29,"column":15},"end":{"line":29,"column":30}},"6":{"start":{"line":30,"column":18},"end":{"line":30,"column":41}},"7":{"start":{"line":31,"column":18},"end":{"line":31,"column":42}},"8":{"start":{"line":32,"column":16},"end":{"line":32,"column":40}},"9":{"start":{"line":33,"column":15},"end":{"line":33,"column":37}},"10":{"start":{"line":35,"column":2},"end":{"line":35,"column":null}},"11":{"start":{"line":35,"column":19},"end":{"line":35,"column":null}},"12":{"start":{"line":36,"column":2},"end":{"line":36,"column":null}},"13":{"start":{"line":36,"column":20},"end":{"line":36,"column":null}},"14":{"start":{"line":37,"column":2},"end":{"line":37,"column":null}},"15":{"start":{"line":37,"column":20},"end":{"line":37,"column":null}},"16":{"start":{"line":38,"column":2},"end":{"line":38,"column":null}},"17":{"start":{"line":38,"column":18},"end":{"line":38,"column":null}},"18":{"start":{"line":39,"column":2},"end":{"line":39,"column":null}},"19":{"start":{"line":46,"column":2},"end":{"line":55,"column":null}},"20":{"start":{"line":48,"column":6},"end":{"line":48,"column":null}},"21":{"start":{"line":50,"column":6},"end":{"line":50,"column":null}},"22":{"start":{"line":52,"column":6},"end":{"line":52,"column":null}},"23":{"start":{"line":54,"column":6},"end":{"line":54,"column":null}},"24":{"start":{"line":62,"column":2},"end":{"line":152,"column":null}},"25":{"start":{"line":63,"column":21},"end":{"line":63,"column":72}},"26":{"start":{"line":64,"column":26},"end":{"line":64,"column":53}},"27":{"start":{"line":66,"column":4},"end":{"line":138,"column":null}},"28":{"start":{"line":68,"column":6},"end":{"line":74,"column":null}},"29":{"start":{"line":69,"column":8},"end":{"line":72,"column":null}},"30":{"start":{"line":73,"column":8},"end":{"line":73,"column":null}},"31":{"start":{"line":77,"column":21},"end":{"line":77,"column":54}},"32":{"start":{"line":79,"column":20},"end":{"line":109,"column":57}},"33":{"start":{"line":111,"column":6},"end":{"line":111,"column":null}},"34":{"start":{"line":114,"column":21},"end":{"line":114,"column":46}},"35":{"start":{"line":116,"column":20},"end":{"line":135,"column":71}},"36":{"start":{"line":137,"column":6},"end":{"line":137,"column":null}},"37":{"start":{"line":140,"column":3},"end":{"line":140,"column":null}},"38":{"start":{"line":142,"column":18},"end":{"line":145,"column":5}},"39":{"start":{"line":147,"column":4},"end":{"line":151,"column":null}},"40":{"start":{"line":148,"column":6},"end":{"line":148,"column":null}},"41":{"start":{"line":150,"column":6},"end":{"line":150,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":15,"column":20},"end":{"line":15,"column":21}},"loc":{"start":{"line":16,"column":4},"end":{"line":19,"column":25}},"line":16},"1":{"name":"formatRelativeTime","decl":{"start":{"line":25,"column":9},"end":{"line":25,"column":27}},"loc":{"start":{"line":25,"column":39},"end":{"line":40,"column":null}},"line":25},"2":{"name":"getStatusEmoji","decl":{"start":{"line":45,"column":9},"end":{"line":45,"column":23}},"loc":{"start":{"line":45,"column":32},"end":{"line":56,"column":null}},"line":45},"3":{"name":"execute","decl":{"start":{"line":61,"column":22},"end":{"line":61,"column":29}},"loc":{"start":{"line":61,"column":43},"end":{"line":153,"column":null}},"line":61},"4":{"name":"(anonymous_4)","decl":{"start":{"line":148,"column":46},"end":{"line":148,"column":47}},"loc":{"start":{"line":148,"column":52},"end":{"line":148,"column":54}},"line":148},"5":{"name":"(anonymous_5)","decl":{"start":{"line":150,"column":43},"end":{"line":150,"column":44}},"loc":{"start":{"line":150,"column":49},"end":{"line":150,"column":51}},"line":150}},"branchMap":{"0":{"loc":{"start":{"line":26,"column":2},"end":{"line":26,"column":null}},"type":"if","locations":[{"start":{"line":26,"column":2},"end":{"line":26,"column":null}},{"start":{},"end":{}}],"line":26},"1":{"loc":{"start":{"line":35,"column":2},"end":{"line":35,"column":null}},"type":"if","locations":[{"start":{"line":35,"column":2},"end":{"line":35,"column":null}},{"start":{},"end":{}}],"line":35},"2":{"loc":{"start":{"line":36,"column":2},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":36,"column":2},"end":{"line":36,"column":null}},{"start":{},"end":{}}],"line":36},"3":{"loc":{"start":{"line":37,"column":2},"end":{"line":37,"column":null}},"type":"if","locations":[{"start":{"line":37,"column":2},"end":{"line":37,"column":null}},{"start":{},"end":{}}],"line":37},"4":{"loc":{"start":{"line":38,"column":2},"end":{"line":38,"column":null}},"type":"if","locations":[{"start":{"line":38,"column":2},"end":{"line":38,"column":null}},{"start":{},"end":{}}],"line":38},"5":{"loc":{"start":{"line":46,"column":2},"end":{"line":55,"column":null}},"type":"switch","locations":[{"start":{"line":47,"column":4},"end":{"line":48,"column":null}},{"start":{"line":49,"column":4},"end":{"line":50,"column":null}},{"start":{"line":51,"column":4},"end":{"line":52,"column":null}},{"start":{"line":53,"column":4},"end":{"line":54,"column":null}}],"line":46},"6":{"loc":{"start":{"line":63,"column":21},"end":{"line":63,"column":72}},"type":"binary-expr","locations":[{"start":{"line":63,"column":21},"end":{"line":63,"column":63}},{"start":{"line":63,"column":67},"end":{"line":63,"column":72}}],"line":63},"7":{"loc":{"start":{"line":66,"column":4},"end":{"line":138,"column":null}},"type":"if","locations":[{"start":{"line":66,"column":4},"end":{"line":138,"column":null}},{"start":{"line":112,"column":11},"end":{"line":138,"column":null}}],"line":66},"8":{"loc":{"start":{"line":68,"column":6},"end":{"line":74,"column":null}},"type":"if","locations":[{"start":{"line":68,"column":6},"end":{"line":74,"column":null}},{"start":{},"end":{}}],"line":68},"9":{"loc":{"start":{"line":147,"column":4},"end":{"line":151,"column":null}},"type":"if","locations":[{"start":{"line":147,"column":4},"end":{"line":151,"column":null}},{"start":{"line":149,"column":11},"end":{"line":151,"column":null}}],"line":147},"10":{"loc":{"start":{"line":147,"column":8},"end":{"line":147,"column":51}},"type":"binary-expr","locations":[{"start":{"line":147,"column":8},"end":{"line":147,"column":27}},{"start":{"line":147,"column":31},"end":{"line":147,"column":51}}],"line":147}},"s":{"0":2,"1":2,"2":16,"3":0,"4":16,"5":16,"6":16,"7":16,"8":16,"9":16,"10":16,"11":0,"12":16,"13":0,"14":16,"15":16,"16":0,"17":0,"18":0,"19":16,"20":16,"21":0,"22":0,"23":0,"24":21,"25":21,"26":21,"27":21,"28":9,"29":1,"30":1,"31":8,"32":8,"33":8,"34":8,"35":8,"36":8,"37":4,"38":4,"39":4,"40":2,"41":2},"f":{"0":2,"1":16,"2":16,"3":21,"4":1,"5":0},"b":{"0":[0,16],"1":[0,16],"2":[0,16],"3":[16,0],"4":[0,0],"5":[16,0,0,0],"6":[21,12],"7":[9,8],"8":[1,8],"9":[2,2],"10":[4,2]},"meta":{"lastBranch":11,"lastFunction":6,"lastStatement":42,"seen":{"s:12:20:20:3":0,"f:15:20:15:21":0,"s:16:4:19:25":1,"f:25:9:25:27":1,"b:26:2:26:Infinity:undefined:undefined:undefined:undefined":0,"s:26:2:26:Infinity":2,"s:26:18:26:Infinity":3,"s:28:14:28:24":4,"s:29:15:29:30":5,"s:30:18:30:41":6,"s:31:18:31:42":7,"s:32:16:32:40":8,"s:33:15:33:37":9,"b:35:2:35:Infinity:undefined:undefined:undefined:undefined":1,"s:35:2:35:Infinity":10,"s:35:19:35:Infinity":11,"b:36:2:36:Infinity:undefined:undefined:undefined:undefined":2,"s:36:2:36:Infinity":12,"s:36:20:36:Infinity":13,"b:37:2:37:Infinity:undefined:undefined:undefined:undefined":3,"s:37:2:37:Infinity":14,"s:37:20:37:Infinity":15,"b:38:2:38:Infinity:undefined:undefined:undefined:undefined":4,"s:38:2:38:Infinity":16,"s:38:18:38:Infinity":17,"s:39:2:39:Infinity":18,"f:45:9:45:23":2,"b:47:4:48:Infinity:49:4:50:Infinity:51:4:52:Infinity:53:4:54:Infinity":5,"s:46:2:55:Infinity":19,"s:48:6:48:Infinity":20,"s:50:6:50:Infinity":21,"s:52:6:52:Infinity":22,"s:54:6:54:Infinity":23,"f:61:22:61:29":3,"s:62:2:152:Infinity":24,"s:63:21:63:72":25,"b:63:21:63:63:63:67:63:72":6,"s:64:26:64:53":26,"b:66:4:138:Infinity:112:11:138:Infinity":7,"s:66:4:138:Infinity":27,"b:68:6:74:Infinity:undefined:undefined:undefined:undefined":8,"s:68:6:74:Infinity":28,"s:69:8:72:Infinity":29,"s:73:8:73:Infinity":30,"s:77:21:77:54":31,"s:79:20:109:57":32,"s:111:6:111:Infinity":33,"s:114:21:114:46":34,"s:116:20:135:71":35,"s:137:6:137:Infinity":36,"s:140:3:140:Infinity":37,"s:142:18:145:5":38,"b:147:4:151:Infinity:149:11:151:Infinity":9,"s:147:4:151:Infinity":39,"b:147:8:147:27:147:31:147:51":10,"s:148:6:148:Infinity":40,"f:148:46:148:47":4,"s:150:6:150:Infinity":41,"f:150:43:150:44":5}}} +,"/home/jailuser/git/src/modules/ai.js": {"path":"/home/jailuser/git/src/modules/ai.js","statementMap":{"0":{"start":{"line":9,"column":26},"end":{"line":9,"column":35}},"1":{"start":{"line":10,"column":20},"end":{"line":10,"column":22}},"2":{"start":{"line":17,"column":2},"end":{"line":17,"column":null}},"3":{"start":{"line":25,"column":2},"end":{"line":25,"column":null}},"4":{"start":{"line":32,"column":2},"end":{"line":34,"column":46}},"5":{"start":{"line":35,"column":30},"end":{"line":35,"column":94}},"6":{"start":{"line":43,"column":2},"end":{"line":45,"column":null}},"7":{"start":{"line":44,"column":4},"end":{"line":44,"column":null}},"8":{"start":{"line":46,"column":2},"end":{"line":46,"column":null}},"9":{"start":{"line":56,"column":18},"end":{"line":56,"column":39}},"10":{"start":{"line":57,"column":2},"end":{"line":57,"column":null}},"11":{"start":{"line":60,"column":2},"end":{"line":62,"column":null}},"12":{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},"13":{"start":{"line":81,"column":18},"end":{"line":81,"column":39}},"14":{"start":{"line":84,"column":4},"end":{"line":88,"column":41}},"15":{"start":{"line":91,"column":19},"end":{"line":95,"column":3}},"16":{"start":{"line":98,"column":1},"end":{"line":98,"column":null}},"17":{"start":{"line":100,"column":2},"end":{"line":144,"column":null}},"18":{"start":{"line":101,"column":21},"end":{"line":112,"column":6}},"19":{"start":{"line":114,"column":4},"end":{"line":119,"column":null}},"20":{"start":{"line":115,"column":6},"end":{"line":117,"column":null}},"21":{"start":{"line":116,"column":8},"end":{"line":116,"column":null}},"22":{"start":{"line":118,"column":6},"end":{"line":118,"column":null}},"23":{"start":{"line":121,"column":17},"end":{"line":121,"column":38}},"24":{"start":{"line":122,"column":18},"end":{"line":122,"column":84}},"25":{"start":{"line":125,"column":3},"end":{"line":125,"column":null}},"26":{"start":{"line":128,"column":4},"end":{"line":131,"column":null}},"27":{"start":{"line":129,"column":6},"end":{"line":129,"column":null}},"28":{"start":{"line":130,"column":6},"end":{"line":130,"column":null}},"29":{"start":{"line":134,"column":4},"end":{"line":134,"column":null}},"30":{"start":{"line":135,"column":4},"end":{"line":135,"column":null}},"31":{"start":{"line":137,"column":4},"end":{"line":137,"column":null}},"32":{"start":{"line":139,"column":3},"end":{"line":139,"column":null}},"33":{"start":{"line":140,"column":4},"end":{"line":142,"column":null}},"34":{"start":{"line":141,"column":6},"end":{"line":141,"column":null}},"35":{"start":{"line":143,"column":4},"end":{"line":143,"column":null}}},"fnMap":{"0":{"name":"getConversationHistory","decl":{"start":{"line":16,"column":16},"end":{"line":16,"column":38}},"loc":{"start":{"line":16,"column":41},"end":{"line":18,"column":null}},"line":16},"1":{"name":"setConversationHistory","decl":{"start":{"line":24,"column":16},"end":{"line":24,"column":38}},"loc":{"start":{"line":24,"column":48},"end":{"line":26,"column":null}},"line":24},"2":{"name":"getHistory","decl":{"start":{"line":42,"column":16},"end":{"line":42,"column":26}},"loc":{"start":{"line":42,"column":38},"end":{"line":47,"column":null}},"line":42},"3":{"name":"addToHistory","decl":{"start":{"line":55,"column":16},"end":{"line":55,"column":28}},"loc":{"start":{"line":55,"column":55},"end":{"line":63,"column":null}},"line":55},"4":{"name":"generateResponse","decl":{"start":{"line":74,"column":22},"end":{"line":74,"column":38}},"loc":{"start":{"line":80,"column":2},"end":{"line":145,"column":null}},"line":80}},"branchMap":{"0":{"loc":{"start":{"line":32,"column":2},"end":{"line":34,"column":46}},"type":"binary-expr","locations":[{"start":{"line":32,"column":2},"end":{"line":32,"column":30}},{"start":{"line":33,"column":2},"end":{"line":33,"column":26}},{"start":{"line":34,"column":2},"end":{"line":34,"column":46}}],"line":32},"1":{"loc":{"start":{"line":35,"column":30},"end":{"line":35,"column":94}},"type":"binary-expr","locations":[{"start":{"line":35,"column":30},"end":{"line":35,"column":58}},{"start":{"line":35,"column":62},"end":{"line":35,"column":88}},{"start":{"line":35,"column":92},"end":{"line":35,"column":94}}],"line":35},"2":{"loc":{"start":{"line":43,"column":2},"end":{"line":45,"column":null}},"type":"if","locations":[{"start":{"line":43,"column":2},"end":{"line":45,"column":null}},{"start":{},"end":{}}],"line":43},"3":{"loc":{"start":{"line":79,"column":2},"end":{"line":79,"column":22}},"type":"default-arg","locations":[{"start":{"line":79,"column":18},"end":{"line":79,"column":22}}],"line":79},"4":{"loc":{"start":{"line":84,"column":4},"end":{"line":88,"column":41}},"type":"binary-expr","locations":[{"start":{"line":84,"column":4},"end":{"line":84,"column":27}},{"start":{"line":85,"column":4},"end":{"line":88,"column":41}}],"line":84},"5":{"loc":{"start":{"line":105,"column":12},"end":{"line":105,"column":75}},"type":"binary-expr","locations":[{"start":{"line":105,"column":12},"end":{"line":105,"column":26}},{"start":{"line":105,"column":30},"end":{"line":105,"column":75}}],"line":105},"6":{"loc":{"start":{"line":108,"column":15},"end":{"line":108,"column":61}},"type":"binary-expr","locations":[{"start":{"line":108,"column":15},"end":{"line":108,"column":31}},{"start":{"line":108,"column":35},"end":{"line":108,"column":61}}],"line":108},"7":{"loc":{"start":{"line":109,"column":20},"end":{"line":109,"column":48}},"type":"binary-expr","locations":[{"start":{"line":109,"column":20},"end":{"line":109,"column":40}},{"start":{"line":109,"column":44},"end":{"line":109,"column":48}}],"line":109},"8":{"loc":{"start":{"line":114,"column":4},"end":{"line":119,"column":null}},"type":"if","locations":[{"start":{"line":114,"column":4},"end":{"line":119,"column":null}},{"start":{},"end":{}}],"line":114},"9":{"loc":{"start":{"line":115,"column":6},"end":{"line":117,"column":null}},"type":"if","locations":[{"start":{"line":115,"column":6},"end":{"line":117,"column":null}},{"start":{},"end":{}}],"line":115},"10":{"loc":{"start":{"line":122,"column":18},"end":{"line":122,"column":84}},"type":"binary-expr","locations":[{"start":{"line":122,"column":18},"end":{"line":122,"column":53}},{"start":{"line":122,"column":57},"end":{"line":122,"column":84}}],"line":122},"11":{"loc":{"start":{"line":128,"column":4},"end":{"line":131,"column":null}},"type":"if","locations":[{"start":{"line":128,"column":4},"end":{"line":131,"column":null}},{"start":{},"end":{}}],"line":128},"12":{"loc":{"start":{"line":140,"column":4},"end":{"line":142,"column":null}},"type":"if","locations":[{"start":{"line":140,"column":4},"end":{"line":142,"column":null}},{"start":{},"end":{}}],"line":140}},"s":{"0":1,"1":1,"2":2,"3":26,"4":1,"5":1,"6":77,"7":21,"8":77,"9":54,"10":54,"11":54,"12":5,"13":14,"14":14,"15":14,"16":14,"17":14,"18":14,"19":13,"20":2,"21":1,"22":2,"23":11,"24":11,"25":14,"26":14,"27":1,"28":1,"29":11,"30":11,"31":11,"32":3,"33":3,"34":1,"35":3},"f":{"0":2,"1":26,"2":77,"3":54,"4":14},"b":{"0":[1,1,1],"1":[1,1,1],"2":[21,56],"3":[14],"4":[14,2],"5":[14,0],"6":[14,2],"7":[14,2],"8":[2,11],"9":[1,1],"10":[11,1],"11":[1,13],"12":[1,2]},"meta":{"lastBranch":13,"lastFunction":5,"lastStatement":36,"seen":{"s:9:26:9:35":0,"s:10:20:10:22":1,"f:16:16:16:38":0,"s:17:2:17:Infinity":2,"f:24:16:24:38":1,"s:25:2:25:Infinity":3,"s:32:2:34:46":4,"b:32:2:32:30:33:2:33:26:34:2:34:46":0,"s:35:30:35:94":5,"b:35:30:35:58:35:62:35:88:35:92:35:94":1,"f:42:16:42:26":2,"b:43:2:45:Infinity:undefined:undefined:undefined:undefined":2,"s:43:2:45:Infinity":6,"s:44:4:44:Infinity":7,"s:46:2:46:Infinity":8,"f:55:16:55:28":3,"s:56:18:56:39":9,"s:57:2:57:Infinity":10,"s:60:2:62:Infinity":11,"s:61:4:61:Infinity":12,"f:74:22:74:38":4,"b:79:18:79:22":3,"s:81:18:81:39":13,"s:84:4:88:41":14,"b:84:4:84:27:85:4:88:41":4,"s:91:19:95:3":15,"s:98:1:98:Infinity":16,"s:100:2:144:Infinity":17,"s:101:21:112:6":18,"b:105:12:105:26:105:30:105:75":5,"b:108:15:108:31:108:35:108:61":6,"b:109:20:109:40:109:44:109:48":7,"b:114:4:119:Infinity:undefined:undefined:undefined:undefined":8,"s:114:4:119:Infinity":19,"b:115:6:117:Infinity:undefined:undefined:undefined:undefined":9,"s:115:6:117:Infinity":20,"s:116:8:116:Infinity":21,"s:118:6:118:Infinity":22,"s:121:17:121:38":23,"s:122:18:122:84":24,"b:122:18:122:53:122:57:122:84":10,"s:125:3:125:Infinity":25,"b:128:4:131:Infinity:undefined:undefined:undefined:undefined":11,"s:128:4:131:Infinity":26,"s:129:6:129:Infinity":27,"s:130:6:130:Infinity":28,"s:134:4:134:Infinity":29,"s:135:4:135:Infinity":30,"s:137:4:137:Infinity":31,"s:139:3:139:Infinity":32,"b:140:4:142:Infinity:undefined:undefined:undefined:undefined":12,"s:140:4:142:Infinity":33,"s:141:6:141:Infinity":34,"s:143:4:143:Infinity":35}}} +,"/home/jailuser/git/src/modules/chimeIn.js": {"path":"/home/jailuser/git/src/modules/chimeIn.js","statementMap":{"0":{"start":{"line":18,"column":23},"end":{"line":18,"column":32}},"1":{"start":{"line":21,"column":27},"end":{"line":21,"column":36}},"2":{"start":{"line":24,"column":29},"end":{"line":24,"column":32}},"3":{"start":{"line":25,"column":28},"end":{"line":25,"column":42}},"4":{"start":{"line":33,"column":14},"end":{"line":33,"column":24}},"5":{"start":{"line":34,"column":2},"end":{"line":38,"column":null}},"6":{"start":{"line":35,"column":4},"end":{"line":37,"column":null}},"7":{"start":{"line":36,"column":6},"end":{"line":36,"column":null}},"8":{"start":{"line":41,"column":2},"end":{"line":47,"column":null}},"9":{"start":{"line":42,"column":20},"end":{"line":42,"column":99}},"10":{"start":{"line":42,"column":65},"end":{"line":42,"column":98}},"11":{"start":{"line":43,"column":20},"end":{"line":43,"column":80}},"12":{"start":{"line":44,"column":4},"end":{"line":46,"column":null}},"13":{"start":{"line":45,"column":6},"end":{"line":45,"column":null}},"14":{"start":{"line":54,"column":2},"end":{"line":62,"column":null}},"15":{"start":{"line":55,"column":4},"end":{"line":55,"column":null}},"16":{"start":{"line":56,"column":4},"end":{"line":61,"column":null}},"17":{"start":{"line":63,"column":14},"end":{"line":63,"column":43}},"18":{"start":{"line":64,"column":2},"end":{"line":64,"column":null}},"19":{"start":{"line":65,"column":2},"end":{"line":65,"column":null}},"20":{"start":{"line":72,"column":50},"end":{"line":72,"column":63}},"21":{"start":{"line":75,"column":2},"end":{"line":75,"column":null}},"22":{"start":{"line":75,"column":43},"end":{"line":75,"column":null}},"23":{"start":{"line":78,"column":2},"end":{"line":78,"column":null}},"24":{"start":{"line":78,"column":29},"end":{"line":78,"column":null}},"25":{"start":{"line":80,"column":2},"end":{"line":80,"column":null}},"26":{"start":{"line":87,"column":24},"end":{"line":87,"column":44}},"27":{"start":{"line":88,"column":16},"end":{"line":88,"column":57}},"28":{"start":{"line":89,"column":23},"end":{"line":89,"column":82}},"29":{"start":{"line":92,"column":27},"end":{"line":92,"column":93}},"30":{"start":{"line":92,"column":54},"end":{"line":92,"column":81}},"31":{"start":{"line":95,"column":19},"end":{"line":104,"column":3}},"32":{"start":{"line":106,"column":2},"end":{"line":137,"column":null}},"33":{"start":{"line":107,"column":24},"end":{"line":109,"column":35}},"34":{"start":{"line":111,"column":21},"end":{"line":123,"column":6}},"35":{"start":{"line":125,"column":4},"end":{"line":128,"column":null}},"36":{"start":{"line":126,"column":5},"end":{"line":126,"column":null}},"37":{"start":{"line":127,"column":6},"end":{"line":127,"column":null}},"38":{"start":{"line":130,"column":17},"end":{"line":130,"column":38}},"39":{"start":{"line":131,"column":18},"end":{"line":131,"column":82}},"40":{"start":{"line":132,"column":3},"end":{"line":132,"column":null}},"41":{"start":{"line":133,"column":4},"end":{"line":133,"column":null}},"42":{"start":{"line":135,"column":3},"end":{"line":135,"column":null}},"43":{"start":{"line":136,"column":4},"end":{"line":136,"column":null}},"44":{"start":{"line":145,"column":23},"end":{"line":145,"column":82}},"45":{"start":{"line":146,"column":16},"end":{"line":146,"column":62}},"46":{"start":{"line":147,"column":20},"end":{"line":147,"column":48}},"47":{"start":{"line":149,"column":27},"end":{"line":149,"column":93}},"48":{"start":{"line":149,"column":54},"end":{"line":149,"column":81}},"49":{"start":{"line":151,"column":19},"end":{"line":157,"column":3}},"50":{"start":{"line":159,"column":22},"end":{"line":161,"column":33}},"51":{"start":{"line":163,"column":19},"end":{"line":175,"column":4}},"52":{"start":{"line":177,"column":2},"end":{"line":179,"column":null}},"53":{"start":{"line":178,"column":4},"end":{"line":178,"column":null}},"54":{"start":{"line":181,"column":15},"end":{"line":181,"column":36}},"55":{"start":{"line":182,"column":2},"end":{"line":182,"column":null}},"56":{"start":{"line":195,"column":24},"end":{"line":195,"column":38}},"57":{"start":{"line":196,"column":2},"end":{"line":196,"column":null}},"58":{"start":{"line":196,"column":31},"end":{"line":196,"column":null}},"59":{"start":{"line":197,"column":2},"end":{"line":197,"column":null}},"60":{"start":{"line":197,"column":61},"end":{"line":197,"column":null}},"61":{"start":{"line":200,"column":2},"end":{"line":200,"column":null}},"62":{"start":{"line":200,"column":32},"end":{"line":200,"column":null}},"63":{"start":{"line":202,"column":20},"end":{"line":202,"column":38}},"64":{"start":{"line":203,"column":14},"end":{"line":203,"column":34}},"65":{"start":{"line":204,"column":24},"end":{"line":204,"column":57}},"66":{"start":{"line":205,"column":24},"end":{"line":205,"column":57}},"67":{"start":{"line":208,"column":2},"end":{"line":211,"column":null}},"68":{"start":{"line":214,"column":2},"end":{"line":216,"column":null}},"69":{"start":{"line":215,"column":4},"end":{"line":215,"column":null}},"70":{"start":{"line":219,"column":2},"end":{"line":219,"column":null}},"71":{"start":{"line":222,"column":2},"end":{"line":222,"column":null}},"72":{"start":{"line":222,"column":35},"end":{"line":222,"column":null}},"73":{"start":{"line":225,"column":2},"end":{"line":225,"column":null}},"74":{"start":{"line":225,"column":41},"end":{"line":225,"column":null}},"75":{"start":{"line":226,"column":2},"end":{"line":226,"column":null}},"76":{"start":{"line":229,"column":26},"end":{"line":229,"column":47}},"77":{"start":{"line":230,"column":2},"end":{"line":230,"column":null}},"78":{"start":{"line":232,"column":2},"end":{"line":285,"column":null}},"79":{"start":{"line":233,"column":3},"end":{"line":233,"column":null}},"80":{"start":{"line":235,"column":16},"end":{"line":235,"column":72}},"81":{"start":{"line":238,"column":4},"end":{"line":241,"column":null}},"82":{"start":{"line":239,"column":5},"end":{"line":239,"column":null}},"83":{"start":{"line":240,"column":6},"end":{"line":240,"column":null}},"84":{"start":{"line":243,"column":4},"end":{"line":278,"column":null}},"85":{"start":{"line":244,"column":5},"end":{"line":244,"column":null}},"86":{"start":{"line":246,"column":6},"end":{"line":246,"column":null}},"87":{"start":{"line":249,"column":23},"end":{"line":249,"column":89}},"88":{"start":{"line":252,"column":6},"end":{"line":255,"column":null}},"89":{"start":{"line":253,"column":7},"end":{"line":253,"column":null}},"90":{"start":{"line":254,"column":8},"end":{"line":254,"column":null}},"91":{"start":{"line":258,"column":6},"end":{"line":270,"column":null}},"92":{"start":{"line":259,"column":7},"end":{"line":259,"column":null}},"93":{"start":{"line":262,"column":8},"end":{"line":269,"column":null}},"94":{"start":{"line":263,"column":24},"end":{"line":263,"column":47}},"95":{"start":{"line":264,"column":10},"end":{"line":266,"column":null}},"96":{"start":{"line":265,"column":12},"end":{"line":265,"column":null}},"97":{"start":{"line":268,"column":10},"end":{"line":268,"column":null}},"98":{"start":{"line":273,"column":6},"end":{"line":273,"column":null}},"99":{"start":{"line":274,"column":6},"end":{"line":274,"column":null}},"100":{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},"101":{"start":{"line":280,"column":3},"end":{"line":280,"column":null}},"102":{"start":{"line":282,"column":4},"end":{"line":282,"column":null}},"103":{"start":{"line":284,"column":4},"end":{"line":284,"column":null}},"104":{"start":{"line":295,"column":14},"end":{"line":295,"column":43}},"105":{"start":{"line":296,"column":2},"end":{"line":304,"column":null}},"106":{"start":{"line":297,"column":4},"end":{"line":297,"column":null}},"107":{"start":{"line":300,"column":4},"end":{"line":303,"column":null}},"108":{"start":{"line":301,"column":6},"end":{"line":301,"column":null}},"109":{"start":{"line":302,"column":6},"end":{"line":302,"column":null}}},"fnMap":{"0":{"name":"evictInactiveChannels","decl":{"start":{"line":32,"column":9},"end":{"line":32,"column":30}},"loc":{"start":{"line":32,"column":33},"end":{"line":48,"column":null}},"line":32},"1":{"name":"(anonymous_1)","decl":{"start":{"line":42,"column":55},"end":{"line":42,"column":56}},"loc":{"start":{"line":42,"column":65},"end":{"line":42,"column":98}},"line":42},"2":{"name":"getBuffer","decl":{"start":{"line":53,"column":9},"end":{"line":53,"column":18}},"loc":{"start":{"line":53,"column":30},"end":{"line":66,"column":null}},"line":53},"3":{"name":"isChannelEligible","decl":{"start":{"line":71,"column":9},"end":{"line":71,"column":26}},"loc":{"start":{"line":71,"column":53},"end":{"line":81,"column":null}},"line":71},"4":{"name":"shouldChimeIn","decl":{"start":{"line":86,"column":15},"end":{"line":86,"column":28}},"loc":{"start":{"line":86,"column":53},"end":{"line":138,"column":null}},"line":86},"5":{"name":"(anonymous_5)","decl":{"start":{"line":92,"column":47},"end":{"line":92,"column":48}},"loc":{"start":{"line":92,"column":54},"end":{"line":92,"column":81}},"line":92},"6":{"name":"generateChimeInResponse","decl":{"start":{"line":144,"column":15},"end":{"line":144,"column":38}},"loc":{"start":{"line":144,"column":63},"end":{"line":183,"column":null}},"line":144},"7":{"name":"(anonymous_7)","decl":{"start":{"line":149,"column":47},"end":{"line":149,"column":48}},"loc":{"start":{"line":149,"column":54},"end":{"line":149,"column":81}},"line":149},"8":{"name":"accumulate","decl":{"start":{"line":194,"column":22},"end":{"line":194,"column":32}},"loc":{"start":{"line":194,"column":50},"end":{"line":286,"column":null}},"line":194},"9":{"name":"resetCounter","decl":{"start":{"line":294,"column":16},"end":{"line":294,"column":28}},"loc":{"start":{"line":294,"column":40},"end":{"line":305,"column":null}},"line":294}},"branchMap":{"0":{"loc":{"start":{"line":35,"column":4},"end":{"line":37,"column":null}},"type":"if","locations":[{"start":{"line":35,"column":4},"end":{"line":37,"column":null}},{"start":{},"end":{}}],"line":35},"1":{"loc":{"start":{"line":41,"column":2},"end":{"line":47,"column":null}},"type":"if","locations":[{"start":{"line":41,"column":2},"end":{"line":47,"column":null}},{"start":{},"end":{}}],"line":41},"2":{"loc":{"start":{"line":54,"column":2},"end":{"line":62,"column":null}},"type":"if","locations":[{"start":{"line":54,"column":2},"end":{"line":62,"column":null}},{"start":{},"end":{}}],"line":54},"3":{"loc":{"start":{"line":72,"column":10},"end":{"line":72,"column":23}},"type":"default-arg","locations":[{"start":{"line":72,"column":21},"end":{"line":72,"column":23}}],"line":72},"4":{"loc":{"start":{"line":72,"column":25},"end":{"line":72,"column":45}},"type":"default-arg","locations":[{"start":{"line":72,"column":43},"end":{"line":72,"column":45}}],"line":72},"5":{"loc":{"start":{"line":75,"column":2},"end":{"line":75,"column":null}},"type":"if","locations":[{"start":{"line":75,"column":2},"end":{"line":75,"column":null}},{"start":{},"end":{}}],"line":75},"6":{"loc":{"start":{"line":78,"column":2},"end":{"line":78,"column":null}},"type":"if","locations":[{"start":{"line":78,"column":2},"end":{"line":78,"column":null}},{"start":{},"end":{}}],"line":78},"7":{"loc":{"start":{"line":87,"column":24},"end":{"line":87,"column":44}},"type":"binary-expr","locations":[{"start":{"line":87,"column":24},"end":{"line":87,"column":38}},{"start":{"line":87,"column":42},"end":{"line":87,"column":44}}],"line":87},"8":{"loc":{"start":{"line":88,"column":16},"end":{"line":88,"column":57}},"type":"binary-expr","locations":[{"start":{"line":88,"column":16},"end":{"line":88,"column":35}},{"start":{"line":88,"column":39},"end":{"line":88,"column":57}}],"line":88},"9":{"loc":{"start":{"line":89,"column":23},"end":{"line":89,"column":82}},"type":"binary-expr","locations":[{"start":{"line":89,"column":23},"end":{"line":89,"column":46}},{"start":{"line":89,"column":50},"end":{"line":89,"column":82}}],"line":89},"10":{"loc":{"start":{"line":107,"column":24},"end":{"line":109,"column":35}},"type":"cond-expr","locations":[{"start":{"line":108,"column":8},"end":{"line":108,"column":null}},{"start":{"line":109,"column":8},"end":{"line":109,"column":35}}],"line":107},"11":{"loc":{"start":{"line":115,"column":12},"end":{"line":115,"column":75}},"type":"binary-expr","locations":[{"start":{"line":115,"column":12},"end":{"line":115,"column":26}},{"start":{"line":115,"column":30},"end":{"line":115,"column":75}}],"line":115},"12":{"loc":{"start":{"line":125,"column":4},"end":{"line":128,"column":null}},"type":"if","locations":[{"start":{"line":125,"column":4},"end":{"line":128,"column":null}},{"start":{},"end":{}}],"line":125},"13":{"loc":{"start":{"line":131,"column":19},"end":{"line":131,"column":60}},"type":"binary-expr","locations":[{"start":{"line":131,"column":19},"end":{"line":131,"column":54}},{"start":{"line":131,"column":58},"end":{"line":131,"column":60}}],"line":131},"14":{"loc":{"start":{"line":145,"column":23},"end":{"line":145,"column":82}},"type":"binary-expr","locations":[{"start":{"line":145,"column":23},"end":{"line":145,"column":46}},{"start":{"line":145,"column":50},"end":{"line":145,"column":82}}],"line":145},"15":{"loc":{"start":{"line":146,"column":16},"end":{"line":146,"column":62}},"type":"binary-expr","locations":[{"start":{"line":146,"column":16},"end":{"line":146,"column":32}},{"start":{"line":146,"column":36},"end":{"line":146,"column":62}}],"line":146},"16":{"loc":{"start":{"line":147,"column":20},"end":{"line":147,"column":48}},"type":"binary-expr","locations":[{"start":{"line":147,"column":20},"end":{"line":147,"column":40}},{"start":{"line":147,"column":44},"end":{"line":147,"column":48}}],"line":147},"17":{"loc":{"start":{"line":159,"column":22},"end":{"line":161,"column":33}},"type":"cond-expr","locations":[{"start":{"line":160,"column":6},"end":{"line":160,"column":null}},{"start":{"line":161,"column":6},"end":{"line":161,"column":33}}],"line":159},"18":{"loc":{"start":{"line":167,"column":10},"end":{"line":167,"column":73}},"type":"binary-expr","locations":[{"start":{"line":167,"column":10},"end":{"line":167,"column":24}},{"start":{"line":167,"column":28},"end":{"line":167,"column":73}}],"line":167},"19":{"loc":{"start":{"line":177,"column":2},"end":{"line":179,"column":null}},"type":"if","locations":[{"start":{"line":177,"column":2},"end":{"line":179,"column":null}},{"start":{},"end":{}}],"line":177},"20":{"loc":{"start":{"line":182,"column":9},"end":{"line":182,"column":50}},"type":"binary-expr","locations":[{"start":{"line":182,"column":9},"end":{"line":182,"column":44}},{"start":{"line":182,"column":48},"end":{"line":182,"column":50}}],"line":182},"21":{"loc":{"start":{"line":196,"column":2},"end":{"line":196,"column":null}},"type":"if","locations":[{"start":{"line":196,"column":2},"end":{"line":196,"column":null}},{"start":{},"end":{}}],"line":196},"22":{"loc":{"start":{"line":197,"column":2},"end":{"line":197,"column":null}},"type":"if","locations":[{"start":{"line":197,"column":2},"end":{"line":197,"column":null}},{"start":{},"end":{}}],"line":197},"23":{"loc":{"start":{"line":200,"column":2},"end":{"line":200,"column":null}},"type":"if","locations":[{"start":{"line":200,"column":2},"end":{"line":200,"column":null}},{"start":{},"end":{}}],"line":200},"24":{"loc":{"start":{"line":204,"column":24},"end":{"line":204,"column":57}},"type":"binary-expr","locations":[{"start":{"line":204,"column":24},"end":{"line":204,"column":51}},{"start":{"line":204,"column":55},"end":{"line":204,"column":57}}],"line":204},"25":{"loc":{"start":{"line":205,"column":24},"end":{"line":205,"column":57}},"type":"binary-expr","locations":[{"start":{"line":205,"column":24},"end":{"line":205,"column":51}},{"start":{"line":205,"column":55},"end":{"line":205,"column":57}}],"line":205},"26":{"loc":{"start":{"line":222,"column":2},"end":{"line":222,"column":null}},"type":"if","locations":[{"start":{"line":222,"column":2},"end":{"line":222,"column":null}},{"start":{},"end":{}}],"line":222},"27":{"loc":{"start":{"line":225,"column":2},"end":{"line":225,"column":null}},"type":"if","locations":[{"start":{"line":225,"column":2},"end":{"line":225,"column":null}},{"start":{},"end":{}}],"line":225},"28":{"loc":{"start":{"line":238,"column":4},"end":{"line":241,"column":null}},"type":"if","locations":[{"start":{"line":238,"column":4},"end":{"line":241,"column":null}},{"start":{},"end":{}}],"line":238},"29":{"loc":{"start":{"line":243,"column":4},"end":{"line":278,"column":null}},"type":"if","locations":[{"start":{"line":243,"column":4},"end":{"line":278,"column":null}},{"start":{"line":275,"column":11},"end":{"line":278,"column":null}}],"line":243},"30":{"loc":{"start":{"line":252,"column":6},"end":{"line":255,"column":null}},"type":"if","locations":[{"start":{"line":252,"column":6},"end":{"line":255,"column":null}},{"start":{},"end":{}}],"line":252},"31":{"loc":{"start":{"line":258,"column":6},"end":{"line":270,"column":null}},"type":"if","locations":[{"start":{"line":258,"column":6},"end":{"line":270,"column":null}},{"start":{"line":260,"column":13},"end":{"line":270,"column":null}}],"line":258},"32":{"loc":{"start":{"line":262,"column":8},"end":{"line":269,"column":null}},"type":"if","locations":[{"start":{"line":262,"column":8},"end":{"line":269,"column":null}},{"start":{"line":267,"column":15},"end":{"line":269,"column":null}}],"line":262},"33":{"loc":{"start":{"line":296,"column":2},"end":{"line":304,"column":null}},"type":"if","locations":[{"start":{"line":296,"column":2},"end":{"line":304,"column":null}},{"start":{},"end":{}}],"line":296},"34":{"loc":{"start":{"line":300,"column":4},"end":{"line":303,"column":null}},"type":"if","locations":[{"start":{"line":300,"column":4},"end":{"line":303,"column":null}},{"start":{},"end":{}}],"line":300}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0,"104":0,"105":0,"106":0,"107":0,"108":0,"109":0},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0],"4":[0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0],"18":[0,0],"19":[0,0],"20":[0,0],"21":[0,0],"22":[0,0],"23":[0,0],"24":[0,0],"25":[0,0],"26":[0,0],"27":[0,0],"28":[0,0],"29":[0,0],"30":[0,0],"31":[0,0],"32":[0,0],"33":[0,0],"34":[0,0]},"meta":{"lastBranch":35,"lastFunction":10,"lastStatement":110,"seen":{"s:18:23:18:32":0,"s:21:27:21:36":1,"s:24:29:24:32":2,"s:25:28:25:42":3,"f:32:9:32:30":0,"s:33:14:33:24":4,"s:34:2:38:Infinity":5,"b:35:4:37:Infinity:undefined:undefined:undefined:undefined":0,"s:35:4:37:Infinity":6,"s:36:6:36:Infinity":7,"b:41:2:47:Infinity:undefined:undefined:undefined:undefined":1,"s:41:2:47:Infinity":8,"s:42:20:42:99":9,"f:42:55:42:56":1,"s:42:65:42:98":10,"s:43:20:43:80":11,"s:44:4:46:Infinity":12,"s:45:6:45:Infinity":13,"f:53:9:53:18":2,"b:54:2:62:Infinity:undefined:undefined:undefined:undefined":2,"s:54:2:62:Infinity":14,"s:55:4:55:Infinity":15,"s:56:4:61:Infinity":16,"s:63:14:63:43":17,"s:64:2:64:Infinity":18,"s:65:2:65:Infinity":19,"f:71:9:71:26":3,"s:72:50:72:63":20,"b:72:21:72:23":3,"b:72:43:72:45":4,"b:75:2:75:Infinity:undefined:undefined:undefined:undefined":5,"s:75:2:75:Infinity":21,"s:75:43:75:Infinity":22,"b:78:2:78:Infinity:undefined:undefined:undefined:undefined":6,"s:78:2:78:Infinity":23,"s:78:29:78:Infinity":24,"s:80:2:80:Infinity":25,"f:86:15:86:28":4,"s:87:24:87:44":26,"b:87:24:87:38:87:42:87:44":7,"s:88:16:88:57":27,"b:88:16:88:35:88:39:88:57":8,"s:89:23:89:82":28,"b:89:23:89:46:89:50:89:82":9,"s:92:27:92:93":29,"f:92:47:92:48":5,"s:92:54:92:81":30,"s:95:19:104:3":31,"s:106:2:137:Infinity":32,"s:107:24:109:35":33,"b:108:8:108:Infinity:109:8:109:35":10,"s:111:21:123:6":34,"b:115:12:115:26:115:30:115:75":11,"b:125:4:128:Infinity:undefined:undefined:undefined:undefined":12,"s:125:4:128:Infinity":35,"s:126:5:126:Infinity":36,"s:127:6:127:Infinity":37,"s:130:17:130:38":38,"s:131:18:131:82":39,"b:131:19:131:54:131:58:131:60":13,"s:132:3:132:Infinity":40,"s:133:4:133:Infinity":41,"s:135:3:135:Infinity":42,"s:136:4:136:Infinity":43,"f:144:15:144:38":6,"s:145:23:145:82":44,"b:145:23:145:46:145:50:145:82":14,"s:146:16:146:62":45,"b:146:16:146:32:146:36:146:62":15,"s:147:20:147:48":46,"b:147:20:147:40:147:44:147:48":16,"s:149:27:149:93":47,"f:149:47:149:48":7,"s:149:54:149:81":48,"s:151:19:157:3":49,"s:159:22:161:33":50,"b:160:6:160:Infinity:161:6:161:33":17,"s:163:19:175:4":51,"b:167:10:167:24:167:28:167:73":18,"b:177:2:179:Infinity:undefined:undefined:undefined:undefined":19,"s:177:2:179:Infinity":52,"s:178:4:178:Infinity":53,"s:181:15:181:36":54,"s:182:2:182:Infinity":55,"b:182:9:182:44:182:48:182:50":20,"f:194:22:194:32":8,"s:195:24:195:38":56,"b:196:2:196:Infinity:undefined:undefined:undefined:undefined":21,"s:196:2:196:Infinity":57,"s:196:31:196:Infinity":58,"b:197:2:197:Infinity:undefined:undefined:undefined:undefined":22,"s:197:2:197:Infinity":59,"s:197:61:197:Infinity":60,"b:200:2:200:Infinity:undefined:undefined:undefined:undefined":23,"s:200:2:200:Infinity":61,"s:200:32:200:Infinity":62,"s:202:20:202:38":63,"s:203:14:203:34":64,"s:204:24:204:57":65,"b:204:24:204:51:204:55:204:57":24,"s:205:24:205:57":66,"b:205:24:205:51:205:55:205:57":25,"s:208:2:211:Infinity":67,"s:214:2:216:Infinity":68,"s:215:4:215:Infinity":69,"s:219:2:219:Infinity":70,"b:222:2:222:Infinity:undefined:undefined:undefined:undefined":26,"s:222:2:222:Infinity":71,"s:222:35:222:Infinity":72,"b:225:2:225:Infinity:undefined:undefined:undefined:undefined":27,"s:225:2:225:Infinity":73,"s:225:41:225:Infinity":74,"s:226:2:226:Infinity":75,"s:229:26:229:47":76,"s:230:2:230:Infinity":77,"s:232:2:285:Infinity":78,"s:233:3:233:Infinity":79,"s:235:16:235:72":80,"b:238:4:241:Infinity:undefined:undefined:undefined:undefined":28,"s:238:4:241:Infinity":81,"s:239:5:239:Infinity":82,"s:240:6:240:Infinity":83,"b:243:4:278:Infinity:275:11:278:Infinity":29,"s:243:4:278:Infinity":84,"s:244:5:244:Infinity":85,"s:246:6:246:Infinity":86,"s:249:23:249:89":87,"b:252:6:255:Infinity:undefined:undefined:undefined:undefined":30,"s:252:6:255:Infinity":88,"s:253:7:253:Infinity":89,"s:254:8:254:Infinity":90,"b:258:6:270:Infinity:260:13:270:Infinity":31,"s:258:6:270:Infinity":91,"s:259:7:259:Infinity":92,"b:262:8:269:Infinity:267:15:269:Infinity":32,"s:262:8:269:Infinity":93,"s:263:24:263:47":94,"s:264:10:266:Infinity":95,"s:265:12:265:Infinity":96,"s:268:10:268:Infinity":97,"s:273:6:273:Infinity":98,"s:274:6:274:Infinity":99,"s:277:6:277:Infinity":100,"s:280:3:280:Infinity":101,"s:282:4:282:Infinity":102,"s:284:4:284:Infinity":103,"f:294:16:294:28":9,"s:295:14:295:43":104,"b:296:2:304:Infinity:undefined:undefined:undefined:undefined":33,"s:296:2:304:Infinity":105,"s:297:4:297:Infinity":106,"b:300:4:303:Infinity:undefined:undefined:undefined:undefined":34,"s:300:4:303:Infinity":107,"s:301:6:301:Infinity":108,"s:302:6:302:Infinity":109}}} +,"/home/jailuser/git/src/modules/config.js": {"path":"/home/jailuser/git/src/modules/config.js","statementMap":{"0":{"start":{"line":12,"column":17},"end":{"line":12,"column":57}},"1":{"start":{"line":13,"column":18},"end":{"line":13,"column":61}},"2":{"start":{"line":16,"column":18},"end":{"line":16,"column":20}},"3":{"start":{"line":19,"column":22},"end":{"line":19,"column":26}},"4":{"start":{"line":27,"column":2},"end":{"line":27,"column":null}},"5":{"start":{"line":27,"column":23},"end":{"line":27,"column":null}},"6":{"start":{"line":29,"column":2},"end":{"line":33,"column":null}},"7":{"start":{"line":30,"column":16},"end":{"line":30,"column":51}},"8":{"start":{"line":31,"column":4},"end":{"line":31,"column":null}},"9":{"start":{"line":32,"column":4},"end":{"line":32,"column":null}},"10":{"start":{"line":34,"column":2},"end":{"line":39,"column":null}},"11":{"start":{"line":35,"column":4},"end":{"line":35,"column":null}},"12":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"13":{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},"14":{"start":{"line":50,"column":2},"end":{"line":55,"column":null}},"15":{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},"16":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"17":{"start":{"line":54,"column":3},"end":{"line":54,"column":null}},"18":{"start":{"line":57,"column":2},"end":{"line":121,"column":null}},"19":{"start":{"line":59,"column":4},"end":{"line":71,"column":null}},"20":{"start":{"line":60,"column":6},"end":{"line":60,"column":null}},"21":{"start":{"line":63,"column":6},"end":{"line":67,"column":null}},"22":{"start":{"line":64,"column":8},"end":{"line":66,"column":null}},"23":{"start":{"line":68,"column":5},"end":{"line":68,"column":null}},"24":{"start":{"line":69,"column":6},"end":{"line":69,"column":null}},"25":{"start":{"line":70,"column":6},"end":{"line":70,"column":null}},"26":{"start":{"line":74,"column":21},"end":{"line":74,"column":70}},"27":{"start":{"line":76,"column":4},"end":{"line":113,"column":null}},"28":{"start":{"line":77,"column":6},"end":{"line":81,"column":null}},"29":{"start":{"line":78,"column":8},"end":{"line":80,"column":null}},"30":{"start":{"line":83,"column":5},"end":{"line":83,"column":null}},"31":{"start":{"line":84,"column":21},"end":{"line":84,"column":41}},"32":{"start":{"line":85,"column":6},"end":{"line":105,"column":null}},"33":{"start":{"line":86,"column":8},"end":{"line":86,"column":null}},"34":{"start":{"line":87,"column":8},"end":{"line":92,"column":null}},"35":{"start":{"line":88,"column":10},"end":{"line":91,"column":null}},"36":{"start":{"line":93,"column":8},"end":{"line":93,"column":null}},"37":{"start":{"line":94,"column":7},"end":{"line":94,"column":null}},"38":{"start":{"line":95,"column":8},"end":{"line":95,"column":null}},"39":{"start":{"line":97,"column":8},"end":{"line":101,"column":null}},"40":{"start":{"line":98,"column":10},"end":{"line":98,"column":null}},"41":{"start":{"line":102,"column":8},"end":{"line":102,"column":null}},"42":{"start":{"line":104,"column":8},"end":{"line":104,"column":null}},"43":{"start":{"line":108,"column":6},"end":{"line":108,"column":null}},"44":{"start":{"line":109,"column":6},"end":{"line":111,"column":null}},"45":{"start":{"line":110,"column":8},"end":{"line":110,"column":null}},"46":{"start":{"line":112,"column":5},"end":{"line":112,"column":null}},"47":{"start":{"line":115,"column":4},"end":{"line":118,"column":null}},"48":{"start":{"line":117,"column":6},"end":{"line":117,"column":null}},"49":{"start":{"line":119,"column":3},"end":{"line":119,"column":null}},"50":{"start":{"line":120,"column":4},"end":{"line":120,"column":null}},"51":{"start":{"line":123,"column":2},"end":{"line":123,"column":null}},"52":{"start":{"line":131,"column":2},"end":{"line":131,"column":null}},"53":{"start":{"line":142,"column":16},"end":{"line":142,"column":31}},"54":{"start":{"line":143,"column":2},"end":{"line":145,"column":null}},"55":{"start":{"line":144,"column":4},"end":{"line":144,"column":null}},"56":{"start":{"line":148,"column":2},"end":{"line":148,"column":null}},"57":{"start":{"line":150,"column":18},"end":{"line":150,"column":26}},"58":{"start":{"line":151,"column":22},"end":{"line":151,"column":36}},"59":{"start":{"line":152,"column":20},"end":{"line":152,"column":37}},"60":{"start":{"line":155,"column":23},"end":{"line":155,"column":66}},"61":{"start":{"line":156,"column":2},"end":{"line":156,"column":null}},"62":{"start":{"line":162,"column":20},"end":{"line":162,"column":25}},"63":{"start":{"line":167,"column":2},"end":{"line":172,"column":null}},"64":{"start":{"line":168,"column":4},"end":{"line":168,"column":null}},"65":{"start":{"line":171,"column":3},"end":{"line":171,"column":null}},"66":{"start":{"line":174,"column":2},"end":{"line":211,"column":null}},"67":{"start":{"line":175,"column":19},"end":{"line":175,"column":39}},"68":{"start":{"line":176,"column":4},"end":{"line":210,"column":null}},"69":{"start":{"line":177,"column":6},"end":{"line":177,"column":null}},"70":{"start":{"line":179,"column":23},"end":{"line":181,"column":8}},"71":{"start":{"line":183,"column":6},"end":{"line":198,"column":null}},"72":{"start":{"line":185,"column":26},"end":{"line":185,"column":39}},"73":{"start":{"line":186,"column":8},"end":{"line":186,"column":null}},"74":{"start":{"line":188,"column":8},"end":{"line":191,"column":null}},"75":{"start":{"line":194,"column":8},"end":{"line":197,"column":null}},"76":{"start":{"line":199,"column":6},"end":{"line":199,"column":null}},"77":{"start":{"line":200,"column":6},"end":{"line":200,"column":null}},"78":{"start":{"line":202,"column":6},"end":{"line":206,"column":null}},"79":{"start":{"line":203,"column":8},"end":{"line":203,"column":null}},"80":{"start":{"line":207,"column":6},"end":{"line":207,"column":null}},"81":{"start":{"line":209,"column":6},"end":{"line":209,"column":null}},"82":{"start":{"line":214,"column":2},"end":{"line":220,"column":null}},"83":{"start":{"line":219,"column":4},"end":{"line":219,"column":null}},"84":{"start":{"line":221,"column":2},"end":{"line":221,"column":null}},"85":{"start":{"line":223,"column":1},"end":{"line":223,"column":null}},"86":{"start":{"line":224,"column":2},"end":{"line":224,"column":null}},"87":{"start":{"line":234,"column":2},"end":{"line":241,"column":null}},"88":{"start":{"line":235,"column":4},"end":{"line":235,"column":null}},"89":{"start":{"line":237,"column":4},"end":{"line":240,"column":null}},"90":{"start":{"line":243,"column":13},"end":{"line":243,"column":17}},"91":{"start":{"line":244,"column":2},"end":{"line":248,"column":null}},"92":{"start":{"line":245,"column":4},"end":{"line":245,"column":null}},"93":{"start":{"line":247,"column":3},"end":{"line":247,"column":null}},"94":{"start":{"line":250,"column":2},"end":{"line":327,"column":null}},"95":{"start":{"line":251,"column":4},"end":{"line":253,"column":null}},"96":{"start":{"line":252,"column":6},"end":{"line":252,"column":null}},"97":{"start":{"line":255,"column":4},"end":{"line":267,"column":null}},"98":{"start":{"line":256,"column":6},"end":{"line":266,"column":null}},"99":{"start":{"line":257,"column":8},"end":{"line":260,"column":null}},"100":{"start":{"line":262,"column":7},"end":{"line":265,"column":null}},"101":{"start":{"line":270,"column":24},"end":{"line":270,"column":44}},"102":{"start":{"line":271,"column":4},"end":{"line":278,"column":null}},"103":{"start":{"line":272,"column":6},"end":{"line":272,"column":null}},"104":{"start":{"line":272,"column":50},"end":{"line":272,"column":null}},"105":{"start":{"line":273,"column":6},"end":{"line":273,"column":null}},"106":{"start":{"line":275,"column":6},"end":{"line":277,"column":null}},"107":{"start":{"line":279,"column":3},"end":{"line":279,"column":null}},"108":{"start":{"line":282,"column":4},"end":{"line":310,"column":null}},"109":{"start":{"line":283,"column":21},"end":{"line":283,"column":41}},"110":{"start":{"line":284,"column":6},"end":{"line":309,"column":null}},"111":{"start":{"line":285,"column":8},"end":{"line":285,"column":null}},"112":{"start":{"line":286,"column":8},"end":{"line":291,"column":null}},"113":{"start":{"line":287,"column":10},"end":{"line":290,"column":null}},"114":{"start":{"line":293,"column":25},"end":{"line":293,"column":48}},"115":{"start":{"line":294,"column":8},"end":{"line":296,"column":null}},"116":{"start":{"line":295,"column":10},"end":{"line":295,"column":null}},"117":{"start":{"line":297,"column":8},"end":{"line":297,"column":null}},"118":{"start":{"line":299,"column":8},"end":{"line":303,"column":null}},"119":{"start":{"line":300,"column":10},"end":{"line":300,"column":null}},"120":{"start":{"line":304,"column":7},"end":{"line":306,"column":null}},"121":{"start":{"line":308,"column":8},"end":{"line":308,"column":null}},"122":{"start":{"line":313,"column":4},"end":{"line":317,"column":null}},"123":{"start":{"line":314,"column":6},"end":{"line":316,"column":null}},"124":{"start":{"line":315,"column":8},"end":{"line":315,"column":null}},"125":{"start":{"line":318,"column":4},"end":{"line":325,"column":null}},"126":{"start":{"line":319,"column":6},"end":{"line":324,"column":null}},"127":{"start":{"line":320,"column":8},"end":{"line":320,"column":null}},"128":{"start":{"line":320,"column":55},"end":{"line":320,"column":null}},"129":{"start":{"line":321,"column":8},"end":{"line":321,"column":null}},"130":{"start":{"line":323,"column":8},"end":{"line":323,"column":null}},"131":{"start":{"line":326,"column":3},"end":{"line":326,"column":null}},"132":{"start":{"line":329,"column":2},"end":{"line":329,"column":null}},"133":{"start":{"line":333,"column":23},"end":{"line":333,"column":73}},"134":{"start":{"line":341,"column":2},"end":{"line":345,"column":null}},"135":{"start":{"line":342,"column":4},"end":{"line":344,"column":null}},"136":{"start":{"line":343,"column":6},"end":{"line":343,"column":null}},"137":{"start":{"line":356,"column":2},"end":{"line":358,"column":null}},"138":{"start":{"line":357,"column":4},"end":{"line":357,"column":null}},"139":{"start":{"line":359,"column":16},"end":{"line":359,"column":20}},"140":{"start":{"line":360,"column":2},"end":{"line":375,"column":null}},"141":{"start":{"line":360,"column":15},"end":{"line":360,"column":16}},"142":{"start":{"line":362,"column":4},"end":{"line":364,"column":null}},"143":{"start":{"line":363,"column":6},"end":{"line":363,"column":null}},"144":{"start":{"line":365,"column":4},"end":{"line":373,"column":null}},"145":{"start":{"line":366,"column":6},"end":{"line":366,"column":null}},"146":{"start":{"line":367,"column":11},"end":{"line":373,"column":null}},"147":{"start":{"line":370,"column":6},"end":{"line":372,"column":null}},"148":{"start":{"line":371,"column":8},"end":{"line":371,"column":null}},"149":{"start":{"line":374,"column":4},"end":{"line":374,"column":null}},"150":{"start":{"line":376,"column":18},"end":{"line":376,"column":49}},"151":{"start":{"line":377,"column":2},"end":{"line":379,"column":null}},"152":{"start":{"line":378,"column":4},"end":{"line":378,"column":null}},"153":{"start":{"line":380,"column":2},"end":{"line":380,"column":null}},"154":{"start":{"line":389,"column":2},"end":{"line":389,"column":null}},"155":{"start":{"line":409,"column":2},"end":{"line":409,"column":null}},"156":{"start":{"line":409,"column":33},"end":{"line":409,"column":null}},"157":{"start":{"line":412,"column":2},"end":{"line":412,"column":null}},"158":{"start":{"line":412,"column":24},"end":{"line":412,"column":null}},"159":{"start":{"line":413,"column":2},"end":{"line":413,"column":null}},"160":{"start":{"line":413,"column":25},"end":{"line":413,"column":null}},"161":{"start":{"line":416,"column":2},"end":{"line":416,"column":null}},"162":{"start":{"line":416,"column":24},"end":{"line":416,"column":null}},"163":{"start":{"line":420,"column":2},"end":{"line":425,"column":null}},"164":{"start":{"line":421,"column":16},"end":{"line":421,"column":29}},"165":{"start":{"line":422,"column":4},"end":{"line":422,"column":null}},"166":{"start":{"line":422,"column":31},"end":{"line":422,"column":null}},"167":{"start":{"line":423,"column":4},"end":{"line":423,"column":null}},"168":{"start":{"line":423,"column":60},"end":{"line":423,"column":null}},"169":{"start":{"line":424,"column":4},"end":{"line":424,"column":null}},"170":{"start":{"line":428,"column":2},"end":{"line":438,"column":null}},"171":{"start":{"line":433,"column":4},"end":{"line":437,"column":null}},"172":{"start":{"line":434,"column":6},"end":{"line":434,"column":null}},"173":{"start":{"line":436,"column":6},"end":{"line":436,"column":null}},"174":{"start":{"line":441,"column":2},"end":{"line":441,"column":null}}},"fnMap":{"0":{"name":"loadConfigFromFile","decl":{"start":{"line":26,"column":16},"end":{"line":26,"column":34}},"loc":{"start":{"line":26,"column":37},"end":{"line":40,"column":null}},"line":26},"1":{"name":"loadConfig","decl":{"start":{"line":47,"column":22},"end":{"line":47,"column":32}},"loc":{"start":{"line":47,"column":35},"end":{"line":124,"column":null}},"line":47},"2":{"name":"getConfig","decl":{"start":{"line":130,"column":16},"end":{"line":130,"column":25}},"loc":{"start":{"line":130,"column":28},"end":{"line":132,"column":null}},"line":130},"3":{"name":"setConfigValue","decl":{"start":{"line":141,"column":22},"end":{"line":141,"column":36}},"loc":{"start":{"line":141,"column":50},"end":{"line":225,"column":null}},"line":141},"4":{"name":"resetConfig","decl":{"start":{"line":232,"column":22},"end":{"line":232,"column":33}},"loc":{"start":{"line":232,"column":43},"end":{"line":330,"column":null}},"line":232},"5":{"name":"validatePathSegments","decl":{"start":{"line":340,"column":9},"end":{"line":340,"column":29}},"loc":{"start":{"line":340,"column":40},"end":{"line":346,"column":null}},"line":340},"6":{"name":"setNestedValue","decl":{"start":{"line":355,"column":9},"end":{"line":355,"column":23}},"loc":{"start":{"line":355,"column":48},"end":{"line":381,"column":null}},"line":355},"7":{"name":"isPlainObject","decl":{"start":{"line":388,"column":9},"end":{"line":388,"column":22}},"loc":{"start":{"line":388,"column":28},"end":{"line":390,"column":null}},"line":388},"8":{"name":"parseValue","decl":{"start":{"line":408,"column":9},"end":{"line":408,"column":19}},"loc":{"start":{"line":408,"column":27},"end":{"line":442,"column":null}},"line":408}},"branchMap":{"0":{"loc":{"start":{"line":27,"column":2},"end":{"line":27,"column":null}},"type":"if","locations":[{"start":{"line":27,"column":2},"end":{"line":27,"column":null}},{"start":{},"end":{}}],"line":27},"1":{"loc":{"start":{"line":29,"column":2},"end":{"line":33,"column":null}},"type":"if","locations":[{"start":{"line":29,"column":2},"end":{"line":33,"column":null}},{"start":{},"end":{}}],"line":29},"2":{"loc":{"start":{"line":63,"column":6},"end":{"line":67,"column":null}},"type":"if","locations":[{"start":{"line":63,"column":6},"end":{"line":67,"column":null}},{"start":{},"end":{}}],"line":63},"3":{"loc":{"start":{"line":76,"column":4},"end":{"line":113,"column":null}},"type":"if","locations":[{"start":{"line":76,"column":4},"end":{"line":113,"column":null}},{"start":{"line":106,"column":11},"end":{"line":113,"column":null}}],"line":76},"4":{"loc":{"start":{"line":77,"column":6},"end":{"line":81,"column":null}},"type":"if","locations":[{"start":{"line":77,"column":6},"end":{"line":81,"column":null}},{"start":{},"end":{}}],"line":77},"5":{"loc":{"start":{"line":115,"column":4},"end":{"line":118,"column":null}},"type":"if","locations":[{"start":{"line":115,"column":4},"end":{"line":118,"column":null}},{"start":{},"end":{}}],"line":115},"6":{"loc":{"start":{"line":143,"column":2},"end":{"line":145,"column":null}},"type":"if","locations":[{"start":{"line":143,"column":2},"end":{"line":145,"column":null}},{"start":{},"end":{}}],"line":143},"7":{"loc":{"start":{"line":155,"column":39},"end":{"line":155,"column":65}},"type":"binary-expr","locations":[{"start":{"line":155,"column":39},"end":{"line":155,"column":59}},{"start":{"line":155,"column":63},"end":{"line":155,"column":65}}],"line":155},"8":{"loc":{"start":{"line":174,"column":2},"end":{"line":211,"column":null}},"type":"if","locations":[{"start":{"line":174,"column":2},"end":{"line":211,"column":null}},{"start":{},"end":{}}],"line":174},"9":{"loc":{"start":{"line":183,"column":6},"end":{"line":198,"column":null}},"type":"if","locations":[{"start":{"line":183,"column":6},"end":{"line":198,"column":null}},{"start":{"line":192,"column":13},"end":{"line":198,"column":null}}],"line":183},"10":{"loc":{"start":{"line":214,"column":2},"end":{"line":220,"column":null}},"type":"if","locations":[{"start":{"line":214,"column":2},"end":{"line":220,"column":null}},{"start":{},"end":{}}],"line":214},"11":{"loc":{"start":{"line":215,"column":4},"end":{"line":217,"column":null}},"type":"binary-expr","locations":[{"start":{"line":215,"column":4},"end":{"line":215,"column":25}},{"start":{"line":216,"column":4},"end":{"line":216,"column":44}},{"start":{"line":217,"column":4},"end":{"line":217,"column":null}}],"line":215},"12":{"loc":{"start":{"line":250,"column":2},"end":{"line":327,"column":null}},"type":"if","locations":[{"start":{"line":250,"column":2},"end":{"line":327,"column":null}},{"start":{"line":280,"column":9},"end":{"line":327,"column":null}}],"line":250},"13":{"loc":{"start":{"line":251,"column":4},"end":{"line":253,"column":null}},"type":"if","locations":[{"start":{"line":251,"column":4},"end":{"line":253,"column":null}},{"start":{},"end":{}}],"line":251},"14":{"loc":{"start":{"line":255,"column":4},"end":{"line":267,"column":null}},"type":"if","locations":[{"start":{"line":255,"column":4},"end":{"line":267,"column":null}},{"start":{},"end":{}}],"line":255},"15":{"loc":{"start":{"line":271,"column":4},"end":{"line":278,"column":null}},"type":"if","locations":[{"start":{"line":271,"column":4},"end":{"line":278,"column":null}},{"start":{"line":274,"column":11},"end":{"line":278,"column":null}}],"line":271},"16":{"loc":{"start":{"line":271,"column":8},"end":{"line":271,"column":85}},"type":"binary-expr","locations":[{"start":{"line":271,"column":8},"end":{"line":271,"column":19}},{"start":{"line":271,"column":23},"end":{"line":271,"column":54}},{"start":{"line":271,"column":58},"end":{"line":271,"column":85}}],"line":271},"17":{"loc":{"start":{"line":275,"column":29},"end":{"line":277,"column":29}},"type":"cond-expr","locations":[{"start":{"line":276,"column":10},"end":{"line":276,"column":null}},{"start":{"line":277,"column":10},"end":{"line":277,"column":29}}],"line":275},"18":{"loc":{"start":{"line":282,"column":4},"end":{"line":310,"column":null}},"type":"if","locations":[{"start":{"line":282,"column":4},"end":{"line":310,"column":null}},{"start":{},"end":{}}],"line":282},"19":{"loc":{"start":{"line":294,"column":8},"end":{"line":296,"column":null}},"type":"if","locations":[{"start":{"line":294,"column":8},"end":{"line":296,"column":null}},{"start":{},"end":{}}],"line":294},"20":{"loc":{"start":{"line":314,"column":6},"end":{"line":316,"column":null}},"type":"if","locations":[{"start":{"line":314,"column":6},"end":{"line":316,"column":null}},{"start":{},"end":{}}],"line":314},"21":{"loc":{"start":{"line":319,"column":6},"end":{"line":324,"column":null}},"type":"if","locations":[{"start":{"line":319,"column":6},"end":{"line":324,"column":null}},{"start":{"line":322,"column":13},"end":{"line":324,"column":null}}],"line":319},"22":{"loc":{"start":{"line":319,"column":10},"end":{"line":319,"column":85}},"type":"binary-expr","locations":[{"start":{"line":319,"column":10},"end":{"line":319,"column":26}},{"start":{"line":319,"column":30},"end":{"line":319,"column":61}},{"start":{"line":319,"column":65},"end":{"line":319,"column":85}}],"line":319},"23":{"loc":{"start":{"line":323,"column":27},"end":{"line":323,"column":80}},"type":"cond-expr","locations":[{"start":{"line":323,"column":50},"end":{"line":323,"column":72}},{"start":{"line":323,"column":75},"end":{"line":323,"column":80}}],"line":323},"24":{"loc":{"start":{"line":342,"column":4},"end":{"line":344,"column":null}},"type":"if","locations":[{"start":{"line":342,"column":4},"end":{"line":344,"column":null}},{"start":{},"end":{}}],"line":342},"25":{"loc":{"start":{"line":356,"column":2},"end":{"line":358,"column":null}},"type":"if","locations":[{"start":{"line":356,"column":2},"end":{"line":358,"column":null}},{"start":{},"end":{}}],"line":356},"26":{"loc":{"start":{"line":362,"column":4},"end":{"line":364,"column":null}},"type":"if","locations":[{"start":{"line":362,"column":4},"end":{"line":364,"column":null}},{"start":{},"end":{}}],"line":362},"27":{"loc":{"start":{"line":365,"column":4},"end":{"line":373,"column":null}},"type":"if","locations":[{"start":{"line":365,"column":4},"end":{"line":373,"column":null}},{"start":{"line":367,"column":11},"end":{"line":373,"column":null}}],"line":365},"28":{"loc":{"start":{"line":365,"column":8},"end":{"line":365,"column":82}},"type":"binary-expr","locations":[{"start":{"line":365,"column":8},"end":{"line":365,"column":37}},{"start":{"line":365,"column":41},"end":{"line":365,"column":82}}],"line":365},"29":{"loc":{"start":{"line":367,"column":11},"end":{"line":373,"column":null}},"type":"if","locations":[{"start":{"line":367,"column":11},"end":{"line":373,"column":null}},{"start":{},"end":{}}],"line":367},"30":{"loc":{"start":{"line":370,"column":6},"end":{"line":372,"column":null}},"type":"if","locations":[{"start":{"line":370,"column":6},"end":{"line":372,"column":null}},{"start":{},"end":{}}],"line":370},"31":{"loc":{"start":{"line":377,"column":2},"end":{"line":379,"column":null}},"type":"if","locations":[{"start":{"line":377,"column":2},"end":{"line":379,"column":null}},{"start":{},"end":{}}],"line":377},"32":{"loc":{"start":{"line":389,"column":9},"end":{"line":389,"column":71}},"type":"binary-expr","locations":[{"start":{"line":389,"column":9},"end":{"line":389,"column":32}},{"start":{"line":389,"column":36},"end":{"line":389,"column":48}},{"start":{"line":389,"column":52},"end":{"line":389,"column":71}}],"line":389},"33":{"loc":{"start":{"line":409,"column":2},"end":{"line":409,"column":null}},"type":"if","locations":[{"start":{"line":409,"column":2},"end":{"line":409,"column":null}},{"start":{},"end":{}}],"line":409},"34":{"loc":{"start":{"line":412,"column":2},"end":{"line":412,"column":null}},"type":"if","locations":[{"start":{"line":412,"column":2},"end":{"line":412,"column":null}},{"start":{},"end":{}}],"line":412},"35":{"loc":{"start":{"line":413,"column":2},"end":{"line":413,"column":null}},"type":"if","locations":[{"start":{"line":413,"column":2},"end":{"line":413,"column":null}},{"start":{},"end":{}}],"line":413},"36":{"loc":{"start":{"line":416,"column":2},"end":{"line":416,"column":null}},"type":"if","locations":[{"start":{"line":416,"column":2},"end":{"line":416,"column":null}},{"start":{},"end":{}}],"line":416},"37":{"loc":{"start":{"line":420,"column":2},"end":{"line":425,"column":null}},"type":"if","locations":[{"start":{"line":420,"column":2},"end":{"line":425,"column":null}},{"start":{},"end":{}}],"line":420},"38":{"loc":{"start":{"line":422,"column":4},"end":{"line":422,"column":null}},"type":"if","locations":[{"start":{"line":422,"column":4},"end":{"line":422,"column":null}},{"start":{},"end":{}}],"line":422},"39":{"loc":{"start":{"line":423,"column":4},"end":{"line":423,"column":null}},"type":"if","locations":[{"start":{"line":423,"column":4},"end":{"line":423,"column":null}},{"start":{},"end":{}}],"line":423},"40":{"loc":{"start":{"line":423,"column":8},"end":{"line":423,"column":58}},"type":"binary-expr","locations":[{"start":{"line":423,"column":8},"end":{"line":423,"column":28}},{"start":{"line":423,"column":32},"end":{"line":423,"column":58}}],"line":423},"41":{"loc":{"start":{"line":428,"column":2},"end":{"line":438,"column":null}},"type":"if","locations":[{"start":{"line":428,"column":2},"end":{"line":438,"column":null}},{"start":{},"end":{}}],"line":428},"42":{"loc":{"start":{"line":429,"column":4},"end":{"line":431,"column":null}},"type":"binary-expr","locations":[{"start":{"line":429,"column":5},"end":{"line":429,"column":26}},{"start":{"line":429,"column":30},"end":{"line":429,"column":49}},{"start":{"line":430,"column":5},"end":{"line":430,"column":26}},{"start":{"line":430,"column":30},"end":{"line":430,"column":49}},{"start":{"line":431,"column":5},"end":{"line":431,"column":26}},{"start":{"line":431,"column":30},"end":{"line":431,"column":49}}],"line":429}},"s":{"0":1,"1":1,"2":1,"3":1,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0,"104":0,"105":0,"106":0,"107":0,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":0,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0,"121":0,"122":0,"123":0,"124":0,"125":0,"126":0,"127":0,"128":0,"129":0,"130":0,"131":0,"132":0,"133":1,"134":0,"135":0,"136":0,"137":0,"138":0,"139":0,"140":0,"141":0,"142":0,"143":0,"144":0,"145":0,"146":0,"147":0,"148":0,"149":0,"150":0,"151":0,"152":0,"153":0,"154":0,"155":0,"156":0,"157":0,"158":0,"159":0,"160":0,"161":0,"162":0,"163":0,"164":0,"165":0,"166":0,"167":0,"168":0,"169":0,"170":0,"171":0,"172":0,"173":0,"174":0},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0,0],"17":[0,0],"18":[0,0],"19":[0,0],"20":[0,0],"21":[0,0],"22":[0,0,0],"23":[0,0],"24":[0,0],"25":[0,0],"26":[0,0],"27":[0,0],"28":[0,0],"29":[0,0],"30":[0,0],"31":[0,0],"32":[0,0,0],"33":[0,0],"34":[0,0],"35":[0,0],"36":[0,0],"37":[0,0],"38":[0,0],"39":[0,0],"40":[0,0],"41":[0,0],"42":[0,0,0,0,0,0]},"meta":{"lastBranch":43,"lastFunction":9,"lastStatement":175,"seen":{"s:12:17:12:57":0,"s:13:18:13:61":1,"s:16:18:16:20":2,"s:19:22:19:26":3,"f:26:16:26:34":0,"b:27:2:27:Infinity:undefined:undefined:undefined:undefined":0,"s:27:2:27:Infinity":4,"s:27:23:27:Infinity":5,"b:29:2:33:Infinity:undefined:undefined:undefined:undefined":1,"s:29:2:33:Infinity":6,"s:30:16:30:51":7,"s:31:4:31:Infinity":8,"s:32:4:32:Infinity":9,"s:34:2:39:Infinity":10,"s:35:4:35:Infinity":11,"s:36:4:36:Infinity":12,"s:38:4:38:Infinity":13,"f:47:22:47:32":1,"s:50:2:55:Infinity":14,"s:51:4:51:Infinity":15,"s:53:4:53:Infinity":16,"s:54:3:54:Infinity":17,"s:57:2:121:Infinity":18,"s:59:4:71:Infinity":19,"s:60:6:60:Infinity":20,"b:63:6:67:Infinity:undefined:undefined:undefined:undefined":2,"s:63:6:67:Infinity":21,"s:64:8:66:Infinity":22,"s:68:5:68:Infinity":23,"s:69:6:69:Infinity":24,"s:70:6:70:Infinity":25,"s:74:21:74:70":26,"b:76:4:113:Infinity:106:11:113:Infinity":3,"s:76:4:113:Infinity":27,"b:77:6:81:Infinity:undefined:undefined:undefined:undefined":4,"s:77:6:81:Infinity":28,"s:78:8:80:Infinity":29,"s:83:5:83:Infinity":30,"s:84:21:84:41":31,"s:85:6:105:Infinity":32,"s:86:8:86:Infinity":33,"s:87:8:92:Infinity":34,"s:88:10:91:Infinity":35,"s:93:8:93:Infinity":36,"s:94:7:94:Infinity":37,"s:95:8:95:Infinity":38,"s:97:8:101:Infinity":39,"s:98:10:98:Infinity":40,"s:102:8:102:Infinity":41,"s:104:8:104:Infinity":42,"s:108:6:108:Infinity":43,"s:109:6:111:Infinity":44,"s:110:8:110:Infinity":45,"s:112:5:112:Infinity":46,"b:115:4:118:Infinity:undefined:undefined:undefined:undefined":5,"s:115:4:118:Infinity":47,"s:117:6:117:Infinity":48,"s:119:3:119:Infinity":49,"s:120:4:120:Infinity":50,"s:123:2:123:Infinity":51,"f:130:16:130:25":2,"s:131:2:131:Infinity":52,"f:141:22:141:36":3,"s:142:16:142:31":53,"b:143:2:145:Infinity:undefined:undefined:undefined:undefined":6,"s:143:2:145:Infinity":54,"s:144:4:144:Infinity":55,"s:148:2:148:Infinity":56,"s:150:18:150:26":57,"s:151:22:151:36":58,"s:152:20:152:37":59,"s:155:23:155:66":60,"b:155:39:155:59:155:63:155:65":7,"s:156:2:156:Infinity":61,"s:162:20:162:25":62,"s:167:2:172:Infinity":63,"s:168:4:168:Infinity":64,"s:171:3:171:Infinity":65,"b:174:2:211:Infinity:undefined:undefined:undefined:undefined":8,"s:174:2:211:Infinity":66,"s:175:19:175:39":67,"s:176:4:210:Infinity":68,"s:177:6:177:Infinity":69,"s:179:23:181:8":70,"b:183:6:198:Infinity:192:13:198:Infinity":9,"s:183:6:198:Infinity":71,"s:185:26:185:39":72,"s:186:8:186:Infinity":73,"s:188:8:191:Infinity":74,"s:194:8:197:Infinity":75,"s:199:6:199:Infinity":76,"s:200:6:200:Infinity":77,"s:202:6:206:Infinity":78,"s:203:8:203:Infinity":79,"s:207:6:207:Infinity":80,"s:209:6:209:Infinity":81,"b:214:2:220:Infinity:undefined:undefined:undefined:undefined":10,"s:214:2:220:Infinity":82,"b:215:4:215:25:216:4:216:44:217:4:217:Infinity":11,"s:219:4:219:Infinity":83,"s:221:2:221:Infinity":84,"s:223:1:223:Infinity":85,"s:224:2:224:Infinity":86,"f:232:22:232:33":4,"s:234:2:241:Infinity":87,"s:235:4:235:Infinity":88,"s:237:4:240:Infinity":89,"s:243:13:243:17":90,"s:244:2:248:Infinity":91,"s:245:4:245:Infinity":92,"s:247:3:247:Infinity":93,"b:250:2:327:Infinity:280:9:327:Infinity":12,"s:250:2:327:Infinity":94,"b:251:4:253:Infinity:undefined:undefined:undefined:undefined":13,"s:251:4:253:Infinity":95,"s:252:6:252:Infinity":96,"b:255:4:267:Infinity:undefined:undefined:undefined:undefined":14,"s:255:4:267:Infinity":97,"s:256:6:266:Infinity":98,"s:257:8:260:Infinity":99,"s:262:7:265:Infinity":100,"s:270:24:270:44":101,"b:271:4:278:Infinity:274:11:278:Infinity":15,"s:271:4:278:Infinity":102,"b:271:8:271:19:271:23:271:54:271:58:271:85":16,"s:272:6:272:Infinity":103,"s:272:50:272:Infinity":104,"s:273:6:273:Infinity":105,"s:275:6:277:Infinity":106,"b:276:10:276:Infinity:277:10:277:29":17,"s:279:3:279:Infinity":107,"b:282:4:310:Infinity:undefined:undefined:undefined:undefined":18,"s:282:4:310:Infinity":108,"s:283:21:283:41":109,"s:284:6:309:Infinity":110,"s:285:8:285:Infinity":111,"s:286:8:291:Infinity":112,"s:287:10:290:Infinity":113,"s:293:25:293:48":114,"b:294:8:296:Infinity:undefined:undefined:undefined:undefined":19,"s:294:8:296:Infinity":115,"s:295:10:295:Infinity":116,"s:297:8:297:Infinity":117,"s:299:8:303:Infinity":118,"s:300:10:300:Infinity":119,"s:304:7:306:Infinity":120,"s:308:8:308:Infinity":121,"s:313:4:317:Infinity":122,"b:314:6:316:Infinity:undefined:undefined:undefined:undefined":20,"s:314:6:316:Infinity":123,"s:315:8:315:Infinity":124,"s:318:4:325:Infinity":125,"b:319:6:324:Infinity:322:13:324:Infinity":21,"s:319:6:324:Infinity":126,"b:319:10:319:26:319:30:319:61:319:65:319:85":22,"s:320:8:320:Infinity":127,"s:320:55:320:Infinity":128,"s:321:8:321:Infinity":129,"s:323:8:323:Infinity":130,"b:323:50:323:72:323:75:323:80":23,"s:326:3:326:Infinity":131,"s:329:2:329:Infinity":132,"s:333:23:333:73":133,"f:340:9:340:29":5,"s:341:2:345:Infinity":134,"b:342:4:344:Infinity:undefined:undefined:undefined:undefined":24,"s:342:4:344:Infinity":135,"s:343:6:343:Infinity":136,"f:355:9:355:23":6,"b:356:2:358:Infinity:undefined:undefined:undefined:undefined":25,"s:356:2:358:Infinity":137,"s:357:4:357:Infinity":138,"s:359:16:359:20":139,"s:360:2:375:Infinity":140,"s:360:15:360:16":141,"b:362:4:364:Infinity:undefined:undefined:undefined:undefined":26,"s:362:4:364:Infinity":142,"s:363:6:363:Infinity":143,"b:365:4:373:Infinity:367:11:373:Infinity":27,"s:365:4:373:Infinity":144,"b:365:8:365:37:365:41:365:82":28,"s:366:6:366:Infinity":145,"b:367:11:373:Infinity:undefined:undefined:undefined:undefined":29,"s:367:11:373:Infinity":146,"b:370:6:372:Infinity:undefined:undefined:undefined:undefined":30,"s:370:6:372:Infinity":147,"s:371:8:371:Infinity":148,"s:374:4:374:Infinity":149,"s:376:18:376:49":150,"b:377:2:379:Infinity:undefined:undefined:undefined:undefined":31,"s:377:2:379:Infinity":151,"s:378:4:378:Infinity":152,"s:380:2:380:Infinity":153,"f:388:9:388:22":7,"s:389:2:389:Infinity":154,"b:389:9:389:32:389:36:389:48:389:52:389:71":32,"f:408:9:408:19":8,"b:409:2:409:Infinity:undefined:undefined:undefined:undefined":33,"s:409:2:409:Infinity":155,"s:409:33:409:Infinity":156,"b:412:2:412:Infinity:undefined:undefined:undefined:undefined":34,"s:412:2:412:Infinity":157,"s:412:24:412:Infinity":158,"b:413:2:413:Infinity:undefined:undefined:undefined:undefined":35,"s:413:2:413:Infinity":159,"s:413:25:413:Infinity":160,"b:416:2:416:Infinity:undefined:undefined:undefined:undefined":36,"s:416:2:416:Infinity":161,"s:416:24:416:Infinity":162,"b:420:2:425:Infinity:undefined:undefined:undefined:undefined":37,"s:420:2:425:Infinity":163,"s:421:16:421:29":164,"b:422:4:422:Infinity:undefined:undefined:undefined:undefined":38,"s:422:4:422:Infinity":165,"s:422:31:422:Infinity":166,"b:423:4:423:Infinity:undefined:undefined:undefined:undefined":39,"s:423:4:423:Infinity":167,"b:423:8:423:28:423:32:423:58":40,"s:423:60:423:Infinity":168,"s:424:4:424:Infinity":169,"b:428:2:438:Infinity:undefined:undefined:undefined:undefined":41,"s:428:2:438:Infinity":170,"b:429:5:429:26:429:30:429:49:430:5:430:26:430:30:430:49:431:5:431:26:431:30:431:49":42,"s:433:4:437:Infinity":171,"s:434:6:434:Infinity":172,"s:436:6:436:Infinity":173,"s:441:2:441:Infinity":174}}} +,"/home/jailuser/git/src/modules/events.js": {"path":"/home/jailuser/git/src/modules/events.js","statementMap":{"0":{"start":{"line":20,"column":2},"end":{"line":37,"column":null}},"1":{"start":{"line":21,"column":3},"end":{"line":21,"column":null}},"2":{"start":{"line":24,"column":4},"end":{"line":26,"column":null}},"3":{"start":{"line":25,"column":6},"end":{"line":25,"column":null}},"4":{"start":{"line":28,"column":4},"end":{"line":30,"column":null}},"5":{"start":{"line":29,"column":5},"end":{"line":29,"column":null}},"6":{"start":{"line":31,"column":4},"end":{"line":33,"column":null}},"7":{"start":{"line":32,"column":5},"end":{"line":32,"column":null}},"8":{"start":{"line":34,"column":4},"end":{"line":36,"column":null}},"9":{"start":{"line":35,"column":5},"end":{"line":35,"column":null}},"10":{"start":{"line":46,"column":2},"end":{"line":48,"column":null}},"11":{"start":{"line":47,"column":4},"end":{"line":47,"column":null}},"12":{"start":{"line":58,"column":2},"end":{"line":125,"column":null}},"13":{"start":{"line":60,"column":4},"end":{"line":60,"column":null}},"14":{"start":{"line":60,"column":28},"end":{"line":60,"column":null}},"15":{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},"16":{"start":{"line":61,"column":24},"end":{"line":61,"column":null}},"17":{"start":{"line":64,"column":4},"end":{"line":68,"column":null}},"18":{"start":{"line":65,"column":5},"end":{"line":65,"column":null}},"19":{"start":{"line":66,"column":6},"end":{"line":66,"column":null}},"20":{"start":{"line":67,"column":6},"end":{"line":67,"column":null}},"21":{"start":{"line":71,"column":3},"end":{"line":71,"column":null}},"22":{"start":{"line":74,"column":4},"end":{"line":119,"column":null}},"23":{"start":{"line":75,"column":26},"end":{"line":75,"column":59}},"24":{"start":{"line":76,"column":22},"end":{"line":76,"column":94}},"25":{"start":{"line":79,"column":30},"end":{"line":79,"column":55}},"26":{"start":{"line":81,"column":8},"end":{"line":81,"column":84}},"27":{"start":{"line":83,"column":6},"end":{"line":118,"column":null}},"28":{"start":{"line":85,"column":7},"end":{"line":85,"column":null}},"29":{"start":{"line":88,"column":29},"end":{"line":90,"column":17}},"30":{"start":{"line":92,"column":8},"end":{"line":95,"column":null}},"31":{"start":{"line":93,"column":10},"end":{"line":93,"column":null}},"32":{"start":{"line":94,"column":10},"end":{"line":94,"column":null}},"33":{"start":{"line":97,"column":8},"end":{"line":97,"column":null}},"34":{"start":{"line":99,"column":25},"end":{"line":105,"column":9}},"35":{"start":{"line":108,"column":8},"end":{"line":115,"column":null}},"36":{"start":{"line":109,"column":24},"end":{"line":109,"column":47}},"37":{"start":{"line":110,"column":10},"end":{"line":112,"column":null}},"38":{"start":{"line":111,"column":12},"end":{"line":111,"column":null}},"39":{"start":{"line":114,"column":10},"end":{"line":114,"column":null}},"40":{"start":{"line":117,"column":8},"end":{"line":117,"column":15}},"41":{"start":{"line":122,"column":3},"end":{"line":124,"column":null}},"42":{"start":{"line":123,"column":5},"end":{"line":123,"column":null}},"43":{"start":{"line":133,"column":2},"end":{"line":135,"column":null}},"44":{"start":{"line":134,"column":3},"end":{"line":134,"column":null}},"45":{"start":{"line":137,"column":2},"end":{"line":139,"column":null}},"46":{"start":{"line":138,"column":3},"end":{"line":138,"column":null}},"47":{"start":{"line":149,"column":2},"end":{"line":149,"column":null}},"48":{"start":{"line":150,"column":2},"end":{"line":150,"column":null}},"49":{"start":{"line":151,"column":2},"end":{"line":151,"column":null}},"50":{"start":{"line":152,"column":2},"end":{"line":152,"column":null}}},"fnMap":{"0":{"name":"registerReadyHandler","decl":{"start":{"line":19,"column":16},"end":{"line":19,"column":36}},"loc":{"start":{"line":19,"column":68},"end":{"line":38,"column":null}},"line":19},"1":{"name":"(anonymous_1)","decl":{"start":{"line":20,"column":29},"end":{"line":20,"column":30}},"loc":{"start":{"line":20,"column":35},"end":{"line":37,"column":3}},"line":20},"2":{"name":"registerGuildMemberAddHandler","decl":{"start":{"line":45,"column":16},"end":{"line":45,"column":45}},"loc":{"start":{"line":45,"column":62},"end":{"line":49,"column":null}},"line":45},"3":{"name":"(anonymous_3)","decl":{"start":{"line":46,"column":30},"end":{"line":46,"column":35}},"loc":{"start":{"line":46,"column":48},"end":{"line":48,"column":3}},"line":46},"4":{"name":"registerMessageCreateHandler","decl":{"start":{"line":57,"column":16},"end":{"line":57,"column":44}},"loc":{"start":{"line":57,"column":76},"end":{"line":126,"column":null}},"line":57},"5":{"name":"(anonymous_5)","decl":{"start":{"line":58,"column":29},"end":{"line":58,"column":34}},"loc":{"start":{"line":58,"column":48},"end":{"line":125,"column":3}},"line":58},"6":{"name":"(anonymous_6)","decl":{"start":{"line":122,"column":38},"end":{"line":122,"column":39}},"loc":{"start":{"line":122,"column":47},"end":{"line":124,"column":5}},"line":122},"7":{"name":"registerErrorHandlers","decl":{"start":{"line":132,"column":16},"end":{"line":132,"column":37}},"loc":{"start":{"line":132,"column":46},"end":{"line":140,"column":null}},"line":132},"8":{"name":"(anonymous_8)","decl":{"start":{"line":133,"column":21},"end":{"line":133,"column":22}},"loc":{"start":{"line":133,"column":30},"end":{"line":135,"column":3}},"line":133},"9":{"name":"(anonymous_9)","decl":{"start":{"line":137,"column":35},"end":{"line":137,"column":36}},"loc":{"start":{"line":137,"column":44},"end":{"line":139,"column":3}},"line":137},"10":{"name":"registerEventHandlers","decl":{"start":{"line":148,"column":16},"end":{"line":148,"column":37}},"loc":{"start":{"line":148,"column":69},"end":{"line":153,"column":null}},"line":148}},"branchMap":{"0":{"loc":{"start":{"line":24,"column":4},"end":{"line":26,"column":null}},"type":"if","locations":[{"start":{"line":24,"column":4},"end":{"line":26,"column":null}},{"start":{},"end":{}}],"line":24},"1":{"loc":{"start":{"line":28,"column":4},"end":{"line":30,"column":null}},"type":"if","locations":[{"start":{"line":28,"column":4},"end":{"line":30,"column":null}},{"start":{},"end":{}}],"line":28},"2":{"loc":{"start":{"line":31,"column":4},"end":{"line":33,"column":null}},"type":"if","locations":[{"start":{"line":31,"column":4},"end":{"line":33,"column":null}},{"start":{},"end":{}}],"line":31},"3":{"loc":{"start":{"line":32,"column":39},"end":{"line":32,"column":84}},"type":"binary-expr","locations":[{"start":{"line":32,"column":39},"end":{"line":32,"column":54}},{"start":{"line":32,"column":58},"end":{"line":32,"column":84}}],"line":32},"4":{"loc":{"start":{"line":34,"column":4},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":34,"column":4},"end":{"line":36,"column":null}},{"start":{},"end":{}}],"line":34},"5":{"loc":{"start":{"line":60,"column":4},"end":{"line":60,"column":null}},"type":"if","locations":[{"start":{"line":60,"column":4},"end":{"line":60,"column":null}},{"start":{},"end":{}}],"line":60},"6":{"loc":{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},"type":"if","locations":[{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},{"start":{},"end":{}}],"line":61},"7":{"loc":{"start":{"line":64,"column":4},"end":{"line":68,"column":null}},"type":"if","locations":[{"start":{"line":64,"column":4},"end":{"line":68,"column":null}},{"start":{},"end":{}}],"line":64},"8":{"loc":{"start":{"line":64,"column":8},"end":{"line":64,"column":61}},"type":"binary-expr","locations":[{"start":{"line":64,"column":8},"end":{"line":64,"column":34}},{"start":{"line":64,"column":37},"end":{"line":64,"column":61}}],"line":64},"9":{"loc":{"start":{"line":74,"column":4},"end":{"line":119,"column":null}},"type":"if","locations":[{"start":{"line":74,"column":4},"end":{"line":119,"column":null}},{"start":{},"end":{}}],"line":74},"10":{"loc":{"start":{"line":76,"column":22},"end":{"line":76,"column":94}},"type":"binary-expr","locations":[{"start":{"line":76,"column":22},"end":{"line":76,"column":39}},{"start":{"line":76,"column":43},"end":{"line":76,"column":94}}],"line":76},"11":{"loc":{"start":{"line":79,"column":30},"end":{"line":79,"column":55}},"type":"binary-expr","locations":[{"start":{"line":79,"column":30},"end":{"line":79,"column":49}},{"start":{"line":79,"column":53},"end":{"line":79,"column":55}}],"line":79},"12":{"loc":{"start":{"line":81,"column":8},"end":{"line":81,"column":84}},"type":"binary-expr","locations":[{"start":{"line":81,"column":8},"end":{"line":81,"column":36}},{"start":{"line":81,"column":40},"end":{"line":81,"column":84}}],"line":81},"13":{"loc":{"start":{"line":83,"column":6},"end":{"line":118,"column":null}},"type":"if","locations":[{"start":{"line":83,"column":6},"end":{"line":118,"column":null}},{"start":{},"end":{}}],"line":83},"14":{"loc":{"start":{"line":83,"column":10},"end":{"line":83,"column":54}},"type":"binary-expr","locations":[{"start":{"line":83,"column":11},"end":{"line":83,"column":22}},{"start":{"line":83,"column":26},"end":{"line":83,"column":33}},{"start":{"line":83,"column":38},"end":{"line":83,"column":54}}],"line":83},"15":{"loc":{"start":{"line":92,"column":8},"end":{"line":95,"column":null}},"type":"if","locations":[{"start":{"line":92,"column":8},"end":{"line":95,"column":null}},{"start":{},"end":{}}],"line":92},"16":{"loc":{"start":{"line":108,"column":8},"end":{"line":115,"column":null}},"type":"if","locations":[{"start":{"line":108,"column":8},"end":{"line":115,"column":null}},{"start":{"line":113,"column":15},"end":{"line":115,"column":null}}],"line":108}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0,0],"15":[0,0],"16":[0,0]},"meta":{"lastBranch":17,"lastFunction":11,"lastStatement":51,"seen":{"f:19:16:19:36":0,"s:20:2:37:Infinity":0,"f:20:29:20:30":1,"s:21:3:21:Infinity":1,"b:24:4:26:Infinity:undefined:undefined:undefined:undefined":0,"s:24:4:26:Infinity":2,"s:25:6:25:Infinity":3,"b:28:4:30:Infinity:undefined:undefined:undefined:undefined":1,"s:28:4:30:Infinity":4,"s:29:5:29:Infinity":5,"b:31:4:33:Infinity:undefined:undefined:undefined:undefined":2,"s:31:4:33:Infinity":6,"s:32:5:32:Infinity":7,"b:32:39:32:54:32:58:32:84":3,"b:34:4:36:Infinity:undefined:undefined:undefined:undefined":4,"s:34:4:36:Infinity":8,"s:35:5:35:Infinity":9,"f:45:16:45:45":2,"s:46:2:48:Infinity":10,"f:46:30:46:35":3,"s:47:4:47:Infinity":11,"f:57:16:57:44":4,"s:58:2:125:Infinity":12,"f:58:29:58:34":5,"b:60:4:60:Infinity:undefined:undefined:undefined:undefined":5,"s:60:4:60:Infinity":13,"s:60:28:60:Infinity":14,"b:61:4:61:Infinity:undefined:undefined:undefined:undefined":6,"s:61:4:61:Infinity":15,"s:61:24:61:Infinity":16,"b:64:4:68:Infinity:undefined:undefined:undefined:undefined":7,"s:64:4:68:Infinity":17,"b:64:8:64:34:64:37:64:61":8,"s:65:5:65:Infinity":18,"s:66:6:66:Infinity":19,"s:67:6:67:Infinity":20,"s:71:3:71:Infinity":21,"b:74:4:119:Infinity:undefined:undefined:undefined:undefined":9,"s:74:4:119:Infinity":22,"s:75:26:75:59":23,"s:76:22:76:94":24,"b:76:22:76:39:76:43:76:94":10,"s:79:30:79:55":25,"b:79:30:79:49:79:53:79:55":11,"s:81:8:81:84":26,"b:81:8:81:36:81:40:81:84":12,"b:83:6:118:Infinity:undefined:undefined:undefined:undefined":13,"s:83:6:118:Infinity":27,"b:83:11:83:22:83:26:83:33:83:38:83:54":14,"s:85:7:85:Infinity":28,"s:88:29:90:17":29,"b:92:8:95:Infinity:undefined:undefined:undefined:undefined":15,"s:92:8:95:Infinity":30,"s:93:10:93:Infinity":31,"s:94:10:94:Infinity":32,"s:97:8:97:Infinity":33,"s:99:25:105:9":34,"b:108:8:115:Infinity:113:15:115:Infinity":16,"s:108:8:115:Infinity":35,"s:109:24:109:47":36,"s:110:10:112:Infinity":37,"s:111:12:111:Infinity":38,"s:114:10:114:Infinity":39,"s:117:8:117:15":40,"s:122:3:124:Infinity":41,"f:122:38:122:39":6,"s:123:5:123:Infinity":42,"f:132:16:132:37":7,"s:133:2:135:Infinity":43,"f:133:21:133:22":8,"s:134:3:134:Infinity":44,"s:137:2:139:Infinity":45,"f:137:35:137:36":9,"s:138:3:138:Infinity":46,"f:148:16:148:37":10,"s:149:2:149:Infinity":47,"s:150:2:150:Infinity":48,"s:151:2:151:Infinity":49,"s:152:2:152:Infinity":50}}} +,"/home/jailuser/git/src/modules/spam.js": {"path":"/home/jailuser/git/src/modules/spam.js","statementMap":{"0":{"start":{"line":9,"column":22},"end":{"line":19,"column":1}},"1":{"start":{"line":27,"column":2},"end":{"line":27,"column":null}},"2":{"start":{"line":27,"column":41},"end":{"line":27,"column":62}},"3":{"start":{"line":37,"column":2},"end":{"line":37,"column":null}},"4":{"start":{"line":37,"column":42},"end":{"line":37,"column":null}},"5":{"start":{"line":39,"column":23},"end":{"line":41,"column":22}},"6":{"start":{"line":41,"column":17},"end":{"line":41,"column":21}},"7":{"start":{"line":42,"column":2},"end":{"line":42,"column":null}},"8":{"start":{"line":42,"column":21},"end":{"line":42,"column":null}},"9":{"start":{"line":44,"column":16},"end":{"line":53,"column":19}},"10":{"start":{"line":55,"column":2},"end":{"line":55,"column":null}},"11":{"start":{"line":58,"column":2},"end":{"line":60,"column":null}},"12":{"start":{"line":59,"column":4},"end":{"line":59,"column":null}}},"fnMap":{"0":{"name":"isSpam","decl":{"start":{"line":26,"column":16},"end":{"line":26,"column":22}},"loc":{"start":{"line":26,"column":32},"end":{"line":28,"column":null}},"line":26},"1":{"name":"(anonymous_1)","decl":{"start":{"line":27,"column":28},"end":{"line":27,"column":29}},"loc":{"start":{"line":27,"column":41},"end":{"line":27,"column":62}},"line":27},"2":{"name":"sendSpamAlert","decl":{"start":{"line":36,"column":22},"end":{"line":36,"column":35}},"loc":{"start":{"line":36,"column":61},"end":{"line":61,"column":null}},"line":36},"3":{"name":"(anonymous_3)","decl":{"start":{"line":41,"column":11},"end":{"line":41,"column":12}},"loc":{"start":{"line":41,"column":17},"end":{"line":41,"column":21}},"line":41},"4":{"name":"(anonymous_4)","decl":{"start":{"line":59,"column":33},"end":{"line":59,"column":34}},"loc":{"start":{"line":59,"column":39},"end":{"line":59,"column":41}},"line":59}},"branchMap":{"0":{"loc":{"start":{"line":37,"column":2},"end":{"line":37,"column":null}},"type":"if","locations":[{"start":{"line":37,"column":2},"end":{"line":37,"column":null}},{"start":{},"end":{}}],"line":37},"1":{"loc":{"start":{"line":42,"column":2},"end":{"line":42,"column":null}},"type":"if","locations":[{"start":{"line":42,"column":2},"end":{"line":42,"column":null}},{"start":{},"end":{}}],"line":42},"2":{"loc":{"start":{"line":50,"column":32},"end":{"line":50,"column":75}},"type":"binary-expr","locations":[{"start":{"line":50,"column":32},"end":{"line":50,"column":62}},{"start":{"line":50,"column":66},"end":{"line":50,"column":75}}],"line":50},"3":{"loc":{"start":{"line":58,"column":2},"end":{"line":60,"column":null}},"type":"if","locations":[{"start":{"line":58,"column":2},"end":{"line":60,"column":null}},{"start":{},"end":{}}],"line":58}},"s":{"0":1,"1":33,"2":155,"3":16,"4":1,"5":15,"6":1,"7":15,"8":2,"9":13,"10":16,"11":13,"12":2},"f":{"0":33,"1":155,"2":16,"3":1,"4":1},"b":{"0":[1,15],"1":[2,13],"2":[13,1],"3":[2,11]},"meta":{"lastBranch":4,"lastFunction":5,"lastStatement":13,"seen":{"s:9:22:19:1":0,"f:26:16:26:22":0,"s:27:2:27:Infinity":1,"f:27:28:27:29":1,"s:27:41:27:62":2,"f:36:22:36:35":2,"b:37:2:37:Infinity:undefined:undefined:undefined:undefined":0,"s:37:2:37:Infinity":3,"s:37:42:37:Infinity":4,"s:39:23:41:22":5,"f:41:11:41:12":3,"s:41:17:41:21":6,"b:42:2:42:Infinity:undefined:undefined:undefined:undefined":1,"s:42:2:42:Infinity":7,"s:42:21:42:Infinity":8,"s:44:16:53:19":9,"b:50:32:50:62:50:66:50:75":2,"s:55:2:55:Infinity":10,"b:58:2:60:Infinity:undefined:undefined:undefined:undefined":3,"s:58:2:60:Infinity":11,"s:59:4:59:Infinity":12,"f:59:33:59:34":4}}} +,"/home/jailuser/git/src/modules/welcome.js": {"path":"/home/jailuser/git/src/modules/welcome.js","statementMap":{"0":{"start":{"line":8,"column":22},"end":{"line":8,"column":31}},"1":{"start":{"line":9,"column":40},"end":{"line":9,"column":42}},"2":{"start":{"line":10,"column":31},"end":{"line":10,"column":34}},"3":{"start":{"line":13,"column":27},"end":{"line":13,"column":69}},"4":{"start":{"line":16,"column":28},"end":{"line":16,"column":32}},"5":{"start":{"line":26,"column":2},"end":{"line":30,"column":null}},"6":{"start":{"line":40,"column":2},"end":{"line":40,"column":null}},"7":{"start":{"line":40,"column":67},"end":{"line":40,"column":null}},"8":{"start":{"line":41,"column":2},"end":{"line":41,"column":null}},"9":{"start":{"line":41,"column":41},"end":{"line":41,"column":null}},"10":{"start":{"line":43,"column":25},"end":{"line":43,"column":55}},"11":{"start":{"line":44,"column":22},"end":{"line":44,"column":58}},"12":{"start":{"line":45,"column":19},"end":{"line":45,"column":40}},"13":{"start":{"line":46,"column":2},"end":{"line":48,"column":null}},"14":{"start":{"line":47,"column":4},"end":{"line":47,"column":null}},"15":{"start":{"line":49,"column":2},"end":{"line":49,"column":null}},"16":{"start":{"line":49,"column":57},"end":{"line":49,"column":null}},"17":{"start":{"line":51,"column":14},"end":{"line":51,"column":24}},"18":{"start":{"line":52,"column":19},"end":{"line":52,"column":54}},"19":{"start":{"line":53,"column":17},"end":{"line":53,"column":31}},"20":{"start":{"line":55,"column":2},"end":{"line":57,"column":null}},"21":{"start":{"line":56,"column":4},"end":{"line":56,"column":null}},"22":{"start":{"line":59,"column":22},"end":{"line":59,"column":57}},"23":{"start":{"line":60,"column":21},"end":{"line":60,"column":62}},"24":{"start":{"line":62,"column":2},"end":{"line":62,"column":null}},"25":{"start":{"line":63,"column":2},"end":{"line":65,"column":null}},"26":{"start":{"line":64,"column":4},"end":{"line":64,"column":null}},"27":{"start":{"line":66,"column":2},"end":{"line":68,"column":null}},"28":{"start":{"line":67,"column":4},"end":{"line":67,"column":null}},"29":{"start":{"line":70,"column":2},"end":{"line":70,"column":null}},"30":{"start":{"line":80,"column":2},"end":{"line":80,"column":null}},"31":{"start":{"line":80,"column":62},"end":{"line":80,"column":null}},"32":{"start":{"line":82,"column":2},"end":{"line":100,"column":null}},"33":{"start":{"line":83,"column":20},"end":{"line":83,"column":73}},"34":{"start":{"line":84,"column":4},"end":{"line":84,"column":null}},"35":{"start":{"line":84,"column":18},"end":{"line":84,"column":null}},"36":{"start":{"line":86,"column":23},"end":{"line":86,"column":64}},"37":{"start":{"line":88,"column":20},"end":{"line":94,"column":9}},"38":{"start":{"line":96,"column":4},"end":{"line":96,"column":null}},"39":{"start":{"line":97,"column":3},"end":{"line":97,"column":null}},"40":{"start":{"line":99,"column":3},"end":{"line":99,"column":null}},"41":{"start":{"line":110,"column":25},"end":{"line":110,"column":55}},"42":{"start":{"line":111,"column":19},"end":{"line":111,"column":64}},"43":{"start":{"line":113,"column":24},"end":{"line":118,"column":3}},"44":{"start":{"line":120,"column":20},"end":{"line":120,"column":42}},"45":{"start":{"line":121,"column":19},"end":{"line":121,"column":69}},"46":{"start":{"line":122,"column":24},"end":{"line":122,"column":83}},"47":{"start":{"line":123,"column":28},"end":{"line":123,"column":74}},"48":{"start":{"line":125,"column":19},"end":{"line":125,"column":75}},"49":{"start":{"line":126,"column":19},"end":{"line":126,"column":61}},"50":{"start":{"line":127,"column":18},"end":{"line":127,"column":49}},"51":{"start":{"line":129,"column":16},"end":{"line":129,"column":26}},"52":{"start":{"line":131,"column":2},"end":{"line":135,"column":null}},"53":{"start":{"line":132,"column":4},"end":{"line":132,"column":null}},"54":{"start":{"line":134,"column":4},"end":{"line":134,"column":null}},"55":{"start":{"line":137,"column":2},"end":{"line":137,"column":null}},"56":{"start":{"line":138,"column":2},"end":{"line":138,"column":null}},"57":{"start":{"line":140,"column":2},"end":{"line":140,"column":null}},"58":{"start":{"line":150,"column":22},"end":{"line":150,"column":62}},"59":{"start":{"line":151,"column":14},"end":{"line":151,"column":24}},"60":{"start":{"line":152,"column":19},"end":{"line":152,"column":48}},"61":{"start":{"line":153,"column":17},"end":{"line":153,"column":31}},"62":{"start":{"line":155,"column":21},"end":{"line":155,"column":22}},"63":{"start":{"line":156,"column":24},"end":{"line":156,"column":26}},"64":{"start":{"line":158,"column":2},"end":{"line":171,"column":null}},"65":{"start":{"line":159,"column":19},"end":{"line":159,"column":56}},"66":{"start":{"line":159,"column":44},"end":{"line":159,"column":55}},"67":{"start":{"line":161,"column":4},"end":{"line":164,"column":null}},"68":{"start":{"line":162,"column":6},"end":{"line":162,"column":null}},"69":{"start":{"line":163,"column":6},"end":{"line":163,"column":null}},"70":{"start":{"line":167,"column":4},"end":{"line":167,"column":null}},"71":{"start":{"line":169,"column":4},"end":{"line":169,"column":null}},"72":{"start":{"line":170,"column":4},"end":{"line":170,"column":null}},"73":{"start":{"line":174,"column":2},"end":{"line":176,"column":null}},"74":{"start":{"line":175,"column":4},"end":{"line":175,"column":null}},"75":{"start":{"line":178,"column":24},"end":{"line":181,"column":36}},"76":{"start":{"line":179,"column":20},"end":{"line":179,"column":37}},"77":{"start":{"line":181,"column":20},"end":{"line":181,"column":35}},"78":{"start":{"line":183,"column":30},"end":{"line":185,"column":3}},"79":{"start":{"line":184,"column":17},"end":{"line":184,"column":71}},"80":{"start":{"line":187,"column":24},"end":{"line":187,"column":48}},"81":{"start":{"line":188,"column":28},"end":{"line":191,"column":3}},"82":{"start":{"line":189,"column":22},"end":{"line":189,"column":56}},"83":{"start":{"line":193,"column":16},"end":{"line":193,"column":65}},"84":{"start":{"line":195,"column":2},"end":{"line":202,"column":null}},"85":{"start":{"line":212,"column":2},"end":{"line":212,"column":null}},"86":{"start":{"line":212,"column":53},"end":{"line":212,"column":null}},"87":{"start":{"line":213,"column":2},"end":{"line":213,"column":null}},"88":{"start":{"line":213,"column":52},"end":{"line":213,"column":null}},"89":{"start":{"line":214,"column":2},"end":{"line":214,"column":null}},"90":{"start":{"line":214,"column":51},"end":{"line":214,"column":null}},"91":{"start":{"line":215,"column":2},"end":{"line":215,"column":null}},"92":{"start":{"line":215,"column":51},"end":{"line":215,"column":null}},"93":{"start":{"line":216,"column":2},"end":{"line":216,"column":null}},"94":{"start":{"line":226,"column":22},"end":{"line":226,"column":68}},"95":{"start":{"line":226,"column":57},"end":{"line":226,"column":67}},"96":{"start":{"line":227,"column":22},"end":{"line":227,"column":88}},"97":{"start":{"line":228,"column":22},"end":{"line":228,"column":45}},"98":{"start":{"line":229,"column":22},"end":{"line":229,"column":44}},"99":{"start":{"line":231,"column":2},"end":{"line":256,"column":null}},"100":{"start":{"line":233,"column":6},"end":{"line":235,"column":null}},"101":{"start":{"line":237,"column":6},"end":{"line":239,"column":null}},"102":{"start":{"line":241,"column":6},"end":{"line":243,"column":null}},"103":{"start":{"line":245,"column":6},"end":{"line":247,"column":null}},"104":{"start":{"line":246,"column":8},"end":{"line":246,"column":null}},"105":{"start":{"line":248,"column":6},"end":{"line":250,"column":null}},"106":{"start":{"line":249,"column":8},"end":{"line":249,"column":null}},"107":{"start":{"line":251,"column":6},"end":{"line":253,"column":null}},"108":{"start":{"line":255,"column":6},"end":{"line":255,"column":null}},"109":{"start":{"line":265,"column":33},"end":{"line":265,"column":41}},"110":{"start":{"line":267,"column":2},"end":{"line":269,"column":null}},"111":{"start":{"line":268,"column":4},"end":{"line":268,"column":null}},"112":{"start":{"line":270,"column":2},"end":{"line":272,"column":null}},"113":{"start":{"line":271,"column":4},"end":{"line":271,"column":null}},"114":{"start":{"line":273,"column":2},"end":{"line":275,"column":null}},"115":{"start":{"line":274,"column":4},"end":{"line":274,"column":null}},"116":{"start":{"line":277,"column":2},"end":{"line":277,"column":null}},"117":{"start":{"line":287,"column":2},"end":{"line":287,"column":null}},"118":{"start":{"line":287,"column":20},"end":{"line":287,"column":null}},"119":{"start":{"line":289,"column":19},"end":{"line":289,"column":59}},"120":{"start":{"line":291,"column":2},"end":{"line":293,"column":null}},"121":{"start":{"line":292,"column":4},"end":{"line":292,"column":null}},"122":{"start":{"line":295,"column":2},"end":{"line":295,"column":null}},"123":{"start":{"line":304,"column":15},"end":{"line":304,"column":42}},"124":{"start":{"line":306,"column":2},"end":{"line":306,"column":null}},"125":{"start":{"line":306,"column":30},"end":{"line":306,"column":null}},"126":{"start":{"line":307,"column":2},"end":{"line":307,"column":null}},"127":{"start":{"line":307,"column":31},"end":{"line":307,"column":null}},"128":{"start":{"line":308,"column":2},"end":{"line":308,"column":null}},"129":{"start":{"line":308,"column":31},"end":{"line":308,"column":null}},"130":{"start":{"line":309,"column":2},"end":{"line":309,"column":null}},"131":{"start":{"line":318,"column":2},"end":{"line":329,"column":null}},"132":{"start":{"line":319,"column":23},"end":{"line":323,"column":25}},"133":{"start":{"line":325,"column":17},"end":{"line":325,"column":35}},"134":{"start":{"line":326,"column":4},"end":{"line":326,"column":null}},"135":{"start":{"line":328,"column":4},"end":{"line":328,"column":null}},"136":{"start":{"line":338,"column":20},"end":{"line":360,"column":3}},"137":{"start":{"line":340,"column":15},"end":{"line":340,"column":75}},"138":{"start":{"line":341,"column":15},"end":{"line":341,"column":93}},"139":{"start":{"line":342,"column":15},"end":{"line":342,"column":80}},"140":{"start":{"line":345,"column":15},"end":{"line":345,"column":63}},"141":{"start":{"line":347,"column":8},"end":{"line":347,"column":94}},"142":{"start":{"line":348,"column":15},"end":{"line":348,"column":75}},"143":{"start":{"line":351,"column":15},"end":{"line":351,"column":73}},"144":{"start":{"line":352,"column":15},"end":{"line":352,"column":96}},"145":{"start":{"line":353,"column":15},"end":{"line":353,"column":87}},"146":{"start":{"line":356,"column":15},"end":{"line":356,"column":82}},"147":{"start":{"line":357,"column":15},"end":{"line":357,"column":75}},"148":{"start":{"line":358,"column":15},"end":{"line":358,"column":90}},"149":{"start":{"line":362,"column":2},"end":{"line":362,"column":null}},"150":{"start":{"line":373,"column":18},"end":{"line":373,"column":48}},"151":{"start":{"line":374,"column":21},"end":{"line":374,"column":94}},"152":{"start":{"line":375,"column":17},"end":{"line":375,"column":78}},"153":{"start":{"line":376,"column":14},"end":{"line":376,"column":42}},"154":{"start":{"line":378,"column":21},"end":{"line":381,"column":16}},"155":{"start":{"line":380,"column":20},"end":{"line":380,"column":55}},"156":{"start":{"line":383,"column":2},"end":{"line":383,"column":null}},"157":{"start":{"line":383,"column":32},"end":{"line":383,"column":42}},"158":{"start":{"line":392,"column":18},"end":{"line":392,"column":51}},"159":{"start":{"line":393,"column":2},"end":{"line":393,"column":null}},"160":{"start":{"line":393,"column":32},"end":{"line":393,"column":59}},"161":{"start":{"line":402,"column":18},"end":{"line":402,"column":91}},"162":{"start":{"line":403,"column":2},"end":{"line":403,"column":null}},"163":{"start":{"line":413,"column":2},"end":{"line":413,"column":null}},"164":{"start":{"line":413,"column":25},"end":{"line":413,"column":null}},"165":{"start":{"line":414,"column":16},"end":{"line":414,"column":60}},"166":{"start":{"line":415,"column":2},"end":{"line":415,"column":null}}},"fnMap":{"0":{"name":"renderWelcomeMessage","decl":{"start":{"line":25,"column":16},"end":{"line":25,"column":36}},"loc":{"start":{"line":25,"column":69},"end":{"line":31,"column":null}},"line":25},"1":{"name":"recordCommunityActivity","decl":{"start":{"line":39,"column":16},"end":{"line":39,"column":39}},"loc":{"start":{"line":39,"column":57},"end":{"line":71,"column":null}},"line":39},"2":{"name":"sendWelcomeMessage","decl":{"start":{"line":79,"column":22},"end":{"line":79,"column":40}},"loc":{"start":{"line":79,"column":65},"end":{"line":101,"column":null}},"line":79},"3":{"name":"buildDynamicWelcomeMessage","decl":{"start":{"line":109,"column":9},"end":{"line":109,"column":35}},"loc":{"start":{"line":109,"column":52},"end":{"line":141,"column":null}},"line":109},"4":{"name":"getCommunitySnapshot","decl":{"start":{"line":149,"column":9},"end":{"line":149,"column":29}},"loc":{"start":{"line":149,"column":47},"end":{"line":203,"column":null}},"line":149},"5":{"name":"(anonymous_5)","decl":{"start":{"line":159,"column":37},"end":{"line":159,"column":38}},"loc":{"start":{"line":159,"column":44},"end":{"line":159,"column":55}},"line":159},"6":{"name":"(anonymous_6)","decl":{"start":{"line":179,"column":10},"end":{"line":179,"column":11}},"loc":{"start":{"line":179,"column":20},"end":{"line":179,"column":37}},"line":179},"7":{"name":"(anonymous_7)","decl":{"start":{"line":181,"column":9},"end":{"line":181,"column":10}},"loc":{"start":{"line":181,"column":20},"end":{"line":181,"column":35}},"line":181},"8":{"name":"(anonymous_8)","decl":{"start":{"line":184,"column":4},"end":{"line":184,"column":5}},"loc":{"start":{"line":184,"column":17},"end":{"line":184,"column":71}},"line":184},"9":{"name":"(anonymous_9)","decl":{"start":{"line":189,"column":4},"end":{"line":189,"column":5}},"loc":{"start":{"line":189,"column":22},"end":{"line":189,"column":56}},"line":189},"10":{"name":"getActivityLevel","decl":{"start":{"line":211,"column":9},"end":{"line":211,"column":25}},"loc":{"start":{"line":211,"column":59},"end":{"line":217,"column":null}},"line":211},"11":{"name":"buildVibeLine","decl":{"start":{"line":225,"column":9},"end":{"line":225,"column":22}},"loc":{"start":{"line":225,"column":52},"end":{"line":257,"column":null}},"line":225},"12":{"name":"(anonymous_12)","decl":{"start":{"line":226,"column":49},"end":{"line":226,"column":50}},"loc":{"start":{"line":226,"column":57},"end":{"line":226,"column":67}},"line":226},"13":{"name":"buildCtaLine","decl":{"start":{"line":264,"column":9},"end":{"line":264,"column":21}},"loc":{"start":{"line":264,"column":32},"end":{"line":278,"column":null}},"line":264},"14":{"name":"getMilestoneLine","decl":{"start":{"line":286,"column":9},"end":{"line":286,"column":25}},"loc":{"start":{"line":286,"column":49},"end":{"line":296,"column":null}},"line":286},"15":{"name":"getTimeOfDay","decl":{"start":{"line":303,"column":9},"end":{"line":303,"column":21}},"loc":{"start":{"line":303,"column":32},"end":{"line":310,"column":null}},"line":303},"16":{"name":"getHourInTimezone","decl":{"start":{"line":317,"column":9},"end":{"line":317,"column":26}},"loc":{"start":{"line":317,"column":37},"end":{"line":330,"column":null}},"line":317},"17":{"name":"getGreetingTemplates","decl":{"start":{"line":337,"column":9},"end":{"line":337,"column":29}},"loc":{"start":{"line":337,"column":41},"end":{"line":363,"column":null}},"line":337},"18":{"name":"(anonymous_18)","decl":{"start":{"line":340,"column":6},"end":{"line":340,"column":7}},"loc":{"start":{"line":340,"column":15},"end":{"line":340,"column":75}},"line":340},"19":{"name":"(anonymous_19)","decl":{"start":{"line":341,"column":6},"end":{"line":341,"column":7}},"loc":{"start":{"line":341,"column":15},"end":{"line":341,"column":93}},"line":341},"20":{"name":"(anonymous_20)","decl":{"start":{"line":342,"column":6},"end":{"line":342,"column":7}},"loc":{"start":{"line":342,"column":15},"end":{"line":342,"column":80}},"line":342},"21":{"name":"(anonymous_21)","decl":{"start":{"line":345,"column":6},"end":{"line":345,"column":7}},"loc":{"start":{"line":345,"column":15},"end":{"line":345,"column":63}},"line":345},"22":{"name":"(anonymous_22)","decl":{"start":{"line":346,"column":6},"end":{"line":346,"column":7}},"loc":{"start":{"line":347,"column":8},"end":{"line":347,"column":94}},"line":347},"23":{"name":"(anonymous_23)","decl":{"start":{"line":348,"column":6},"end":{"line":348,"column":7}},"loc":{"start":{"line":348,"column":15},"end":{"line":348,"column":75}},"line":348},"24":{"name":"(anonymous_24)","decl":{"start":{"line":351,"column":6},"end":{"line":351,"column":7}},"loc":{"start":{"line":351,"column":15},"end":{"line":351,"column":73}},"line":351},"25":{"name":"(anonymous_25)","decl":{"start":{"line":352,"column":6},"end":{"line":352,"column":7}},"loc":{"start":{"line":352,"column":15},"end":{"line":352,"column":96}},"line":352},"26":{"name":"(anonymous_26)","decl":{"start":{"line":353,"column":6},"end":{"line":353,"column":7}},"loc":{"start":{"line":353,"column":15},"end":{"line":353,"column":87}},"line":353},"27":{"name":"(anonymous_27)","decl":{"start":{"line":356,"column":6},"end":{"line":356,"column":7}},"loc":{"start":{"line":356,"column":15},"end":{"line":356,"column":82}},"line":356},"28":{"name":"(anonymous_28)","decl":{"start":{"line":357,"column":6},"end":{"line":357,"column":7}},"loc":{"start":{"line":357,"column":15},"end":{"line":357,"column":75}},"line":357},"29":{"name":"(anonymous_29)","decl":{"start":{"line":358,"column":6},"end":{"line":358,"column":7}},"loc":{"start":{"line":358,"column":15},"end":{"line":358,"column":90}},"line":358},"30":{"name":"getSuggestedChannels","decl":{"start":{"line":372,"column":9},"end":{"line":372,"column":29}},"loc":{"start":{"line":372,"column":56},"end":{"line":384,"column":null}},"line":372},"31":{"name":"(anonymous_31)","decl":{"start":{"line":380,"column":12},"end":{"line":380,"column":13}},"loc":{"start":{"line":380,"column":20},"end":{"line":380,"column":55}},"line":380},"32":{"name":"(anonymous_32)","decl":{"start":{"line":383,"column":24},"end":{"line":383,"column":25}},"loc":{"start":{"line":383,"column":32},"end":{"line":383,"column":42}},"line":383},"33":{"name":"extractChannelIdsFromTemplate","decl":{"start":{"line":391,"column":9},"end":{"line":391,"column":38}},"loc":{"start":{"line":391,"column":49},"end":{"line":394,"column":null}},"line":391},"34":{"name":"(anonymous_34)","decl":{"start":{"line":393,"column":21},"end":{"line":393,"column":22}},"loc":{"start":{"line":393,"column":32},"end":{"line":393,"column":59}},"line":393},"35":{"name":"getActivityWindowMs","decl":{"start":{"line":401,"column":9},"end":{"line":401,"column":28}},"loc":{"start":{"line":401,"column":39},"end":{"line":404,"column":null}},"line":401},"36":{"name":"pickFrom","decl":{"start":{"line":412,"column":9},"end":{"line":412,"column":17}},"loc":{"start":{"line":412,"column":38},"end":{"line":416,"column":null}},"line":412}},"branchMap":{"0":{"loc":{"start":{"line":28,"column":28},"end":{"line":28,"column":56}},"type":"binary-expr","locations":[{"start":{"line":28,"column":28},"end":{"line":28,"column":43}},{"start":{"line":28,"column":47},"end":{"line":28,"column":56}}],"line":28},"1":{"loc":{"start":{"line":40,"column":2},"end":{"line":40,"column":null}},"type":"if","locations":[{"start":{"line":40,"column":2},"end":{"line":40,"column":null}},{"start":{},"end":{}}],"line":40},"2":{"loc":{"start":{"line":40,"column":6},"end":{"line":40,"column":65}},"type":"binary-expr","locations":[{"start":{"line":40,"column":6},"end":{"line":40,"column":21}},{"start":{"line":40,"column":25},"end":{"line":40,"column":42}},{"start":{"line":40,"column":46},"end":{"line":40,"column":65}}],"line":40},"3":{"loc":{"start":{"line":41,"column":2},"end":{"line":41,"column":null}},"type":"if","locations":[{"start":{"line":41,"column":2},"end":{"line":41,"column":null}},{"start":{},"end":{}}],"line":41},"4":{"loc":{"start":{"line":43,"column":25},"end":{"line":43,"column":55}},"type":"binary-expr","locations":[{"start":{"line":43,"column":25},"end":{"line":43,"column":49}},{"start":{"line":43,"column":53},"end":{"line":43,"column":55}}],"line":43},"5":{"loc":{"start":{"line":44,"column":22},"end":{"line":44,"column":58}},"type":"binary-expr","locations":[{"start":{"line":44,"column":22},"end":{"line":44,"column":52}},{"start":{"line":44,"column":56},"end":{"line":44,"column":58}}],"line":44},"6":{"loc":{"start":{"line":46,"column":2},"end":{"line":48,"column":null}},"type":"if","locations":[{"start":{"line":46,"column":2},"end":{"line":48,"column":null}},{"start":{},"end":{}}],"line":46},"7":{"loc":{"start":{"line":46,"column":6},"end":{"line":46,"column":70}},"type":"binary-expr","locations":[{"start":{"line":46,"column":6},"end":{"line":46,"column":28}},{"start":{"line":46,"column":32},"end":{"line":46,"column":70}}],"line":46},"8":{"loc":{"start":{"line":49,"column":2},"end":{"line":49,"column":null}},"type":"if","locations":[{"start":{"line":49,"column":2},"end":{"line":49,"column":null}},{"start":{},"end":{}}],"line":49},"9":{"loc":{"start":{"line":55,"column":2},"end":{"line":57,"column":null}},"type":"if","locations":[{"start":{"line":55,"column":2},"end":{"line":57,"column":null}},{"start":{},"end":{}}],"line":55},"10":{"loc":{"start":{"line":60,"column":21},"end":{"line":60,"column":62}},"type":"binary-expr","locations":[{"start":{"line":60,"column":21},"end":{"line":60,"column":56}},{"start":{"line":60,"column":60},"end":{"line":60,"column":62}}],"line":60},"11":{"loc":{"start":{"line":63,"column":9},"end":{"line":63,"column":52}},"type":"binary-expr","locations":[{"start":{"line":63,"column":9},"end":{"line":63,"column":26}},{"start":{"line":63,"column":30},"end":{"line":63,"column":52}}],"line":63},"12":{"loc":{"start":{"line":66,"column":2},"end":{"line":68,"column":null}},"type":"if","locations":[{"start":{"line":66,"column":2},"end":{"line":68,"column":null}},{"start":{},"end":{}}],"line":66},"13":{"loc":{"start":{"line":80,"column":2},"end":{"line":80,"column":null}},"type":"if","locations":[{"start":{"line":80,"column":2},"end":{"line":80,"column":null}},{"start":{},"end":{}}],"line":80},"14":{"loc":{"start":{"line":80,"column":6},"end":{"line":80,"column":60}},"type":"binary-expr","locations":[{"start":{"line":80,"column":6},"end":{"line":80,"column":30}},{"start":{"line":80,"column":34},"end":{"line":80,"column":60}}],"line":80},"15":{"loc":{"start":{"line":84,"column":4},"end":{"line":84,"column":null}},"type":"if","locations":[{"start":{"line":84,"column":4},"end":{"line":84,"column":null}},{"start":{},"end":{}}],"line":84},"16":{"loc":{"start":{"line":88,"column":20},"end":{"line":94,"column":9}},"type":"cond-expr","locations":[{"start":{"line":89,"column":8},"end":{"line":89,"column":null}},{"start":{"line":90,"column":8},"end":{"line":94,"column":9}}],"line":88},"17":{"loc":{"start":{"line":91,"column":10},"end":{"line":91,"column":54}},"type":"binary-expr","locations":[{"start":{"line":91,"column":10},"end":{"line":91,"column":32}},{"start":{"line":91,"column":36},"end":{"line":91,"column":54}}],"line":91},"18":{"loc":{"start":{"line":110,"column":25},"end":{"line":110,"column":55}},"type":"binary-expr","locations":[{"start":{"line":110,"column":25},"end":{"line":110,"column":49}},{"start":{"line":110,"column":53},"end":{"line":110,"column":55}}],"line":110},"19":{"loc":{"start":{"line":111,"column":19},"end":{"line":111,"column":64}},"type":"binary-expr","locations":[{"start":{"line":111,"column":19},"end":{"line":111,"column":42}},{"start":{"line":111,"column":46},"end":{"line":111,"column":64}}],"line":111},"20":{"loc":{"start":{"line":115,"column":14},"end":{"line":115,"column":48}},"type":"binary-expr","locations":[{"start":{"line":115,"column":14},"end":{"line":115,"column":35}},{"start":{"line":115,"column":39},"end":{"line":115,"column":48}}],"line":115},"21":{"loc":{"start":{"line":116,"column":12},"end":{"line":116,"column":46}},"type":"binary-expr","locations":[{"start":{"line":116,"column":12},"end":{"line":116,"column":30}},{"start":{"line":116,"column":34},"end":{"line":116,"column":46}}],"line":116},"22":{"loc":{"start":{"line":117,"column":17},"end":{"line":117,"column":47}},"type":"binary-expr","locations":[{"start":{"line":117,"column":17},"end":{"line":117,"column":42}},{"start":{"line":117,"column":46},"end":{"line":117,"column":47}}],"line":117},"23":{"loc":{"start":{"line":131,"column":2},"end":{"line":135,"column":null}},"type":"if","locations":[{"start":{"line":131,"column":2},"end":{"line":135,"column":null}},{"start":{"line":133,"column":9},"end":{"line":135,"column":null}}],"line":131},"24":{"loc":{"start":{"line":150,"column":22},"end":{"line":150,"column":62}},"type":"binary-expr","locations":[{"start":{"line":150,"column":22},"end":{"line":150,"column":49}},{"start":{"line":150,"column":53},"end":{"line":150,"column":62}}],"line":150},"25":{"loc":{"start":{"line":161,"column":4},"end":{"line":164,"column":null}},"type":"if","locations":[{"start":{"line":161,"column":4},"end":{"line":164,"column":null}},{"start":{},"end":{}}],"line":161},"26":{"loc":{"start":{"line":174,"column":2},"end":{"line":176,"column":null}},"type":"if","locations":[{"start":{"line":174,"column":2},"end":{"line":176,"column":null}},{"start":{},"end":{}}],"line":174},"27":{"loc":{"start":{"line":184,"column":17},"end":{"line":184,"column":71}},"type":"binary-expr","locations":[{"start":{"line":184,"column":17},"end":{"line":184,"column":42}},{"start":{"line":184,"column":46},"end":{"line":184,"column":71}}],"line":184},"28":{"loc":{"start":{"line":189,"column":29},"end":{"line":189,"column":55}},"type":"binary-expr","locations":[{"start":{"line":189,"column":29},"end":{"line":189,"column":50}},{"start":{"line":189,"column":54},"end":{"line":189,"column":55}}],"line":189},"29":{"loc":{"start":{"line":212,"column":2},"end":{"line":212,"column":null}},"type":"if","locations":[{"start":{"line":212,"column":2},"end":{"line":212,"column":null}},{"start":{},"end":{}}],"line":212},"30":{"loc":{"start":{"line":212,"column":6},"end":{"line":212,"column":51}},"type":"binary-expr","locations":[{"start":{"line":212,"column":6},"end":{"line":212,"column":24}},{"start":{"line":212,"column":28},"end":{"line":212,"column":51}}],"line":212},"31":{"loc":{"start":{"line":213,"column":2},"end":{"line":213,"column":null}},"type":"if","locations":[{"start":{"line":213,"column":2},"end":{"line":213,"column":null}},{"start":{},"end":{}}],"line":213},"32":{"loc":{"start":{"line":213,"column":6},"end":{"line":213,"column":50}},"type":"binary-expr","locations":[{"start":{"line":213,"column":6},"end":{"line":213,"column":24}},{"start":{"line":213,"column":28},"end":{"line":213,"column":50}}],"line":213},"33":{"loc":{"start":{"line":214,"column":2},"end":{"line":214,"column":null}},"type":"if","locations":[{"start":{"line":214,"column":2},"end":{"line":214,"column":null}},{"start":{},"end":{}}],"line":214},"34":{"loc":{"start":{"line":214,"column":6},"end":{"line":214,"column":49}},"type":"binary-expr","locations":[{"start":{"line":214,"column":6},"end":{"line":214,"column":23}},{"start":{"line":214,"column":27},"end":{"line":214,"column":49}}],"line":214},"35":{"loc":{"start":{"line":215,"column":2},"end":{"line":215,"column":null}},"type":"if","locations":[{"start":{"line":215,"column":2},"end":{"line":215,"column":null}},{"start":{},"end":{}}],"line":215},"36":{"loc":{"start":{"line":215,"column":6},"end":{"line":215,"column":49}},"type":"binary-expr","locations":[{"start":{"line":215,"column":6},"end":{"line":215,"column":23}},{"start":{"line":215,"column":27},"end":{"line":215,"column":49}}],"line":215},"37":{"loc":{"start":{"line":227,"column":23},"end":{"line":227,"column":75}},"type":"cond-expr","locations":[{"start":{"line":227,"column":44},"end":{"line":227,"column":55}},{"start":{"line":227,"column":58},"end":{"line":227,"column":75}}],"line":227},"38":{"loc":{"start":{"line":231,"column":2},"end":{"line":256,"column":null}},"type":"switch","locations":[{"start":{"line":232,"column":4},"end":{"line":235,"column":null}},{"start":{"line":236,"column":4},"end":{"line":239,"column":null}},{"start":{"line":240,"column":4},"end":{"line":243,"column":null}},{"start":{"line":244,"column":4},"end":{"line":253,"column":null}},{"start":{"line":254,"column":4},"end":{"line":255,"column":null}}],"line":231},"39":{"loc":{"start":{"line":233,"column":13},"end":{"line":235,"column":67}},"type":"cond-expr","locations":[{"start":{"line":234,"column":10},"end":{"line":234,"column":null}},{"start":{"line":235,"column":10},"end":{"line":235,"column":67}}],"line":233},"40":{"loc":{"start":{"line":237,"column":13},"end":{"line":239,"column":184}},"type":"cond-expr","locations":[{"start":{"line":238,"column":10},"end":{"line":238,"column":null}},{"start":{"line":239,"column":10},"end":{"line":239,"column":184}}],"line":237},"41":{"loc":{"start":{"line":239,"column":100},"end":{"line":239,"column":180}},"type":"cond-expr","locations":[{"start":{"line":239,"column":133},"end":{"line":239,"column":175}},{"start":{"line":239,"column":178},"end":{"line":239,"column":180}}],"line":239},"42":{"loc":{"start":{"line":241,"column":13},"end":{"line":243,"column":72}},"type":"cond-expr","locations":[{"start":{"line":242,"column":10},"end":{"line":242,"column":null}},{"start":{"line":243,"column":10},"end":{"line":243,"column":72}}],"line":241},"43":{"loc":{"start":{"line":245,"column":6},"end":{"line":247,"column":null}},"type":"if","locations":[{"start":{"line":245,"column":6},"end":{"line":247,"column":null}},{"start":{},"end":{}}],"line":245},"44":{"loc":{"start":{"line":245,"column":10},"end":{"line":245,"column":52}},"type":"binary-expr","locations":[{"start":{"line":245,"column":10},"end":{"line":245,"column":36}},{"start":{"line":245,"column":40},"end":{"line":245,"column":52}}],"line":245},"45":{"loc":{"start":{"line":246,"column":48},"end":{"line":246,"column":109}},"type":"cond-expr","locations":[{"start":{"line":246,"column":83},"end":{"line":246,"column":94}},{"start":{"line":246,"column":97},"end":{"line":246,"column":109}}],"line":246},"46":{"loc":{"start":{"line":248,"column":6},"end":{"line":250,"column":null}},"type":"if","locations":[{"start":{"line":248,"column":6},"end":{"line":250,"column":null}},{"start":{},"end":{}}],"line":248},"47":{"loc":{"start":{"line":249,"column":48},"end":{"line":249,"column":109}},"type":"cond-expr","locations":[{"start":{"line":249,"column":83},"end":{"line":249,"column":94}},{"start":{"line":249,"column":97},"end":{"line":249,"column":109}}],"line":249},"48":{"loc":{"start":{"line":251,"column":13},"end":{"line":253,"column":60}},"type":"cond-expr","locations":[{"start":{"line":252,"column":10},"end":{"line":252,"column":null}},{"start":{"line":253,"column":10},"end":{"line":253,"column":60}}],"line":251},"49":{"loc":{"start":{"line":267,"column":2},"end":{"line":269,"column":null}},"type":"if","locations":[{"start":{"line":267,"column":2},"end":{"line":269,"column":null}},{"start":{},"end":{}}],"line":267},"50":{"loc":{"start":{"line":267,"column":6},"end":{"line":267,"column":30}},"type":"binary-expr","locations":[{"start":{"line":267,"column":6},"end":{"line":267,"column":11}},{"start":{"line":267,"column":15},"end":{"line":267,"column":21}},{"start":{"line":267,"column":25},"end":{"line":267,"column":30}}],"line":267},"51":{"loc":{"start":{"line":270,"column":2},"end":{"line":272,"column":null}},"type":"if","locations":[{"start":{"line":270,"column":2},"end":{"line":272,"column":null}},{"start":{},"end":{}}],"line":270},"52":{"loc":{"start":{"line":270,"column":6},"end":{"line":270,"column":21}},"type":"binary-expr","locations":[{"start":{"line":270,"column":6},"end":{"line":270,"column":11}},{"start":{"line":270,"column":15},"end":{"line":270,"column":21}}],"line":270},"53":{"loc":{"start":{"line":273,"column":2},"end":{"line":275,"column":null}},"type":"if","locations":[{"start":{"line":273,"column":2},"end":{"line":275,"column":null}},{"start":{},"end":{}}],"line":273},"54":{"loc":{"start":{"line":287,"column":2},"end":{"line":287,"column":null}},"type":"if","locations":[{"start":{"line":287,"column":2},"end":{"line":287,"column":null}},{"start":{},"end":{}}],"line":287},"55":{"loc":{"start":{"line":289,"column":19},"end":{"line":289,"column":59}},"type":"binary-expr","locations":[{"start":{"line":289,"column":19},"end":{"line":289,"column":53}},{"start":{"line":289,"column":57},"end":{"line":289,"column":59}}],"line":289},"56":{"loc":{"start":{"line":291,"column":2},"end":{"line":293,"column":null}},"type":"if","locations":[{"start":{"line":291,"column":2},"end":{"line":293,"column":null}},{"start":{},"end":{}}],"line":291},"57":{"loc":{"start":{"line":291,"column":6},"end":{"line":291,"column":91}},"type":"binary-expr","locations":[{"start":{"line":291,"column":6},"end":{"line":291,"column":41}},{"start":{"line":291,"column":46},"end":{"line":291,"column":58}},{"start":{"line":291,"column":62},"end":{"line":291,"column":90}}],"line":291},"58":{"loc":{"start":{"line":306,"column":2},"end":{"line":306,"column":null}},"type":"if","locations":[{"start":{"line":306,"column":2},"end":{"line":306,"column":null}},{"start":{},"end":{}}],"line":306},"59":{"loc":{"start":{"line":306,"column":6},"end":{"line":306,"column":28}},"type":"binary-expr","locations":[{"start":{"line":306,"column":6},"end":{"line":306,"column":15}},{"start":{"line":306,"column":19},"end":{"line":306,"column":28}}],"line":306},"60":{"loc":{"start":{"line":307,"column":2},"end":{"line":307,"column":null}},"type":"if","locations":[{"start":{"line":307,"column":2},"end":{"line":307,"column":null}},{"start":{},"end":{}}],"line":307},"61":{"loc":{"start":{"line":307,"column":6},"end":{"line":307,"column":29}},"type":"binary-expr","locations":[{"start":{"line":307,"column":6},"end":{"line":307,"column":16}},{"start":{"line":307,"column":20},"end":{"line":307,"column":29}}],"line":307},"62":{"loc":{"start":{"line":308,"column":2},"end":{"line":308,"column":null}},"type":"if","locations":[{"start":{"line":308,"column":2},"end":{"line":308,"column":null}},{"start":{},"end":{}}],"line":308},"63":{"loc":{"start":{"line":308,"column":6},"end":{"line":308,"column":29}},"type":"binary-expr","locations":[{"start":{"line":308,"column":6},"end":{"line":308,"column":16}},{"start":{"line":308,"column":20},"end":{"line":308,"column":29}}],"line":308},"64":{"loc":{"start":{"line":326,"column":11},"end":{"line":326,"column":63}},"type":"cond-expr","locations":[{"start":{"line":326,"column":35},"end":{"line":326,"column":39}},{"start":{"line":326,"column":42},"end":{"line":326,"column":63}}],"line":326},"65":{"loc":{"start":{"line":362,"column":9},"end":{"line":362,"column":52}},"type":"binary-expr","locations":[{"start":{"line":362,"column":9},"end":{"line":362,"column":29}},{"start":{"line":362,"column":33},"end":{"line":362,"column":52}}],"line":362},"66":{"loc":{"start":{"line":373,"column":18},"end":{"line":373,"column":48}},"type":"binary-expr","locations":[{"start":{"line":373,"column":18},"end":{"line":373,"column":42}},{"start":{"line":373,"column":46},"end":{"line":373,"column":48}}],"line":373},"67":{"loc":{"start":{"line":374,"column":21},"end":{"line":374,"column":94}},"type":"cond-expr","locations":[{"start":{"line":374,"column":64},"end":{"line":374,"column":89}},{"start":{"line":374,"column":92},"end":{"line":374,"column":94}}],"line":374},"68":{"loc":{"start":{"line":375,"column":47},"end":{"line":375,"column":77}},"type":"binary-expr","locations":[{"start":{"line":375,"column":47},"end":{"line":375,"column":71}},{"start":{"line":375,"column":75},"end":{"line":375,"column":77}}],"line":375},"69":{"loc":{"start":{"line":376,"column":14},"end":{"line":376,"column":42}},"type":"binary-expr","locations":[{"start":{"line":376,"column":14},"end":{"line":376,"column":36}},{"start":{"line":376,"column":40},"end":{"line":376,"column":42}}],"line":376},"70":{"loc":{"start":{"line":392,"column":18},"end":{"line":392,"column":51}},"type":"binary-expr","locations":[{"start":{"line":392,"column":18},"end":{"line":392,"column":45}},{"start":{"line":392,"column":49},"end":{"line":392,"column":51}}],"line":392},"71":{"loc":{"start":{"line":402,"column":18},"end":{"line":402,"column":91}},"type":"binary-expr","locations":[{"start":{"line":402,"column":18},"end":{"line":402,"column":56}},{"start":{"line":402,"column":60},"end":{"line":402,"column":91}}],"line":402},"72":{"loc":{"start":{"line":413,"column":2},"end":{"line":413,"column":null}},"type":"if","locations":[{"start":{"line":413,"column":2},"end":{"line":413,"column":null}},{"start":{},"end":{}}],"line":413}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":14,"6":11,"7":2,"8":9,"9":1,"10":8,"11":11,"12":11,"13":11,"14":3,"15":8,"16":1,"17":7,"18":7,"19":7,"20":7,"21":1,"22":7,"23":7,"24":11,"25":11,"26":0,"27":7,"28":0,"29":7,"30":18,"31":2,"32":16,"33":16,"34":15,"35":1,"36":14,"37":18,"38":18,"39":5,"40":10,"41":8,"42":8,"43":8,"44":8,"45":8,"46":8,"47":8,"48":8,"49":8,"50":8,"51":8,"52":8,"53":0,"54":0,"55":0,"56":0,"57":0,"58":8,"59":8,"60":8,"61":8,"62":8,"63":8,"64":8,"65":16,"66":56,"67":16,"68":0,"69":0,"70":16,"71":16,"72":16,"73":8,"74":0,"75":8,"76":8,"77":16,"78":8,"79":0,"80":8,"81":8,"82":0,"83":8,"84":8,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0,"104":0,"105":0,"106":0,"107":0,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":0,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0,"121":0,"122":0,"123":8,"124":8,"125":6,"126":2,"127":2,"128":0,"129":0,"130":0,"131":8,"132":8,"133":8,"134":8,"135":1,"136":0,"137":0,"138":0,"139":0,"140":0,"141":0,"142":0,"143":0,"144":0,"145":0,"146":0,"147":0,"148":0,"149":0,"150":0,"151":0,"152":0,"153":0,"154":0,"155":0,"156":0,"157":0,"158":0,"159":0,"160":0,"161":15,"162":15,"163":0,"164":0,"165":0,"166":0},"f":{"0":14,"1":11,"2":18,"3":8,"4":8,"5":56,"6":8,"7":16,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":8,"16":8,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":15,"36":0},"b":{"0":[14,1],"1":[2,9],"2":[11,10,10],"3":[1,8],"4":[8,1],"5":[11,1],"6":[3,8],"7":[11,7],"8":[1,7],"9":[1,6],"10":[7,2],"11":[11,7],"12":[0,7],"13":[2,16],"14":[18,17],"15":[1,14],"16":[8,6],"17":[6,1],"18":[8,0],"19":[8,1],"20":[8,0],"21":[8,0],"22":[8,0],"23":[0,0],"24":[8,0],"25":[0,16],"26":[0,8],"27":[0,0],"28":[0,0],"29":[0,0],"30":[0,0],"31":[0,0],"32":[0,0],"33":[0,0],"34":[0,0],"35":[0,0],"36":[0,0],"37":[0,0],"38":[0,0,0,0,0],"39":[0,0],"40":[0,0],"41":[0,0],"42":[0,0],"43":[0,0],"44":[0,0],"45":[0,0],"46":[0,0],"47":[0,0],"48":[0,0],"49":[0,0],"50":[0,0,0],"51":[0,0],"52":[0,0],"53":[0,0],"54":[0,0],"55":[0,0],"56":[0,0],"57":[0,0,0],"58":[6,2],"59":[8,8],"60":[2,0],"61":[2,2],"62":[0,0],"63":[0,0],"64":[7,0],"65":[0,0],"66":[0,0],"67":[0,0],"68":[0,0],"69":[0,0],"70":[0,0],"71":[15,3],"72":[0,0]},"meta":{"lastBranch":73,"lastFunction":37,"lastStatement":167,"seen":{"s:8:22:8:31":0,"s:9:40:9:42":1,"s:10:31:10:34":2,"s:13:27:13:69":3,"s:16:28:16:32":4,"f:25:16:25:36":0,"s:26:2:30:Infinity":5,"b:28:28:28:43:28:47:28:56":0,"f:39:16:39:39":1,"b:40:2:40:Infinity:undefined:undefined:undefined:undefined":1,"s:40:2:40:Infinity":6,"b:40:6:40:21:40:25:40:42:40:46:40:65":2,"s:40:67:40:Infinity":7,"b:41:2:41:Infinity:undefined:undefined:undefined:undefined":3,"s:41:2:41:Infinity":8,"s:41:41:41:Infinity":9,"s:43:25:43:55":10,"b:43:25:43:49:43:53:43:55":4,"s:44:22:44:58":11,"b:44:22:44:52:44:56:44:58":5,"s:45:19:45:40":12,"b:46:2:48:Infinity:undefined:undefined:undefined:undefined":6,"s:46:2:48:Infinity":13,"b:46:6:46:28:46:32:46:70":7,"s:47:4:47:Infinity":14,"b:49:2:49:Infinity:undefined:undefined:undefined:undefined":8,"s:49:2:49:Infinity":15,"s:49:57:49:Infinity":16,"s:51:14:51:24":17,"s:52:19:52:54":18,"s:53:17:53:31":19,"b:55:2:57:Infinity:undefined:undefined:undefined:undefined":9,"s:55:2:57:Infinity":20,"s:56:4:56:Infinity":21,"s:59:22:59:57":22,"s:60:21:60:62":23,"b:60:21:60:56:60:60:60:62":10,"s:62:2:62:Infinity":24,"s:63:2:65:Infinity":25,"b:63:9:63:26:63:30:63:52":11,"s:64:4:64:Infinity":26,"b:66:2:68:Infinity:undefined:undefined:undefined:undefined":12,"s:66:2:68:Infinity":27,"s:67:4:67:Infinity":28,"s:70:2:70:Infinity":29,"f:79:22:79:40":2,"b:80:2:80:Infinity:undefined:undefined:undefined:undefined":13,"s:80:2:80:Infinity":30,"b:80:6:80:30:80:34:80:60":14,"s:80:62:80:Infinity":31,"s:82:2:100:Infinity":32,"s:83:20:83:73":33,"b:84:4:84:Infinity:undefined:undefined:undefined:undefined":15,"s:84:4:84:Infinity":34,"s:84:18:84:Infinity":35,"s:86:23:86:64":36,"s:88:20:94:9":37,"b:89:8:89:Infinity:90:8:94:9":16,"b:91:10:91:32:91:36:91:54":17,"s:96:4:96:Infinity":38,"s:97:3:97:Infinity":39,"s:99:3:99:Infinity":40,"f:109:9:109:35":3,"s:110:25:110:55":41,"b:110:25:110:49:110:53:110:55":18,"s:111:19:111:64":42,"b:111:19:111:42:111:46:111:64":19,"s:113:24:118:3":43,"b:115:14:115:35:115:39:115:48":20,"b:116:12:116:30:116:34:116:46":21,"b:117:17:117:42:117:46:117:47":22,"s:120:20:120:42":44,"s:121:19:121:69":45,"s:122:24:122:83":46,"s:123:28:123:74":47,"s:125:19:125:75":48,"s:126:19:126:61":49,"s:127:18:127:49":50,"s:129:16:129:26":51,"b:131:2:135:Infinity:133:9:135:Infinity":23,"s:131:2:135:Infinity":52,"s:132:4:132:Infinity":53,"s:134:4:134:Infinity":54,"s:137:2:137:Infinity":55,"s:138:2:138:Infinity":56,"s:140:2:140:Infinity":57,"f:149:9:149:29":4,"s:150:22:150:62":58,"b:150:22:150:49:150:53:150:62":24,"s:151:14:151:24":59,"s:152:19:152:48":60,"s:153:17:153:31":61,"s:155:21:155:22":62,"s:156:24:156:26":63,"s:158:2:171:Infinity":64,"s:159:19:159:56":65,"f:159:37:159:38":5,"s:159:44:159:55":66,"b:161:4:164:Infinity:undefined:undefined:undefined:undefined":25,"s:161:4:164:Infinity":67,"s:162:6:162:Infinity":68,"s:163:6:163:Infinity":69,"s:167:4:167:Infinity":70,"s:169:4:169:Infinity":71,"s:170:4:170:Infinity":72,"b:174:2:176:Infinity:undefined:undefined:undefined:undefined":26,"s:174:2:176:Infinity":73,"s:175:4:175:Infinity":74,"s:178:24:181:36":75,"f:179:10:179:11":6,"s:179:20:179:37":76,"f:181:9:181:10":7,"s:181:20:181:35":77,"s:183:30:185:3":78,"f:184:4:184:5":8,"s:184:17:184:71":79,"b:184:17:184:42:184:46:184:71":27,"s:187:24:187:48":80,"s:188:28:191:3":81,"f:189:4:189:5":9,"s:189:22:189:56":82,"b:189:29:189:50:189:54:189:55":28,"s:193:16:193:65":83,"s:195:2:202:Infinity":84,"f:211:9:211:25":10,"b:212:2:212:Infinity:undefined:undefined:undefined:undefined":29,"s:212:2:212:Infinity":85,"b:212:6:212:24:212:28:212:51":30,"s:212:53:212:Infinity":86,"b:213:2:213:Infinity:undefined:undefined:undefined:undefined":31,"s:213:2:213:Infinity":87,"b:213:6:213:24:213:28:213:50":32,"s:213:52:213:Infinity":88,"b:214:2:214:Infinity:undefined:undefined:undefined:undefined":33,"s:214:2:214:Infinity":89,"b:214:6:214:23:214:27:214:49":34,"s:214:51:214:Infinity":90,"b:215:2:215:Infinity:undefined:undefined:undefined:undefined":35,"s:215:2:215:Infinity":91,"b:215:6:215:23:215:27:215:49":36,"s:215:51:215:Infinity":92,"s:216:2:216:Infinity":93,"f:225:9:225:22":11,"s:226:22:226:68":94,"f:226:49:226:50":12,"s:226:57:226:67":95,"s:227:22:227:88":96,"b:227:44:227:55:227:58:227:75":37,"s:228:22:228:45":97,"s:229:22:229:44":98,"b:232:4:235:Infinity:236:4:239:Infinity:240:4:243:Infinity:244:4:253:Infinity:254:4:255:Infinity":38,"s:231:2:256:Infinity":99,"s:233:6:235:Infinity":100,"b:234:10:234:Infinity:235:10:235:67":39,"s:237:6:239:Infinity":101,"b:238:10:238:Infinity:239:10:239:184":40,"b:239:133:239:175:239:178:239:180":41,"s:241:6:243:Infinity":102,"b:242:10:242:Infinity:243:10:243:72":42,"b:245:6:247:Infinity:undefined:undefined:undefined:undefined":43,"s:245:6:247:Infinity":103,"b:245:10:245:36:245:40:245:52":44,"s:246:8:246:Infinity":104,"b:246:83:246:94:246:97:246:109":45,"b:248:6:250:Infinity:undefined:undefined:undefined:undefined":46,"s:248:6:250:Infinity":105,"s:249:8:249:Infinity":106,"b:249:83:249:94:249:97:249:109":47,"s:251:6:253:Infinity":107,"b:252:10:252:Infinity:253:10:253:60":48,"s:255:6:255:Infinity":108,"f:264:9:264:21":13,"s:265:33:265:41":109,"b:267:2:269:Infinity:undefined:undefined:undefined:undefined":49,"s:267:2:269:Infinity":110,"b:267:6:267:11:267:15:267:21:267:25:267:30":50,"s:268:4:268:Infinity":111,"b:270:2:272:Infinity:undefined:undefined:undefined:undefined":51,"s:270:2:272:Infinity":112,"b:270:6:270:11:270:15:270:21":52,"s:271:4:271:Infinity":113,"b:273:2:275:Infinity:undefined:undefined:undefined:undefined":53,"s:273:2:275:Infinity":114,"s:274:4:274:Infinity":115,"s:277:2:277:Infinity":116,"f:286:9:286:25":14,"b:287:2:287:Infinity:undefined:undefined:undefined:undefined":54,"s:287:2:287:Infinity":117,"s:287:20:287:Infinity":118,"s:289:19:289:59":119,"b:289:19:289:53:289:57:289:59":55,"b:291:2:293:Infinity:undefined:undefined:undefined:undefined":56,"s:291:2:293:Infinity":120,"b:291:6:291:41:291:46:291:58:291:62:291:90":57,"s:292:4:292:Infinity":121,"s:295:2:295:Infinity":122,"f:303:9:303:21":15,"s:304:15:304:42":123,"b:306:2:306:Infinity:undefined:undefined:undefined:undefined":58,"s:306:2:306:Infinity":124,"b:306:6:306:15:306:19:306:28":59,"s:306:30:306:Infinity":125,"b:307:2:307:Infinity:undefined:undefined:undefined:undefined":60,"s:307:2:307:Infinity":126,"b:307:6:307:16:307:20:307:29":61,"s:307:31:307:Infinity":127,"b:308:2:308:Infinity:undefined:undefined:undefined:undefined":62,"s:308:2:308:Infinity":128,"b:308:6:308:16:308:20:308:29":63,"s:308:31:308:Infinity":129,"s:309:2:309:Infinity":130,"f:317:9:317:26":16,"s:318:2:329:Infinity":131,"s:319:23:323:25":132,"s:325:17:325:35":133,"s:326:4:326:Infinity":134,"b:326:35:326:39:326:42:326:63":64,"s:328:4:328:Infinity":135,"f:337:9:337:29":17,"s:338:20:360:3":136,"f:340:6:340:7":18,"s:340:15:340:75":137,"f:341:6:341:7":19,"s:341:15:341:93":138,"f:342:6:342:7":20,"s:342:15:342:80":139,"f:345:6:345:7":21,"s:345:15:345:63":140,"f:346:6:346:7":22,"s:347:8:347:94":141,"f:348:6:348:7":23,"s:348:15:348:75":142,"f:351:6:351:7":24,"s:351:15:351:73":143,"f:352:6:352:7":25,"s:352:15:352:96":144,"f:353:6:353:7":26,"s:353:15:353:87":145,"f:356:6:356:7":27,"s:356:15:356:82":146,"f:357:6:357:7":28,"s:357:15:357:75":147,"f:358:6:358:7":29,"s:358:15:358:90":148,"s:362:2:362:Infinity":149,"b:362:9:362:29:362:33:362:52":65,"f:372:9:372:29":30,"s:373:18:373:48":150,"b:373:18:373:42:373:46:373:48":66,"s:374:21:374:94":151,"b:374:64:374:89:374:92:374:94":67,"s:375:17:375:78":152,"b:375:47:375:71:375:75:375:77":68,"s:376:14:376:42":153,"b:376:14:376:36:376:40:376:42":69,"s:378:21:381:16":154,"f:380:12:380:13":31,"s:380:20:380:55":155,"s:383:2:383:Infinity":156,"f:383:24:383:25":32,"s:383:32:383:42":157,"f:391:9:391:38":33,"s:392:18:392:51":158,"b:392:18:392:45:392:49:392:51":70,"s:393:2:393:Infinity":159,"f:393:21:393:22":34,"s:393:32:393:59":160,"f:401:9:401:28":35,"s:402:18:402:91":161,"b:402:18:402:56:402:60:402:91":71,"s:403:2:403:Infinity":162,"f:412:9:412:17":36,"b:413:2:413:Infinity:undefined:undefined:undefined:undefined":72,"s:413:2:413:Infinity":163,"s:413:25:413:Infinity":164,"s:414:16:414:60":165,"s:415:2:415:Infinity":166}}} +,"/home/jailuser/git/src/utils/errors.js": {"path":"/home/jailuser/git/src/utils/errors.js","statementMap":{"0":{"start":{"line":11,"column":25},"end":{"line":34,"column":1}},"1":{"start":{"line":44,"column":2},"end":{"line":44,"column":null}},"2":{"start":{"line":44,"column":14},"end":{"line":44,"column":null}},"3":{"start":{"line":46,"column":18},"end":{"line":46,"column":52}},"4":{"start":{"line":47,"column":15},"end":{"line":47,"column":41}},"5":{"start":{"line":48,"column":17},"end":{"line":48,"column":69}},"6":{"start":{"line":51,"column":2},"end":{"line":53,"column":null}},"7":{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},"8":{"start":{"line":54,"column":2},"end":{"line":56,"column":null}},"9":{"start":{"line":55,"column":4},"end":{"line":55,"column":null}},"10":{"start":{"line":57,"column":2},"end":{"line":59,"column":null}},"11":{"start":{"line":58,"column":4},"end":{"line":58,"column":null}},"12":{"start":{"line":62,"column":2},"end":{"line":78,"column":null}},"13":{"start":{"line":63,"column":4},"end":{"line":65,"column":null}},"14":{"start":{"line":64,"column":6},"end":{"line":64,"column":null}},"15":{"start":{"line":66,"column":4},"end":{"line":68,"column":null}},"16":{"start":{"line":67,"column":6},"end":{"line":67,"column":null}},"17":{"start":{"line":69,"column":4},"end":{"line":71,"column":null}},"18":{"start":{"line":70,"column":6},"end":{"line":70,"column":null}},"19":{"start":{"line":72,"column":4},"end":{"line":74,"column":null}},"20":{"start":{"line":73,"column":6},"end":{"line":73,"column":null}},"21":{"start":{"line":75,"column":4},"end":{"line":77,"column":null}},"22":{"start":{"line":76,"column":6},"end":{"line":76,"column":null}},"23":{"start":{"line":81,"column":2},"end":{"line":83,"column":null}},"24":{"start":{"line":82,"column":4},"end":{"line":82,"column":null}},"25":{"start":{"line":84,"column":2},"end":{"line":86,"column":null}},"26":{"start":{"line":85,"column":4},"end":{"line":85,"column":null}},"27":{"start":{"line":87,"column":2},"end":{"line":89,"column":null}},"28":{"start":{"line":88,"column":4},"end":{"line":88,"column":null}},"29":{"start":{"line":92,"column":2},"end":{"line":94,"column":null}},"30":{"start":{"line":93,"column":4},"end":{"line":93,"column":null}},"31":{"start":{"line":95,"column":2},"end":{"line":97,"column":null}},"32":{"start":{"line":96,"column":4},"end":{"line":96,"column":null}},"33":{"start":{"line":100,"column":2},"end":{"line":102,"column":null}},"34":{"start":{"line":101,"column":4},"end":{"line":101,"column":null}},"35":{"start":{"line":104,"column":2},"end":{"line":104,"column":null}},"36":{"start":{"line":115,"column":20},"end":{"line":115,"column":49}},"37":{"start":{"line":117,"column":19},"end":{"line":156,"column":3}},"38":{"start":{"line":158,"column":2},"end":{"line":158,"column":null}},"39":{"start":{"line":169,"column":20},"end":{"line":169,"column":49}},"40":{"start":{"line":171,"column":22},"end":{"line":200,"column":3}},"41":{"start":{"line":202,"column":2},"end":{"line":202,"column":null}},"42":{"start":{"line":213,"column":20},"end":{"line":213,"column":49}},"43":{"start":{"line":216,"column":25},"end":{"line":221,"column":3}},"44":{"start":{"line":223,"column":2},"end":{"line":223,"column":null}}},"fnMap":{"0":{"name":"classifyError","decl":{"start":{"line":43,"column":16},"end":{"line":43,"column":29}},"loc":{"start":{"line":43,"column":51},"end":{"line":105,"column":null}},"line":43},"1":{"name":"getUserFriendlyMessage","decl":{"start":{"line":114,"column":16},"end":{"line":114,"column":38}},"loc":{"start":{"line":114,"column":60},"end":{"line":159,"column":null}},"line":114},"2":{"name":"getSuggestedNextSteps","decl":{"start":{"line":168,"column":16},"end":{"line":168,"column":37}},"loc":{"start":{"line":168,"column":59},"end":{"line":203,"column":null}},"line":168},"3":{"name":"isRetryable","decl":{"start":{"line":212,"column":16},"end":{"line":212,"column":27}},"loc":{"start":{"line":212,"column":49},"end":{"line":224,"column":null}},"line":212}},"branchMap":{"0":{"loc":{"start":{"line":43,"column":37},"end":{"line":43,"column":49}},"type":"default-arg","locations":[{"start":{"line":43,"column":47},"end":{"line":43,"column":49}}],"line":43},"1":{"loc":{"start":{"line":44,"column":2},"end":{"line":44,"column":null}},"type":"if","locations":[{"start":{"line":44,"column":2},"end":{"line":44,"column":null}},{"start":{},"end":{}}],"line":44},"2":{"loc":{"start":{"line":46,"column":18},"end":{"line":46,"column":52}},"type":"binary-expr","locations":[{"start":{"line":46,"column":18},"end":{"line":46,"column":46}},{"start":{"line":46,"column":50},"end":{"line":46,"column":52}}],"line":46},"3":{"loc":{"start":{"line":47,"column":15},"end":{"line":47,"column":41}},"type":"binary-expr","locations":[{"start":{"line":47,"column":15},"end":{"line":47,"column":25}},{"start":{"line":47,"column":29},"end":{"line":47,"column":41}}],"line":47},"4":{"loc":{"start":{"line":48,"column":17},"end":{"line":48,"column":69}},"type":"binary-expr","locations":[{"start":{"line":48,"column":17},"end":{"line":48,"column":29}},{"start":{"line":48,"column":33},"end":{"line":48,"column":47}},{"start":{"line":48,"column":51},"end":{"line":48,"column":69}}],"line":48},"5":{"loc":{"start":{"line":51,"column":2},"end":{"line":53,"column":null}},"type":"if","locations":[{"start":{"line":51,"column":2},"end":{"line":53,"column":null}},{"start":{},"end":{}}],"line":51},"6":{"loc":{"start":{"line":51,"column":6},"end":{"line":51,"column":77}},"type":"binary-expr","locations":[{"start":{"line":51,"column":6},"end":{"line":51,"column":29}},{"start":{"line":51,"column":33},"end":{"line":51,"column":53}},{"start":{"line":51,"column":57},"end":{"line":51,"column":77}}],"line":51},"7":{"loc":{"start":{"line":54,"column":2},"end":{"line":56,"column":null}},"type":"if","locations":[{"start":{"line":54,"column":2},"end":{"line":56,"column":null}},{"start":{},"end":{}}],"line":54},"8":{"loc":{"start":{"line":54,"column":6},"end":{"line":54,"column":57}},"type":"binary-expr","locations":[{"start":{"line":54,"column":6},"end":{"line":54,"column":26}},{"start":{"line":54,"column":30},"end":{"line":54,"column":57}}],"line":54},"9":{"loc":{"start":{"line":57,"column":2},"end":{"line":59,"column":null}},"type":"if","locations":[{"start":{"line":57,"column":2},"end":{"line":59,"column":null}},{"start":{},"end":{}}],"line":57},"10":{"loc":{"start":{"line":57,"column":6},"end":{"line":57,"column":69}},"type":"binary-expr","locations":[{"start":{"line":57,"column":6},"end":{"line":57,"column":38}},{"start":{"line":57,"column":42},"end":{"line":57,"column":69}}],"line":57},"11":{"loc":{"start":{"line":62,"column":2},"end":{"line":78,"column":null}},"type":"if","locations":[{"start":{"line":62,"column":2},"end":{"line":78,"column":null}},{"start":{},"end":{}}],"line":62},"12":{"loc":{"start":{"line":63,"column":4},"end":{"line":65,"column":null}},"type":"if","locations":[{"start":{"line":63,"column":4},"end":{"line":65,"column":null}},{"start":{},"end":{}}],"line":63},"13":{"loc":{"start":{"line":63,"column":8},"end":{"line":63,"column":40}},"type":"binary-expr","locations":[{"start":{"line":63,"column":8},"end":{"line":63,"column":22}},{"start":{"line":63,"column":26},"end":{"line":63,"column":40}}],"line":63},"14":{"loc":{"start":{"line":66,"column":4},"end":{"line":68,"column":null}},"type":"if","locations":[{"start":{"line":66,"column":4},"end":{"line":68,"column":null}},{"start":{},"end":{}}],"line":66},"15":{"loc":{"start":{"line":69,"column":4},"end":{"line":71,"column":null}},"type":"if","locations":[{"start":{"line":69,"column":4},"end":{"line":71,"column":null}},{"start":{},"end":{}}],"line":69},"16":{"loc":{"start":{"line":72,"column":4},"end":{"line":74,"column":null}},"type":"if","locations":[{"start":{"line":72,"column":4},"end":{"line":74,"column":null}},{"start":{},"end":{}}],"line":72},"17":{"loc":{"start":{"line":75,"column":4},"end":{"line":77,"column":null}},"type":"if","locations":[{"start":{"line":75,"column":4},"end":{"line":77,"column":null}},{"start":{},"end":{}}],"line":75},"18":{"loc":{"start":{"line":81,"column":2},"end":{"line":83,"column":null}},"type":"if","locations":[{"start":{"line":81,"column":2},"end":{"line":83,"column":null}},{"start":{},"end":{}}],"line":81},"19":{"loc":{"start":{"line":81,"column":6},"end":{"line":81,"column":58}},"type":"binary-expr","locations":[{"start":{"line":81,"column":6},"end":{"line":81,"column":20}},{"start":{"line":81,"column":24},"end":{"line":81,"column":58}}],"line":81},"20":{"loc":{"start":{"line":84,"column":2},"end":{"line":86,"column":null}},"type":"if","locations":[{"start":{"line":84,"column":2},"end":{"line":86,"column":null}},{"start":{},"end":{}}],"line":84},"21":{"loc":{"start":{"line":84,"column":6},"end":{"line":84,"column":63}},"type":"binary-expr","locations":[{"start":{"line":84,"column":6},"end":{"line":84,"column":20}},{"start":{"line":84,"column":24},"end":{"line":84,"column":63}}],"line":84},"22":{"loc":{"start":{"line":87,"column":2},"end":{"line":89,"column":null}},"type":"if","locations":[{"start":{"line":87,"column":2},"end":{"line":89,"column":null}},{"start":{},"end":{}}],"line":87},"23":{"loc":{"start":{"line":87,"column":6},"end":{"line":87,"column":59}},"type":"binary-expr","locations":[{"start":{"line":87,"column":6},"end":{"line":87,"column":20}},{"start":{"line":87,"column":24},"end":{"line":87,"column":59}}],"line":87},"24":{"loc":{"start":{"line":92,"column":2},"end":{"line":94,"column":null}},"type":"if","locations":[{"start":{"line":92,"column":2},"end":{"line":94,"column":null}},{"start":{},"end":{}}],"line":92},"25":{"loc":{"start":{"line":92,"column":6},"end":{"line":92,"column":77}},"type":"binary-expr","locations":[{"start":{"line":92,"column":6},"end":{"line":92,"column":47}},{"start":{"line":92,"column":51},"end":{"line":92,"column":77}}],"line":92},"26":{"loc":{"start":{"line":95,"column":2},"end":{"line":97,"column":null}},"type":"if","locations":[{"start":{"line":95,"column":2},"end":{"line":97,"column":null}},{"start":{},"end":{}}],"line":95},"27":{"loc":{"start":{"line":95,"column":6},"end":{"line":95,"column":63}},"type":"binary-expr","locations":[{"start":{"line":95,"column":6},"end":{"line":95,"column":33}},{"start":{"line":95,"column":37},"end":{"line":95,"column":63}}],"line":95},"28":{"loc":{"start":{"line":100,"column":2},"end":{"line":102,"column":null}},"type":"if","locations":[{"start":{"line":100,"column":2},"end":{"line":102,"column":null}},{"start":{},"end":{}}],"line":100},"29":{"loc":{"start":{"line":100,"column":6},"end":{"line":100,"column":57}},"type":"binary-expr","locations":[{"start":{"line":100,"column":6},"end":{"line":100,"column":35}},{"start":{"line":100,"column":39},"end":{"line":100,"column":57}}],"line":100},"30":{"loc":{"start":{"line":114,"column":46},"end":{"line":114,"column":58}},"type":"default-arg","locations":[{"start":{"line":114,"column":56},"end":{"line":114,"column":58}}],"line":114},"31":{"loc":{"start":{"line":158,"column":9},"end":{"line":158,"column":59}},"type":"binary-expr","locations":[{"start":{"line":158,"column":9},"end":{"line":158,"column":28}},{"start":{"line":158,"column":32},"end":{"line":158,"column":59}}],"line":158},"32":{"loc":{"start":{"line":168,"column":45},"end":{"line":168,"column":57}},"type":"default-arg","locations":[{"start":{"line":168,"column":55},"end":{"line":168,"column":57}}],"line":168},"33":{"loc":{"start":{"line":202,"column":9},"end":{"line":202,"column":39}},"type":"binary-expr","locations":[{"start":{"line":202,"column":9},"end":{"line":202,"column":31}},{"start":{"line":202,"column":35},"end":{"line":202,"column":39}}],"line":202},"34":{"loc":{"start":{"line":212,"column":35},"end":{"line":212,"column":47}},"type":"default-arg","locations":[{"start":{"line":212,"column":45},"end":{"line":212,"column":47}}],"line":212}},"s":{"0":1,"1":51,"2":2,"3":49,"4":51,"5":51,"6":51,"7":7,"8":42,"9":3,"10":39,"11":2,"12":37,"13":17,"14":5,"15":12,"16":4,"17":8,"18":4,"19":4,"20":3,"21":1,"22":1,"23":20,"24":2,"25":18,"26":3,"27":15,"28":2,"29":13,"30":5,"31":8,"32":1,"33":7,"34":2,"35":5,"36":6,"37":6,"38":6,"39":6,"40":6,"41":6,"42":9,"43":9,"44":9},"f":{"0":51,"1":6,"2":6,"3":9},"b":{"0":[51],"1":[2,49],"2":[49,1],"3":[51,38],"4":[51,48,33],"5":[7,44],"6":[51,45,44],"7":[3,39],"8":[42,42],"9":[2,37],"10":[39,38],"11":[17,20],"12":[5,12],"13":[17,13],"14":[4,8],"15":[4,4],"16":[3,1],"17":[1,0],"18":[2,18],"19":[20,19],"20":[3,15],"21":[18,16],"22":[2,13],"23":[15,14],"24":[5,8],"25":[13,9],"26":[1,7],"27":[8,1],"28":[2,5],"29":[7,6],"30":[6],"31":[6,0],"32":[6],"33":[6,1],"34":[9]},"meta":{"lastBranch":35,"lastFunction":4,"lastStatement":45,"seen":{"s:11:25:34:1":0,"f:43:16:43:29":0,"b:43:47:43:49":0,"b:44:2:44:Infinity:undefined:undefined:undefined:undefined":1,"s:44:2:44:Infinity":1,"s:44:14:44:Infinity":2,"s:46:18:46:52":3,"b:46:18:46:46:46:50:46:52":2,"s:47:15:47:41":4,"b:47:15:47:25:47:29:47:41":3,"s:48:17:48:69":5,"b:48:17:48:29:48:33:48:47:48:51:48:69":4,"b:51:2:53:Infinity:undefined:undefined:undefined:undefined":5,"s:51:2:53:Infinity":6,"b:51:6:51:29:51:33:51:53:51:57:51:77":6,"s:52:4:52:Infinity":7,"b:54:2:56:Infinity:undefined:undefined:undefined:undefined":7,"s:54:2:56:Infinity":8,"b:54:6:54:26:54:30:54:57":8,"s:55:4:55:Infinity":9,"b:57:2:59:Infinity:undefined:undefined:undefined:undefined":9,"s:57:2:59:Infinity":10,"b:57:6:57:38:57:42:57:69":10,"s:58:4:58:Infinity":11,"b:62:2:78:Infinity:undefined:undefined:undefined:undefined":11,"s:62:2:78:Infinity":12,"b:63:4:65:Infinity:undefined:undefined:undefined:undefined":12,"s:63:4:65:Infinity":13,"b:63:8:63:22:63:26:63:40":13,"s:64:6:64:Infinity":14,"b:66:4:68:Infinity:undefined:undefined:undefined:undefined":14,"s:66:4:68:Infinity":15,"s:67:6:67:Infinity":16,"b:69:4:71:Infinity:undefined:undefined:undefined:undefined":15,"s:69:4:71:Infinity":17,"s:70:6:70:Infinity":18,"b:72:4:74:Infinity:undefined:undefined:undefined:undefined":16,"s:72:4:74:Infinity":19,"s:73:6:73:Infinity":20,"b:75:4:77:Infinity:undefined:undefined:undefined:undefined":17,"s:75:4:77:Infinity":21,"s:76:6:76:Infinity":22,"b:81:2:83:Infinity:undefined:undefined:undefined:undefined":18,"s:81:2:83:Infinity":23,"b:81:6:81:20:81:24:81:58":19,"s:82:4:82:Infinity":24,"b:84:2:86:Infinity:undefined:undefined:undefined:undefined":20,"s:84:2:86:Infinity":25,"b:84:6:84:20:84:24:84:63":21,"s:85:4:85:Infinity":26,"b:87:2:89:Infinity:undefined:undefined:undefined:undefined":22,"s:87:2:89:Infinity":27,"b:87:6:87:20:87:24:87:59":23,"s:88:4:88:Infinity":28,"b:92:2:94:Infinity:undefined:undefined:undefined:undefined":24,"s:92:2:94:Infinity":29,"b:92:6:92:47:92:51:92:77":25,"s:93:4:93:Infinity":30,"b:95:2:97:Infinity:undefined:undefined:undefined:undefined":26,"s:95:2:97:Infinity":31,"b:95:6:95:33:95:37:95:63":27,"s:96:4:96:Infinity":32,"b:100:2:102:Infinity:undefined:undefined:undefined:undefined":28,"s:100:2:102:Infinity":33,"b:100:6:100:35:100:39:100:57":29,"s:101:4:101:Infinity":34,"s:104:2:104:Infinity":35,"f:114:16:114:38":1,"b:114:56:114:58":30,"s:115:20:115:49":36,"s:117:19:156:3":37,"s:158:2:158:Infinity":38,"b:158:9:158:28:158:32:158:59":31,"f:168:16:168:37":2,"b:168:55:168:57":32,"s:169:20:169:49":39,"s:171:22:200:3":40,"s:202:2:202:Infinity":41,"b:202:9:202:31:202:35:202:39":33,"f:212:16:212:27":3,"b:212:45:212:47":34,"s:213:20:213:49":42,"s:216:25:221:3":43,"s:223:2:223:Infinity":44}}} +,"/home/jailuser/git/src/utils/health.js": {"path":"/home/jailuser/git/src/utils/health.js","statementMap":{"0":{"start":{"line":16,"column":4},"end":{"line":18,"column":null}},"1":{"start":{"line":17,"column":6},"end":{"line":17,"column":null}},"2":{"start":{"line":20,"column":4},"end":{"line":20,"column":null}},"3":{"start":{"line":21,"column":4},"end":{"line":21,"column":null}},"4":{"start":{"line":22,"column":4},"end":{"line":22,"column":null}},"5":{"start":{"line":23,"column":4},"end":{"line":23,"column":null}},"6":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"7":{"start":{"line":32,"column":4},"end":{"line":34,"column":null}},"8":{"start":{"line":33,"column":6},"end":{"line":33,"column":null}},"9":{"start":{"line":35,"column":4},"end":{"line":35,"column":null}},"10":{"start":{"line":42,"column":4},"end":{"line":42,"column":null}},"11":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"12":{"start":{"line":57,"column":4},"end":{"line":57,"column":null}},"13":{"start":{"line":58,"column":4},"end":{"line":58,"column":null}},"14":{"start":{"line":65,"column":4},"end":{"line":65,"column":null}},"15":{"start":{"line":72,"column":19},"end":{"line":72,"column":35}},"16":{"start":{"line":73,"column":20},"end":{"line":73,"column":45}},"17":{"start":{"line":74,"column":20},"end":{"line":74,"column":44}},"18":{"start":{"line":75,"column":18},"end":{"line":75,"column":42}},"19":{"start":{"line":76,"column":17},"end":{"line":76,"column":39}},"20":{"start":{"line":78,"column":4},"end":{"line":86,"column":null}},"21":{"start":{"line":79,"column":6},"end":{"line":79,"column":null}},"22":{"start":{"line":80,"column":11},"end":{"line":86,"column":null}},"23":{"start":{"line":81,"column":6},"end":{"line":81,"column":null}},"24":{"start":{"line":82,"column":11},"end":{"line":86,"column":null}},"25":{"start":{"line":83,"column":6},"end":{"line":83,"column":null}},"26":{"start":{"line":85,"column":6},"end":{"line":85,"column":null}},"27":{"start":{"line":93,"column":18},"end":{"line":93,"column":39}},"28":{"start":{"line":94,"column":4},"end":{"line":99,"column":null}},"29":{"start":{"line":106,"column":16},"end":{"line":106,"column":37}},"30":{"start":{"line":107,"column":4},"end":{"line":107,"column":null}},"31":{"start":{"line":114,"column":19},"end":{"line":114,"column":40}},"32":{"start":{"line":116,"column":4},"end":{"line":132,"column":null}},"33":{"start":{"line":139,"column":19},"end":{"line":139,"column":35}},"34":{"start":{"line":140,"column":19},"end":{"line":140,"column":40}},"35":{"start":{"line":142,"column":4},"end":{"line":155,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":15,"column":2},"end":{"line":15,"column":13}},"loc":{"start":{"line":15,"column":16},"end":{"line":26,"column":null}},"line":15},"1":{"name":"(anonymous_1)","decl":{"start":{"line":31,"column":9},"end":{"line":31,"column":20}},"loc":{"start":{"line":31,"column":23},"end":{"line":36,"column":null}},"line":31},"2":{"name":"(anonymous_2)","decl":{"start":{"line":41,"column":2},"end":{"line":41,"column":13}},"loc":{"start":{"line":41,"column":16},"end":{"line":43,"column":null}},"line":41},"3":{"name":"(anonymous_3)","decl":{"start":{"line":48,"column":2},"end":{"line":48,"column":17}},"loc":{"start":{"line":48,"column":20},"end":{"line":50,"column":null}},"line":48},"4":{"name":"(anonymous_4)","decl":{"start":{"line":56,"column":2},"end":{"line":56,"column":14}},"loc":{"start":{"line":56,"column":23},"end":{"line":59,"column":null}},"line":56},"5":{"name":"(anonymous_5)","decl":{"start":{"line":64,"column":2},"end":{"line":64,"column":11}},"loc":{"start":{"line":64,"column":14},"end":{"line":66,"column":null}},"line":64},"6":{"name":"(anonymous_6)","decl":{"start":{"line":71,"column":2},"end":{"line":71,"column":20}},"loc":{"start":{"line":71,"column":23},"end":{"line":87,"column":null}},"line":71},"7":{"name":"(anonymous_7)","decl":{"start":{"line":92,"column":2},"end":{"line":92,"column":16}},"loc":{"start":{"line":92,"column":19},"end":{"line":100,"column":null}},"line":92},"8":{"name":"(anonymous_8)","decl":{"start":{"line":105,"column":2},"end":{"line":105,"column":20}},"loc":{"start":{"line":105,"column":23},"end":{"line":108,"column":null}},"line":105},"9":{"name":"(anonymous_9)","decl":{"start":{"line":113,"column":2},"end":{"line":113,"column":11}},"loc":{"start":{"line":113,"column":14},"end":{"line":133,"column":null}},"line":113},"10":{"name":"(anonymous_10)","decl":{"start":{"line":138,"column":2},"end":{"line":138,"column":19}},"loc":{"start":{"line":138,"column":22},"end":{"line":156,"column":null}},"line":138}},"branchMap":{"0":{"loc":{"start":{"line":16,"column":4},"end":{"line":18,"column":null}},"type":"if","locations":[{"start":{"line":16,"column":4},"end":{"line":18,"column":null}},{"start":{},"end":{}}],"line":16},"1":{"loc":{"start":{"line":32,"column":4},"end":{"line":34,"column":null}},"type":"if","locations":[{"start":{"line":32,"column":4},"end":{"line":34,"column":null}},{"start":{},"end":{}}],"line":32},"2":{"loc":{"start":{"line":78,"column":4},"end":{"line":86,"column":null}},"type":"if","locations":[{"start":{"line":78,"column":4},"end":{"line":86,"column":null}},{"start":{"line":80,"column":11},"end":{"line":86,"column":null}}],"line":78},"3":{"loc":{"start":{"line":80,"column":11},"end":{"line":86,"column":null}},"type":"if","locations":[{"start":{"line":80,"column":11},"end":{"line":86,"column":null}},{"start":{"line":82,"column":11},"end":{"line":86,"column":null}}],"line":80},"4":{"loc":{"start":{"line":82,"column":11},"end":{"line":86,"column":null}},"type":"if","locations":[{"start":{"line":82,"column":11},"end":{"line":86,"column":null}},{"start":{"line":84,"column":11},"end":{"line":86,"column":null}}],"line":82}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0},"f":{"0":0,"1":1,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0]},"meta":{"lastBranch":5,"lastFunction":11,"lastStatement":36,"seen":{"f:15:2:15:13":0,"b:16:4:18:Infinity:undefined:undefined:undefined:undefined":0,"s:16:4:18:Infinity":0,"s:17:6:17:Infinity":1,"s:20:4:20:Infinity":2,"s:21:4:21:Infinity":3,"s:22:4:22:Infinity":4,"s:23:4:23:Infinity":5,"s:25:4:25:Infinity":6,"f:31:9:31:20":1,"b:32:4:34:Infinity:undefined:undefined:undefined:undefined":1,"s:32:4:34:Infinity":7,"s:33:6:33:Infinity":8,"s:35:4:35:Infinity":9,"f:41:2:41:13":2,"s:42:4:42:Infinity":10,"f:48:2:48:17":3,"s:49:4:49:Infinity":11,"f:56:2:56:14":4,"s:57:4:57:Infinity":12,"s:58:4:58:Infinity":13,"f:64:2:64:11":5,"s:65:4:65:Infinity":14,"f:71:2:71:20":6,"s:72:19:72:35":15,"s:73:20:73:45":16,"s:74:20:74:44":17,"s:75:18:75:42":18,"s:76:17:76:39":19,"b:78:4:86:Infinity:80:11:86:Infinity":2,"s:78:4:86:Infinity":20,"s:79:6:79:Infinity":21,"b:80:11:86:Infinity:82:11:86:Infinity":3,"s:80:11:86:Infinity":22,"s:81:6:81:Infinity":23,"b:82:11:86:Infinity:84:11:86:Infinity":4,"s:82:11:86:Infinity":24,"s:83:6:83:Infinity":25,"s:85:6:85:Infinity":26,"f:92:2:92:16":7,"s:93:18:93:39":27,"s:94:4:99:Infinity":28,"f:105:2:105:20":8,"s:106:16:106:37":29,"s:107:4:107:Infinity":30,"f:113:2:113:11":9,"s:114:19:114:40":31,"s:116:4:132:Infinity":32,"f:138:2:138:19":10,"s:139:19:139:35":33,"s:140:19:140:40":34,"s:142:4:155:Infinity":35}}} +,"/home/jailuser/git/src/utils/permissions.js": {"path":"/home/jailuser/git/src/utils/permissions.js","statementMap":{"0":{"start":{"line":17,"column":2},"end":{"line":17,"column":null}},"1":{"start":{"line":17,"column":26},"end":{"line":17,"column":null}},"2":{"start":{"line":20,"column":2},"end":{"line":22,"column":null}},"3":{"start":{"line":21,"column":4},"end":{"line":21,"column":null}},"4":{"start":{"line":25,"column":2},"end":{"line":27,"column":null}},"5":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"6":{"start":{"line":29,"column":2},"end":{"line":29,"column":null}},"7":{"start":{"line":41,"column":2},"end":{"line":41,"column":null}},"8":{"start":{"line":41,"column":42},"end":{"line":41,"column":null}},"9":{"start":{"line":44,"column":2},"end":{"line":46,"column":null}},"10":{"start":{"line":45,"column":4},"end":{"line":45,"column":null}},"11":{"start":{"line":49,"column":26},"end":{"line":49,"column":76}},"12":{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},"13":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"14":{"start":{"line":57,"column":2},"end":{"line":59,"column":null}},"15":{"start":{"line":58,"column":4},"end":{"line":58,"column":null}},"16":{"start":{"line":61,"column":2},"end":{"line":63,"column":null}},"17":{"start":{"line":62,"column":4},"end":{"line":62,"column":null}},"18":{"start":{"line":66,"column":2},"end":{"line":66,"column":null}},"19":{"start":{"line":76,"column":2},"end":{"line":76,"column":null}}},"fnMap":{"0":{"name":"isAdmin","decl":{"start":{"line":16,"column":16},"end":{"line":16,"column":23}},"loc":{"start":{"line":16,"column":40},"end":{"line":30,"column":null}},"line":16},"1":{"name":"hasPermission","decl":{"start":{"line":40,"column":16},"end":{"line":40,"column":29}},"loc":{"start":{"line":40,"column":59},"end":{"line":67,"column":null}},"line":40},"2":{"name":"getPermissionError","decl":{"start":{"line":75,"column":16},"end":{"line":75,"column":34}},"loc":{"start":{"line":75,"column":48},"end":{"line":77,"column":null}},"line":75}},"branchMap":{"0":{"loc":{"start":{"line":17,"column":2},"end":{"line":17,"column":null}},"type":"if","locations":[{"start":{"line":17,"column":2},"end":{"line":17,"column":null}},{"start":{},"end":{}}],"line":17},"1":{"loc":{"start":{"line":17,"column":6},"end":{"line":17,"column":24}},"type":"binary-expr","locations":[{"start":{"line":17,"column":6},"end":{"line":17,"column":13}},{"start":{"line":17,"column":17},"end":{"line":17,"column":24}}],"line":17},"2":{"loc":{"start":{"line":20,"column":2},"end":{"line":22,"column":null}},"type":"if","locations":[{"start":{"line":20,"column":2},"end":{"line":22,"column":null}},{"start":{},"end":{}}],"line":20},"3":{"loc":{"start":{"line":25,"column":2},"end":{"line":27,"column":null}},"type":"if","locations":[{"start":{"line":25,"column":2},"end":{"line":27,"column":null}},{"start":{},"end":{}}],"line":25},"4":{"loc":{"start":{"line":41,"column":2},"end":{"line":41,"column":null}},"type":"if","locations":[{"start":{"line":41,"column":2},"end":{"line":41,"column":null}},{"start":{},"end":{}}],"line":41},"5":{"loc":{"start":{"line":41,"column":6},"end":{"line":41,"column":40}},"type":"binary-expr","locations":[{"start":{"line":41,"column":6},"end":{"line":41,"column":13}},{"start":{"line":41,"column":17},"end":{"line":41,"column":29}},{"start":{"line":41,"column":33},"end":{"line":41,"column":40}}],"line":41},"6":{"loc":{"start":{"line":44,"column":2},"end":{"line":46,"column":null}},"type":"if","locations":[{"start":{"line":44,"column":2},"end":{"line":46,"column":null}},{"start":{},"end":{}}],"line":44},"7":{"loc":{"start":{"line":44,"column":6},"end":{"line":44,"column":73}},"type":"binary-expr","locations":[{"start":{"line":44,"column":6},"end":{"line":44,"column":34}},{"start":{"line":44,"column":38},"end":{"line":44,"column":73}}],"line":44},"8":{"loc":{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},"type":"if","locations":[{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},{"start":{},"end":{}}],"line":52},"9":{"loc":{"start":{"line":57,"column":2},"end":{"line":59,"column":null}},"type":"if","locations":[{"start":{"line":57,"column":2},"end":{"line":59,"column":null}},{"start":{},"end":{}}],"line":57},"10":{"loc":{"start":{"line":61,"column":2},"end":{"line":63,"column":null}},"type":"if","locations":[{"start":{"line":61,"column":2},"end":{"line":63,"column":null}},{"start":{},"end":{}}],"line":61}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0},"f":{"0":0,"1":0,"2":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0]},"meta":{"lastBranch":11,"lastFunction":3,"lastStatement":20,"seen":{"f:16:16:16:23":0,"b:17:2:17:Infinity:undefined:undefined:undefined:undefined":0,"s:17:2:17:Infinity":0,"b:17:6:17:13:17:17:17:24":1,"s:17:26:17:Infinity":1,"b:20:2:22:Infinity:undefined:undefined:undefined:undefined":2,"s:20:2:22:Infinity":2,"s:21:4:21:Infinity":3,"b:25:2:27:Infinity:undefined:undefined:undefined:undefined":3,"s:25:2:27:Infinity":4,"s:26:4:26:Infinity":5,"s:29:2:29:Infinity":6,"f:40:16:40:29":1,"b:41:2:41:Infinity:undefined:undefined:undefined:undefined":4,"s:41:2:41:Infinity":7,"b:41:6:41:13:41:17:41:29:41:33:41:40":5,"s:41:42:41:Infinity":8,"b:44:2:46:Infinity:undefined:undefined:undefined:undefined":6,"s:44:2:46:Infinity":9,"b:44:6:44:34:44:38:44:73":7,"s:45:4:45:Infinity":10,"s:49:26:49:76":11,"b:52:2:54:Infinity:undefined:undefined:undefined:undefined":8,"s:52:2:54:Infinity":12,"s:53:4:53:Infinity":13,"b:57:2:59:Infinity:undefined:undefined:undefined:undefined":9,"s:57:2:59:Infinity":14,"s:58:4:58:Infinity":15,"b:61:2:63:Infinity:undefined:undefined:undefined:undefined":10,"s:61:2:63:Infinity":16,"s:62:4:62:Infinity":17,"s:66:2:66:Infinity":18,"f:75:16:75:34":2,"s:76:2:76:Infinity":19}}} +,"/home/jailuser/git/src/utils/registerCommands.js": {"path":"/home/jailuser/git/src/utils/registerCommands.js","statementMap":{"0":{"start":{"line":20,"column":2},"end":{"line":22,"column":null}},"1":{"start":{"line":21,"column":4},"end":{"line":21,"column":null}},"2":{"start":{"line":24,"column":2},"end":{"line":26,"column":null}},"3":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"4":{"start":{"line":29,"column":22},"end":{"line":34,"column":4}},"5":{"start":{"line":30,"column":4},"end":{"line":32,"column":null}},"6":{"start":{"line":31,"column":6},"end":{"line":31,"column":null}},"7":{"start":{"line":33,"column":4},"end":{"line":33,"column":null}},"8":{"start":{"line":36,"column":15},"end":{"line":36,"column":58}},"9":{"start":{"line":38,"column":2},"end":{"line":56,"column":null}},"10":{"start":{"line":39,"column":3},"end":{"line":39,"column":null}},"11":{"start":{"line":42,"column":4},"end":{"line":50,"column":null}},"12":{"start":{"line":44,"column":6},"end":{"line":46,"column":null}},"13":{"start":{"line":49,"column":6},"end":{"line":49,"column":null}},"14":{"start":{"line":52,"column":3},"end":{"line":52,"column":null}},"15":{"start":{"line":54,"column":3},"end":{"line":54,"column":null}},"16":{"start":{"line":55,"column":4},"end":{"line":55,"column":null}}},"fnMap":{"0":{"name":"registerCommands","decl":{"start":{"line":19,"column":22},"end":{"line":19,"column":38}},"loc":{"start":{"line":19,"column":82},"end":{"line":57,"column":null}},"line":19},"1":{"name":"(anonymous_1)","decl":{"start":{"line":29,"column":35},"end":{"line":29,"column":36}},"loc":{"start":{"line":29,"column":44},"end":{"line":34,"column":3}},"line":29}},"branchMap":{"0":{"loc":{"start":{"line":19,"column":66},"end":{"line":19,"column":80}},"type":"default-arg","locations":[{"start":{"line":19,"column":76},"end":{"line":19,"column":80}}],"line":19},"1":{"loc":{"start":{"line":20,"column":2},"end":{"line":22,"column":null}},"type":"if","locations":[{"start":{"line":20,"column":2},"end":{"line":22,"column":null}},{"start":{},"end":{}}],"line":20},"2":{"loc":{"start":{"line":20,"column":6},"end":{"line":20,"column":43}},"type":"binary-expr","locations":[{"start":{"line":20,"column":6},"end":{"line":20,"column":15}},{"start":{"line":20,"column":19},"end":{"line":20,"column":43}}],"line":20},"3":{"loc":{"start":{"line":24,"column":2},"end":{"line":26,"column":null}},"type":"if","locations":[{"start":{"line":24,"column":2},"end":{"line":26,"column":null}},{"start":{},"end":{}}],"line":24},"4":{"loc":{"start":{"line":24,"column":6},"end":{"line":24,"column":25}},"type":"binary-expr","locations":[{"start":{"line":24,"column":6},"end":{"line":24,"column":15}},{"start":{"line":24,"column":19},"end":{"line":24,"column":25}}],"line":24},"5":{"loc":{"start":{"line":30,"column":4},"end":{"line":32,"column":null}},"type":"if","locations":[{"start":{"line":30,"column":4},"end":{"line":32,"column":null}},{"start":{},"end":{}}],"line":30},"6":{"loc":{"start":{"line":30,"column":8},"end":{"line":30,"column":58}},"type":"binary-expr","locations":[{"start":{"line":30,"column":8},"end":{"line":30,"column":17}},{"start":{"line":30,"column":21},"end":{"line":30,"column":58}}],"line":30},"7":{"loc":{"start":{"line":42,"column":4},"end":{"line":50,"column":null}},"type":"if","locations":[{"start":{"line":42,"column":4},"end":{"line":50,"column":null}},{"start":{"line":47,"column":11},"end":{"line":50,"column":null}}],"line":42},"8":{"loc":{"start":{"line":52,"column":77},"end":{"line":52,"column":105}},"type":"cond-expr","locations":[{"start":{"line":52,"column":87},"end":{"line":52,"column":94}},{"start":{"line":52,"column":97},"end":{"line":52,"column":105}}],"line":52}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0},"f":{"0":0,"1":0},"b":{"0":[0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0]},"meta":{"lastBranch":9,"lastFunction":2,"lastStatement":17,"seen":{"f:19:22:19:38":0,"b:19:76:19:80":0,"b:20:2:22:Infinity:undefined:undefined:undefined:undefined":1,"s:20:2:22:Infinity":0,"b:20:6:20:15:20:19:20:43":2,"s:21:4:21:Infinity":1,"b:24:2:26:Infinity:undefined:undefined:undefined:undefined":3,"s:24:2:26:Infinity":2,"b:24:6:24:15:24:19:24:25":4,"s:25:4:25:Infinity":3,"s:29:22:34:4":4,"f:29:35:29:36":1,"b:30:4:32:Infinity:undefined:undefined:undefined:undefined":5,"s:30:4:32:Infinity":5,"b:30:8:30:17:30:21:30:58":6,"s:31:6:31:Infinity":6,"s:33:4:33:Infinity":7,"s:36:15:36:58":8,"s:38:2:56:Infinity":9,"s:39:3:39:Infinity":10,"b:42:4:50:Infinity:47:11:50:Infinity":7,"s:42:4:50:Infinity":11,"s:44:6:46:Infinity":12,"s:49:6:49:Infinity":13,"s:52:3:52:Infinity":14,"b:52:87:52:94:52:97:52:105":8,"s:54:3:54:Infinity":15,"s:55:4:55:Infinity":16}}} +,"/home/jailuser/git/src/utils/retry.js": {"path":"/home/jailuser/git/src/utils/retry.js","statementMap":{"0":{"start":{"line":17,"column":2},"end":{"line":17,"column":null}},"1":{"start":{"line":17,"column":34},"end":{"line":17,"column":57}},"2":{"start":{"line":29,"column":16},"end":{"line":29,"column":40}},"3":{"start":{"line":32,"column":2},"end":{"line":32,"column":null}},"4":{"start":{"line":55,"column":6},"end":{"line":55,"column":13}},"5":{"start":{"line":59,"column":2},"end":{"line":114,"column":null}},"6":{"start":{"line":59,"column":21},"end":{"line":59,"column":22}},"7":{"start":{"line":60,"column":4},"end":{"line":113,"column":null}},"8":{"start":{"line":62,"column":6},"end":{"line":62,"column":null}},"9":{"start":{"line":64,"column":6},"end":{"line":64,"column":null}},"10":{"start":{"line":67,"column":23},"end":{"line":67,"column":51}},"11":{"start":{"line":68,"column":23},"end":{"line":68,"column":48}},"12":{"start":{"line":71,"column":6},"end":{"line":78,"column":null}},"13":{"start":{"line":72,"column":7},"end":{"line":77,"column":null}},"14":{"start":{"line":81,"column":6},"end":{"line":98,"column":null}},"15":{"start":{"line":82,"column":8},"end":{"line":96,"column":null}},"16":{"start":{"line":83,"column":9},"end":{"line":88,"column":null}},"17":{"start":{"line":90,"column":9},"end":{"line":95,"column":null}},"18":{"start":{"line":97,"column":8},"end":{"line":97,"column":null}},"19":{"start":{"line":101,"column":20},"end":{"line":101,"column":66}},"20":{"start":{"line":103,"column":5},"end":{"line":109,"column":null}},"21":{"start":{"line":112,"column":6},"end":{"line":112,"column":null}},"22":{"start":{"line":117,"column":2},"end":{"line":117,"column":null}},"23":{"start":{"line":127,"column":2},"end":{"line":129,"column":null}},"24":{"start":{"line":128,"column":4},"end":{"line":128,"column":null}}},"fnMap":{"0":{"name":"sleep","decl":{"start":{"line":16,"column":9},"end":{"line":16,"column":14}},"loc":{"start":{"line":16,"column":19},"end":{"line":18,"column":null}},"line":16},"1":{"name":"(anonymous_1)","decl":{"start":{"line":17,"column":21},"end":{"line":17,"column":22}},"loc":{"start":{"line":17,"column":34},"end":{"line":17,"column":57}},"line":17},"2":{"name":"calculateBackoff","decl":{"start":{"line":27,"column":9},"end":{"line":27,"column":25}},"loc":{"start":{"line":27,"column":56},"end":{"line":33,"column":null}},"line":27},"3":{"name":"withRetry","decl":{"start":{"line":48,"column":22},"end":{"line":48,"column":31}},"loc":{"start":{"line":48,"column":50},"end":{"line":118,"column":null}},"line":48},"4":{"name":"createRetryWrapper","decl":{"start":{"line":126,"column":16},"end":{"line":126,"column":34}},"loc":{"start":{"line":126,"column":56},"end":{"line":130,"column":null}},"line":126},"5":{"name":"(anonymous_5)","decl":{"start":{"line":127,"column":9},"end":{"line":127,"column":10}},"loc":{"start":{"line":127,"column":31},"end":{"line":129,"column":3}},"line":127}},"branchMap":{"0":{"loc":{"start":{"line":48,"column":36},"end":{"line":48,"column":48}},"type":"default-arg","locations":[{"start":{"line":48,"column":46},"end":{"line":48,"column":48}}],"line":48},"1":{"loc":{"start":{"line":50,"column":4},"end":{"line":50,"column":18}},"type":"default-arg","locations":[{"start":{"line":50,"column":17},"end":{"line":50,"column":18}}],"line":50},"2":{"loc":{"start":{"line":51,"column":4},"end":{"line":51,"column":20}},"type":"default-arg","locations":[{"start":{"line":51,"column":16},"end":{"line":51,"column":20}}],"line":51},"3":{"loc":{"start":{"line":52,"column":4},"end":{"line":52,"column":20}},"type":"default-arg","locations":[{"start":{"line":52,"column":15},"end":{"line":52,"column":20}}],"line":52},"4":{"loc":{"start":{"line":53,"column":4},"end":{"line":53,"column":29}},"type":"default-arg","locations":[{"start":{"line":53,"column":18},"end":{"line":53,"column":29}}],"line":53},"5":{"loc":{"start":{"line":54,"column":4},"end":{"line":54,"column":16}},"type":"default-arg","locations":[{"start":{"line":54,"column":14},"end":{"line":54,"column":16}}],"line":54},"6":{"loc":{"start":{"line":71,"column":6},"end":{"line":78,"column":null}},"type":"if","locations":[{"start":{"line":71,"column":6},"end":{"line":78,"column":null}},{"start":{},"end":{}}],"line":71},"7":{"loc":{"start":{"line":81,"column":6},"end":{"line":98,"column":null}},"type":"if","locations":[{"start":{"line":81,"column":6},"end":{"line":98,"column":null}},{"start":{},"end":{}}],"line":81},"8":{"loc":{"start":{"line":81,"column":10},"end":{"line":81,"column":44}},"type":"binary-expr","locations":[{"start":{"line":81,"column":10},"end":{"line":81,"column":31}},{"start":{"line":81,"column":35},"end":{"line":81,"column":44}}],"line":81},"9":{"loc":{"start":{"line":82,"column":8},"end":{"line":96,"column":null}},"type":"if","locations":[{"start":{"line":82,"column":8},"end":{"line":96,"column":null}},{"start":{"line":89,"column":15},"end":{"line":96,"column":null}}],"line":82},"10":{"loc":{"start":{"line":126,"column":35},"end":{"line":126,"column":54}},"type":"default-arg","locations":[{"start":{"line":126,"column":52},"end":{"line":126,"column":54}}],"line":126},"11":{"loc":{"start":{"line":127,"column":14},"end":{"line":127,"column":26}},"type":"default-arg","locations":[{"start":{"line":127,"column":24},"end":{"line":127,"column":26}}],"line":127}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0},"b":{"0":[0],"1":[0],"2":[0],"3":[0],"4":[0],"5":[0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0],"11":[0]},"meta":{"lastBranch":12,"lastFunction":6,"lastStatement":25,"seen":{"f:16:9:16:14":0,"s:17:2:17:Infinity":0,"f:17:21:17:22":1,"s:17:34:17:57":1,"f:27:9:27:25":2,"s:29:16:29:40":2,"s:32:2:32:Infinity":3,"f:48:22:48:31":3,"b:48:46:48:48":0,"s:55:6:55:13":4,"b:50:17:50:18":1,"b:51:16:51:20":2,"b:52:15:52:20":3,"b:53:18:53:29":4,"b:54:14:54:16":5,"s:59:2:114:Infinity":5,"s:59:21:59:22":6,"s:60:4:113:Infinity":7,"s:62:6:62:Infinity":8,"s:64:6:64:Infinity":9,"s:67:23:67:51":10,"s:68:23:68:48":11,"b:71:6:78:Infinity:undefined:undefined:undefined:undefined":6,"s:71:6:78:Infinity":12,"s:72:7:77:Infinity":13,"b:81:6:98:Infinity:undefined:undefined:undefined:undefined":7,"s:81:6:98:Infinity":14,"b:81:10:81:31:81:35:81:44":8,"b:82:8:96:Infinity:89:15:96:Infinity":9,"s:82:8:96:Infinity":15,"s:83:9:88:Infinity":16,"s:90:9:95:Infinity":17,"s:97:8:97:Infinity":18,"s:101:20:101:66":19,"s:103:5:109:Infinity":20,"s:112:6:112:Infinity":21,"s:117:2:117:Infinity":22,"f:126:16:126:34":4,"b:126:52:126:54":10,"s:127:2:129:Infinity":23,"f:127:9:127:10":5,"b:127:24:127:26":11,"s:128:4:128:Infinity":24}}} +,"/home/jailuser/git/src/utils/splitMessage.js": {"path":"/home/jailuser/git/src/utils/splitMessage.js","statementMap":{"0":{"start":{"line":9,"column":27},"end":{"line":9,"column":31}},"1":{"start":{"line":14,"column":24},"end":{"line":14,"column":28}},"2":{"start":{"line":25,"column":2},"end":{"line":27,"column":null}},"3":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"4":{"start":{"line":29,"column":17},"end":{"line":29,"column":19}},"5":{"start":{"line":30,"column":18},"end":{"line":30,"column":22}},"6":{"start":{"line":32,"column":2},"end":{"line":48,"column":null}},"7":{"start":{"line":33,"column":4},"end":{"line":36,"column":null}},"8":{"start":{"line":34,"column":6},"end":{"line":34,"column":null}},"9":{"start":{"line":35,"column":6},"end":{"line":35,"column":null}},"10":{"start":{"line":39,"column":18},"end":{"line":39,"column":55}},"11":{"start":{"line":42,"column":4},"end":{"line":44,"column":null}},"12":{"start":{"line":43,"column":6},"end":{"line":43,"column":null}},"13":{"start":{"line":46,"column":4},"end":{"line":46,"column":null}},"14":{"start":{"line":47,"column":4},"end":{"line":47,"column":null}},"15":{"start":{"line":50,"column":2},"end":{"line":50,"column":null}},"16":{"start":{"line":60,"column":2},"end":{"line":60,"column":null}}},"fnMap":{"0":{"name":"splitMessage","decl":{"start":{"line":24,"column":16},"end":{"line":24,"column":28}},"loc":{"start":{"line":24,"column":64},"end":{"line":51,"column":null}},"line":24},"1":{"name":"needsSplitting","decl":{"start":{"line":59,"column":16},"end":{"line":59,"column":30}},"loc":{"start":{"line":59,"column":37},"end":{"line":61,"column":null}},"line":59}},"branchMap":{"0":{"loc":{"start":{"line":24,"column":35},"end":{"line":24,"column":62}},"type":"default-arg","locations":[{"start":{"line":24,"column":47},"end":{"line":24,"column":62}}],"line":24},"1":{"loc":{"start":{"line":25,"column":2},"end":{"line":27,"column":null}},"type":"if","locations":[{"start":{"line":25,"column":2},"end":{"line":27,"column":null}},{"start":{},"end":{}}],"line":25},"2":{"loc":{"start":{"line":25,"column":6},"end":{"line":25,"column":39}},"type":"binary-expr","locations":[{"start":{"line":25,"column":6},"end":{"line":25,"column":11}},{"start":{"line":25,"column":15},"end":{"line":25,"column":39}}],"line":25},"3":{"loc":{"start":{"line":26,"column":11},"end":{"line":26,"column":29}},"type":"cond-expr","locations":[{"start":{"line":26,"column":18},"end":{"line":26,"column":24}},{"start":{"line":26,"column":27},"end":{"line":26,"column":29}}],"line":26},"4":{"loc":{"start":{"line":33,"column":4},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":33,"column":4},"end":{"line":36,"column":null}},{"start":{},"end":{}}],"line":33},"5":{"loc":{"start":{"line":42,"column":4},"end":{"line":44,"column":null}},"type":"if","locations":[{"start":{"line":42,"column":4},"end":{"line":44,"column":null}},{"start":{},"end":{}}],"line":42},"6":{"loc":{"start":{"line":60,"column":9},"end":{"line":60,"column":49}},"type":"binary-expr","locations":[{"start":{"line":60,"column":9},"end":{"line":60,"column":13}},{"start":{"line":60,"column":17},"end":{"line":60,"column":49}}],"line":60}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0},"f":{"0":0,"1":0},"b":{"0":[0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0]},"meta":{"lastBranch":7,"lastFunction":2,"lastStatement":17,"seen":{"s:9:27:9:31":0,"s:14:24:14:28":1,"f:24:16:24:28":0,"b:24:47:24:62":0,"b:25:2:27:Infinity:undefined:undefined:undefined:undefined":1,"s:25:2:27:Infinity":2,"b:25:6:25:11:25:15:25:39":2,"s:26:4:26:Infinity":3,"b:26:18:26:24:26:27:26:29":3,"s:29:17:29:19":4,"s:30:18:30:22":5,"s:32:2:48:Infinity":6,"b:33:4:36:Infinity:undefined:undefined:undefined:undefined":4,"s:33:4:36:Infinity":7,"s:34:6:34:Infinity":8,"s:35:6:35:Infinity":9,"s:39:18:39:55":10,"b:42:4:44:Infinity:undefined:undefined:undefined:undefined":5,"s:42:4:44:Infinity":11,"s:43:6:43:Infinity":12,"s:46:4:46:Infinity":13,"s:47:4:47:Infinity":14,"s:50:2:50:Infinity":15,"f:59:16:59:30":1,"s:60:2:60:Infinity":16,"b:60:9:60:13:60:17:60:49":6}}} +} \ No newline at end of file diff --git a/coverage/favicon.png b/coverage/favicon.png new file mode 100644 index 00000000..e69de29b diff --git a/coverage/index.html b/coverage/index.html new file mode 100644 index 00000000..e316bfc3 --- /dev/null +++ b/coverage/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 26.39% + Statements + 293/1110 +
+ + +
+ 28.21% + Branches + 206/730 +
+ + +
+ 29.76% + Functions + 50/168 +
+ + +
+ 26.5% + Lines + 278/1049 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
src +
+
26.2%60/22934.28%36/10531.42%11/3526.24%58/221
src/commands +
+
28.99%49/16915.95%15/9446.42%13/2829.26%48/164
src/modules +
+
25.18%139/55223.17%89/38427.27%21/7725.14%128/509
src/utils +
+
28.12%45/16044.89%66/14717.85%5/2828.38%44/155
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/prettify.css b/coverage/prettify.css new file mode 100644 index 00000000..d44b3a22 --- /dev/null +++ b/coverage/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} \ No newline at end of file diff --git a/coverage/prettify.js b/coverage/prettify.js new file mode 100644 index 00000000..84567ecd --- /dev/null +++ b/coverage/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); \ No newline at end of file diff --git a/coverage/sort-arrow-sprite.png b/coverage/sort-arrow-sprite.png new file mode 100644 index 00000000..e69de29b diff --git a/coverage/sorter.js b/coverage/sorter.js new file mode 100644 index 00000000..83256b57 --- /dev/null +++ b/coverage/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); \ No newline at end of file diff --git a/coverage/src/commands/config.js.html b/coverage/src/commands/config.js.html new file mode 100644 index 00000000..657943ac --- /dev/null +++ b/coverage/src/commands/config.js.html @@ -0,0 +1,1138 @@ + + + + + + Code coverage report for src/commands/config.js + + + + + + + + + +
+
+

All files / src/commands config.js

+
+ +
+ 8.26% + Statements + 10/121 +
+ + +
+ 0% + Branches + 0/70 +
+ + +
+ 33.33% + Functions + 7/21 +
+ + +
+ 8.26% + Lines + 10/121 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +1x +  +  +  +1x +  +  +  +  +  +  +  +1x +  +  +  +1x +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +1x +  +  +  +1x +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Config Command
+ * View, set, and reset bot configuration via slash commands
+ */
+ 
+import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
+import { getConfig, resetConfig, setConfigValue } from '../modules/config.js';
+ 
+/**
+ * Escape backticks in user-provided strings to prevent breaking Discord inline code formatting.
+ * @param {string} str - Raw string to sanitize
+ * @returns {string} Sanitized string safe for embedding inside backtick-delimited code spans
+ */
+function escapeInlineCode(str) {
+  return String(str).replace(/`/g, '\\`');
+}
+ 
+export const data = new SlashCommandBuilder()
+  .setName('config')
+  .setDescription('View or manage bot configuration (Admin only)')
+  .addSubcommand((subcommand) =>
+    subcommand
+      .setName('view')
+      .setDescription('View current configuration')
+      .addStringOption((option) =>
+        option
+          .setName('section')
+          .setDescription('Specific config section to view')
+          .setRequired(false)
+          .setAutocomplete(true),
+      ),
+  )
+  .addSubcommand((subcommand) =>
+    subcommand
+      .setName('set')
+      .setDescription('Set a configuration value')
+      .addStringOption((option) =>
+        option
+          .setName('path')
+          .setDescription('Dot-notation path (e.g., ai.model, welcome.enabled)')
+          .setRequired(true)
+          .setAutocomplete(true),
+      )
+      .addStringOption((option) =>
+        option
+          .setName('value')
+          .setDescription(
+            'Value (auto-coerces true/false/null/numbers; use "\\"text\\"" for literal strings)',
+          )
+          .setRequired(true),
+      ),
+  )
+  .addSubcommand((subcommand) =>
+    subcommand
+      .setName('reset')
+      .setDescription('Reset configuration to defaults from config.json')
+      .addStringOption((option) =>
+        option
+          .setName('section')
+          .setDescription('Section to reset (omit to reset all)')
+          .setRequired(false)
+          .setAutocomplete(true),
+      ),
+  );
+ 
+export const adminOnly = true;
+ 
+/**
+ * Recursively collect leaf-only dot-notation paths for a config object.
+ * Only emits paths that point to non-object values (leaves).
+ * @param {*} source - Config value to traverse
+ * @param {string} [prefix] - Current path prefix
+ * @param {string[]} [paths] - Accumulator array
+ * @returns {string[]} Dot-notation config paths (leaf-only)
+ */
+function collectConfigPaths(source, prefix = '', paths = []) {
+  if (Array.isArray(source)) {
+    // Emit path for empty arrays so they're discoverable in autocomplete
+    if (source.length === 0 && prefix) {
+      paths.push(prefix);
+      return paths;
+    }
+    source.forEach((value, index) => {
+      const path = prefix ? `${prefix}.${index}` : String(index);
+      if (value && typeof value === 'object') {
+        collectConfigPaths(value, path, paths);
+      } else {
+        paths.push(path);
+      }
+    });
+    return paths;
+  }
+ 
+  if (!source || typeof source !== 'object') {
+    return paths;
+  }
+ 
+  // Emit path for empty objects so they're discoverable in autocomplete
+  if (Object.keys(source).length === 0 && prefix) {
+    paths.push(prefix);
+    return paths;
+  }
+ 
+  for (const [key, value] of Object.entries(source)) {
+    const path = prefix ? `${prefix}.${key}` : key;
+    if (value && typeof value === 'object') {
+      collectConfigPaths(value, path, paths);
+    } else {
+      paths.push(path);
+    }
+  }
+ 
+  return paths;
+}
+ 
+/**
+ * Handle autocomplete for config paths and section names
+ * @param {Object} interaction - Discord interaction
+ */
+export async function autocomplete(interaction) {
+  const focusedOption = interaction.options.getFocused(true);
+  const focusedValue = focusedOption.value.toLowerCase().trim();
+  const config = getConfig();
+ 
+  let choices;
+  if (focusedOption.name === 'section') {
+    // Autocomplete section names from live config
+    choices = Object.keys(config)
+      .filter((s) => s.toLowerCase().includes(focusedValue))
+      .slice(0, 25)
+      .map((s) => ({ name: s, value: s }));
+  } else {
+    // Autocomplete dot-notation paths (leaf-only)
+    const paths = collectConfigPaths(config);
+    choices = paths
+      .filter((p) => p.toLowerCase().includes(focusedValue))
+      .sort((a, b) => {
+        const aLower = a.toLowerCase();
+        const bLower = b.toLowerCase();
+        const aStartsWithFocus = aLower.startsWith(focusedValue);
+        const bStartsWithFocus = bLower.startsWith(focusedValue);
+        if (aStartsWithFocus !== bStartsWithFocus) {
+          return aStartsWithFocus ? -1 : 1;
+        }
+        return aLower.localeCompare(bLower);
+      })
+      .slice(0, 25)
+      .map((p) => ({ name: p, value: p }));
+  }
+ 
+  await interaction.respond(choices);
+}
+ 
+/**
+ * Execute the config command
+ * @param {Object} interaction - Discord interaction
+ */
+export async function execute(interaction) {
+  const subcommand = interaction.options.getSubcommand();
+ 
+  switch (subcommand) {
+    case 'view':
+      await handleView(interaction);
+      break;
+    case 'set':
+      await handleSet(interaction);
+      break;
+    case 'reset':
+      await handleReset(interaction);
+      break;
+    default:
+      await interaction.reply({
+        content: `❌ Unknown subcommand: \`${subcommand}\``,
+        ephemeral: true,
+      });
+      break;
+  }
+}
+ 
+/** @type {number} Discord embed total character limit */
+const EMBED_CHAR_LIMIT = 6000;
+ 
+/**
+ * Handle /config view
+ */
+async function handleView(interaction) {
+  try {
+    const config = getConfig();
+    const section = interaction.options.getString('section');
+ 
+    const embed = new EmbedBuilder()
+      .setColor(0x5865f2)
+      .setTitle('⚙️ Bot Configuration')
+      .setFooter({
+        text: `${process.env.DATABASE_URL ? 'Stored in PostgreSQL' : 'Stored in memory (config.json)'} • Use /config set to modify`,
+      })
+      .setTimestamp();
+ 
+    if (section) {
+      const sectionData = config[section];
+      if (!sectionData) {
+        const safeSection = escapeInlineCode(section);
+        return await interaction.reply({
+          content: `❌ Section \`${safeSection}\` not found in config`,
+          ephemeral: true,
+        });
+      }
+ 
+      embed.setDescription(`**${section.toUpperCase()} Configuration**`);
+      const sectionJson = JSON.stringify(sectionData, null, 2);
+      embed.addFields({
+        name: 'Settings',
+        value:
+          '```json\n' +
+          (sectionJson.length > 1000 ? `${sectionJson.slice(0, 997)}...` : sectionJson) +
+          '\n```',
+      });
+    } else {
+      embed.setDescription('Current bot configuration');
+ 
+      // Track cumulative embed size to stay under Discord's 6000-char limit
+      let totalLength = (embed.data.title?.length || 0) + (embed.data.description?.length || 0);
+      let truncated = false;
+ 
+      for (const [key, value] of Object.entries(config)) {
+        const jsonStr = JSON.stringify(value, null, 2);
+        const fieldValue = `\`\`\`json\n${jsonStr.length > 1000 ? `${jsonStr.slice(0, 997)}...` : jsonStr}\n\`\`\``;
+        const fieldName = key.toUpperCase();
+        const fieldLength = fieldName.length + fieldValue.length;
+ 
+        if (totalLength + fieldLength > EMBED_CHAR_LIMIT - 200) {
+          // Reserve space for a truncation notice
+          embed.addFields({
+            name: '⚠️ Truncated',
+            value: 'Use `/config view section:<name>` to see remaining sections.',
+            inline: false,
+          });
+          truncated = true;
+          break;
+        }
+ 
+        totalLength += fieldLength;
+        embed.addFields({
+          name: fieldName,
+          value: fieldValue,
+          inline: false,
+        });
+      }
+ 
+      if (truncated) {
+        embed.setFooter({
+          text: 'Some sections omitted • Use /config view section:<name> for details',
+        });
+      }
+    }
+ 
+    await interaction.reply({ embeds: [embed], ephemeral: true });
+  } catch (err) {
+    await interaction.reply({
+      content: `❌ Failed to load config: ${err.message}`,
+      ephemeral: true,
+    });
+  }
+}
+ 
+/**
+ * Handle /config set
+ */
+async function handleSet(interaction) {
+  const path = interaction.options.getString('path');
+  const value = interaction.options.getString('value');
+ 
+  // Validate section exists in live config
+  const section = path.split('.')[0];
+  const validSections = Object.keys(getConfig());
+  if (!validSections.includes(section)) {
+    const safeSection = escapeInlineCode(section);
+    return await interaction.reply({
+      content: `❌ Invalid section \`${safeSection}\`. Valid sections: ${validSections.join(', ')}`,
+      ephemeral: true,
+    });
+  }
+ 
+  try {
+    await interaction.deferReply({ ephemeral: true });
+ 
+    const updatedSection = await setConfigValue(path, value);
+ 
+    // Traverse to the actual leaf value for display
+    const leafValue = path
+      .split('.')
+      .slice(1)
+      .reduce((obj, k) => obj?.[k], updatedSection);
+ 
+    const displayValue = JSON.stringify(leafValue, null, 2) ?? value;
+    const truncatedValue =
+      displayValue.length > 1000 ? `${displayValue.slice(0, 997)}...` : displayValue;
+ 
+    const embed = new EmbedBuilder()
+      .setColor(0x57f287)
+      .setTitle('✅ Config Updated')
+      .addFields(
+        { name: 'Path', value: `\`${path}\``, inline: true },
+        { name: 'New Value', value: `\`${truncatedValue}\``, inline: true },
+      )
+      .setFooter({ text: 'Changes take effect immediately' })
+      .setTimestamp();
+ 
+    await interaction.editReply({ embeds: [embed] });
+  } catch (err) {
+    const content = `❌ Failed to set config: ${err.message}`;
+    if (interaction.deferred) {
+      await interaction.editReply({ content });
+    } else {
+      await interaction.reply({ content, ephemeral: true });
+    }
+  }
+}
+ 
+/**
+ * Handle /config reset
+ */
+async function handleReset(interaction) {
+  const section = interaction.options.getString('section');
+ 
+  try {
+    await interaction.deferReply({ ephemeral: true });
+ 
+    await resetConfig(section || undefined);
+ 
+    const embed = new EmbedBuilder()
+      .setColor(0xfee75c)
+      .setTitle('🔄 Config Reset')
+      .setDescription(
+        section
+          ? `Section **${section}** has been reset to defaults from config.json.`
+          : 'All configuration has been reset to defaults from config.json.',
+      )
+      .setFooter({ text: 'Changes take effect immediately' })
+      .setTimestamp();
+ 
+    await interaction.editReply({ embeds: [embed] });
+  } catch (err) {
+    const content = `❌ Failed to reset config: ${err.message}`;
+    if (interaction.deferred) {
+      await interaction.editReply({ content });
+    } else {
+      await interaction.reply({ content, ephemeral: true });
+    }
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/commands/index.html b/coverage/src/commands/index.html new file mode 100644 index 00000000..40367df8 --- /dev/null +++ b/coverage/src/commands/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for src/commands + + + + + + + + + +
+
+

All files src/commands

+
+ +
+ 28.99% + Statements + 49/169 +
+ + +
+ 15.95% + Branches + 15/94 +
+ + +
+ 46.42% + Functions + 13/28 +
+ + +
+ 29.26% + Lines + 48/164 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
config.js +
+
8.26%10/1210%0/7033.33%7/218.26%10/121
ping.js +
+
100%6/6100%0/0100%1/1100%6/6
status.js +
+
78.57%33/4262.5%15/2483.33%5/686.48%32/37
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/commands/ping.js.html b/coverage/src/commands/ping.js.html new file mode 100644 index 00000000..7e8dc24e --- /dev/null +++ b/coverage/src/commands/ping.js.html @@ -0,0 +1,139 @@ + + + + + + Code coverage report for src/commands/ping.js + + + + + + + + + +
+
+

All files / src/commands ping.js

+
+ +
+ 100% + Statements + 6/6 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19  +  +2x +  +  +  +  +9x +  +  +  +  +9x +9x +9x +  +9x +  + 
import { SlashCommandBuilder } from 'discord.js';
+ 
+export const data = new SlashCommandBuilder()
+  .setName('ping')
+  .setDescription('Check bot latency and responsiveness');
+ 
+export async function execute(interaction) {
+  const response = await interaction.reply({
+    content: 'Pinging...',
+    withResponse: true,
+  });
+ 
+  const sent = response.resource.message;
+  const latency = sent.createdTimestamp - interaction.createdTimestamp;
+  const apiLatency = Math.round(interaction.client.ws.ping);
+ 
+  await interaction.editReply(`🏓 Pong!\n📡 Latency: ${latency}ms\n💓 API: ${apiLatency}ms`);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/commands/status.js.html b/coverage/src/commands/status.js.html new file mode 100644 index 00000000..7ac04ed3 --- /dev/null +++ b/coverage/src/commands/status.js.html @@ -0,0 +1,544 @@ + + + + + + Code coverage report for src/commands/status.js + + + + + + + + + +
+
+

All files / src/commands status.js

+
+ +
+ 78.57% + Statements + 33/42 +
+ + +
+ 62.5% + Branches + 15/24 +
+ + +
+ 83.33% + Functions + 5/6 +
+ + +
+ 86.48% + Lines + 32/37 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +2x +  +  +  +  +  +  +  +  +  +16x +  +16x +16x +16x +16x +16x +16x +  +16x +16x +16x +  +  +  +  +  +  +  +  +16x +  +16x +  +  +  +  +  +  +  +  +  +  +  +  +  +21x +21x +21x +  +21x +  +9x +1x +  +  +  +1x +  +  +  +8x +  +8x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +8x +  +  +8x +  +8x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +8x +  +  +4x +  +4x +  +  +  +  +4x +2x +  +2x +  +  +  + 
/**
+ * Status Command - Display bot health metrics
+ *
+ * Shows uptime, memory usage, API status, and last AI request
+ * Admin mode (detailed: true) shows additional diagnostics
+ */
+ 
+import { EmbedBuilder, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
+import { error as logError } from '../logger.js';
+import { HealthMonitor } from '../utils/health.js';
+ 
+export const data = new SlashCommandBuilder()
+  .setName('status')
+  .setDescription('Display bot health metrics and status')
+  .addBooleanOption((option) =>
+    option
+      .setName('detailed')
+      .setDescription('Show detailed diagnostics (admin only)')
+      .setRequired(false),
+  );
+ 
+/**
+ * Format timestamp as relative time
+ */
+function formatRelativeTime(timestamp) {
+  Iif (!timestamp) return 'Never';
+ 
+  const now = Date.now();
+  const diff = now - timestamp;
+  const seconds = Math.floor(diff / 1000);
+  const minutes = Math.floor(seconds / 60);
+  const hours = Math.floor(minutes / 60);
+  const days = Math.floor(hours / 24);
+ 
+  Iif (diff < 1000) return 'Just now';
+  Iif (seconds < 60) return `${seconds}s ago`;
+  Eif (minutes < 60) return `${minutes}m ago`;
+  if (hours < 24) return `${hours}h ago`;
+  return `${days}d ago`;
+}
+ 
+/**
+ * Get status emoji based on API status
+ */
+function getStatusEmoji(status) {
+  switch (status) {
+    case 'ok':
+      return '🟢';
+    case 'error':
+      return '🔴';
+    case 'unknown':
+      return '🟡';
+    default:
+      return '⚪';
+  }
+}
+ 
+/**
+ * Execute the status command
+ */
+export async function execute(interaction) {
+  try {
+    const detailed = interaction.options.getBoolean('detailed') || false;
+    const healthMonitor = HealthMonitor.getInstance();
+ 
+    if (detailed) {
+      // Check if user has admin permissions
+      if (!interaction.memberPermissions?.has(PermissionFlagsBits.Administrator)) {
+        await interaction.reply({
+          content: '❌ Detailed diagnostics are only available to administrators.',
+          ephemeral: true,
+        });
+        return;
+      }
+ 
+      // Detailed mode - admin diagnostics
+      const status = healthMonitor.getDetailedStatus();
+ 
+      const embed = new EmbedBuilder()
+        .setColor(0x5865f2)
+        .setTitle('🔍 Bot Status - Detailed Diagnostics')
+        .addFields(
+          { name: '⏱️ Uptime', value: status.uptimeFormatted, inline: true },
+          { name: '🧠 Memory', value: status.memory.formatted, inline: true },
+          {
+            name: '🌐 API',
+            value: `${getStatusEmoji(status.api.status)} ${status.api.status}`,
+            inline: true,
+          },
+          {
+            name: '🤖 Last AI Request',
+            value: formatRelativeTime(status.lastAIRequest),
+            inline: true,
+          },
+          { name: '📊 Process ID', value: `${status.process.pid}`, inline: true },
+          { name: '🖥️ Platform', value: status.process.platform, inline: true },
+          { name: '📦 Node Version', value: status.process.nodeVersion, inline: true },
+          {
+            name: '⚙️ Process Uptime',
+            value: `${Math.floor(status.process.uptime)}s`,
+            inline: true,
+          },
+          { name: '🔢 Heap Used', value: `${status.memory.heapUsed}MB`, inline: true },
+          { name: '💾 RSS', value: `${status.memory.rss}MB`, inline: true },
+          { name: '📡 External', value: `${status.memory.external}MB`, inline: true },
+          { name: '🔢 Array Buffers', value: `${status.memory.arrayBuffers}MB`, inline: true },
+        )
+        .setTimestamp()
+        .setFooter({ text: 'Detailed diagnostics mode' });
+ 
+      await interaction.reply({ embeds: [embed], ephemeral: true });
+    } else {
+      // Basic mode - user-friendly status
+      const status = healthMonitor.getStatus();
+ 
+      const embed = new EmbedBuilder()
+        .setColor(0x57f287)
+        .setTitle('📊 Bot Status')
+        .setDescription('Current health and performance metrics')
+        .addFields(
+          { name: '⏱️ Uptime', value: status.uptimeFormatted, inline: true },
+          { name: '🧠 Memory', value: status.memory.formatted, inline: true },
+          {
+            name: '🌐 API Status',
+            value: `${getStatusEmoji(status.api.status)} ${status.api.status.toUpperCase()}`,
+            inline: true,
+          },
+          {
+            name: '🤖 Last AI Request',
+            value: formatRelativeTime(status.lastAIRequest),
+            inline: false,
+          },
+        )
+        .setTimestamp()
+        .setFooter({ text: 'Use /status detailed:true for more info' });
+ 
+      await interaction.reply({ embeds: [embed] });
+    }
+  } catch (err) {
+    logError('Status command error', { error: err.message });
+ 
+    const reply = {
+      content: "Sorry, I couldn't retrieve the status. Try again in a moment!",
+      ephemeral: true,
+    };
+ 
+    if (interaction.replied || interaction.deferred) {
+      await interaction.followUp(reply).catch(() => {});
+    } else {
+      await interaction.reply(reply).catch(() => {});
+    }
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/db.js.html b/coverage/src/db.js.html new file mode 100644 index 00000000..a386abc7 --- /dev/null +++ b/coverage/src/db.js.html @@ -0,0 +1,502 @@ + + + + + + Code coverage report for src/db.js + + + + + + + + + +
+
+

All files / src db.js

+
+ +
+ 6.66% + Statements + 3/45 +
+ + +
+ 0% + Branches + 0/20 +
+ + +
+ 0% + Functions + 0/6 +
+ + +
+ 6.81% + Lines + 3/44 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140  +  +  +  +  +  +  +  +1x +  +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Database Module
+ * PostgreSQL connection pool and schema initialization
+ */
+ 
+import pg from 'pg';
+import { info, error as logError } from './logger.js';
+ 
+const { Pool } = pg;
+ 
+/** @type {pg.Pool | null} */
+let pool = null;
+ 
+/** @type {boolean} Re-entrancy guard for initDb */
+let initializing = false;
+ 
+/**
+ * Determine SSL configuration based on DATABASE_SSL env var and connection string.
+ *
+ * DATABASE_SSL values:
+ *   "false" / "off"      → SSL disabled
+ *   "no-verify"          → SSL enabled but server cert not verified
+ *   "true" / "on" / unset → SSL enabled with full verification
+ *
+ * Railway internal connections always disable SSL regardless of env var.
+ *
+ * @param {string} connectionString - Database connection URL
+ * @returns {false|{rejectUnauthorized: boolean}} SSL config for pg.Pool
+ */
+function getSslConfig(connectionString) {
+  // Railway internal connections never need SSL
+  if (connectionString.includes('railway.internal')) {
+    return false;
+  }
+ 
+  const sslEnv = (process.env.DATABASE_SSL || '').toLowerCase().trim();
+ 
+  if (sslEnv === 'false' || sslEnv === 'off') {
+    return false;
+  }
+ 
+  if (sslEnv === 'no-verify') {
+    return { rejectUnauthorized: false };
+  }
+ 
+  // Default: SSL with full verification
+  return { rejectUnauthorized: true };
+}
+ 
+/**
+ * Initialize the database connection pool and create schema
+ * @returns {Promise<pg.Pool>} The connection pool
+ */
+export async function initDb() {
+  if (pool) return pool;
+  if (initializing) {
+    throw new Error('initDb is already in progress');
+  }
+ 
+  initializing = true;
+  try {
+    const connectionString = process.env.DATABASE_URL;
+    if (!connectionString) {
+      throw new Error('DATABASE_URL environment variable is not set');
+    }
+ 
+    pool = new Pool({
+      connectionString,
+      max: 5,
+      idleTimeoutMillis: 30000,
+      connectionTimeoutMillis: 10000,
+      ssl: getSslConfig(connectionString),
+    });
+ 
+    // Prevent unhandled pool errors from crashing the process
+    pool.on('error', (err) => {
+      logError('Unexpected database pool error', { error: err.message });
+    });
+ 
+    try {
+      // Test connection
+      const client = await pool.connect();
+      try {
+        await client.query('SELECT NOW()');
+        info('Database connected');
+      } finally {
+        client.release();
+      }
+ 
+      // Create schema
+      await pool.query(`
+        CREATE TABLE IF NOT EXISTS config (
+          key TEXT PRIMARY KEY,
+          value JSONB NOT NULL,
+          updated_at TIMESTAMPTZ DEFAULT NOW()
+        )
+      `);
+ 
+      info('Database schema initialized');
+    } catch (err) {
+      // Clean up the pool so getPool() doesn't return an unusable instance
+      await pool.end().catch(() => {});
+      pool = null;
+      throw err;
+    }
+ 
+    return pool;
+  } finally {
+    initializing = false;
+  }
+}
+ 
+/**
+ * Get the database pool
+ * @returns {pg.Pool} The connection pool
+ * @throws {Error} If pool is not initialized
+ */
+export function getPool() {
+  if (!pool) {
+    throw new Error('Database not initialized. Call initDb() first.');
+  }
+  return pool;
+}
+ 
+/**
+ * Gracefully close the database pool
+ */
+export async function closeDb() {
+  if (pool) {
+    try {
+      await pool.end();
+      info('Database pool closed');
+    } catch (err) {
+      logError('Error closing database pool', { error: err.message });
+    } finally {
+      pool = null;
+    }
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/index.html b/coverage/src/index.html new file mode 100644 index 00000000..13d8c982 --- /dev/null +++ b/coverage/src/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for src + + + + + + + + + +
+
+

All files src

+
+ +
+ 26.2% + Statements + 60/229 +
+ + +
+ 34.28% + Branches + 36/105 +
+ + +
+ 31.42% + Functions + 11/35 +
+ + +
+ 26.24% + Lines + 58/221 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
db.js +
+
6.66%3/450%0/200%0/66.81%3/44
index.js +
+
0%0/1220%0/380%0/180%0/117
logger.js +
+
91.93%57/6276.59%36/47100%11/1191.66%55/60
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/index.js.html b/coverage/src/index.js.html new file mode 100644 index 00000000..19d6341d --- /dev/null +++ b/coverage/src/index.js.html @@ -0,0 +1,1075 @@ + + + + + + Code coverage report for src/index.js + + + + + + + + + +
+
+

All files / src index.js

+
+ +
+ 0% + Statements + 0/122 +
+ + +
+ 0% + Branches + 0/38 +
+ + +
+ 0% + Functions + 0/18 +
+ + +
+ 0% + Lines + 0/117 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Bill Bot - Volvox Discord Bot
+ * Main entry point - orchestrates modules
+ *
+ * Features:
+ * - AI chat powered by Claude
+ * - Welcome messages for new members
+ * - Spam/scam detection and moderation
+ * - Health monitoring and status command
+ * - Graceful shutdown handling
+ * - Structured logging
+ */
+ 
+import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { Client, Collection, GatewayIntentBits } from 'discord.js';
+import { config as dotenvConfig } from 'dotenv';
+import { closeDb, initDb } from './db.js';
+import { error, info, warn } from './logger.js';
+import { getConversationHistory, setConversationHistory } from './modules/ai.js';
+import { loadConfig } from './modules/config.js';
+import { registerEventHandlers } from './modules/events.js';
+import { HealthMonitor } from './utils/health.js';
+import { getPermissionError, hasPermission } from './utils/permissions.js';
+import { registerCommands } from './utils/registerCommands.js';
+ 
+// ES module dirname equivalent
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+ 
+// State persistence path
+const dataDir = join(__dirname, '..', 'data');
+const statePath = join(dataDir, 'state.json');
+ 
+// Load environment variables
+dotenvConfig();
+ 
+// Config is loaded asynchronously after DB init (see startup below).
+// After loadConfig() resolves, `config` points to the same object as
+// configCache inside modules/config.js, so in-place mutations from
+// setConfigValue() propagate here automatically without re-assignment.
+let config = {};
+ 
+// Initialize Discord client with required intents
+const client = new Client({
+  intents: [
+    GatewayIntentBits.Guilds,
+    GatewayIntentBits.GuildMessages,
+    GatewayIntentBits.MessageContent,
+    GatewayIntentBits.GuildMembers,
+    GatewayIntentBits.GuildVoiceStates,
+  ],
+});
+ 
+// Initialize command collection
+client.commands = new Collection();
+ 
+// Initialize health monitor
+const healthMonitor = HealthMonitor.getInstance();
+ 
+// Track pending AI requests for graceful shutdown
+const pendingRequests = new Set();
+ 
+/**
+ * Register a pending request for tracking
+ * @returns {Symbol} Request ID to use for cleanup
+ */
+export function registerPendingRequest() {
+  const requestId = Symbol('request');
+  pendingRequests.add(requestId);
+  return requestId;
+}
+ 
+/**
+ * Remove a pending request from tracking
+ * @param {Symbol} requestId - Request ID to remove
+ */
+export function removePendingRequest(requestId) {
+  pendingRequests.delete(requestId);
+}
+ 
+/**
+ * Save conversation history to disk
+ */
+function saveState() {
+  try {
+    // Ensure data directory exists
+    if (!existsSync(dataDir)) {
+      mkdirSync(dataDir, { recursive: true });
+    }
+ 
+    const conversationHistory = getConversationHistory();
+    const stateData = {
+      conversationHistory: Array.from(conversationHistory.entries()),
+      timestamp: new Date().toISOString(),
+    };
+    writeFileSync(statePath, JSON.stringify(stateData, null, 2), 'utf-8');
+    info('State saved successfully');
+  } catch (err) {
+    error('Failed to save state', { error: err.message });
+  }
+}
+ 
+/**
+ * Load conversation history from disk
+ */
+function loadState() {
+  try {
+    if (!existsSync(statePath)) {
+      return;
+    }
+    const stateData = JSON.parse(readFileSync(statePath, 'utf-8'));
+    if (stateData.conversationHistory) {
+      setConversationHistory(new Map(stateData.conversationHistory));
+      info('State loaded successfully');
+    }
+  } catch (err) {
+    error('Failed to load state', { error: err.message });
+  }
+}
+ 
+/**
+ * Load all commands from the commands directory
+ */
+async function loadCommands() {
+  const commandsPath = join(__dirname, 'commands');
+  const commandFiles = readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
+ 
+  for (const file of commandFiles) {
+    const filePath = join(commandsPath, file);
+    try {
+      const command = await import(filePath);
+      if (command.data && command.execute) {
+        client.commands.set(command.data.name, command);
+        info('Loaded command', { command: command.data.name });
+      } else {
+        warn('Command missing data or execute export', { file });
+      }
+    } catch (err) {
+      error('Failed to load command', { file, error: err.message });
+    }
+  }
+}
+ 
+// Event handlers are registered after config loads (see startup below)
+ 
+// Extend ready handler to register slash commands
+client.once('clientReady', async () => {
+  // Register slash commands with Discord
+  try {
+    const commands = Array.from(client.commands.values());
+    const guildId = process.env.GUILD_ID || null;
+ 
+    await registerCommands(commands, client.user.id, process.env.DISCORD_TOKEN, guildId);
+  } catch (err) {
+    error('Command registration failed', { error: err.message });
+  }
+});
+ 
+// Handle slash commands and autocomplete
+client.on('interactionCreate', async (interaction) => {
+  // Handle autocomplete
+  if (interaction.isAutocomplete()) {
+    const command = client.commands.get(interaction.commandName);
+    if (command?.autocomplete) {
+      try {
+        await command.autocomplete(interaction);
+      } catch (err) {
+        error('Autocomplete error', { command: interaction.commandName, error: err.message });
+      }
+    }
+    return;
+  }
+ 
+  if (!interaction.isChatInputCommand()) return;
+ 
+  const { commandName, member } = interaction;
+ 
+  try {
+    info('Slash command received', { command: commandName, user: interaction.user.tag });
+ 
+    // Permission check
+    if (!hasPermission(member, commandName, config)) {
+      await interaction.reply({
+        content: getPermissionError(commandName),
+        ephemeral: true,
+      });
+      warn('Permission denied', { user: interaction.user.tag, command: commandName });
+      return;
+    }
+ 
+    // Execute command from collection
+    const command = client.commands.get(commandName);
+    if (!command) {
+      await interaction.reply({
+        content: '❌ Command not found.',
+        ephemeral: true,
+      });
+      return;
+    }
+ 
+    await command.execute(interaction);
+    info('Command executed', { command: commandName, user: interaction.user.tag });
+  } catch (err) {
+    error('Command error', { command: commandName, error: err.message, stack: err.stack });
+ 
+    const errorMessage = {
+      content: '❌ An error occurred while executing this command.',
+      ephemeral: true,
+    };
+ 
+    if (interaction.replied || interaction.deferred) {
+      await interaction.followUp(errorMessage).catch(() => {});
+    } else {
+      await interaction.reply(errorMessage).catch(() => {});
+    }
+  }
+});
+ 
+/**
+ * Graceful shutdown handler
+ * @param {string} signal - Signal that triggered shutdown
+ */
+async function gracefulShutdown(signal) {
+  info('Shutdown initiated', { signal });
+ 
+  // 1. Wait for pending requests with timeout
+  const SHUTDOWN_TIMEOUT = 10000; // 10 seconds
+  if (pendingRequests.size > 0) {
+    info('Waiting for pending requests', { count: pendingRequests.size });
+    const startTime = Date.now();
+ 
+    while (pendingRequests.size > 0 && Date.now() - startTime < SHUTDOWN_TIMEOUT) {
+      await new Promise((resolve) => setTimeout(resolve, 100));
+    }
+ 
+    if (pendingRequests.size > 0) {
+      warn('Shutdown timeout, requests still pending', { count: pendingRequests.size });
+    } else {
+      info('All requests completed');
+    }
+  }
+ 
+  // 2. Save state after pending requests complete
+  info('Saving conversation state');
+  saveState();
+ 
+  // 3. Close database pool
+  info('Closing database connection');
+  try {
+    await closeDb();
+  } catch (err) {
+    error('Failed to close database pool', { error: err.message });
+  }
+ 
+  // 4. Destroy Discord client
+  info('Disconnecting from Discord');
+  client.destroy();
+ 
+  // 5. Log clean exit
+  info('Shutdown complete');
+  process.exit(0);
+}
+ 
+// Handle shutdown signals
+process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
+process.on('SIGINT', () => gracefulShutdown('SIGINT'));
+ 
+// Error handling
+client.on('error', (err) => {
+  error('Discord client error', {
+    error: err.message,
+    stack: err.stack,
+    code: err.code,
+  });
+});
+ 
+process.on('unhandledRejection', (err) => {
+  error('Unhandled promise rejection', {
+    error: err?.message || String(err),
+    stack: err?.stack,
+    type: typeof err,
+  });
+});
+ 
+// Start bot
+const token = process.env.DISCORD_TOKEN;
+if (!token) {
+  error('DISCORD_TOKEN not set');
+  process.exit(1);
+}
+ 
+/**
+ * Main startup sequence
+ * 1. Initialize database
+ * 2. Load config from DB (seeds from config.json if empty)
+ * 3. Load previous conversation state
+ * 4. Register event handlers with live config
+ * 5. Load commands
+ * 6. Login to Discord
+ */
+async function startup() {
+  // Initialize database
+  if (process.env.DATABASE_URL) {
+    await initDb();
+    info('Database initialized');
+  } else {
+    warn('DATABASE_URL not set — using config.json only (no persistence)');
+  }
+ 
+  // Load config (from DB if available, else config.json)
+  config = await loadConfig();
+  info('Configuration loaded', { sections: Object.keys(config) });
+ 
+  // Load previous conversation state
+  loadState();
+ 
+  // Register event handlers with live config reference
+  registerEventHandlers(client, config, healthMonitor);
+ 
+  // Load commands and login
+  await loadCommands();
+  await client.login(token);
+}
+ 
+startup().catch((err) => {
+  error('Startup failed', { error: err.message, stack: err.stack });
+  process.exit(1);
+});
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/logger.js.html b/coverage/src/logger.js.html new file mode 100644 index 00000000..7d4c5155 --- /dev/null +++ b/coverage/src/logger.js.html @@ -0,0 +1,808 @@ + + + + + + Code coverage report for src/logger.js + + + + + + + + + +
+
+

All files / src logger.js

+
+ +
+ 91.93% + Statements + 57/62 +
+ + +
+ 76.59% + Branches + 36/47 +
+ + +
+ 100% + Functions + 11/11 +
+ + +
+ 91.66% + Lines + 55/60 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +5x +5x +  +  +5x +5x +  +5x +5x +5x +5x +5x +  +  +  +  +  +  +  +5x +5x +5x +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +5431x +  +  +  +5431x +  +  +  +5431x +4x +  +  +5429x +5429x +  +75986x +  +10856x +2x +10854x +5422x +  +5432x +  +  +  +6x +  +  +  +  +  +5x +  +148x +  +  +148x +514x +  +217x +1487x +  +  +217x +8x +209x +  +5x +  +  +  +  +147x +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +5x +64x +64x +  +  +  +  +  +5x +  +  +64x +64x +  +64x +  +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +  +  +  +5x +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +45x +  +  +  +  +  +  +1x +  +  +  +  +  +  +19x +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Structured Logger Module
+ *
+ * Provides centralized logging with:
+ * - Multiple log levels (debug, info, warn, error)
+ * - Timestamp formatting
+ * - Structured output
+ * - Console transport (file transport added in phase 3)
+ */
+ 
+import { existsSync, mkdirSync, readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import winston from 'winston';
+import DailyRotateFile from 'winston-daily-rotate-file';
+ 
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const configPath = join(__dirname, '..', 'config.json');
+const logsDir = join(__dirname, '..', 'logs');
+ 
+// Load config to get log level and file output setting
+let logLevel = 'info';
+let fileOutputEnabled = false;
+ 
+try {
+  Eif (existsSync(configPath)) {
+    const config = JSON.parse(readFileSync(configPath, 'utf-8'));
+    logLevel = process.env.LOG_LEVEL || config.logging?.level || 'info';
+    fileOutputEnabled = config.logging?.fileOutput || false;
+  }
+} catch (_err) {
+  // Fallback to default if config can't be loaded
+  logLevel = process.env.LOG_LEVEL || 'info';
+}
+ 
+// Create logs directory if file output is enabled
+Eif (fileOutputEnabled) {
+  try {
+    Iif (!existsSync(logsDir)) {
+      mkdirSync(logsDir, { recursive: true });
+    }
+  } catch (_err) {
+    // Log directory creation failed, but continue without file logging
+    fileOutputEnabled = false;
+  }
+}
+ 
+/**
+ * Sensitive field names that should be redacted from logs
+ */
+const SENSITIVE_FIELDS = [
+  'DISCORD_TOKEN',
+  'OPENCLAW_API_KEY',
+  'OPENCLAW_TOKEN',
+  'token',
+  'password',
+  'apiKey',
+  'authorization',
+];
+ 
+/**
+ * Recursively filter sensitive data from objects
+ */
+function filterSensitiveData(obj) {
+  Iif (obj === null || obj === undefined) {
+    return obj;
+  }
+ 
+  Iif (typeof obj !== 'object') {
+    return obj;
+  }
+ 
+  if (Array.isArray(obj)) {
+    return obj.map((item) => filterSensitiveData(item));
+  }
+ 
+  const filtered = {};
+  for (const [key, value] of Object.entries(obj)) {
+    // Check if key matches any sensitive field (case-insensitive)
+    const isSensitive = SENSITIVE_FIELDS.some((field) => key.toLowerCase() === field.toLowerCase());
+ 
+    if (isSensitive) {
+      filtered[key] = '[REDACTED]';
+    } else if (typeof value === 'object' && value !== null) {
+      filtered[key] = filterSensitiveData(value);
+    } else {
+      filtered[key] = value;
+    }
+  }
+ 
+  return filtered;
+}
+ 
+/**
+ * Winston format that redacts sensitive data
+ */
+const redactSensitiveData = winston.format((info) => {
+  // Reserved winston properties that should not be filtered
+  const reserved = ['level', 'message', 'timestamp', 'stack'];
+ 
+  // Filter each property in the info object
+  for (const key in info) {
+    if (Object.hasOwn(info, key) && !reserved.includes(key)) {
+      // Check if this key is sensitive (case-insensitive)
+      const isSensitive = SENSITIVE_FIELDS.some(
+        (field) => key.toLowerCase() === field.toLowerCase(),
+      );
+ 
+      if (isSensitive) {
+        info[key] = '[REDACTED]';
+      } else if (typeof info[key] === 'object' && info[key] !== null) {
+        // Recursively filter nested objects
+        info[key] = filterSensitiveData(info[key]);
+      }
+    }
+  }
+ 
+  return info;
+})();
+ 
+/**
+ * Emoji mapping for log levels
+ */
+const EMOJI_MAP = {
+  error: '❌',
+  warn: '⚠️',
+  info: '✅',
+  debug: '🔍',
+};
+ 
+/**
+ * Format that stores the original level before colorization
+ */
+const preserveOriginalLevel = winston.format((info) => {
+  info.originalLevel = info.level;
+  return info;
+})();
+ 
+/**
+ * Custom format for console output with emoji prefixes
+ */
+const consoleFormat = winston.format.printf(
+  ({ level, message, timestamp, originalLevel, ...meta }) => {
+    // Use originalLevel for emoji lookup since 'level' may contain ANSI color codes
+    const prefix = EMOJI_MAP[originalLevel] || '📝';
+    const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
+ 
+    return `${prefix} [${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;
+  },
+);
+ 
+/**
+ * Create winston logger instance
+ */
+const transports = [
+  new winston.transports.Console({
+    format: winston.format.combine(
+      redactSensitiveData,
+      preserveOriginalLevel,
+      winston.format.colorize(),
+      winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
+      consoleFormat,
+    ),
+  }),
+];
+ 
+// Add file transport if enabled in config
+Eif (fileOutputEnabled) {
+  transports.push(
+    new DailyRotateFile({
+      filename: join(logsDir, 'combined-%DATE%.log'),
+      datePattern: 'YYYY-MM-DD',
+      maxSize: '20m',
+      maxFiles: '14d',
+      format: winston.format.combine(
+        redactSensitiveData,
+        winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
+        winston.format.json(),
+      ),
+    }),
+  );
+ 
+  // Separate transport for error-level logs only
+  transports.push(
+    new DailyRotateFile({
+      level: 'error',
+      filename: join(logsDir, 'error-%DATE%.log'),
+      datePattern: 'YYYY-MM-DD',
+      maxSize: '20m',
+      maxFiles: '14d',
+      format: winston.format.combine(
+        redactSensitiveData,
+        winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
+        winston.format.json(),
+      ),
+    }),
+  );
+}
+ 
+const logger = winston.createLogger({
+  level: logLevel,
+  format: winston.format.combine(winston.format.errors({ stack: true }), winston.format.splat()),
+  transports,
+});
+ 
+/**
+ * Log at debug level
+ */
+export function debug(message, meta = {}) {
+  logger.debug(message, meta);
+}
+ 
+/**
+ * Log at info level
+ */
+export function info(message, meta = {}) {
+  logger.info(message, meta);
+}
+ 
+/**
+ * Log at warn level
+ */
+export function warn(message, meta = {}) {
+  logger.warn(message, meta);
+}
+ 
+/**
+ * Log at error level
+ */
+export function error(message, meta = {}) {
+  logger.error(message, meta);
+}
+ 
+// Default export for convenience
+export default {
+  debug,
+  info,
+  warn,
+  error,
+  logger, // Export winston logger instance for advanced usage
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/modules/ai.js.html b/coverage/src/modules/ai.js.html new file mode 100644 index 00000000..58f58e91 --- /dev/null +++ b/coverage/src/modules/ai.js.html @@ -0,0 +1,520 @@ + + + + + + Code coverage report for src/modules/ai.js + + + + + + + + + +
+
+

All files / src/modules ai.js

+
+ +
+ 100% + Statements + 36/36 +
+ + +
+ 96.29% + Branches + 26/27 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 100% + Lines + 36/36 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146  +  +  +  +  +  +  +  +1x +1x +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +26x +  +  +  +  +  +  +1x +  +  +1x +  +  +  +  +  +  +  +77x +21x +  +77x +  +  +  +  +  +  +  +  +  +54x +54x +  +  +54x +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +14x +  +  +14x +  +  +  +  +  +  +14x +  +  +  +  +  +  +14x +  +14x +14x +  +  +  +  +  +  +  +  +  +  +  +  +13x +2x +1x +  +2x +  +  +11x +11x +  +  +14x +  +  +14x +1x +1x +  +  +  +11x +11x +  +11x +  +3x +3x +1x +  +3x +  +  + 
/**
+ * AI Module
+ * Handles AI chat functionality powered by Claude via OpenClaw
+ */
+ 
+import { error as logError, info } from '../logger.js';
+ 
+// Conversation history per channel (simple in-memory store)
+let conversationHistory = new Map();
+const MAX_HISTORY = 20;
+ 
+/**
+ * Get the full conversation history map (for state persistence)
+ * @returns {Map} Conversation history map
+ */
+export function getConversationHistory() {
+  return conversationHistory;
+}
+ 
+/**
+ * Set the conversation history map (for state restoration)
+ * @param {Map} history - Conversation history map to restore
+ */
+export function setConversationHistory(history) {
+  conversationHistory = history;
+}
+ 
+// OpenClaw API endpoint/token (exported for shared use by other modules)
+// Preferred env vars: OPENCLAW_API_URL + OPENCLAW_API_KEY
+// Backward-compatible aliases: OPENCLAW_URL + OPENCLAW_TOKEN
+export const OPENCLAW_URL =
+  process.env.OPENCLAW_API_URL ||
+  process.env.OPENCLAW_URL ||
+  'http://localhost:18789/v1/chat/completions';
+export const OPENCLAW_TOKEN = process.env.OPENCLAW_API_KEY || process.env.OPENCLAW_TOKEN || '';
+ 
+/**
+ * Get or create conversation history for a channel
+ * @param {string} channelId - Channel ID
+ * @returns {Array} Conversation history
+ */
+export function getHistory(channelId) {
+  if (!conversationHistory.has(channelId)) {
+    conversationHistory.set(channelId, []);
+  }
+  return conversationHistory.get(channelId);
+}
+ 
+/**
+ * Add message to conversation history
+ * @param {string} channelId - Channel ID
+ * @param {string} role - Message role (user/assistant)
+ * @param {string} content - Message content
+ */
+export function addToHistory(channelId, role, content) {
+  const history = getHistory(channelId);
+  history.push({ role, content });
+ 
+  // Trim old messages
+  while (history.length > MAX_HISTORY) {
+    history.shift();
+  }
+}
+ 
+/**
+ * Generate AI response using OpenClaw's chat completions endpoint
+ * @param {string} channelId - Channel ID
+ * @param {string} userMessage - User's message
+ * @param {string} username - Username
+ * @param {Object} config - Bot configuration
+ * @param {Object} healthMonitor - Health monitor instance (optional)
+ * @returns {Promise<string>} AI response
+ */
+export async function generateResponse(
+  channelId,
+  userMessage,
+  username,
+  config,
+  healthMonitor = null,
+) {
+  const history = getHistory(channelId);
+ 
+  const systemPrompt =
+    config.ai?.systemPrompt ||
+    `You are Volvox Bot, a helpful and friendly Discord bot for the Volvox developer community.
+You're witty, knowledgeable about programming and tech, and always eager to help.
+Keep responses concise and Discord-friendly (under 2000 chars).
+You can use Discord markdown formatting.`;
+ 
+  // Build messages array for OpenAI-compatible API
+  const messages = [
+    { role: 'system', content: systemPrompt },
+    ...history,
+    { role: 'user', content: `${username}: ${userMessage}` },
+  ];
+ 
+  // Log incoming AI request
+  info('AI request', { channelId, username, message: userMessage });
+ 
+  try {
+    const response = await fetch(OPENCLAW_URL, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        ...(OPENCLAW_TOKEN && { Authorization: `Bearer ${OPENCLAW_TOKEN}` }),
+      },
+      body: JSON.stringify({
+        model: config.ai?.model || 'claude-sonnet-4-20250514',
+        max_tokens: config.ai?.maxTokens || 1024,
+        messages: messages,
+      }),
+    });
+ 
+    if (!response.ok) {
+      if (healthMonitor) {
+        healthMonitor.setAPIStatus('error');
+      }
+      throw new Error(`API error: ${response.status} ${response.statusText}`);
+    }
+ 
+    const data = await response.json();
+    const reply = data.choices?.[0]?.message?.content || 'I got nothing. Try again?';
+ 
+    // Log AI response
+    info('AI response', { channelId, username, response: reply.substring(0, 500) });
+ 
+    // Record successful AI request
+    if (healthMonitor) {
+      healthMonitor.recordAIRequest();
+      healthMonitor.setAPIStatus('ok');
+    }
+ 
+    // Update history
+    addToHistory(channelId, 'user', `${username}: ${userMessage}`);
+    addToHistory(channelId, 'assistant', reply);
+ 
+    return reply;
+  } catch (err) {
+    logError('OpenClaw API error', { error: err.message });
+    if (healthMonitor) {
+      healthMonitor.setAPIStatus('error');
+    }
+    return "Sorry, I'm having trouble thinking right now. Try again in a moment!";
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/modules/chimeIn.js.html b/coverage/src/modules/chimeIn.js.html new file mode 100644 index 00000000..d3b96cda --- /dev/null +++ b/coverage/src/modules/chimeIn.js.html @@ -0,0 +1,1000 @@ + + + + + + Code coverage report for src/modules/chimeIn.js + + + + + + + + + +
+
+

All files / src/modules chimeIn.js

+
+ +
+ 0% + Statements + 0/110 +
+ + +
+ 0% + Branches + 0/68 +
+ + +
+ 0% + Functions + 0/10 +
+ + +
+ 0% + Lines + 0/100 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Chime-In Module
+ * Allows the bot to organically join conversations without being @mentioned.
+ *
+ * How it works:
+ * - Accumulates messages per channel in a ring buffer (capped at maxBufferSize)
+ * - After every `evaluateEvery` messages, asks a cheap LLM: should I chime in?
+ * - If YES → generates a full response via a separate AI context and sends it
+ * - If NO  → resets the counter but keeps the buffer for context continuity
+ */
+ 
+import { info, error as logError, warn } from '../logger.js';
+import { needsSplitting, splitMessage } from '../utils/splitMessage.js';
+import { OPENCLAW_TOKEN, OPENCLAW_URL } from './ai.js';
+ 
+// ── Per-channel state ──────────────────────────────────────────────────────────
+// Map<channelId, { messages: Array<{author, content}>, counter: number, lastActive: number, abortController: AbortController|null }>
+const channelBuffers = new Map();
+ 
+// Guard against concurrent evaluations on the same channel
+const evaluatingChannels = new Set();
+ 
+// LRU eviction settings
+const MAX_TRACKED_CHANNELS = 100;
+const CHANNEL_INACTIVE_MS = 30 * 60 * 1000; // 30 minutes
+ 
+// ── Helpers ────────────────────────────────────────────────────────────────────
+ 
+/**
+ * Evict inactive channels from the buffer to prevent unbounded memory growth.
+ */
+function evictInactiveChannels() {
+  const now = Date.now();
+  for (const [channelId, buf] of channelBuffers) {
+    if (now - buf.lastActive > CHANNEL_INACTIVE_MS) {
+      channelBuffers.delete(channelId);
+    }
+  }
+ 
+  // If still over limit, evict oldest
+  if (channelBuffers.size > MAX_TRACKED_CHANNELS) {
+    const entries = [...channelBuffers.entries()].sort((a, b) => a[1].lastActive - b[1].lastActive);
+    const toEvict = entries.slice(0, channelBuffers.size - MAX_TRACKED_CHANNELS);
+    for (const [channelId] of toEvict) {
+      channelBuffers.delete(channelId);
+    }
+  }
+}
+ 
+/**
+ * Get or create the buffer state for a channel
+ */
+function getBuffer(channelId) {
+  if (!channelBuffers.has(channelId)) {
+    evictInactiveChannels();
+    channelBuffers.set(channelId, {
+      messages: [],
+      counter: 0,
+      lastActive: Date.now(),
+      abortController: null,
+    });
+  }
+  const buf = channelBuffers.get(channelId);
+  buf.lastActive = Date.now();
+  return buf;
+}
+ 
+/**
+ * Check whether a channel is eligible for chime-in
+ */
+function isChannelEligible(channelId, chimeInConfig) {
+  const { channels = [], excludeChannels = [] } = chimeInConfig;
+ 
+  // Explicit exclusion always wins
+  if (excludeChannels.includes(channelId)) return false;
+ 
+  // Empty allow-list → all channels allowed
+  if (channels.length === 0) return true;
+ 
+  return channels.includes(channelId);
+}
+ 
+/**
+ * Call the evaluation LLM (cheap / fast) to decide whether to chime in
+ */
+async function shouldChimeIn(buffer, config, signal) {
+  const chimeInConfig = config.chimeIn || {};
+  const model = chimeInConfig.model || 'claude-haiku-4-5';
+  const systemPrompt = config.ai?.systemPrompt || 'You are a helpful Discord bot.';
+ 
+  // Format the buffered conversation with structured delimiters to prevent injection
+  const conversationText = buffer.messages.map((m) => `${m.author}: ${m.content}`).join('\n');
+ 
+  // System instruction first (required by OpenAI-compatible proxies for Anthropic models)
+  const messages = [
+    {
+      role: 'system',
+      content: `You have the following personality:\n${systemPrompt}\n\nYou're monitoring a Discord conversation shown inside <conversation> tags. Based on those messages, could you add something genuinely valuable, interesting, funny, or helpful? Only say YES if a real person would actually want to chime in. Don't chime in just to be present. Reply with only YES or NO.`,
+    },
+    {
+      role: 'user',
+      content: `<conversation>\n${conversationText}\n</conversation>`,
+    },
+  ];
+ 
+  try {
+    const fetchSignal = signal
+      ? AbortSignal.any([signal, AbortSignal.timeout(10_000)])
+      : AbortSignal.timeout(10_000);
+ 
+    const response = await fetch(OPENCLAW_URL, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        ...(OPENCLAW_TOKEN && { Authorization: `Bearer ${OPENCLAW_TOKEN}` }),
+      },
+      body: JSON.stringify({
+        model,
+        max_tokens: 10,
+        messages,
+      }),
+      signal: fetchSignal,
+    });
+ 
+    if (!response.ok) {
+      warn('ChimeIn evaluation API error', { status: response.status });
+      return false;
+    }
+ 
+    const data = await response.json();
+    const reply = (data.choices?.[0]?.message?.content || '').trim().toUpperCase();
+    info('ChimeIn evaluation result', { reply, model });
+    return reply.startsWith('YES');
+  } catch (err) {
+    logError('ChimeIn evaluation failed', { error: err.message });
+    return false;
+  }
+}
+ 
+/**
+ * Generate a chime-in response using a separate context (not shared AI history).
+ * This avoids polluting the main conversation history used by @mention responses.
+ */
+async function generateChimeInResponse(buffer, config, signal) {
+  const systemPrompt = config.ai?.systemPrompt || 'You are a helpful Discord bot.';
+  const model = config.ai?.model || 'claude-sonnet-4-20250514';
+  const maxTokens = config.ai?.maxTokens || 1024;
+ 
+  const conversationText = buffer.messages.map((m) => `${m.author}: ${m.content}`).join('\n');
+ 
+  const messages = [
+    { role: 'system', content: systemPrompt },
+    {
+      role: 'user',
+      content: `[Conversation context — you noticed this discussion and decided to chime in. Respond naturally as if you're joining the conversation organically. Don't announce that you're "chiming in" — just contribute.]\n\n<conversation>\n${conversationText}\n</conversation>`,
+    },
+  ];
+ 
+  const fetchSignal = signal
+    ? AbortSignal.any([signal, AbortSignal.timeout(30_000)])
+    : AbortSignal.timeout(30_000);
+ 
+  const response = await fetch(OPENCLAW_URL, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(OPENCLAW_TOKEN && { Authorization: `Bearer ${OPENCLAW_TOKEN}` }),
+    },
+    body: JSON.stringify({
+      model,
+      max_tokens: maxTokens,
+      messages,
+    }),
+    signal: fetchSignal,
+  });
+ 
+  if (!response.ok) {
+    throw new Error(`API error: ${response.status} ${response.statusText}`);
+  }
+ 
+  const data = await response.json();
+  return data.choices?.[0]?.message?.content || '';
+}
+ 
+// ── Public API ─────────────────────────────────────────────────────────────────
+ 
+/**
+ * Accumulate a message and potentially trigger a chime-in.
+ * Called from the messageCreate handler for every non-bot guild message.
+ *
+ * @param {Object} message - Discord.js Message object
+ * @param {Object} config  - Bot configuration
+ */
+export async function accumulate(message, config) {
+  const chimeInConfig = config.chimeIn;
+  if (!chimeInConfig?.enabled) return;
+  if (!isChannelEligible(message.channel.id, chimeInConfig)) return;
+ 
+  // Skip empty or attachment-only messages
+  if (!message.content?.trim()) return;
+ 
+  const channelId = message.channel.id;
+  const buf = getBuffer(channelId);
+  const maxBufferSize = chimeInConfig.maxBufferSize || 30;
+  const evaluateEvery = chimeInConfig.evaluateEvery || 10;
+ 
+  // Push to ring buffer
+  buf.messages.push({
+    author: message.author.username,
+    content: message.content,
+  });
+ 
+  // Trim if over cap
+  while (buf.messages.length > maxBufferSize) {
+    buf.messages.shift();
+  }
+ 
+  // Increment counter
+  buf.counter += 1;
+ 
+  // Not enough messages yet → bail
+  if (buf.counter < evaluateEvery) return;
+ 
+  // Prevent concurrent evaluations for the same channel
+  if (evaluatingChannels.has(channelId)) return;
+  evaluatingChannels.add(channelId);
+ 
+  // Create a new AbortController for this evaluation cycle
+  const abortController = new AbortController();
+  buf.abortController = abortController;
+ 
+  try {
+    info('ChimeIn evaluating', { channelId, buffered: buf.messages.length, counter: buf.counter });
+ 
+    const yes = await shouldChimeIn(buf, config, abortController.signal);
+ 
+    // Check if this evaluation was cancelled (e.g. bot was @mentioned during evaluation)
+    if (abortController.signal.aborted) {
+      info('ChimeIn evaluation cancelled — bot was mentioned or counter reset', { channelId });
+      return;
+    }
+ 
+    if (yes) {
+      info('ChimeIn triggered — generating response', { channelId });
+ 
+      await message.channel.sendTyping();
+ 
+      // Use separate context to avoid polluting shared AI history
+      const response = await generateChimeInResponse(buf, config, abortController.signal);
+ 
+      // Re-check cancellation after response generation
+      if (abortController.signal.aborted) {
+        info('ChimeIn response suppressed — bot was mentioned during generation', { channelId });
+        return;
+      }
+ 
+      // Don't send empty/whitespace responses as unsolicited messages
+      if (!response?.trim()) {
+        warn('ChimeIn suppressed empty response', { channelId });
+      } else {
+        // Send as a plain channel message (not a reply)
+        if (needsSplitting(response)) {
+          const chunks = splitMessage(response);
+          for (const chunk of chunks) {
+            await message.channel.send(chunk);
+          }
+        } else {
+          await message.channel.send(response);
+        }
+      }
+ 
+      // Clear the buffer entirely after a chime-in attempt
+      buf.messages = [];
+      buf.counter = 0;
+    } else {
+      // Reset counter only — keep the buffer for context continuity
+      buf.counter = 0;
+    }
+  } catch (err) {
+    logError('ChimeIn error', { channelId, error: err.message });
+    // Reset counter so we don't spin on errors
+    buf.counter = 0;
+  } finally {
+    evaluatingChannels.delete(channelId);
+  }
+}
+ 
+/**
+ * Reset the chime-in counter for a channel (call when the bot is @mentioned
+ * so the mention handler doesn't double-fire with a chime-in).
+ *
+ * @param {string} channelId
+ */
+export function resetCounter(channelId) {
+  const buf = channelBuffers.get(channelId);
+  if (buf) {
+    buf.counter = 0;
+ 
+    // Cancel any in-flight chime-in evaluation to prevent double-responses
+    if (buf.abortController) {
+      buf.abortController.abort();
+      buf.abortController = null;
+    }
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/modules/config.js.html b/coverage/src/modules/config.js.html new file mode 100644 index 00000000..6e9d84fc --- /dev/null +++ b/coverage/src/modules/config.js.html @@ -0,0 +1,1411 @@ + + + + + + Code coverage report for src/modules/config.js + + + + + + + + + +
+
+

All files / src/modules config.js

+
+ +
+ 2.85% + Statements + 5/175 +
+ + +
+ 0% + Branches + 0/94 +
+ + +
+ 0% + Functions + 0/9 +
+ + +
+ 3.03% + Lines + 5/165 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443  +  +  +  +  +  +  +  +  +  +  +1x +1x +  +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Configuration Module
+ * Loads config from PostgreSQL with config.json as the seed/fallback
+ */
+ 
+import { existsSync, readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { getPool } from '../db.js';
+import { info, error as logError, warn as logWarn } from '../logger.js';
+ 
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const configPath = join(__dirname, '..', '..', 'config.json');
+ 
+/** @type {Object} In-memory config cache */
+let configCache = {};
+ 
+/** @type {Object|null} Cached config.json contents (loaded once, never invalidated) */
+let fileConfigCache = null;
+ 
+/**
+ * Load config.json from disk (used as seed/fallback)
+ * @returns {Object} Configuration object from file
+ * @throws {Error} If config.json is missing or unparseable
+ */
+export function loadConfigFromFile() {
+  if (fileConfigCache) return fileConfigCache;
+ 
+  if (!existsSync(configPath)) {
+    const err = new Error('config.json not found!');
+    err.code = 'CONFIG_NOT_FOUND';
+    throw err;
+  }
+  try {
+    fileConfigCache = JSON.parse(readFileSync(configPath, 'utf-8'));
+    return fileConfigCache;
+  } catch (err) {
+    throw new Error(`Failed to load config.json: ${err.message}`);
+  }
+}
+ 
+/**
+ * Load config from PostgreSQL, seeding from config.json if empty
+ * Falls back to config.json if database is unavailable
+ * @returns {Promise<Object>} Configuration object
+ */
+export async function loadConfig() {
+  // Try loading config.json — DB may have valid config even if file is missing
+  let fileConfig;
+  try {
+    fileConfig = loadConfigFromFile();
+  } catch {
+    fileConfig = null;
+    info('config.json not available, will rely on database for configuration');
+  }
+ 
+  try {
+    let pool;
+    try {
+      pool = getPool();
+    } catch {
+      // DB not initialized — file config is our only option
+      if (!fileConfig) {
+        throw new Error(
+          'No configuration source available: config.json is missing and database is not initialized',
+        );
+      }
+      info('Database not available, using config.json');
+      configCache = structuredClone(fileConfig);
+      return configCache;
+    }
+ 
+    // Check if config table has any rows
+    const { rows } = await pool.query('SELECT key, value FROM config');
+ 
+    if (rows.length === 0) {
+      if (!fileConfig) {
+        throw new Error(
+          'No configuration source available: database is empty and config.json is missing',
+        );
+      }
+      // Seed database from config.json inside a transaction
+      info('No config in database, seeding from config.json');
+      const client = await pool.connect();
+      try {
+        await client.query('BEGIN');
+        for (const [key, value] of Object.entries(fileConfig)) {
+          await client.query(
+            'INSERT INTO config (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()',
+            [key, JSON.stringify(value)],
+          );
+        }
+        await client.query('COMMIT');
+        info('Config seeded to database');
+        configCache = structuredClone(fileConfig);
+      } catch (txErr) {
+        try {
+          await client.query('ROLLBACK');
+        } catch {
+          /* ignore rollback failure */
+        }
+        throw txErr;
+      } finally {
+        client.release();
+      }
+    } else {
+      // Load from database
+      configCache = {};
+      for (const row of rows) {
+        configCache[row.key] = row.value;
+      }
+      info('Config loaded from database');
+    }
+  } catch (err) {
+    if (!fileConfig) {
+      // No fallback available — re-throw
+      throw err;
+    }
+    logError('Failed to load config from database, using config.json', { error: err.message });
+    configCache = structuredClone(fileConfig);
+  }
+ 
+  return configCache;
+}
+ 
+/**
+ * Get the current config (from cache)
+ * @returns {Object} Configuration object
+ */
+export function getConfig() {
+  return configCache;
+}
+ 
+/**
+ * Set a config value using dot notation (e.g., "ai.model" or "welcome.enabled")
+ * Persists to database and updates in-memory cache
+ * @param {string} path - Dot-notation path (e.g., "ai.model")
+ * @param {*} value - Value to set (automatically parsed from string)
+ * @returns {Promise<Object>} Updated section config
+ */
+export async function setConfigValue(path, value) {
+  const parts = path.split('.');
+  if (parts.length < 2) {
+    throw new Error('Path must include section and key (e.g., "ai.model")');
+  }
+ 
+  // Reject dangerous keys to prevent prototype pollution
+  validatePathSegments(parts);
+ 
+  const section = parts[0];
+  const nestedParts = parts.slice(1);
+  const parsedVal = parseValue(value);
+ 
+  // Deep clone the section for the INSERT case (new section that doesn't exist yet)
+  const sectionClone = structuredClone(configCache[section] || {});
+  setNestedValue(sectionClone, nestedParts, parsedVal);
+ 
+  // Write to database first, then update cache.
+  // Uses a transaction with row lock to prevent concurrent writes from clobbering.
+  // Reads the current row, applies the change in JS (handles arbitrary nesting),
+  // then writes back — safe because the row is locked for the duration.
+  let dbPersisted = false;
+ 
+  // Separate pool acquisition from transaction work so we can distinguish
+  // "DB not configured" (graceful fallback) from real transaction errors (must surface).
+  let pool;
+  try {
+    pool = getPool();
+  } catch {
+    // DB not initialized — skip persistence, fall through to in-memory update
+    logWarn('Database not initialized for config write — updating in-memory only');
+  }
+ 
+  if (pool) {
+    const client = await pool.connect();
+    try {
+      await client.query('BEGIN');
+      // Lock the row (or prepare for INSERT if missing)
+      const { rows } = await client.query('SELECT value FROM config WHERE key = $1 FOR UPDATE', [
+        section,
+      ]);
+ 
+      if (rows.length > 0) {
+        // Row exists — merge change into the live DB value
+        const dbSection = rows[0].value;
+        setNestedValue(dbSection, nestedParts, parsedVal);
+ 
+        await client.query('UPDATE config SET value = $1, updated_at = NOW() WHERE key = $2', [
+          JSON.stringify(dbSection),
+          section,
+        ]);
+      } else {
+        // New section — use ON CONFLICT to handle concurrent inserts safely
+        await client.query(
+          'INSERT INTO config (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()',
+          [section, JSON.stringify(sectionClone)],
+        );
+      }
+      await client.query('COMMIT');
+      dbPersisted = true;
+    } catch (txErr) {
+      try {
+        await client.query('ROLLBACK');
+      } catch {
+        /* ignore rollback failure */
+      }
+      throw txErr;
+    } finally {
+      client.release();
+    }
+  }
+ 
+  // Update in-memory cache (mutate in-place for reference propagation)
+  if (
+    !configCache[section] ||
+    typeof configCache[section] !== 'object' ||
+    Array.isArray(configCache[section])
+  ) {
+    configCache[section] = {};
+  }
+  setNestedValue(configCache[section], nestedParts, parsedVal);
+ 
+  info('Config updated', { path, value: parsedVal, persisted: dbPersisted });
+  return configCache[section];
+}
+ 
+/**
+ * Reset a config section to config.json defaults
+ * @param {string} [section] - Section to reset, or all if omitted
+ * @returns {Promise<Object>} Reset config
+ */
+export async function resetConfig(section) {
+  let fileConfig;
+  try {
+    fileConfig = loadConfigFromFile();
+  } catch {
+    throw new Error(
+      'Cannot reset configuration: config.json is not available. ' +
+        'Reset requires the default config file as a baseline.',
+    );
+  }
+ 
+  let pool = null;
+  try {
+    pool = getPool();
+  } catch {
+    logWarn('Database unavailable for config reset — updating in-memory only');
+  }
+ 
+  if (section) {
+    if (!fileConfig[section]) {
+      throw new Error(`Section '${section}' not found in config.json defaults`);
+    }
+ 
+    if (pool) {
+      try {
+        await pool.query(
+          'INSERT INTO config (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()',
+          [section, JSON.stringify(fileConfig[section])],
+        );
+      } catch (err) {
+        logError('Database error during section reset — updating in-memory only', {
+          section,
+          error: err.message,
+        });
+      }
+    }
+ 
+    // Mutate in-place so references stay valid (deep clone to avoid shared refs)
+    const sectionData = configCache[section];
+    if (sectionData && typeof sectionData === 'object' && !Array.isArray(sectionData)) {
+      for (const key of Object.keys(sectionData)) delete sectionData[key];
+      Object.assign(sectionData, structuredClone(fileConfig[section]));
+    } else {
+      configCache[section] = isPlainObject(fileConfig[section])
+        ? structuredClone(fileConfig[section])
+        : fileConfig[section];
+    }
+    info('Config section reset', { section });
+  } else {
+    // Reset all inside a transaction
+    if (pool) {
+      const client = await pool.connect();
+      try {
+        await client.query('BEGIN');
+        for (const [key, value] of Object.entries(fileConfig)) {
+          await client.query(
+            'INSERT INTO config (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()',
+            [key, JSON.stringify(value)],
+          );
+        }
+        // Remove stale keys that exist in DB but not in config.json
+        const fileKeys = Object.keys(fileConfig);
+        if (fileKeys.length > 0) {
+          await client.query('DELETE FROM config WHERE key != ALL($1::text[])', [fileKeys]);
+        }
+        await client.query('COMMIT');
+      } catch (txErr) {
+        try {
+          await client.query('ROLLBACK');
+        } catch {
+          /* ignore rollback failure */
+        }
+        logError('Database error during full config reset — updating in-memory only', {
+          error: txErr.message,
+        });
+      } finally {
+        client.release();
+      }
+    }
+ 
+    // Mutate in-place and remove stale keys from cache (deep clone to avoid shared refs)
+    for (const key of Object.keys(configCache)) {
+      if (!(key in fileConfig)) {
+        delete configCache[key];
+      }
+    }
+    for (const [key, value] of Object.entries(fileConfig)) {
+      if (configCache[key] && isPlainObject(configCache[key]) && isPlainObject(value)) {
+        for (const k of Object.keys(configCache[key])) delete configCache[key][k];
+        Object.assign(configCache[key], structuredClone(value));
+      } else {
+        configCache[key] = isPlainObject(value) ? structuredClone(value) : value;
+      }
+    }
+    info('All config reset to defaults');
+  }
+ 
+  return configCache;
+}
+ 
+/** Keys that must never be used as path segments (prototype pollution vectors) */
+const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
+ 
+/**
+ * Validate that no path segment is a prototype-pollution vector.
+ * @param {string[]} segments - Path segments to check
+ * @throws {Error} If any segment is a dangerous key
+ */
+function validatePathSegments(segments) {
+  for (const segment of segments) {
+    if (DANGEROUS_KEYS.has(segment)) {
+      throw new Error(`Invalid config path: '${segment}' is a reserved key and cannot be used`);
+    }
+  }
+}
+ 
+/**
+ * Traverse a nested object along dot-notation path segments, creating
+ * intermediate objects as needed, and set the leaf value.
+ * @param {Object} root - Object to traverse
+ * @param {string[]} pathParts - Path segments (excluding the root key)
+ * @param {*} value - Value to set at the leaf
+ */
+function setNestedValue(root, pathParts, value) {
+  if (pathParts.length === 0) {
+    throw new Error('setNestedValue requires at least one path segment');
+  }
+  let current = root;
+  for (let i = 0; i < pathParts.length - 1; i++) {
+    // Defensive: reject prototype-pollution keys even for internal callers
+    if (DANGEROUS_KEYS.has(pathParts[i])) {
+      throw new Error(`Invalid config path segment: '${pathParts[i]}' is a reserved key`);
+    }
+    if (current[pathParts[i]] == null || typeof current[pathParts[i]] !== 'object') {
+      current[pathParts[i]] = {};
+    } else if (Array.isArray(current[pathParts[i]])) {
+      // Keep arrays intact when the next path segment is a valid numeric index;
+      // otherwise replace with a plain object (legacy behaviour for non-numeric keys).
+      if (!/^\d+$/.test(pathParts[i + 1])) {
+        current[pathParts[i]] = {};
+      }
+    }
+    current = current[pathParts[i]];
+  }
+  const leafKey = pathParts[pathParts.length - 1];
+  if (DANGEROUS_KEYS.has(leafKey)) {
+    throw new Error(`Invalid config path segment: '${leafKey}' is a reserved key`);
+  }
+  current[leafKey] = value;
+}
+ 
+/**
+ * Check if a value is a plain object (not null, not array)
+ * @param {*} val - Value to check
+ * @returns {boolean} True if plain object
+ */
+function isPlainObject(val) {
+  return typeof val === 'object' && val !== null && !Array.isArray(val);
+}
+ 
+/**
+ * Parse a string value into its appropriate JS type.
+ *
+ * Coercion rules:
+ * - "true" / "false" → boolean
+ * - "null" → null
+ * - Numeric strings → number (unless beyond Number.MAX_SAFE_INTEGER)
+ * - JSON arrays/objects → parsed value
+ * - Everything else → kept as-is string
+ *
+ * To force a literal string (e.g. the word "true"), wrap it in JSON quotes:
+ *   "\"true\"" → parsed by JSON.parse into the string "true"
+ *
+ * @param {string} value - String value to parse
+ * @returns {*} Parsed value
+ */
+function parseValue(value) {
+  if (typeof value !== 'string') return value;
+ 
+  // Booleans
+  if (value === 'true') return true;
+  if (value === 'false') return false;
+ 
+  // Null
+  if (value === 'null') return null;
+ 
+  // Numbers (keep as string if beyond safe integer range to avoid precision loss)
+  // Matches: 123, -123, 1.5, -1.5, 1., .5, -.5
+  if (/^-?(\d+\.?\d*|\.\d+)$/.test(value)) {
+    const num = Number(value);
+    if (!Number.isFinite(num)) return value;
+    if (!value.includes('.') && !Number.isSafeInteger(num)) return value;
+    return num;
+  }
+ 
+  // JSON strings (e.g. "\"true\"" → force literal string "true"), arrays, and objects
+  if (
+    (value.startsWith('"') && value.endsWith('"')) ||
+    (value.startsWith('[') && value.endsWith(']')) ||
+    (value.startsWith('{') && value.endsWith('}'))
+  ) {
+    try {
+      return JSON.parse(value);
+    } catch {
+      return value;
+    }
+  }
+ 
+  // Plain string
+  return value;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/modules/events.js.html b/coverage/src/modules/events.js.html new file mode 100644 index 00000000..03840e4c --- /dev/null +++ b/coverage/src/modules/events.js.html @@ -0,0 +1,544 @@ + + + + + + Code coverage report for src/modules/events.js + + + + + + + + + +
+
+

All files / src/modules events.js

+
+ +
+ 0% + Statements + 0/51 +
+ + +
+ 0% + Branches + 0/35 +
+ + +
+ 0% + Functions + 0/11 +
+ + +
+ 0% + Lines + 0/49 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Events Module
+ * Handles Discord event listeners and handlers
+ */
+ 
+import { error as logError, info, warn } from '../logger.js';
+import { needsSplitting, splitMessage } from '../utils/splitMessage.js';
+import { generateResponse } from './ai.js';
+import { accumulate, resetCounter } from './chimeIn.js';
+import { isSpam, sendSpamAlert } from './spam.js';
+import { recordCommunityActivity, sendWelcomeMessage } from './welcome.js';
+ 
+/**
+ * Register bot ready event handler
+ * @param {Object} client - Discord client
+ * @param {Object} config - Bot configuration
+ * @param {Object} healthMonitor - Health monitor instance
+ */
+export function registerReadyHandler(client, config, healthMonitor) {
+  client.once('clientReady', () => {
+    info(`${client.user.tag} is online`, { servers: client.guilds.cache.size });
+ 
+    // Record bot start time
+    if (healthMonitor) {
+      healthMonitor.recordStart();
+    }
+ 
+    if (config.welcome?.enabled) {
+      info('Welcome messages enabled', { channelId: config.welcome.channelId });
+    }
+    if (config.ai?.enabled) {
+      info('AI chat enabled', { model: config.ai.model || 'claude-sonnet-4-20250514' });
+    }
+    if (config.moderation?.enabled) {
+      info('Moderation enabled');
+    }
+  });
+}
+ 
+/**
+ * Register guild member add event handler
+ * @param {Object} client - Discord client
+ * @param {Object} config - Bot configuration
+ */
+export function registerGuildMemberAddHandler(client, config) {
+  client.on('guildMemberAdd', async (member) => {
+    await sendWelcomeMessage(member, client, config);
+  });
+}
+ 
+/**
+ * Register message create event handler
+ * @param {Object} client - Discord client
+ * @param {Object} config - Bot configuration
+ * @param {Object} healthMonitor - Health monitor instance
+ */
+export function registerMessageCreateHandler(client, config, healthMonitor) {
+  client.on('messageCreate', async (message) => {
+    // Ignore bots and DMs
+    if (message.author.bot) return;
+    if (!message.guild) return;
+ 
+    // Spam detection
+    if (config.moderation?.enabled && isSpam(message.content)) {
+      warn('Spam detected', { user: message.author.tag, content: message.content.slice(0, 50) });
+      await sendSpamAlert(message, client, config);
+      return;
+    }
+ 
+    // Feed welcome-context activity tracker
+    recordCommunityActivity(message, config);
+ 
+    // AI chat - respond when mentioned (checked BEFORE accumulate to prevent double responses)
+    if (config.ai?.enabled) {
+      const isMentioned = message.mentions.has(client.user);
+      const isReply = message.reference && message.mentions.repliedUser?.id === client.user.id;
+ 
+      // Check if in allowed channel (if configured)
+      const allowedChannels = config.ai?.channels || [];
+      const isAllowedChannel =
+        allowedChannels.length === 0 || allowedChannels.includes(message.channel.id);
+ 
+      if ((isMentioned || isReply) && isAllowedChannel) {
+        // Reset chime-in counter so we don't double-respond
+        resetCounter(message.channel.id);
+ 
+        // Remove the mention from the message
+        const cleanContent = message.content
+          .replace(new RegExp(`<@!?${client.user.id}>`, 'g'), '')
+          .trim();
+ 
+        if (!cleanContent) {
+          await message.reply("Hey! What's up?");
+          return;
+        }
+ 
+        await message.channel.sendTyping();
+ 
+        const response = await generateResponse(
+          message.channel.id,
+          cleanContent,
+          message.author.username,
+          config,
+          healthMonitor,
+        );
+ 
+        // Split long responses
+        if (needsSplitting(response)) {
+          const chunks = splitMessage(response);
+          for (const chunk of chunks) {
+            await message.channel.send(chunk);
+          }
+        } else {
+          await message.reply(response);
+        }
+ 
+        return; // Don't accumulate direct mentions into chime-in buffer
+      }
+    }
+ 
+    // Chime-in: accumulate message for organic participation (fire-and-forget)
+    accumulate(message, config).catch((err) => {
+      logError('ChimeIn accumulate error', { error: err.message });
+    });
+  });
+}
+ 
+/**
+ * Register error event handlers
+ * @param {Object} client - Discord client
+ */
+export function registerErrorHandlers(client) {
+  client.on('error', (err) => {
+    logError('Discord error', { error: err.message, stack: err.stack });
+  });
+ 
+  process.on('unhandledRejection', (err) => {
+    logError('Unhandled rejection', { error: err?.message, stack: err?.stack });
+  });
+}
+ 
+/**
+ * Register all event handlers
+ * @param {Object} client - Discord client
+ * @param {Object} config - Bot configuration
+ * @param {Object} healthMonitor - Health monitor instance
+ */
+export function registerEventHandlers(client, config, healthMonitor) {
+  registerReadyHandler(client, config, healthMonitor);
+  registerGuildMemberAddHandler(client, config);
+  registerMessageCreateHandler(client, config, healthMonitor);
+  registerErrorHandlers(client);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/modules/index.html b/coverage/src/modules/index.html new file mode 100644 index 00000000..4adae95b --- /dev/null +++ b/coverage/src/modules/index.html @@ -0,0 +1,191 @@ + + + + + + Code coverage report for src/modules + + + + + + + + + +
+
+

All files src/modules

+
+ +
+ 25.18% + Statements + 139/552 +
+ + +
+ 23.17% + Branches + 89/384 +
+ + +
+ 27.27% + Functions + 21/77 +
+ + +
+ 25.14% + Lines + 128/509 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
ai.js +
+
100%36/3696.29%26/27100%5/5100%36/36
chimeIn.js +
+
0%0/1100%0/680%0/100%0/100
config.js +
+
2.85%5/1750%0/940%0/93.03%5/165
events.js +
+
0%0/510%0/350%0/110%0/49
spam.js +
+
100%13/13100%8/8100%5/5100%10/10
welcome.js +
+
50.89%85/16736.18%55/15229.72%11/3751.67%77/149
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/modules/spam.js.html b/coverage/src/modules/spam.js.html new file mode 100644 index 00000000..2330c1c0 --- /dev/null +++ b/coverage/src/modules/spam.js.html @@ -0,0 +1,268 @@ + + + + + + Code coverage report for src/modules/spam.js + + + + + + + + + +
+
+

All files / src/modules spam.js

+
+ +
+ 100% + Statements + 13/13 +
+ + +
+ 100% + Branches + 8/8 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 100% + Lines + 10/10 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +155x +  +  +  +  +  +  +  +  +  +16x +  +15x +  +1x +15x +  +13x +  +  +  +  +  +  +  +  +  +  +16x +  +  +13x +2x +  +  + 
/**
+ * Spam Detection Module
+ * Handles spam/scam detection and moderation
+ */
+ 
+import { EmbedBuilder } from 'discord.js';
+ 
+// Spam patterns
+const SPAM_PATTERNS = [
+  /free\s*(crypto|bitcoin|btc|eth|nft)/i,
+  /airdrop.*claim/i,
+  /discord\s*nitro\s*free/i,
+  /nitro\s*gift.*claim/i,
+  /click.*verify.*account/i,
+  /guaranteed.*profit/i,
+  /invest.*double.*money/i,
+  /dm\s*me\s*for.*free/i,
+  /make\s*\$?\d+k?\+?\s*(daily|weekly|monthly)/i,
+];
+ 
+/**
+ * Check if message content is spam
+ * @param {string} content - Message content to check
+ * @returns {boolean} True if spam detected
+ */
+export function isSpam(content) {
+  return SPAM_PATTERNS.some((pattern) => pattern.test(content));
+}
+ 
+/**
+ * Send spam alert to moderation channel
+ * @param {Object} message - Discord message object
+ * @param {Object} client - Discord client
+ * @param {Object} config - Bot configuration
+ */
+export async function sendSpamAlert(message, client, config) {
+  if (!config.moderation?.alertChannelId) return;
+ 
+  const alertChannel = await client.channels
+    .fetch(config.moderation.alertChannelId)
+    .catch(() => null);
+  if (!alertChannel) return;
+ 
+  const embed = new EmbedBuilder()
+    .setColor(0xff6b6b)
+    .setTitle('⚠️ Potential Spam Detected')
+    .addFields(
+      { name: 'Author', value: `<@${message.author.id}>`, inline: true },
+      { name: 'Channel', value: `<#${message.channel.id}>`, inline: true },
+      { name: 'Content', value: message.content.slice(0, 1000) || '*empty*' },
+      { name: 'Link', value: `[Jump](${message.url})` },
+    )
+    .setTimestamp();
+ 
+  await alertChannel.send({ embeds: [embed] });
+ 
+  // Auto-delete if enabled
+  if (config.moderation?.autoDelete) {
+    await message.delete().catch(() => {});
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/modules/welcome.js.html b/coverage/src/modules/welcome.js.html new file mode 100644 index 00000000..3c287589 --- /dev/null +++ b/coverage/src/modules/welcome.js.html @@ -0,0 +1,1333 @@ + + + + + + Code coverage report for src/modules/welcome.js + + + + + + + + + +
+
+

All files / src/modules welcome.js

+
+ +
+ 50.89% + Statements + 85/167 +
+ + +
+ 36.18% + Branches + 55/152 +
+ + +
+ 29.72% + Functions + 11/37 +
+ + +
+ 51.67% + Lines + 77/149 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417  +  +  +  +  +  +  +1x +1x +1x +  +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +14x +  +  +  +  +  +  +  +  +  +  +  +  +  +11x +9x +  +8x +11x +11x +11x +3x +  +8x +  +7x +7x +7x +  +7x +1x +  +  +7x +7x +  +11x +11x +  +  +7x +  +  +  +7x +  +  +  +  +  +  +  +  +  +18x +  +16x +16x +15x +  +14x +  +18x +  +  +  +  +  +  +  +18x +5x +  +10x +  +  +  +  +  +  +  +  +  +  +8x +8x +  +8x +  +  +  +  +  +  +8x +8x +8x +8x +  +8x +8x +8x +  +8x +  +8x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +8x +8x +8x +8x +  +8x +8x +  +8x +56x +  +16x +  +  +  +  +  +16x +  +16x +16x +  +  +  +8x +  +  +  +8x +8x +  +16x +  +8x +  +  +  +8x +8x +  +  +  +  +8x +  +8x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +8x +  +8x +2x +  +  +  +  +  +  +  +  +  +  +8x +8x +  +  +  +  +  +8x +8x +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +15x +15x +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Welcome Module
+ * Handles dynamic welcome messages for new members
+ */
+ 
+import { error as logError, info } from '../logger.js';
+ 
+const guildActivity = new Map();
+const DEFAULT_ACTIVITY_WINDOW_MINUTES = 45;
+const MAX_EVENTS_PER_CHANNEL = 250;
+ 
+/** Notable member-count milestones (hoisted to avoid allocation per welcome event) */
+const NOTABLE_MILESTONES = new Set([10, 25, 50, 100, 250, 500, 1000]);
+ 
+/** @type {{key: string, set: Set<string>} | null} Cached excluded channels Set */
+let excludedChannelsCache = null;
+ 
+/**
+ * Render welcome message with placeholder replacements
+ * @param {string} messageTemplate - Welcome message template
+ * @param {Object} member - Member object with id and optional username
+ * @param {Object} guild - Guild object with name and memberCount
+ * @returns {string} Rendered welcome message
+ */
+export function renderWelcomeMessage(messageTemplate, member, guild) {
+  return messageTemplate
+    .replace(/{user}/g, `<@${member.id}>`)
+    .replace(/{username}/g, member.username || 'Unknown')
+    .replace(/{server}/g, guild.name)
+    .replace(/{memberCount}/g, guild.memberCount.toString());
+}
+ 
+/**
+ * Track message activity for welcome context.
+ * Called from messageCreate handler to build a live community pulse.
+ * @param {Object} message - Discord message
+ * @param {Object} config - Bot configuration
+ */
+export function recordCommunityActivity(message, config) {
+  if (!message?.guild || !message?.channel || message.author?.bot) return;
+  if (!message.channel?.isTextBased?.()) return;
+ 
+  const welcomeDynamic = config?.welcome?.dynamic || {};
+  const excludeList = welcomeDynamic.excludeChannels || [];
+  const cacheKey = excludeList.join(',');
+  if (!excludedChannelsCache || excludedChannelsCache.key !== cacheKey) {
+    excludedChannelsCache = { key: cacheKey, set: new Set(excludeList) };
+  }
+  if (excludedChannelsCache.set.has(message.channel.id)) return;
+ 
+  const now = Date.now();
+  const windowMs = getActivityWindowMs(welcomeDynamic);
+  const cutoff = now - windowMs;
+ 
+  if (!guildActivity.has(message.guild.id)) {
+    guildActivity.set(message.guild.id, new Map());
+  }
+ 
+  const activityMap = guildActivity.get(message.guild.id);
+  const timestamps = activityMap.get(message.channel.id) || [];
+ 
+  timestamps.push(now);
+  while (timestamps.length && timestamps[0] < cutoff) {
+    timestamps.shift();
+  }
+  Iif (timestamps.length > MAX_EVENTS_PER_CHANNEL) {
+    timestamps.splice(0, timestamps.length - MAX_EVENTS_PER_CHANNEL);
+  }
+ 
+  activityMap.set(message.channel.id, timestamps);
+}
+ 
+/**
+ * Send welcome message to new member
+ * @param {Object} member - Discord guild member
+ * @param {Object} client - Discord client
+ * @param {Object} config - Bot configuration
+ */
+export async function sendWelcomeMessage(member, client, config) {
+  if (!config.welcome?.enabled || !config.welcome?.channelId) return;
+ 
+  try {
+    const channel = await client.channels.fetch(config.welcome.channelId);
+    if (!channel) return;
+ 
+    const useDynamic = config.welcome?.dynamic?.enabled === true;
+ 
+    const message = useDynamic
+      ? buildDynamicWelcomeMessage(member, config)
+      : renderWelcomeMessage(
+          config.welcome.message || 'Welcome, {user}!',
+          { id: member.id, username: member.user.username },
+          { name: member.guild.name, memberCount: member.guild.memberCount },
+        );
+ 
+    await channel.send(message);
+    info('Welcome message sent', { user: member.user.tag, guild: member.guild.name });
+  } catch (err) {
+    logError('Welcome error', { error: err.message });
+  }
+}
+ 
+/**
+ * Build contextual welcome message based on time, activity, and milestones.
+ * @param {Object} member - Discord guild member
+ * @param {Object} config - Bot configuration
+ * @returns {string} Dynamic welcome message
+ */
+function buildDynamicWelcomeMessage(member, config) {
+  const welcomeDynamic = config?.welcome?.dynamic || {};
+  const timezone = welcomeDynamic.timezone || 'America/New_York';
+ 
+  const memberContext = {
+    id: member.id,
+    username: member.user?.username || 'Unknown',
+    server: member.guild?.name || 'the server',
+    memberCount: member.guild?.memberCount || 0,
+  };
+ 
+  const timeOfDay = getTimeOfDay(timezone);
+  const snapshot = getCommunitySnapshot(member.guild, welcomeDynamic);
+  const milestoneLine = getMilestoneLine(memberContext.memberCount, welcomeDynamic);
+  const suggestedChannels = getSuggestedChannels(member, config, snapshot);
+ 
+  const greeting = pickFrom(getGreetingTemplates(timeOfDay), memberContext);
+  const vibeLine = buildVibeLine(snapshot, suggestedChannels);
+  const ctaLine = buildCtaLine(suggestedChannels);
+ 
+  const lines = [greeting];
+ 
+  if (milestoneLine) {
+    lines.push(milestoneLine);
+  } else {
+    lines.push(`You just rolled in as member **#${memberContext.memberCount}**.`);
+  }
+ 
+  lines.push(vibeLine);
+  lines.push(ctaLine);
+ 
+  return lines.join('\n\n');
+}
+ 
+/**
+ * Get activity snapshot for the guild.
+ * @param {Object} guild - Discord guild
+ * @param {Object} settings - welcome.dynamic settings
+ * @returns {{messageCount:number,activeTextChannels:number,topChannelIds:string[],voiceParticipants:number,voiceChannels:number,level:string}}
+ */
+function getCommunitySnapshot(guild, settings) {
+  const activityMap = guildActivity.get(guild.id) || new Map();
+  const now = Date.now();
+  const windowMs = getActivityWindowMs(settings);
+  const cutoff = now - windowMs;
+ 
+  let messageCount = 0;
+  const channelCounts = [];
+ 
+  for (const [channelId, timestamps] of activityMap.entries()) {
+    const recent = timestamps.filter((t) => t >= cutoff);
+ 
+    Iif (!recent.length) {
+      activityMap.delete(channelId);
+      continue;
+    }
+ 
+    // Write the pruned array back so stale entries don't accumulate forever
+    activityMap.set(channelId, recent);
+ 
+    messageCount += recent.length;
+    channelCounts.push({ channelId, count: recent.length });
+  }
+ 
+  // Evict guild entry if no channels remain
+  Iif (activityMap.size === 0) {
+    guildActivity.delete(guild.id);
+  }
+ 
+  const topChannelIds = channelCounts
+    .sort((a, b) => b.count - a.count)
+    .slice(0, 3)
+    .map((entry) => entry.channelId);
+ 
+  const activeVoiceChannels = guild.channels.cache.filter(
+    (channel) => channel?.isVoiceBased?.() && channel.members?.size > 0,
+  );
+ 
+  const voiceChannels = activeVoiceChannels.size;
+  const voiceParticipants = [...activeVoiceChannels.values()].reduce(
+    (sum, channel) => sum + (channel.members?.size || 0),
+    0,
+  );
+ 
+  const level = getActivityLevel(messageCount, voiceParticipants);
+ 
+  return {
+    messageCount,
+    activeTextChannels: channelCounts.length,
+    topChannelIds,
+    voiceParticipants,
+    voiceChannels,
+    level,
+  };
+}
+ 
+/**
+ * Get activity level from message + voice activity.
+ * @param {number} messageCount - Messages in rolling window
+ * @param {number} voiceParticipants - Active users in voice channels
+ * @returns {'quiet'|'light'|'steady'|'busy'|'hype'}
+ */
+function getActivityLevel(messageCount, voiceParticipants) {
+  if (messageCount >= 60 || voiceParticipants >= 15) return 'hype';
+  if (messageCount >= 25 || voiceParticipants >= 8) return 'busy';
+  if (messageCount >= 8 || voiceParticipants >= 3) return 'steady';
+  if (messageCount >= 1 || voiceParticipants >= 1) return 'light';
+  return 'quiet';
+}
+ 
+/**
+ * Build vibe line from current community activity.
+ * @param {Object} snapshot - Community snapshot
+ * @param {string[]} suggestedChannels - Channel mentions
+ * @returns {string}
+ */
+function buildVibeLine(snapshot, suggestedChannels) {
+  const topChannels = snapshot.topChannelIds.map((id) => `<#${id}>`);
+  const channelList = (topChannels.length ? topChannels : suggestedChannels).slice(0, 2);
+  const channelText = channelList.join(' + ');
+  const hasChannels = channelList.length > 0;
+ 
+  switch (snapshot.level) {
+    case 'hype':
+      return hasChannels
+        ? `The place is buzzing right now - big energy in ${channelText}.`
+        : `The place is buzzing right now - big energy everywhere.`;
+    case 'busy':
+      return hasChannels
+        ? `Good timing: chat is active (${snapshot.messageCount} messages recently), especially in ${channelText}.`
+        : `Good timing: the server is active right now (${snapshot.messageCount} messages recently${snapshot.voiceParticipants > 0 ? `, ${snapshot.voiceParticipants} in voice` : ''}).`;
+    case 'steady':
+      return hasChannels
+        ? `Things are moving at a healthy pace in ${channelText}, so you'll fit right in.`
+        : `Things are moving at a healthy pace, so you'll fit right in.`;
+    case 'light':
+      if (snapshot.voiceChannels > 0 && !hasChannels) {
+        return `${snapshot.voiceParticipants} ${snapshot.voiceParticipants === 1 ? 'person is' : 'people are'} hanging out in voice right now — jump in anytime.`;
+      }
+      if (snapshot.voiceChannels > 0) {
+        return `${snapshot.voiceParticipants} ${snapshot.voiceParticipants === 1 ? 'person is' : 'people are'} hanging out in voice right now, and ${channelText} is waking up.`;
+      }
+      return hasChannels
+        ? `It's a chill moment, but ${channelText} is where people are checking in.`
+        : `It's a chill moment — perfect time to say hello.`;
+    default:
+      return `You're catching us in a quiet window - perfect time to introduce yourself before the chaos starts.`;
+  }
+}
+ 
+/**
+ * Build CTA line with channel suggestions.
+ * @param {string[]} channels - Channel mentions
+ * @returns {string}
+ */
+function buildCtaLine(channels) {
+  const [first, second, third] = channels;
+ 
+  if (first && second && third) {
+    return `Start in ${first}, share what you're building in ${second}, and lurk project updates in ${third}.`;
+  }
+  if (first && second) {
+    return `Drop a quick intro in ${first} and show off what you're building in ${second}.`;
+  }
+  if (first) {
+    return `Say hey in ${first} and let us know what you're building.`;
+  }
+ 
+  return "Say hey and tell us what you're building — we're glad you're here.";
+}
+ 
+/**
+ * Build milestone line when member count hits notable threshold.
+ * @param {number} memberCount - Current member count
+ * @param {Object} settings - welcome.dynamic settings
+ * @returns {string|null}
+ */
+function getMilestoneLine(memberCount, settings) {
+  if (!memberCount) return null;
+ 
+  const interval = Number(settings.milestoneInterval) || 25;
+ 
+  if (NOTABLE_MILESTONES.has(memberCount) || (interval > 0 && memberCount % interval === 0)) {
+    return `🎉 Perfect timing - you're our **#${memberCount}** member milestone!`;
+  }
+ 
+  return null;
+}
+ 
+/**
+ * Determine time of day for greeting.
+ * @param {string} timezone - IANA timezone
+ * @returns {'morning'|'afternoon'|'evening'|'night'}
+ */
+function getTimeOfDay(timezone) {
+  const hour = getHourInTimezone(timezone);
+ 
+  if (hour >= 5 && hour < 12) return 'morning';
+  Eif (hour >= 12 && hour < 17) return 'afternoon';
+  if (hour >= 17 && hour < 22) return 'evening';
+  return 'night';
+}
+ 
+/**
+ * Get hour in timezone.
+ * @param {string} timezone - IANA timezone
+ * @returns {number}
+ */
+function getHourInTimezone(timezone) {
+  try {
+    const hourString = new Intl.DateTimeFormat('en-US', {
+      hour: '2-digit',
+      hour12: false,
+      timeZone: timezone,
+    }).format(new Date());
+ 
+    const hour = Number(hourString);
+    return Number.isFinite(hour) ? hour : new Date().getHours();
+  } catch {
+    return new Date().getHours();
+  }
+}
+ 
+/**
+ * Get greeting templates by time of day.
+ * @param {'morning'|'afternoon'|'evening'|'night'} timeOfDay - Time context
+ * @returns {Array<(ctx:Object)=>string>}
+ */
+function getGreetingTemplates(timeOfDay) {
+  const templates = {
+    morning: [
+      (ctx) => `☀️ Morning and welcome to **${ctx.server}**, <@${ctx.id}>!`,
+      (ctx) => `Hey <@${ctx.id}> - great way to start the day. Welcome to **${ctx.server}**!`,
+      (ctx) => `Good morning <@${ctx.id}> 👋 You just joined **${ctx.server}**.`,
+    ],
+    afternoon: [
+      (ctx) => `👋 Welcome to **${ctx.server}**, <@${ctx.id}>!`,
+      (ctx) =>
+        `Nice timing, <@${ctx.id}> - welcome to the **${ctx.server}** corner of the internet.`,
+      (ctx) => `Hey <@${ctx.id}>! Glad you made it into **${ctx.server}**.`,
+    ],
+    evening: [
+      (ctx) => `🌆 Evening crew just got better - welcome, <@${ctx.id}>!`,
+      (ctx) => `Welcome to **${ctx.server}**, <@${ctx.id}>. Prime build-hours energy right now.`,
+      (ctx) => `Hey <@${ctx.id}> 👋 Great time to join the party at **${ctx.server}**.`,
+    ],
+    night: [
+      (ctx) => `🌙 Night owl spotted. Welcome to **${ctx.server}**, <@${ctx.id}>!`,
+      (ctx) => `Late-night builders are active - welcome in, <@${ctx.id}>.`,
+      (ctx) => `Welcome <@${ctx.id}>! The night shift at **${ctx.server}** is undefeated.`,
+    ],
+  };
+ 
+  return templates[timeOfDay] || templates.afternoon;
+}
+ 
+/**
+ * Pick channels to suggest based on active channels, configured highlights, and legacy template links.
+ * @param {Object} member - Discord guild member
+ * @param {Object} config - Bot configuration
+ * @param {Object} snapshot - Community snapshot
+ * @returns {string[]} Channel mentions
+ */
+function getSuggestedChannels(member, config, snapshot) {
+  const dynamic = config?.welcome?.dynamic || {};
+  const configured = Array.isArray(dynamic.highlightChannels) ? dynamic.highlightChannels : [];
+  const legacy = extractChannelIdsFromTemplate(config?.welcome?.message || '');
+  const top = snapshot.topChannelIds || [];
+ 
+  const channelIds = [...new Set([...top, ...configured, ...legacy])]
+    .filter(Boolean)
+    .filter((id) => member.guild.channels.cache.has(id))
+    .slice(0, 3);
+ 
+  return channelIds.map((id) => `<#${id}>`);
+}
+ 
+/**
+ * Extract channel IDs from legacy message template (<#...> format)
+ * @param {string} template - Legacy welcome template
+ * @returns {string[]} Channel IDs
+ */
+function extractChannelIdsFromTemplate(template) {
+  const matches = template.match(/<#(\d+)>/g) || [];
+  return matches.map((match) => match.replace(/[^\d]/g, ''));
+}
+ 
+/**
+ * Calculate activity window in ms.
+ * @param {Object} settings - welcome.dynamic settings
+ * @returns {number}
+ */
+function getActivityWindowMs(settings) {
+  const minutes = Number(settings.activityWindowMinutes) || DEFAULT_ACTIVITY_WINDOW_MINUTES;
+  return Math.max(5, minutes) * 60 * 1000;
+}
+ 
+/**
+ * Pick one function from template list and execute with context.
+ * @param {Array<(ctx:Object)=>string>} templates - Template fns
+ * @param {Object} context - Template context
+ * @returns {string}
+ */
+function pickFrom(templates, context) {
+  if (!templates.length) return `Welcome, <@${context.id}>!`;
+  const index = Math.floor(Math.random() * templates.length);
+  return templates[index](context);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utils/errors.js.html b/coverage/src/utils/errors.js.html new file mode 100644 index 00000000..7501d8f1 --- /dev/null +++ b/coverage/src/utils/errors.js.html @@ -0,0 +1,757 @@ + + + + + + Code coverage report for src/utils/errors.js + + + + + + + + + +
+
+

All files / src/utils errors.js

+
+ +
+ 100% + Statements + 45/45 +
+ + +
+ 97.05% + Branches + 66/68 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 44/44 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +51x +  +49x +51x +51x +  +  +51x +7x +  +42x +3x +  +39x +2x +  +  +  +37x +17x +5x +  +12x +4x +  +8x +4x +  +4x +3x +  +1x +1x +  +  +  +  +20x +2x +  +18x +3x +  +15x +2x +  +  +  +13x +5x +  +8x +1x +  +  +  +7x +2x +  +  +5x +  +  +  +  +  +  +  +  +  +  +6x +  +6x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +6x +  +  +  +  +  +  +  +  +  +  +6x +  +6x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +6x +  +  +  +  +  +  +  +  +  +  +9x +  +  +9x +  +  +  +  +  +  +9x +  + 
/**
+ * Error Classification and User-Friendly Messages
+ *
+ * Provides utilities for classifying errors and generating
+ * helpful error messages for users.
+ */
+ 
+/**
+ * Error type classifications
+ */
+export const ErrorType = {
+  // Network-related errors
+  NETWORK: 'network',
+  TIMEOUT: 'timeout',
+ 
+  // API errors
+  API_ERROR: 'api_error',
+  API_RATE_LIMIT: 'api_rate_limit',
+  API_UNAUTHORIZED: 'api_unauthorized',
+  API_NOT_FOUND: 'api_not_found',
+  API_SERVER_ERROR: 'api_server_error',
+ 
+  // Discord-specific errors
+  DISCORD_PERMISSION: 'discord_permission',
+  DISCORD_CHANNEL_NOT_FOUND: 'discord_channel_not_found',
+  DISCORD_MISSING_ACCESS: 'discord_missing_access',
+ 
+  // Configuration errors
+  CONFIG_MISSING: 'config_missing',
+  CONFIG_INVALID: 'config_invalid',
+ 
+  // Unknown/generic errors
+  UNKNOWN: 'unknown',
+};
+ 
+/**
+ * Classify an error into a specific error type
+ *
+ * @param {Error} error - The error to classify
+ * @param {Object} context - Optional context (response, statusCode, etc.)
+ * @returns {string} Error type from ErrorType enum
+ */
+export function classifyError(error, context = {}) {
+  if (!error) return ErrorType.UNKNOWN;
+ 
+  const message = error.message?.toLowerCase() || '';
+  const code = error.code || context.code;
+  const status = error.status || context.status || context.statusCode;
+ 
+  // Network errors
+  if (code === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'ETIMEDOUT') {
+    return ErrorType.NETWORK;
+  }
+  if (code === 'ETIMEDOUT' || message.includes('timeout')) {
+    return ErrorType.TIMEOUT;
+  }
+  if (message.includes('fetch failed') || message.includes('network')) {
+    return ErrorType.NETWORK;
+  }
+ 
+  // HTTP status code errors
+  if (status) {
+    if (status === 401 || status === 403) {
+      return ErrorType.API_UNAUTHORIZED;
+    }
+    if (status === 404) {
+      return ErrorType.API_NOT_FOUND;
+    }
+    if (status === 429) {
+      return ErrorType.API_RATE_LIMIT;
+    }
+    if (status >= 500) {
+      return ErrorType.API_SERVER_ERROR;
+    }
+    Eif (status >= 400) {
+      return ErrorType.API_ERROR;
+    }
+  }
+ 
+  // Discord-specific errors
+  if (code === 50001 || message.includes('missing access')) {
+    return ErrorType.DISCORD_MISSING_ACCESS;
+  }
+  if (code === 50013 || message.includes('missing permissions')) {
+    return ErrorType.DISCORD_PERMISSION;
+  }
+  if (code === 10003 || message.includes('unknown channel')) {
+    return ErrorType.DISCORD_CHANNEL_NOT_FOUND;
+  }
+ 
+  // Config errors
+  if (message.includes('config.json not found') || message.includes('enoent')) {
+    return ErrorType.CONFIG_MISSING;
+  }
+  if (message.includes('invalid') && message.includes('config')) {
+    return ErrorType.CONFIG_INVALID;
+  }
+ 
+  // API errors (generic)
+  if (message.includes('api error') || context.isApiError) {
+    return ErrorType.API_ERROR;
+  }
+ 
+  return ErrorType.UNKNOWN;
+}
+ 
+/**
+ * Get a user-friendly error message based on error type
+ *
+ * @param {Error} error - The error object
+ * @param {Object} context - Optional context for more specific messages
+ * @returns {string} User-friendly error message
+ */
+export function getUserFriendlyMessage(error, context = {}) {
+  const errorType = classifyError(error, context);
+ 
+  const messages = {
+    [ErrorType.NETWORK]:
+      "I'm having trouble connecting to my brain right now. Check if the AI service is running and try again!",
+ 
+    [ErrorType.TIMEOUT]:
+      'That took too long to process. Try again with a shorter message, or wait a moment and retry!',
+ 
+    [ErrorType.API_RATE_LIMIT]:
+      "Whoa, too many requests! Let's take a quick breather. Try again in a minute.",
+ 
+    [ErrorType.API_UNAUTHORIZED]:
+      "I'm having authentication issues with the AI service. An admin needs to check the API credentials.",
+ 
+    [ErrorType.API_NOT_FOUND]:
+      "The AI service endpoint isn't responding. Please check if it's configured correctly.",
+ 
+    [ErrorType.API_SERVER_ERROR]:
+      'The AI service is having technical difficulties. It should recover automatically - try again in a moment!',
+ 
+    [ErrorType.API_ERROR]:
+      'Something went wrong with the AI service. Give it another shot in a moment!',
+ 
+    [ErrorType.DISCORD_PERMISSION]:
+      "I don't have permission to do that! An admin needs to check my role permissions.",
+ 
+    [ErrorType.DISCORD_CHANNEL_NOT_FOUND]:
+      "I can't find that channel. It might have been deleted, or I don't have access to it.",
+ 
+    [ErrorType.DISCORD_MISSING_ACCESS]:
+      "I don't have access to that resource. Please check my permissions!",
+ 
+    [ErrorType.CONFIG_MISSING]:
+      'Configuration file not found! Please create a config.json file (you can copy from config.example.json).',
+ 
+    [ErrorType.CONFIG_INVALID]:
+      'The configuration file has errors. Please check config.json for syntax errors or missing required fields.',
+ 
+    [ErrorType.UNKNOWN]:
+      'Something unexpected happened. Try again, and if it keeps happening, check the logs for details.',
+  };
+ 
+  return messages[errorType] || messages[ErrorType.UNKNOWN];
+}
+ 
+/**
+ * Get suggested next steps for an error
+ *
+ * @param {Error} error - The error object
+ * @param {Object} context - Optional context
+ * @returns {string|null} Suggested next steps or null if none
+ */
+export function getSuggestedNextSteps(error, context = {}) {
+  const errorType = classifyError(error, context);
+ 
+  const suggestions = {
+    [ErrorType.NETWORK]: 'Make sure the AI service (OpenClaw) is running and accessible.',
+ 
+    [ErrorType.TIMEOUT]: 'Try a shorter message or wait a moment before retrying.',
+ 
+    [ErrorType.API_RATE_LIMIT]: 'Wait 60 seconds before trying again.',
+ 
+    [ErrorType.API_UNAUTHORIZED]:
+      'Check the OPENCLAW_API_KEY environment variable (or legacy OPENCLAW_TOKEN) and API credentials.',
+ 
+    [ErrorType.API_NOT_FOUND]:
+      'Verify OPENCLAW_API_URL (or legacy OPENCLAW_URL) points to the correct endpoint.',
+ 
+    [ErrorType.API_SERVER_ERROR]:
+      'The service should recover automatically. If it persists, restart the AI service.',
+ 
+    [ErrorType.DISCORD_PERMISSION]:
+      'Grant the bot appropriate permissions in Server Settings > Roles.',
+ 
+    [ErrorType.DISCORD_CHANNEL_NOT_FOUND]:
+      'Update the channel ID in config.json or verify the channel exists.',
+ 
+    [ErrorType.DISCORD_MISSING_ACCESS]:
+      'Ensure the bot has access to the required channels and roles.',
+ 
+    [ErrorType.CONFIG_MISSING]:
+      'Create config.json from config.example.json and fill in your settings.',
+ 
+    [ErrorType.CONFIG_INVALID]: 'Validate your config.json syntax using a JSON validator.',
+  };
+ 
+  return suggestions[errorType] || null;
+}
+ 
+/**
+ * Check if an error is retryable (transient failure)
+ *
+ * @param {Error} error - The error to check
+ * @param {Object} context - Optional context
+ * @returns {boolean} True if the error should be retried
+ */
+export function isRetryable(error, context = {}) {
+  const errorType = classifyError(error, context);
+ 
+  // Only retry transient failures, not user/config errors
+  const retryableTypes = [
+    ErrorType.NETWORK,
+    ErrorType.TIMEOUT,
+    ErrorType.API_SERVER_ERROR,
+    ErrorType.API_RATE_LIMIT,
+  ];
+ 
+  return retryableTypes.includes(errorType);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utils/health.js.html b/coverage/src/utils/health.js.html new file mode 100644 index 00000000..68b6d163 --- /dev/null +++ b/coverage/src/utils/health.js.html @@ -0,0 +1,562 @@ + + + + + + Code coverage report for src/utils/health.js + + + + + + + + + +
+
+

All files / src/utils health.js

+
+ +
+ 0% + Statements + 0/36 +
+ + +
+ 0% + Branches + 0/10 +
+ + +
+ 9.09% + Functions + 1/11 +
+ + +
+ 0% + Lines + 0/36 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Health Monitor - Tracks bot health metrics
+ *
+ * Monitors:
+ * - Uptime (time since bot started)
+ * - Memory usage
+ * - Last AI request timestamp
+ * - OpenClaw API connectivity status
+ */
+ 
+/**
+ * Singleton health monitor instance
+ */
+class HealthMonitor {
+  constructor() {
+    if (HealthMonitor.instance) {
+      throw new Error('Use HealthMonitor.getInstance() to obtain the singleton');
+    }
+ 
+    this.startTime = Date.now();
+    this.lastAIRequest = null;
+    this.apiStatus = 'unknown';
+    this.lastAPICheck = null;
+ 
+    HealthMonitor.instance = this;
+  }
+ 
+  /**
+   * Get singleton instance
+   */
+  static getInstance() {
+    if (!HealthMonitor.instance) {
+      HealthMonitor.instance = new HealthMonitor();
+    }
+    return HealthMonitor.instance;
+  }
+ 
+  /**
+   * Record the start time (call when bot is ready)
+   */
+  recordStart() {
+    this.startTime = Date.now();
+  }
+ 
+  /**
+   * Record AI request activity
+   */
+  recordAIRequest() {
+    this.lastAIRequest = Date.now();
+  }
+ 
+  /**
+   * Update API status
+   * @param {string} status - 'ok', 'error', or 'unknown'
+   */
+  setAPIStatus(status) {
+    this.apiStatus = status;
+    this.lastAPICheck = Date.now();
+  }
+ 
+  /**
+   * Get current uptime in milliseconds
+   */
+  getUptime() {
+    return Date.now() - this.startTime;
+  }
+ 
+  /**
+   * Get formatted uptime string
+   */
+  getFormattedUptime() {
+    const uptime = this.getUptime();
+    const seconds = Math.floor(uptime / 1000);
+    const minutes = Math.floor(seconds / 60);
+    const hours = Math.floor(minutes / 60);
+    const days = Math.floor(hours / 24);
+ 
+    if (days > 0) {
+      return `${days}d ${hours % 24}h ${minutes % 60}m`;
+    } else if (hours > 0) {
+      return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
+    } else if (minutes > 0) {
+      return `${minutes}m ${seconds % 60}s`;
+    } else {
+      return `${seconds}s`;
+    }
+  }
+ 
+  /**
+   * Get memory usage stats
+   */
+  getMemoryUsage() {
+    const usage = process.memoryUsage();
+    return {
+      heapUsed: Math.round(usage.heapUsed / 1024 / 1024), // MB
+      heapTotal: Math.round(usage.heapTotal / 1024 / 1024), // MB
+      rss: Math.round(usage.rss / 1024 / 1024), // MB
+      external: Math.round(usage.external / 1024 / 1024), // MB
+    };
+  }
+ 
+  /**
+   * Get formatted memory usage string
+   */
+  getFormattedMemory() {
+    const mem = this.getMemoryUsage();
+    return `${mem.heapUsed}MB / ${mem.heapTotal}MB (RSS: ${mem.rss}MB)`;
+  }
+ 
+  /**
+   * Get complete health status
+   */
+  getStatus() {
+    const memory = this.getMemoryUsage();
+ 
+    return {
+      uptime: this.getUptime(),
+      uptimeFormatted: this.getFormattedUptime(),
+      memory: {
+        heapUsed: memory.heapUsed,
+        heapTotal: memory.heapTotal,
+        rss: memory.rss,
+        external: memory.external,
+        formatted: this.getFormattedMemory(),
+      },
+      api: {
+        status: this.apiStatus,
+        lastCheck: this.lastAPICheck,
+      },
+      lastAIRequest: this.lastAIRequest,
+      timestamp: Date.now(),
+    };
+  }
+ 
+  /**
+   * Get detailed diagnostics (for admin use)
+   */
+  getDetailedStatus() {
+    const status = this.getStatus();
+    const memory = process.memoryUsage();
+ 
+    return {
+      ...status,
+      process: {
+        pid: process.pid,
+        platform: process.platform,
+        nodeVersion: process.version,
+        uptime: process.uptime(),
+      },
+      memory: {
+        ...status.memory,
+        arrayBuffers: Math.round(memory.arrayBuffers / 1024 / 1024),
+      },
+      cpu: process.cpuUsage(),
+    };
+  }
+}
+ 
+export { HealthMonitor };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utils/index.html b/coverage/src/utils/index.html new file mode 100644 index 00000000..43a4ad4e --- /dev/null +++ b/coverage/src/utils/index.html @@ -0,0 +1,191 @@ + + + + + + Code coverage report for src/utils + + + + + + + + + +
+
+

All files src/utils

+
+ +
+ 28.12% + Statements + 45/160 +
+ + +
+ 44.89% + Branches + 66/147 +
+ + +
+ 17.85% + Functions + 5/28 +
+ + +
+ 28.38% + Lines + 44/155 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
errors.js +
+
100%45/4597.05%66/68100%4/4100%44/44
health.js +
+
0%0/360%0/109.09%1/110%0/36
permissions.js +
+
0%0/200%0/230%0/30%0/18
registerCommands.js +
+
0%0/170%0/170%0/20%0/17
retry.js +
+
0%0/250%0/160%0/60%0/23
splitMessage.js +
+
0%0/170%0/130%0/20%0/17
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utils/permissions.js.html b/coverage/src/utils/permissions.js.html new file mode 100644 index 00000000..3789a99c --- /dev/null +++ b/coverage/src/utils/permissions.js.html @@ -0,0 +1,316 @@ + + + + + + Code coverage report for src/utils/permissions.js + + + + + + + + + +
+
+

All files / src/utils permissions.js

+
+ +
+ 0% + Statements + 0/20 +
+ + +
+ 0% + Branches + 0/23 +
+ + +
+ 0% + Functions + 0/3 +
+ + +
+ 0% + Lines + 0/18 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Permission checking utilities for Bill Bot
+ *
+ * Provides centralized permission checks for commands and features.
+ */
+ 
+import { PermissionFlagsBits } from 'discord.js';
+ 
+/**
+ * Check if a member is an admin
+ *
+ * @param {GuildMember} member - Discord guild member
+ * @param {Object} config - Bot configuration
+ * @returns {boolean} True if member is admin
+ */
+export function isAdmin(member, config) {
+  if (!member || !config) return false;
+ 
+  // Check if member has Discord Administrator permission
+  if (member.permissions.has(PermissionFlagsBits.Administrator)) {
+    return true;
+  }
+ 
+  // Check if member has the configured admin role
+  if (config.permissions?.adminRoleId) {
+    return member.roles.cache.has(config.permissions.adminRoleId);
+  }
+ 
+  return false;
+}
+ 
+/**
+ * Check if a member has permission to use a command
+ *
+ * @param {GuildMember} member - Discord guild member
+ * @param {string} commandName - Name of the command
+ * @param {Object} config - Bot configuration
+ * @returns {boolean} True if member has permission
+ */
+export function hasPermission(member, commandName, config) {
+  if (!member || !commandName || !config) return false;
+ 
+  // If permissions are disabled, allow everything
+  if (!config.permissions?.enabled || !config.permissions?.usePermissions) {
+    return true;
+  }
+ 
+  // Get permission level for this command
+  const permissionLevel = config.permissions?.allowedCommands?.[commandName];
+ 
+  // If command not in config, default to admin-only for safety
+  if (!permissionLevel) {
+    return isAdmin(member, config);
+  }
+ 
+  // Check permission level
+  if (permissionLevel === 'everyone') {
+    return true;
+  }
+ 
+  if (permissionLevel === 'admin') {
+    return isAdmin(member, config);
+  }
+ 
+  // Unknown permission level - deny for safety
+  return false;
+}
+ 
+/**
+ * Get a helpful error message for permission denied
+ *
+ * @param {string} commandName - Name of the command
+ * @returns {string} User-friendly error message
+ */
+export function getPermissionError(commandName) {
+  return `❌ You don't have permission to use \`/${commandName}\`.\n\nThis command requires administrator access.`;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utils/registerCommands.js.html b/coverage/src/utils/registerCommands.js.html new file mode 100644 index 00000000..9c0a21f6 --- /dev/null +++ b/coverage/src/utils/registerCommands.js.html @@ -0,0 +1,256 @@ + + + + + + Code coverage report for src/utils/registerCommands.js + + + + + + + + + +
+
+

All files / src/utils registerCommands.js

+
+ +
+ 0% + Statements + 0/17 +
+ + +
+ 0% + Branches + 0/17 +
+ + +
+ 0% + Functions + 0/2 +
+ + +
+ 0% + Lines + 0/17 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Command registration utilities for Bill Bot
+ *
+ * Handles registering slash commands with Discord's API
+ */
+ 
+import { REST, Routes } from 'discord.js';
+import { error as logError, info } from '../logger.js';
+ 
+/**
+ * Register slash commands with Discord
+ *
+ * @param {Array} commands - Array of command modules with .data property
+ * @param {string} clientId - Discord application/client ID
+ * @param {string} token - Discord bot token
+ * @param {string} [guildId] - Optional guild ID for guild-specific registration (faster for dev)
+ * @returns {Promise<void>}
+ */
+export async function registerCommands(commands, clientId, token, guildId = null) {
+  if (!commands || !Array.isArray(commands)) {
+    throw new Error('Commands must be an array');
+  }
+ 
+  if (!clientId || !token) {
+    throw new Error('Client ID and token are required');
+  }
+ 
+  // Convert command modules to JSON for API
+  const commandData = commands.map((cmd) => {
+    if (!cmd.data || typeof cmd.data.toJSON !== 'function') {
+      throw new Error('Each command must have a .data property with toJSON() method');
+    }
+    return cmd.data.toJSON();
+  });
+ 
+  const rest = new REST({ version: '10' }).setToken(token);
+ 
+  try {
+    info(`Registering ${commandData.length} slash command(s)`);
+ 
+    let data;
+    if (guildId) {
+      // Guild-specific commands (instant updates, good for development)
+      data = await rest.put(Routes.applicationGuildCommands(clientId, guildId), {
+        body: commandData,
+      });
+    } else {
+      // Global commands (can take up to 1 hour to update)
+      data = await rest.put(Routes.applicationCommands(clientId), { body: commandData });
+    }
+ 
+    info(`Successfully registered ${data.length} slash command(s)`, { scope: guildId ? 'guild' : 'global' });
+  } catch (err) {
+    logError('Failed to register commands', { error: err.message });
+    throw err;
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utils/retry.js.html b/coverage/src/utils/retry.js.html new file mode 100644 index 00000000..3b9f2774 --- /dev/null +++ b/coverage/src/utils/retry.js.html @@ -0,0 +1,475 @@ + + + + + + Code coverage report for src/utils/retry.js + + + + + + + + + +
+
+

All files / src/utils retry.js

+
+ +
+ 0% + Statements + 0/25 +
+ + +
+ 0% + Branches + 0/16 +
+ + +
+ 0% + Functions + 0/6 +
+ + +
+ 0% + Lines + 0/23 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Retry Utility with Exponential Backoff
+ *
+ * Provides utilities for retrying operations with configurable
+ * exponential backoff and integration with error classification.
+ */
+ 
+import { debug, error, warn } from '../logger.js';
+import { classifyError, isRetryable } from './errors.js';
+ 
+/**
+ * Sleep for a specified duration
+ * @param {number} ms - Milliseconds to sleep
+ * @returns {Promise<void>}
+ */
+function sleep(ms) {
+  return new Promise((resolve) => setTimeout(resolve, ms));
+}
+ 
+/**
+ * Calculate delay with exponential backoff
+ * @param {number} attempt - Current attempt number (0-indexed)
+ * @param {number} baseDelay - Base delay in milliseconds
+ * @param {number} maxDelay - Maximum delay in milliseconds
+ * @returns {number} Delay in milliseconds
+ */
+function calculateBackoff(attempt, baseDelay, maxDelay) {
+  // Exponential backoff: baseDelay * 2^attempt
+  const delay = baseDelay * 2 ** attempt;
+ 
+  // Cap at maxDelay
+  return Math.min(delay, maxDelay);
+}
+ 
+/**
+ * Retry an async operation with exponential backoff
+ *
+ * @param {Function} fn - Async function to retry
+ * @param {Object} options - Retry configuration options
+ * @param {number} options.maxRetries - Maximum number of retry attempts (default: 3)
+ * @param {number} options.baseDelay - Initial delay in milliseconds (default: 1000)
+ * @param {number} options.maxDelay - Maximum delay in milliseconds (default: 30000)
+ * @param {Function} options.shouldRetry - Custom function to determine if error is retryable
+ * @param {Object} options.context - Optional context for logging
+ * @returns {Promise<any>} Result of the function
+ * @throws {Error} Throws the last error if all retries fail
+ */
+export async function withRetry(fn, options = {}) {
+  const {
+    maxRetries = 3,
+    baseDelay = 1000,
+    maxDelay = 30000,
+    shouldRetry = isRetryable,
+    context = {},
+  } = options;
+ 
+  let lastError;
+ 
+  for (let attempt = 0; attempt <= maxRetries; attempt++) {
+    try {
+      // Execute the function
+      return await fn();
+    } catch (err) {
+      lastError = err;
+ 
+      // Check if we should retry
+      const errorType = classifyError(err, context);
+      const canRetry = shouldRetry(err, context);
+ 
+      // Log the error
+      if (attempt === 0) {
+        warn(`Operation failed: ${err.message}`, {
+          ...context,
+          errorType,
+          attempt: attempt + 1,
+          maxRetries: maxRetries + 1,
+        });
+      }
+ 
+      // If this was the last attempt or error is not retryable, throw
+      if (attempt >= maxRetries || !canRetry) {
+        if (!canRetry) {
+          error('Operation failed with non-retryable error', {
+            ...context,
+            errorType,
+            attempt: attempt + 1,
+            error: err.message,
+          });
+        } else {
+          error('Operation failed after all retries', {
+            ...context,
+            errorType,
+            totalAttempts: attempt + 1,
+            error: err.message,
+          });
+        }
+        throw err;
+      }
+ 
+      // Calculate backoff delay
+      const delay = calculateBackoff(attempt, baseDelay, maxDelay);
+ 
+      debug(`Retrying in ${delay}ms`, {
+        ...context,
+        attempt: attempt + 1,
+        maxRetries: maxRetries + 1,
+        delay,
+        errorType,
+      });
+ 
+      // Wait before retrying
+      await sleep(delay);
+    }
+  }
+ 
+  // Should never reach here, but just in case
+  throw lastError;
+}
+ 
+/**
+ * Create a retry wrapper with pre-configured options
+ *
+ * @param {Object} defaultOptions - Default retry options
+ * @returns {Function} Configured retry function
+ */
+export function createRetryWrapper(defaultOptions = {}) {
+  return (fn, options = {}) => {
+    return withRetry(fn, { ...defaultOptions, ...options });
+  };
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utils/splitMessage.js.html b/coverage/src/utils/splitMessage.js.html new file mode 100644 index 00000000..a1f29553 --- /dev/null +++ b/coverage/src/utils/splitMessage.js.html @@ -0,0 +1,268 @@ + + + + + + Code coverage report for src/utils/splitMessage.js + + + + + + + + + +
+
+

All files / src/utils splitMessage.js

+
+ +
+ 0% + Statements + 0/17 +
+ + +
+ 0% + Branches + 0/13 +
+ + +
+ 0% + Functions + 0/2 +
+ + +
+ 0% + Lines + 0/17 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Split Message Utility
+ * Splits long messages to fit within Discord's 2000-character limit.
+ */
+ 
+/**
+ * Discord's maximum message length.
+ */
+const DISCORD_MAX_LENGTH = 2000;
+ 
+/**
+ * Safe chunk size leaving room for potential overhead.
+ */
+const SAFE_CHUNK_SIZE = 1990;
+ 
+/**
+ * Splits a message into chunks that fit within Discord's character limit.
+ * Attempts to split on word boundaries to avoid breaking words, URLs, or emoji.
+ *
+ * @param {string} text - The text to split
+ * @param {number} [maxLength=1990] - Maximum length per chunk (default 1990 to stay under 2000)
+ * @returns {string[]} Array of text chunks, each within the specified limit
+ */
+export function splitMessage(text, maxLength = SAFE_CHUNK_SIZE) {
+  if (!text || text.length <= maxLength) {
+    return text ? [text] : [];
+  }
+ 
+  const chunks = [];
+  let remaining = text;
+ 
+  while (remaining.length > 0) {
+    if (remaining.length <= maxLength) {
+      chunks.push(remaining);
+      break;
+    }
+ 
+    // Try to find a space to split on (word boundary)
+    let splitAt = remaining.lastIndexOf(' ', maxLength);
+ 
+    // If no space found or it's at the start, force split at maxLength
+    if (splitAt <= 0) {
+      splitAt = maxLength;
+    }
+ 
+    chunks.push(remaining.slice(0, splitAt));
+    remaining = remaining.slice(splitAt).trimStart();
+  }
+ 
+  return chunks;
+}
+ 
+/**
+ * Checks if a message exceeds Discord's character limit.
+ *
+ * @param {string} text - The text to check
+ * @returns {boolean} True if the message needs splitting
+ */
+export function needsSplitting(text) {
+  return text && text.length > DISCORD_MAX_LENGTH;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/tests/ai.test.js b/tests/ai.test.js new file mode 100644 index 00000000..52cbffb3 --- /dev/null +++ b/tests/ai.test.js @@ -0,0 +1,377 @@ +/** + * Tests for src/modules/ai.js + * AI chat functionality powered by Claude via OpenClaw + */ + +import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; +import { + getConversationHistory, + setConversationHistory, + getHistory, + addToHistory, + generateResponse, + OPENCLAW_URL, + OPENCLAW_TOKEN, +} from '../src/modules/ai.js'; + +// Mock fetch globally +global.fetch = vi.fn(); + +describe('AI module', () => { + beforeEach(() => { + // Reset conversation history before each test + setConversationHistory(new Map()); + vi.clearAllMocks(); + }); + + describe('conversation history management', () => { + it('should return empty Map initially', () => { + const history = getConversationHistory(); + expect(history).toBeInstanceOf(Map); + expect(history.size).toBe(0); + }); + + it('should allow setting conversation history', () => { + const newHistory = new Map(); + newHistory.set('channel1', [{ role: 'user', content: 'hello' }]); + setConversationHistory(newHistory); + + const history = getConversationHistory(); + expect(history.size).toBe(1); + expect(history.get('channel1')).toEqual([{ role: 'user', content: 'hello' }]); + }); + + it('should get empty history for new channel', () => { + const channelHistory = getHistory('channel1'); + expect(Array.isArray(channelHistory)).toBe(true); + expect(channelHistory.length).toBe(0); + }); + + it('should create history for new channel on first access', () => { + const history1 = getHistory('channel1'); + const history2 = getHistory('channel1'); + expect(history1).toBe(history2); // Same reference + }); + }); + + describe('addToHistory', () => { + it('should add message to channel history', () => { + addToHistory('channel1', 'user', 'Hello there'); + const history = getHistory('channel1'); + expect(history).toEqual([{ role: 'user', content: 'Hello there' }]); + }); + + it('should add multiple messages', () => { + addToHistory('channel1', 'user', 'Hello'); + addToHistory('channel1', 'assistant', 'Hi!'); + const history = getHistory('channel1'); + expect(history).toEqual([ + { role: 'user', content: 'Hello' }, + { role: 'assistant', content: 'Hi!' }, + ]); + }); + + it('should trim history when exceeding MAX_HISTORY (20 messages)', () => { + // Add 25 messages + for (let i = 0; i < 25; i++) { + addToHistory('channel1', 'user', `Message ${i}`); + } + const history = getHistory('channel1'); + expect(history.length).toBe(20); + expect(history[0].content).toBe('Message 5'); // First 5 removed + expect(history[19].content).toBe('Message 24'); + }); + + it('should maintain separate history per channel', () => { + addToHistory('channel1', 'user', 'Channel 1 message'); + addToHistory('channel2', 'user', 'Channel 2 message'); + + const history1 = getHistory('channel1'); + const history2 = getHistory('channel2'); + + expect(history1.length).toBe(1); + expect(history2.length).toBe(1); + expect(history1[0].content).toBe('Channel 1 message'); + expect(history2[0].content).toBe('Channel 2 message'); + }); + }); + + describe('OpenClaw configuration', () => { + it('should export OPENCLAW_URL', () => { + expect(OPENCLAW_URL).toBeDefined(); + expect(typeof OPENCLAW_URL).toBe('string'); + }); + + it('should export OPENCLAW_TOKEN', () => { + expect(OPENCLAW_TOKEN).toBeDefined(); + expect(typeof OPENCLAW_TOKEN).toBe('string'); + }); + + it('should have default URL if not in env', () => { + expect(OPENCLAW_URL).toContain('localhost:18789'); + }); + }); + + describe('generateResponse', () => { + const mockConfig = { + ai: { + model: 'claude-sonnet-4-20250514', + maxTokens: 1024, + systemPrompt: 'You are a helpful bot.', + }, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should generate response from API', async () => { + const mockResponse = { + ok: true, + json: async () => ({ + choices: [ + { + message: { + content: 'Hello! How can I help you?', + }, + }, + ], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + const response = await generateResponse( + 'channel1', + 'Hi there', + 'testuser', + mockConfig + ); + + expect(response).toBe('Hello! How can I help you?'); + expect(global.fetch).toHaveBeenCalledTimes(1); + }); + + it('should include username in user message', async () => { + const mockResponse = { + ok: true, + json: async () => ({ + choices: [{ message: { content: 'Response' } }], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'Hello', 'john', mockConfig); + + const callArgs = global.fetch.mock.calls[0]; + const requestBody = JSON.parse(callArgs[1].body); + const lastMessage = requestBody.messages[requestBody.messages.length - 1]; + + expect(lastMessage.content).toContain('john:'); + expect(lastMessage.content).toContain('Hello'); + }); + + it('should include conversation history in request', async () => { + addToHistory('channel1', 'user', 'john: Previous message'); + addToHistory('channel1', 'assistant', 'Previous response'); + + const mockResponse = { + ok: true, + json: async () => ({ + choices: [{ message: { content: 'New response' } }], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'New message', 'john', mockConfig); + + const callArgs = global.fetch.mock.calls[0]; + const requestBody = JSON.parse(callArgs[1].body); + + expect(requestBody.messages.length).toBeGreaterThan(2); // system + history + new message + }); + + it('should update history after successful response', async () => { + const mockResponse = { + ok: true, + json: async () => ({ + choices: [{ message: { content: 'AI response' } }], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'User message', 'john', mockConfig); + + const history = getHistory('channel1'); + expect(history.length).toBe(2); + expect(history[0]).toEqual({ role: 'user', content: 'john: User message' }); + expect(history[1]).toEqual({ role: 'assistant', content: 'AI response' }); + }); + + it('should send Authorization header when token is provided', async () => { + const mockResponse = { + ok: true, + json: async () => ({ + choices: [{ message: { content: 'Response' } }], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'Hello', 'user', mockConfig); + + const callArgs = global.fetch.mock.calls[0]; + const headers = callArgs[1].headers; + + if (OPENCLAW_TOKEN) { + expect(headers.Authorization).toBeDefined(); + } + }); + + it('should use configured model and maxTokens', async () => { + const mockResponse = { + ok: true, + json: async () => ({ + choices: [{ message: { content: 'Response' } }], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'Hello', 'user', mockConfig); + + const callArgs = global.fetch.mock.calls[0]; + const requestBody = JSON.parse(callArgs[1].body); + + expect(requestBody.model).toBe('claude-sonnet-4-20250514'); + expect(requestBody.max_tokens).toBe(1024); + }); + + it('should use default values when config is incomplete', async () => { + const minimalConfig = { ai: {} }; + const mockResponse = { + ok: true, + json: async () => ({ + choices: [{ message: { content: 'Response' } }], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'Hello', 'user', minimalConfig); + + const callArgs = global.fetch.mock.calls[0]; + const requestBody = JSON.parse(callArgs[1].body); + + expect(requestBody.model).toBeDefined(); + expect(requestBody.max_tokens).toBeDefined(); + }); + + it('should handle API errors gracefully', async () => { + const mockResponse = { + ok: false, + status: 500, + statusText: 'Internal Server Error', + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + const response = await generateResponse('channel1', 'Hello', 'user', mockConfig); + + expect(response).toContain("I'm having trouble"); + }); + + it('should handle network errors', async () => { + global.fetch.mockRejectedValueOnce(new Error('Network error')); + + const response = await generateResponse('channel1', 'Hello', 'user', mockConfig); + + expect(response).toContain("I'm having trouble"); + }); + + it('should handle missing response content', async () => { + const mockResponse = { + ok: true, + json: async () => ({ + choices: [], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + const response = await generateResponse('channel1', 'Hello', 'user', mockConfig); + + expect(response).toBe('I got nothing. Try again?'); + }); + + it('should call health monitor when provided', async () => { + const mockHealthMonitor = { + recordAIRequest: vi.fn(), + setAPIStatus: vi.fn(), + }; + + const mockResponse = { + ok: true, + json: async () => ({ + choices: [{ message: { content: 'Response' } }], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'Hello', 'user', mockConfig, mockHealthMonitor); + + expect(mockHealthMonitor.recordAIRequest).toHaveBeenCalled(); + expect(mockHealthMonitor.setAPIStatus).toHaveBeenCalledWith('ok'); + }); + + it('should set health monitor error status on failure', async () => { + const mockHealthMonitor = { + recordAIRequest: vi.fn(), + setAPIStatus: vi.fn(), + }; + + const mockResponse = { + ok: false, + status: 500, + statusText: 'Error', + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'Hello', 'user', mockConfig, mockHealthMonitor); + + expect(mockHealthMonitor.setAPIStatus).toHaveBeenCalledWith('error'); + }); + + it('should include system prompt in messages', async () => { + const mockResponse = { + ok: true, + json: async () => ({ + choices: [{ message: { content: 'Response' } }], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'Hello', 'user', mockConfig); + + const callArgs = global.fetch.mock.calls[0]; + const requestBody = JSON.parse(callArgs[1].body); + const systemMessage = requestBody.messages[0]; + + expect(systemMessage.role).toBe('system'); + expect(systemMessage.content).toContain('helpful bot'); + }); + + it('should use default system prompt when not configured', async () => { + const configWithoutPrompt = { ai: {} }; + const mockResponse = { + ok: true, + json: async () => ({ + choices: [{ message: { content: 'Response' } }], + }), + }; + global.fetch.mockResolvedValueOnce(mockResponse); + + await generateResponse('channel1', 'Hello', 'user', configWithoutPrompt); + + const callArgs = global.fetch.mock.calls[0]; + const requestBody = JSON.parse(callArgs[1].body); + const systemMessage = requestBody.messages[0]; + + expect(systemMessage.content).toContain('Volvox Bot'); + }); + }); +}); \ No newline at end of file diff --git a/tests/errors.test.js b/tests/errors.test.js new file mode 100644 index 00000000..2099d49c --- /dev/null +++ b/tests/errors.test.js @@ -0,0 +1,346 @@ +/** + * Tests for src/utils/errors.js + * Error classification and user-friendly messages + */ + +import { describe, expect, it } from 'vitest'; +import { + ErrorType, + classifyError, + getUserFriendlyMessage, + getSuggestedNextSteps, + isRetryable, +} from '../src/utils/errors.js'; + +describe('errors utility', () => { + describe('ErrorType enum', () => { + it('should export all error types', () => { + expect(ErrorType.NETWORK).toBe('network'); + expect(ErrorType.TIMEOUT).toBe('timeout'); + expect(ErrorType.API_ERROR).toBe('api_error'); + expect(ErrorType.API_RATE_LIMIT).toBe('api_rate_limit'); + expect(ErrorType.API_UNAUTHORIZED).toBe('api_unauthorized'); + expect(ErrorType.API_NOT_FOUND).toBe('api_not_found'); + expect(ErrorType.API_SERVER_ERROR).toBe('api_server_error'); + expect(ErrorType.DISCORD_PERMISSION).toBe('discord_permission'); + expect(ErrorType.DISCORD_CHANNEL_NOT_FOUND).toBe('discord_channel_not_found'); + expect(ErrorType.DISCORD_MISSING_ACCESS).toBe('discord_missing_access'); + expect(ErrorType.CONFIG_MISSING).toBe('config_missing'); + expect(ErrorType.CONFIG_INVALID).toBe('config_invalid'); + expect(ErrorType.UNKNOWN).toBe('unknown'); + }); + }); + + describe('classifyError', () => { + describe('network errors', () => { + it('should classify ECONNREFUSED as network error', () => { + const error = new Error('Connection refused'); + error.code = 'ECONNREFUSED'; + expect(classifyError(error)).toBe(ErrorType.NETWORK); + }); + + it('should classify ENOTFOUND as network error', () => { + const error = new Error('Host not found'); + error.code = 'ENOTFOUND'; + expect(classifyError(error)).toBe(ErrorType.NETWORK); + }); + + it('should classify fetch failed as network error', () => { + const error = new Error('fetch failed'); + expect(classifyError(error)).toBe(ErrorType.NETWORK); + }); + + it('should classify network message as network error', () => { + const error = new Error('network error occurred'); + expect(classifyError(error)).toBe(ErrorType.NETWORK); + }); + }); + + describe('timeout errors', () => { + it('should classify ETIMEDOUT code as network error (takes priority)', () => { + const error = new Error('Request timeout'); + error.code = 'ETIMEDOUT'; + // ETIMEDOUT is checked in network errors first, so it returns network type + expect(classifyError(error)).toBe(ErrorType.NETWORK); + }); + + it('should classify timeout message as timeout', () => { + const error = new Error('Request timeout exceeded'); + expect(classifyError(error)).toBe(ErrorType.TIMEOUT); + }); + }); + + describe('HTTP status code errors', () => { + it('should classify 401 as unauthorized', () => { + const error = new Error('Unauthorized'); + expect(classifyError(error, { status: 401 })).toBe(ErrorType.API_UNAUTHORIZED); + }); + + it('should classify 403 as unauthorized', () => { + const error = new Error('Forbidden'); + expect(classifyError(error, { status: 403 })).toBe(ErrorType.API_UNAUTHORIZED); + }); + + it('should classify 404 as not found', () => { + const error = new Error('Not found'); + expect(classifyError(error, { status: 404 })).toBe(ErrorType.API_NOT_FOUND); + }); + + it('should classify 429 as rate limit', () => { + const error = new Error('Too many requests'); + expect(classifyError(error, { status: 429 })).toBe(ErrorType.API_RATE_LIMIT); + }); + + it('should classify 500 as server error', () => { + const error = new Error('Internal server error'); + expect(classifyError(error, { status: 500 })).toBe(ErrorType.API_SERVER_ERROR); + }); + + it('should classify 503 as server error', () => { + const error = new Error('Service unavailable'); + expect(classifyError(error, { status: 503 })).toBe(ErrorType.API_SERVER_ERROR); + }); + + it('should classify 400 as API error', () => { + const error = new Error('Bad request'); + expect(classifyError(error, { status: 400 })).toBe(ErrorType.API_ERROR); + }); + + it('should use statusCode from context', () => { + const error = new Error('Error'); + expect(classifyError(error, { statusCode: 404 })).toBe(ErrorType.API_NOT_FOUND); + }); + }); + + describe('Discord errors', () => { + it('should classify code 50001 as missing access', () => { + const error = new Error('Missing access'); + error.code = 50001; + expect(classifyError(error)).toBe(ErrorType.DISCORD_MISSING_ACCESS); + }); + + it('should classify missing access message', () => { + const error = new Error('Missing access to channel'); + expect(classifyError(error)).toBe(ErrorType.DISCORD_MISSING_ACCESS); + }); + + it('should classify code 50013 as permission error', () => { + const error = new Error('Missing permissions'); + error.code = 50013; + expect(classifyError(error)).toBe(ErrorType.DISCORD_PERMISSION); + }); + + it('should classify missing permissions message', () => { + const error = new Error('Bot missing permissions'); + expect(classifyError(error)).toBe(ErrorType.DISCORD_PERMISSION); + }); + + it('should classify code 10003 as channel not found', () => { + const error = new Error('Unknown channel'); + error.code = 10003; + expect(classifyError(error)).toBe(ErrorType.DISCORD_CHANNEL_NOT_FOUND); + }); + + it('should classify unknown channel message', () => { + const error = new Error('Unknown channel provided'); + expect(classifyError(error)).toBe(ErrorType.DISCORD_CHANNEL_NOT_FOUND); + }); + }); + + describe('config errors', () => { + it('should classify config.json not found', () => { + const error = new Error('config.json not found'); + expect(classifyError(error)).toBe(ErrorType.CONFIG_MISSING); + }); + + it('should classify ENOENT as config missing', () => { + const error = new Error('ENOENT: no such file'); + expect(classifyError(error)).toBe(ErrorType.CONFIG_MISSING); + }); + + it('should classify invalid config message', () => { + const error = new Error('Invalid config structure'); + expect(classifyError(error)).toBe(ErrorType.CONFIG_INVALID); + }); + }); + + describe('API errors', () => { + it('should classify API error message', () => { + const error = new Error('API error occurred'); + expect(classifyError(error)).toBe(ErrorType.API_ERROR); + }); + + it('should classify with isApiError context flag', () => { + const error = new Error('Something went wrong'); + expect(classifyError(error, { isApiError: true })).toBe(ErrorType.API_ERROR); + }); + }); + + describe('edge cases', () => { + it('should handle null error', () => { + expect(classifyError(null)).toBe(ErrorType.UNKNOWN); + }); + + it('should handle undefined error', () => { + expect(classifyError(undefined)).toBe(ErrorType.UNKNOWN); + }); + + it('should handle error without message', () => { + const error = new Error(); + expect(classifyError(error)).toBe(ErrorType.UNKNOWN); + }); + + it('should handle unknown error type', () => { + const error = new Error('Something random happened'); + expect(classifyError(error)).toBe(ErrorType.UNKNOWN); + }); + + it('should handle error with status from error object', () => { + const error = new Error('API error'); + error.status = 404; + expect(classifyError(error)).toBe(ErrorType.API_NOT_FOUND); + }); + }); + }); + + describe('getUserFriendlyMessage', () => { + it('should return user-friendly message for network error', () => { + const error = new Error('Connection refused'); + error.code = 'ECONNREFUSED'; + const message = getUserFriendlyMessage(error); + expect(message).toContain('trouble connecting'); + expect(message.length).toBeGreaterThan(0); + }); + + it('should return user-friendly message for timeout', () => { + const error = new Error('timeout exceeded'); + // Use message-based timeout detection (not ETIMEDOUT code) + const message = getUserFriendlyMessage(error); + expect(message).toContain('took too long'); + expect(message.length).toBeGreaterThan(0); + }); + + it('should return user-friendly message for rate limit', () => { + const error = new Error('Too many requests'); + const message = getUserFriendlyMessage(error, { status: 429 }); + expect(message).toContain('too many requests'); + expect(message.length).toBeGreaterThan(0); + }); + + it('should return user-friendly message for unauthorized', () => { + const error = new Error('Unauthorized'); + const message = getUserFriendlyMessage(error, { status: 401 }); + expect(message).toContain('authentication'); + expect(message.length).toBeGreaterThan(0); + }); + + it('should return user-friendly message for config missing', () => { + const error = new Error('config.json not found'); + const message = getUserFriendlyMessage(error); + expect(message).toContain('Configuration file not found'); + expect(message.length).toBeGreaterThan(0); + }); + + it('should return user-friendly message for unknown error', () => { + const error = new Error('Unknown error'); + const message = getUserFriendlyMessage(error); + expect(message).toContain('unexpected'); + expect(message.length).toBeGreaterThan(0); + }); + }); + + describe('getSuggestedNextSteps', () => { + it('should return suggestions for network error', () => { + const error = new Error('Connection refused'); + error.code = 'ECONNREFUSED'; + const suggestion = getSuggestedNextSteps(error); + expect(suggestion).toBeTruthy(); + expect(suggestion).toContain('OpenClaw'); + }); + + it('should return suggestions for timeout', () => { + const error = new Error('Timeout'); + error.code = 'ETIMEDOUT'; + const suggestion = getSuggestedNextSteps(error); + expect(suggestion).toBeTruthy(); + expect(suggestion.length).toBeGreaterThan(0); + }); + + it('should return suggestions for rate limit', () => { + const error = new Error('Too many requests'); + const suggestion = getSuggestedNextSteps(error, { status: 429 }); + expect(suggestion).toBeTruthy(); + expect(suggestion).toContain('60 seconds'); + }); + + it('should return suggestions for unauthorized', () => { + const error = new Error('Unauthorized'); + const suggestion = getSuggestedNextSteps(error, { status: 401 }); + expect(suggestion).toBeTruthy(); + expect(suggestion).toContain('API'); + }); + + it('should return suggestions for config missing', () => { + const error = new Error('config.json not found'); + const suggestion = getSuggestedNextSteps(error); + expect(suggestion).toBeTruthy(); + expect(suggestion).toContain('config.json'); + }); + + it('should return null for unknown error type', () => { + const error = new Error('Random error'); + const suggestion = getSuggestedNextSteps(error); + expect(suggestion).toBeNull(); + }); + }); + + describe('isRetryable', () => { + it('should mark network errors as retryable', () => { + const error = new Error('Connection refused'); + error.code = 'ECONNREFUSED'; + expect(isRetryable(error)).toBe(true); + }); + + it('should mark timeout as retryable', () => { + const error = new Error('timeout exceeded'); + // Use message-based detection for timeout + expect(isRetryable(error)).toBe(true); + }); + + it('should mark server errors as retryable', () => { + const error = new Error('Server error'); + expect(isRetryable(error, { status: 500 })).toBe(true); + }); + + it('should mark rate limit as retryable', () => { + const error = new Error('Too many requests'); + expect(isRetryable(error, { status: 429 })).toBe(true); + }); + + it('should not mark unauthorized as retryable', () => { + const error = new Error('Unauthorized'); + expect(isRetryable(error, { status: 401 })).toBe(false); + }); + + it('should not mark not found as retryable', () => { + const error = new Error('Not found'); + expect(isRetryable(error, { status: 404 })).toBe(false); + }); + + it('should not mark config errors as retryable', () => { + const error = new Error('config.json not found'); + expect(isRetryable(error)).toBe(false); + }); + + it('should not mark Discord permission errors as retryable', () => { + const error = new Error('Missing permissions'); + error.code = 50013; + expect(isRetryable(error)).toBe(false); + }); + + it('should not mark unknown errors as retryable', () => { + const error = new Error('Unknown error'); + expect(isRetryable(error)).toBe(false); + }); + }); +}); \ No newline at end of file diff --git a/tests/logger.test.js b/tests/logger.test.js new file mode 100644 index 00000000..926b6bf5 --- /dev/null +++ b/tests/logger.test.js @@ -0,0 +1,231 @@ +/** + * Tests for src/logger.js + * Structured logging with Winston + */ + +import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +describe('logger module', () => { + describe('module exports', () => { + it('should export debug function', async () => { + const logger = await import('../src/logger.js'); + expect(logger.debug).toBeDefined(); + expect(typeof logger.debug).toBe('function'); + }); + + it('should export info function', async () => { + const logger = await import('../src/logger.js'); + expect(logger.info).toBeDefined(); + expect(typeof logger.info).toBe('function'); + }); + + it('should export warn function', async () => { + const logger = await import('../src/logger.js'); + expect(logger.warn).toBeDefined(); + expect(typeof logger.warn).toBe('function'); + }); + + it('should export error function', async () => { + const logger = await import('../src/logger.js'); + expect(logger.error).toBeDefined(); + expect(typeof logger.error).toBe('function'); + }); + + it('should export default object with all logging functions', async () => { + const logger = await import('../src/logger.js'); + expect(logger.default).toBeDefined(); + expect(logger.default.debug).toBeDefined(); + expect(logger.default.info).toBeDefined(); + expect(logger.default.warn).toBeDefined(); + expect(logger.default.error).toBeDefined(); + }); + + it('should export winston logger instance', async () => { + const logger = await import('../src/logger.js'); + expect(logger.default.logger).toBeDefined(); + expect(logger.default.logger.level).toBeDefined(); + }); + }); + + describe('logging functionality', () => { + it('should handle logging with metadata', async () => { + const logger = await import('../src/logger.js'); + + // Should not throw + expect(() => { + logger.info('test message', { foo: 'bar', count: 123 }); + }).not.toThrow(); + }); + + it('should handle logging without metadata', async () => { + const logger = await import('../src/logger.js'); + + // Should not throw + expect(() => { + logger.info('test message'); + }).not.toThrow(); + }); + + it('should handle all log levels', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.debug('debug message'); + logger.info('info message'); + logger.warn('warn message'); + logger.error('error message'); + }).not.toThrow(); + }); + + it('should handle errors with stack traces', async () => { + const logger = await import('../src/logger.js'); + const testError = new Error('Test error'); + + expect(() => { + logger.error('error occurred', { error: testError.message, stack: testError.stack }); + }).not.toThrow(); + }); + }); + + describe('sensitive data redaction', () => { + it('should redact DISCORD_TOKEN from logs', async () => { + const logger = await import('../src/logger.js'); + + // Should not throw and should redact sensitive data + expect(() => { + logger.info('config loaded', { DISCORD_TOKEN: 'secret123' }); + }).not.toThrow(); + }); + + it('should redact OPENCLAW_API_KEY from logs', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.info('API call', { OPENCLAW_API_KEY: 'secret-key' }); + }).not.toThrow(); + }); + + it('should redact token field from logs', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.info('auth data', { token: 'secret-token', user: 'john' }); + }).not.toThrow(); + }); + + it('should redact password field from logs', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.info('login attempt', { username: 'john', password: 'secret123' }); + }).not.toThrow(); + }); + + it('should redact nested sensitive data', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.info('config', { + api: { + token: 'secret', + url: 'https://api.example.com' + } + }); + }).not.toThrow(); + }); + + it('should handle arrays with sensitive data', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.info('headers', { + headers: [ + { name: 'Authorization', value: 'Bearer token123' }, + { name: 'Content-Type', value: 'application/json' } + ] + }); + }).not.toThrow(); + }); + }); + + describe('edge cases', () => { + it('should handle null metadata', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.info('test', null); + }).not.toThrow(); + }); + + it('should handle undefined metadata', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.info('test', undefined); + }).not.toThrow(); + }); + + it('should handle empty object metadata', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.info('test', {}); + }).not.toThrow(); + }); + + it('should handle circular references in metadata gracefully', async () => { + const logger = await import('../src/logger.js'); + const obj = { name: 'test' }; + obj.self = obj; + + // Winston's JSON.stringify will fail on circular refs, so it may throw + // But the logger module should not crash the process + try { + logger.info('circular', { data: obj }); + } catch (err) { + // Expected - circular reference causes stack overflow + expect(err.message).toMatch(/circular|stack|exceeded/i); + } + }); + + it('should handle very long messages', async () => { + const logger = await import('../src/logger.js'); + const longMessage = 'a'.repeat(10000); + + expect(() => { + logger.info(longMessage); + }).not.toThrow(); + }); + + it('should handle special characters in messages', async () => { + const logger = await import('../src/logger.js'); + + expect(() => { + logger.info('Special chars: 🚀 \n\t\r\0'); + }).not.toThrow(); + }); + }); + + describe('winston integration', () => { + it('should have proper log level configuration', async () => { + const logger = await import('../src/logger.js'); + const level = logger.default.logger.level; + + // Level should be one of winston's standard levels + expect(['error', 'warn', 'info', 'debug', 'verbose', 'silly']).toContain(level); + }); + + it('should have transports configured', async () => { + const logger = await import('../src/logger.js'); + const transports = logger.default.logger.transports; + + expect(Array.isArray(transports)).toBe(true); + expect(transports.length).toBeGreaterThan(0); + }); + }); +}); \ No newline at end of file diff --git a/tests/ping.test.js b/tests/ping.test.js new file mode 100644 index 00000000..3159fee1 --- /dev/null +++ b/tests/ping.test.js @@ -0,0 +1,143 @@ +/** + * Tests for src/commands/ping.js + * Ping command for checking bot latency + */ + +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +describe('ping command', () => { + let pingCommand; + let mockInteraction; + + beforeEach(async () => { + pingCommand = await import('../src/commands/ping.js'); + + mockInteraction = { + reply: vi.fn().mockResolvedValue({ + resource: { + message: { + createdTimestamp: 1000, + }, + }, + }), + editReply: vi.fn().mockResolvedValue({}), + createdTimestamp: 900, + client: { + ws: { + ping: 42, + }, + }, + }; + }); + + describe('command structure', () => { + it('should export data property', () => { + expect(pingCommand.data).toBeDefined(); + expect(pingCommand.data.name).toBe('ping'); + }); + + it('should have correct command name', () => { + expect(pingCommand.data.name).toBe('ping'); + }); + + it('should have description', () => { + expect(pingCommand.data.description).toBeDefined(); + expect(pingCommand.data.description.length).toBeGreaterThan(0); + }); + + it('should export execute function', () => { + expect(pingCommand.execute).toBeDefined(); + expect(typeof pingCommand.execute).toBe('function'); + }); + }); + + describe('execute', () => { + it('should reply with initial message', async () => { + await pingCommand.execute(mockInteraction); + + expect(mockInteraction.reply).toHaveBeenCalledWith({ + content: 'Pinging...', + withResponse: true, + }); + }); + + it('should calculate latency', async () => { + await pingCommand.execute(mockInteraction); + + expect(mockInteraction.editReply).toHaveBeenCalled(); + const editedMessage = mockInteraction.editReply.mock.calls[0][0]; + expect(editedMessage).toContain('Pong'); + expect(editedMessage).toContain('ms'); + }); + + it('should include API latency', async () => { + await pingCommand.execute(mockInteraction); + + const editedMessage = mockInteraction.editReply.mock.calls[0][0]; + expect(editedMessage).toContain('API'); + expect(editedMessage).toContain('42ms'); + }); + + it('should include round-trip latency', async () => { + await pingCommand.execute(mockInteraction); + + const editedMessage = mockInteraction.editReply.mock.calls[0][0]; + expect(editedMessage).toContain('Latency'); + expect(editedMessage).toContain('100ms'); // 1000 - 900 = 100 + }); + + it('should handle different latencies', async () => { + mockInteraction.client.ws.ping = 123; + mockInteraction.createdTimestamp = 500; + mockInteraction.reply.mockResolvedValue({ + resource: { + message: { + createdTimestamp: 750, + }, + }, + }); + + await pingCommand.execute(mockInteraction); + + const editedMessage = mockInteraction.editReply.mock.calls[0][0]; + expect(editedMessage).toContain('250ms'); // 750 - 500 = 250 + expect(editedMessage).toContain('123ms'); + }); + + it('should handle negative latency (clock skew)', async () => { + mockInteraction.createdTimestamp = 2000; + mockInteraction.reply.mockResolvedValue({ + resource: { + message: { + createdTimestamp: 1500, + }, + }, + }); + + await pingCommand.execute(mockInteraction); + + expect(mockInteraction.editReply).toHaveBeenCalled(); + }); + + it('should include ping emoji', async () => { + await pingCommand.execute(mockInteraction); + + const editedMessage = mockInteraction.editReply.mock.calls[0][0]; + expect(editedMessage).toContain('🏓'); + }); + + it('should include signal emoji', async () => { + await pingCommand.execute(mockInteraction); + + const editedMessage = mockInteraction.editReply.mock.calls[0][0]; + expect(editedMessage).toContain('📡'); + }); + + it('should include heartbeat emoji', async () => { + await pingCommand.execute(mockInteraction); + + const editedMessage = mockInteraction.editReply.mock.calls[0][0]; + expect(editedMessage).toContain('💓'); + }); + }); +}); \ No newline at end of file diff --git a/tests/spam.test.js b/tests/spam.test.js new file mode 100644 index 00000000..1a19b25d --- /dev/null +++ b/tests/spam.test.js @@ -0,0 +1,262 @@ +/** + * Tests for src/modules/spam.js + * Spam detection and moderation + */ + +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import { isSpam, sendSpamAlert } from '../src/modules/spam.js'; + +describe('spam module', () => { + describe('isSpam', () => { + it('should detect free crypto spam', () => { + expect(isSpam('Get free crypto now!')).toBe(true); + expect(isSpam('FREE BITCOIN HERE')).toBe(true); + expect(isSpam('free btc claim now')).toBe(true); + expect(isSpam('Free ETH giveaway')).toBe(true); + expect(isSpam('Claim your free NFT')).toBe(true); + }); + + it('should detect airdrop scams', () => { + expect(isSpam('Airdrop! Claim your tokens')).toBe(true); + expect(isSpam('AIRDROP claim here')).toBe(true); + }); + + it('should detect Discord Nitro scams', () => { + expect(isSpam('Discord Nitro free')).toBe(true); + expect(isSpam('discord nitro free here')).toBe(true); + expect(isSpam('Nitro gift claim')).toBe(true); + }); + + it('should detect verification phishing', () => { + expect(isSpam('Click here to verify your account')).toBe(true); + expect(isSpam('CLICK TO VERIFY ACCOUNT NOW')).toBe(true); + }); + + it('should detect investment scams', () => { + expect(isSpam('Guaranteed profit investment')).toBe(true); + expect(isSpam('Invest now and double your money')).toBe(true); + }); + + it('should detect DM spam', () => { + expect(isSpam('DM me for free stuff')).toBe(true); + expect(isSpam('dm me for free rewards')).toBe(true); + }); + + it('should detect money-making scams', () => { + expect(isSpam('Make $5000 daily')).toBe(true); + expect(isSpam('make 10k+ weekly')).toBe(true); + expect(isSpam('MAKE $500 MONTHLY')).toBe(true); + }); + + it('should not flag legitimate messages', () => { + expect(isSpam('Hello everyone!')).toBe(false); + expect(isSpam('How do I set up my wallet?')).toBe(false); + expect(isSpam('I bought some Bitcoin today')).toBe(false); + expect(isSpam('What is an airdrop?')).toBe(false); + expect(isSpam('I have Discord Nitro')).toBe(false); + }); + + it('should be case-insensitive', () => { + expect(isSpam('FREE CRYPTO')).toBe(true); + expect(isSpam('free crypto')).toBe(true); + expect(isSpam('FrEe CrYpTo')).toBe(true); + }); + + it('should handle empty strings', () => { + expect(isSpam('')).toBe(false); + }); + + it('should handle whitespace variations', () => { + expect(isSpam('free crypto')).toBe(true); + expect(isSpam('free\ncrypto')).toBe(true); + expect(isSpam('free\tcrypto')).toBe(true); + }); + + it('should detect patterns with extra characters', () => { + expect(isSpam('f.r.e.e crypto!!!')).toBe(false); // Pattern broken by dots + expect(isSpam('FREE CRYPTO 🎉🎉🎉')).toBe(true); + }); + }); + + describe('sendSpamAlert', () => { + let mockMessage; + let mockClient; + let mockAlertChannel; + let mockConfig; + + beforeEach(() => { + mockAlertChannel = { + send: vi.fn().mockResolvedValue({}), + }; + + mockClient = { + channels: { + fetch: vi.fn().mockResolvedValue(mockAlertChannel), + }, + }; + + mockMessage = { + author: { + id: 'user123', + }, + channel: { + id: 'channel456', + }, + content: 'Spam message content', + url: 'https://discord.com/channels/123/456/789', + delete: vi.fn().mockResolvedValue(undefined), + }; + + mockConfig = { + moderation: { + alertChannelId: 'alert123', + autoDelete: false, + }, + }; + }); + + it('should send alert to configured channel', async () => { + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + expect(mockClient.channels.fetch).toHaveBeenCalledWith('alert123'); + expect(mockAlertChannel.send).toHaveBeenCalled(); + }); + + it('should include author in alert', async () => { + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + const callArgs = mockAlertChannel.send.mock.calls[0][0]; + const embed = callArgs.embeds[0]; + const authorField = embed.data.fields.find((f) => f.name === 'Author'); + + expect(authorField.value).toContain('user123'); + }); + + it('should include channel in alert', async () => { + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + const callArgs = mockAlertChannel.send.mock.calls[0][0]; + const embed = callArgs.embeds[0]; + const channelField = embed.data.fields.find((f) => f.name === 'Channel'); + + expect(channelField.value).toContain('channel456'); + }); + + it('should include message content in alert', async () => { + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + const callArgs = mockAlertChannel.send.mock.calls[0][0]; + const embed = callArgs.embeds[0]; + const contentField = embed.data.fields.find((f) => f.name === 'Content'); + + expect(contentField.value).toContain('Spam message content'); + }); + + it('should include message link in alert', async () => { + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + const callArgs = mockAlertChannel.send.mock.calls[0][0]; + const embed = callArgs.embeds[0]; + const linkField = embed.data.fields.find((f) => f.name === 'Link'); + + expect(linkField.value).toContain(mockMessage.url); + }); + + it('should truncate long content to 1000 chars', async () => { + const longContent = 'a'.repeat(2000); + mockMessage.content = longContent; + + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + const callArgs = mockAlertChannel.send.mock.calls[0][0]; + const embed = callArgs.embeds[0]; + const contentField = embed.data.fields.find((f) => f.name === 'Content'); + + expect(contentField.value.length).toBeLessThanOrEqual(1000); + }); + + it('should handle empty content', async () => { + mockMessage.content = ''; + + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + const callArgs = mockAlertChannel.send.mock.calls[0][0]; + const embed = callArgs.embeds[0]; + const contentField = embed.data.fields.find((f) => f.name === 'Content'); + + expect(contentField.value).toBe('*empty*'); + }); + + it('should auto-delete message when enabled', async () => { + mockConfig.moderation.autoDelete = true; + + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + expect(mockMessage.delete).toHaveBeenCalled(); + }); + + it('should not delete message when autoDelete is false', async () => { + mockConfig.moderation.autoDelete = false; + + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + expect(mockMessage.delete).not.toHaveBeenCalled(); + }); + + it('should handle channel fetch errors gracefully', async () => { + mockClient.channels.fetch.mockRejectedValueOnce(new Error('Channel not found')); + + await expect(sendSpamAlert(mockMessage, mockClient, mockConfig)).resolves.not.toThrow(); + expect(mockAlertChannel.send).not.toHaveBeenCalled(); + }); + + it('should handle channel returning null', async () => { + mockClient.channels.fetch.mockResolvedValueOnce(null); + + await expect(sendSpamAlert(mockMessage, mockClient, mockConfig)).resolves.not.toThrow(); + expect(mockAlertChannel.send).not.toHaveBeenCalled(); + }); + + it('should handle message delete errors gracefully', async () => { + mockConfig.moderation.autoDelete = true; + mockMessage.delete.mockRejectedValueOnce(new Error('Delete failed')); + + await expect(sendSpamAlert(mockMessage, mockClient, mockConfig)).resolves.not.toThrow(); + }); + + it('should do nothing when alertChannelId is not configured', async () => { + mockConfig.moderation.alertChannelId = null; + + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + expect(mockClient.channels.fetch).not.toHaveBeenCalled(); + }); + + it('should create embed with correct color', async () => { + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + const callArgs = mockAlertChannel.send.mock.calls[0][0]; + const embed = callArgs.embeds[0]; + + expect(embed.data.color).toBe(0xff6b6b); // Red color for spam + }); + + it('should include timestamp in embed', async () => { + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + const callArgs = mockAlertChannel.send.mock.calls[0][0]; + const embed = callArgs.embeds[0]; + + expect(embed.data.timestamp).toBeDefined(); + }); + + it('should have correct title', async () => { + await sendSpamAlert(mockMessage, mockClient, mockConfig); + + const callArgs = mockAlertChannel.send.mock.calls[0][0]; + const embed = callArgs.embeds[0]; + + expect(embed.data.title).toContain('Spam'); + }); + }); +}); \ No newline at end of file diff --git a/tests/status.test.js b/tests/status.test.js new file mode 100644 index 00000000..36620ba7 --- /dev/null +++ b/tests/status.test.js @@ -0,0 +1,301 @@ +/** + * Tests for src/commands/status.js + * Bot health status command + */ + +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import { PermissionFlagsBits } from 'discord.js'; + +// Mock the health monitor +vi.mock('../src/utils/health.js', () => ({ + HealthMonitor: { + getInstance: vi.fn(() => ({ + getStatus: vi.fn(() => ({ + uptimeFormatted: '2h 30m', + memory: { formatted: '150MB' }, + api: { status: 'ok' }, + lastAIRequest: Date.now() - 60000, + })), + getDetailedStatus: vi.fn(() => ({ + uptimeFormatted: '2h 30m', + memory: { + formatted: '150MB', + heapUsed: 120, + rss: 180, + external: 10, + arrayBuffers: 5, + }, + api: { status: 'ok' }, + lastAIRequest: Date.now() - 60000, + process: { + pid: 12345, + platform: 'linux', + nodeVersion: 'v20.0.0', + uptime: 9000, + }, + })), + })), + }, +})); + +describe('status command', () => { + let statusCommand; + let mockInteraction; + + beforeEach(async () => { + statusCommand = await import('../src/commands/status.js'); + + mockInteraction = { + options: { + getBoolean: vi.fn().mockReturnValue(false), + }, + memberPermissions: { + has: vi.fn().mockReturnValue(false), + }, + reply: vi.fn().mockResolvedValue({}), + followUp: vi.fn().mockResolvedValue({}), + replied: false, + deferred: false, + }; + }); + + describe('command structure', () => { + it('should export data property', () => { + expect(statusCommand.data).toBeDefined(); + expect(statusCommand.data.name).toBe('status'); + }); + + it('should have correct command name', () => { + expect(statusCommand.data.name).toBe('status'); + }); + + it('should have description', () => { + expect(statusCommand.data.description).toBeDefined(); + expect(statusCommand.data.description.length).toBeGreaterThan(0); + }); + + it('should export execute function', () => { + expect(statusCommand.execute).toBeDefined(); + expect(typeof statusCommand.execute).toBe('function'); + }); + + it('should have detailed boolean option', () => { + const options = statusCommand.data.options; + expect(Array.isArray(options)).toBe(true); + expect(options.length).toBeGreaterThan(0); + expect(options[0].name).toBe('detailed'); + expect(options[0].type).toBe(5); // Boolean type + }); + }); + + describe('execute - basic mode', () => { + it('should reply with status embed', async () => { + await statusCommand.execute(mockInteraction); + + expect(mockInteraction.reply).toHaveBeenCalled(); + const replyArgs = mockInteraction.reply.mock.calls[0][0]; + expect(replyArgs.embeds).toBeDefined(); + expect(replyArgs.embeds.length).toBe(1); + }); + + it('should include uptime in status', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const uptimeField = fields.find((f) => f.name.includes('Uptime')); + expect(uptimeField).toBeDefined(); + expect(uptimeField.value).toContain('2h 30m'); + }); + + it('should include memory usage', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const memoryField = fields.find((f) => f.name.includes('Memory')); + expect(memoryField).toBeDefined(); + expect(memoryField.value).toContain('150MB'); + }); + + it('should include API status', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const apiField = fields.find((f) => f.name.includes('API')); + expect(apiField).toBeDefined(); + expect(apiField.value).toContain('OK'); + }); + + it('should include last AI request', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const aiField = fields.find((f) => f.name.includes('AI Request')); + expect(aiField).toBeDefined(); + }); + + it('should show correct status emoji for ok', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const apiField = fields.find((f) => f.name.includes('API')); + expect(apiField.value).toContain('🟢'); + }); + + it('should include footer with hint', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + expect(embed.data.footer.text).toContain('detailed'); + }); + + it('should not be ephemeral by default', async () => { + await statusCommand.execute(mockInteraction); + + const replyArgs = mockInteraction.reply.mock.calls[0][0]; + expect(replyArgs.ephemeral).toBeUndefined(); + }); + }); + + describe('execute - detailed mode', () => { + beforeEach(() => { + mockInteraction.options.getBoolean.mockReturnValue(true); + mockInteraction.memberPermissions.has.mockReturnValue(true); + }); + + it('should show detailed diagnostics for admins', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + expect(embed.data.title).toContain('Detailed'); + }); + + it('should include process ID', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const pidField = fields.find((f) => f.name.includes('Process ID')); + expect(pidField).toBeDefined(); + expect(pidField.value).toContain('12345'); + }); + + it('should include platform', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const platformField = fields.find((f) => f.name.includes('Platform')); + expect(platformField).toBeDefined(); + expect(platformField.value).toContain('linux'); + }); + + it('should include Node version', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const nodeField = fields.find((f) => f.name.includes('Node')); + expect(nodeField).toBeDefined(); + expect(nodeField.value).toContain('v20'); + }); + + it('should include heap usage', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const heapField = fields.find((f) => f.name.includes('Heap')); + expect(heapField).toBeDefined(); + expect(heapField.value).toContain('120'); + }); + + it('should include RSS memory', async () => { + await statusCommand.execute(mockInteraction); + + const embed = mockInteraction.reply.mock.calls[0][0].embeds[0]; + const fields = embed.data.fields; + const rssField = fields.find((f) => f.name.includes('RSS')); + expect(rssField).toBeDefined(); + expect(rssField.value).toContain('180'); + }); + + it('should be ephemeral for detailed mode', async () => { + await statusCommand.execute(mockInteraction); + + const replyArgs = mockInteraction.reply.mock.calls[0][0]; + expect(replyArgs.ephemeral).toBe(true); + }); + + it('should deny non-admins access to detailed mode', async () => { + mockInteraction.memberPermissions.has.mockReturnValue(false); + + await statusCommand.execute(mockInteraction); + + const replyArgs = mockInteraction.reply.mock.calls[0][0]; + expect(replyArgs.content).toContain('administrators'); + expect(replyArgs.ephemeral).toBe(true); + }); + + it('should check for Administrator permission', async () => { + await statusCommand.execute(mockInteraction); + + expect(mockInteraction.memberPermissions.has).toHaveBeenCalledWith( + PermissionFlagsBits.Administrator + ); + }); + }); + + describe('error handling', () => { + it('should handle errors gracefully', async () => { + const { HealthMonitor } = await import('../src/utils/health.js'); + HealthMonitor.getInstance.mockImplementation(() => { + throw new Error('Health monitor error'); + }); + + await expect(statusCommand.execute(mockInteraction)).resolves.not.toThrow(); + }); + + it('should send error message on failure', async () => { + const { HealthMonitor } = await import('../src/utils/health.js'); + HealthMonitor.getInstance.mockImplementation(() => { + throw new Error('Health monitor error'); + }); + + await statusCommand.execute(mockInteraction); + + expect(mockInteraction.reply).toHaveBeenCalled(); + const replyArgs = mockInteraction.reply.mock.calls[0][0]; + expect(replyArgs.content).toContain("couldn't retrieve"); + }); + + it('should use followUp if already replied', async () => { + mockInteraction.replied = true; + const { HealthMonitor } = await import('../src/utils/health.js'); + HealthMonitor.getInstance.mockImplementation(() => { + throw new Error('Health monitor error'); + }); + + await statusCommand.execute(mockInteraction); + + expect(mockInteraction.followUp).toHaveBeenCalled(); + }); + + it('should handle followUp failures silently', async () => { + mockInteraction.replied = true; + mockInteraction.followUp.mockRejectedValue(new Error('Follow up failed')); + const { HealthMonitor } = await import('../src/utils/health.js'); + HealthMonitor.getInstance.mockImplementation(() => { + throw new Error('Health monitor error'); + }); + + await expect(statusCommand.execute(mockInteraction)).resolves.not.toThrow(); + }); + }); + + // Emoji and time formatting already tested in basic mode tests above +}); \ No newline at end of file diff --git a/tests/welcome.test.js b/tests/welcome.test.js new file mode 100644 index 00000000..2bbfe554 --- /dev/null +++ b/tests/welcome.test.js @@ -0,0 +1,413 @@ +/** + * Tests for src/modules/welcome.js + * Dynamic welcome messages for new members + */ + +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import { + renderWelcomeMessage, + recordCommunityActivity, + sendWelcomeMessage, +} from '../src/modules/welcome.js'; + +describe('welcome module', () => { + describe('renderWelcomeMessage', () => { + it('should replace {user} with mention', () => { + const template = 'Welcome {user}!'; + const member = { id: '123', username: 'John' }; + const guild = { name: 'Test Server', memberCount: 50 }; + + const result = renderWelcomeMessage(template, member, guild); + expect(result).toBe('Welcome <@123>!'); + }); + + it('should replace {username} with username', () => { + const template = 'Hello {username}!'; + const member = { id: '123', username: 'John' }; + const guild = { name: 'Test Server', memberCount: 50 }; + + const result = renderWelcomeMessage(template, member, guild); + expect(result).toBe('Hello John!'); + }); + + it('should replace {server} with server name', () => { + const template = 'Welcome to {server}!'; + const member = { id: '123', username: 'John' }; + const guild = { name: 'Test Server', memberCount: 50 }; + + const result = renderWelcomeMessage(template, member, guild); + expect(result).toBe('Welcome to Test Server!'); + }); + + it('should replace {memberCount} with count', () => { + const template = 'You are member #{memberCount}'; + const member = { id: '123', username: 'John' }; + const guild = { name: 'Test Server', memberCount: 100 }; + + const result = renderWelcomeMessage(template, member, guild); + expect(result).toBe('You are member #100'); + }); + + it('should replace multiple placeholders', () => { + const template = 'Welcome {user} to {server}! You are member #{memberCount}. Hello {username}!'; + const member = { id: '123', username: 'John' }; + const guild = { name: 'Test Server', memberCount: 50 }; + + const result = renderWelcomeMessage(template, member, guild); + expect(result).toContain('<@123>'); + expect(result).toContain('Test Server'); + expect(result).toContain('50'); + expect(result).toContain('John'); + }); + + it('should replace all occurrences of placeholders', () => { + const template = 'Hi {user}! Yes, {user}, you!'; + const member = { id: '123', username: 'John' }; + const guild = { name: 'Test Server', memberCount: 50 }; + + const result = renderWelcomeMessage(template, member, guild); + expect(result).toBe('Hi <@123>! Yes, <@123>, you!'); + }); + + it('should handle missing username gracefully', () => { + const template = 'Welcome {username}!'; + const member = { id: '123' }; // No username + const guild = { name: 'Test Server', memberCount: 50 }; + + const result = renderWelcomeMessage(template, member, guild); + expect(result).toBe('Welcome Unknown!'); + }); + + it('should handle template with no placeholders', () => { + const template = 'Welcome!'; + const member = { id: '123', username: 'John' }; + const guild = { name: 'Test Server', memberCount: 50 }; + + const result = renderWelcomeMessage(template, member, guild); + expect(result).toBe('Welcome!'); + }); + }); + + describe('recordCommunityActivity', () => { + let mockMessage; + let mockConfig; + + beforeEach(() => { + mockMessage = { + guild: { id: 'guild123' }, + channel: { + id: 'channel123', + isTextBased: () => true, + }, + author: { + bot: false, + }, + }; + + mockConfig = { + welcome: { + dynamic: { + enabled: true, + excludeChannels: [], + activityWindowMinutes: 45, + }, + }, + }; + }); + + it('should record message activity', () => { + expect(() => { + recordCommunityActivity(mockMessage, mockConfig); + }).not.toThrow(); + }); + + it('should ignore messages from bots', () => { + mockMessage.author.bot = true; + expect(() => { + recordCommunityActivity(mockMessage, mockConfig); + }).not.toThrow(); + }); + + it('should ignore messages without guild', () => { + mockMessage.guild = null; + expect(() => { + recordCommunityActivity(mockMessage, mockConfig); + }).not.toThrow(); + }); + + it('should ignore messages from excluded channels', () => { + mockConfig.welcome.dynamic.excludeChannels = ['channel123']; + expect(() => { + recordCommunityActivity(mockMessage, mockConfig); + }).not.toThrow(); + }); + + it('should ignore non-text channels', () => { + mockMessage.channel.isTextBased = () => false; + expect(() => { + recordCommunityActivity(mockMessage, mockConfig); + }).not.toThrow(); + }); + + it('should handle multiple messages from same channel', () => { + expect(() => { + recordCommunityActivity(mockMessage, mockConfig); + recordCommunityActivity(mockMessage, mockConfig); + recordCommunityActivity(mockMessage, mockConfig); + }).not.toThrow(); + }); + + it('should handle messages from different channels', () => { + expect(() => { + recordCommunityActivity(mockMessage, mockConfig); + mockMessage.channel.id = 'channel456'; + recordCommunityActivity(mockMessage, mockConfig); + }).not.toThrow(); + }); + + it('should handle missing config gracefully', () => { + expect(() => { + recordCommunityActivity(mockMessage, {}); + }).not.toThrow(); + }); + }); + + describe('sendWelcomeMessage', () => { + let mockMember; + let mockClient; + let mockChannel; + let mockConfig; + + beforeEach(() => { + mockChannel = { + send: vi.fn().mockResolvedValue({}), + }; + + mockClient = { + channels: { + fetch: vi.fn().mockResolvedValue(mockChannel), + }, + }; + + mockMember = { + id: '123', + user: { + username: 'John', + tag: 'John#1234', + }, + guild: { + name: 'Test Server', + memberCount: 50, + id: 'guild123', + channels: { + cache: new Map(), + }, + }, + }; + + mockConfig = { + welcome: { + enabled: true, + channelId: 'welcome123', + message: 'Welcome {user} to {server}!', + dynamic: { + enabled: false, + }, + }, + }; + }); + + it('should send welcome message to configured channel', async () => { + await sendWelcomeMessage(mockMember, mockClient, mockConfig); + + expect(mockClient.channels.fetch).toHaveBeenCalledWith('welcome123'); + expect(mockChannel.send).toHaveBeenCalled(); + }); + + it('should use template message when dynamic is disabled', async () => { + await sendWelcomeMessage(mockMember, mockClient, mockConfig); + + const message = mockChannel.send.mock.calls[0][0]; + expect(message).toContain('<@123>'); + expect(message).toContain('Test Server'); + }); + + it('should attempt dynamic message generation when enabled', async () => { + mockConfig.welcome.dynamic.enabled = true; + mockConfig.welcome.dynamic.timezone = 'America/New_York'; + + // Dynamic welcome may fail with incomplete mocks, but should not crash + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + }); + + it('should not send message when disabled', async () => { + mockConfig.welcome.enabled = false; + + await sendWelcomeMessage(mockMember, mockClient, mockConfig); + + expect(mockClient.channels.fetch).not.toHaveBeenCalled(); + expect(mockChannel.send).not.toHaveBeenCalled(); + }); + + it('should not send message when channelId missing', async () => { + mockConfig.welcome.channelId = null; + + await sendWelcomeMessage(mockMember, mockClient, mockConfig); + + expect(mockClient.channels.fetch).not.toHaveBeenCalled(); + expect(mockChannel.send).not.toHaveBeenCalled(); + }); + + it('should handle channel fetch errors gracefully', async () => { + mockClient.channels.fetch.mockRejectedValueOnce(new Error('Channel not found')); + + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + expect(mockChannel.send).not.toHaveBeenCalled(); + }); + + it('should handle null channel', async () => { + mockClient.channels.fetch.mockResolvedValueOnce(null); + + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + expect(mockChannel.send).not.toHaveBeenCalled(); + }); + + it('should handle send errors gracefully', async () => { + mockChannel.send.mockRejectedValueOnce(new Error('Send failed')); + + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + }); + + it('should use default message if not configured', async () => { + mockConfig.welcome.message = undefined; + + await sendWelcomeMessage(mockMember, mockClient, mockConfig); + + const message = mockChannel.send.mock.calls[0][0]; + expect(message).toContain('<@123>'); + }); + + it('should handle milestone member counts without crashing', async () => { + mockConfig.welcome.dynamic.enabled = true; + mockMember.guild.memberCount = 100; // Notable milestone + + // May fail with mocks but should not crash + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + }); + + it('should include member ID in message', async () => { + await sendWelcomeMessage(mockMember, mockClient, mockConfig); + + const message = mockChannel.send.mock.calls[0][0]; + expect(message).toContain('123'); + }); + + it('should include server name in message', async () => { + await sendWelcomeMessage(mockMember, mockClient, mockConfig); + + const message = mockChannel.send.mock.calls[0][0]; + expect(message).toContain('Test Server'); + }); + }); + + describe('dynamic welcome features', () => { + let mockMember; + let mockClient; + let mockChannel; + let mockConfig; + + beforeEach(() => { + mockChannel = { + send: vi.fn().mockResolvedValue({}), + }; + + mockClient = { + channels: { + fetch: vi.fn().mockResolvedValue(mockChannel), + }, + }; + + // Create a proper Map mock with filter method for dynamic welcome + const channelsMap = new Map([ + ['channel1', { id: 'channel1', isVoiceBased: () => false }], + ['channel2', { id: 'channel2', isVoiceBased: () => true, members: new Map() }], + ]); + // Add filter method and other Collection methods to match Discord.js Collection API + channelsMap.filter = function(fn) { + const filtered = new Map(); + filtered.size = 0; + for (const [key, value] of this.entries()) { + if (fn(value)) { + filtered.set(key, value); + filtered.size++; + } + } + // Add Collection methods to filtered result + filtered.filter = this.filter.bind(filtered); + filtered.values = () => Array.from(filtered.values()); + return filtered; + }; + + mockMember = { + id: '123', + user: { + username: 'John', + tag: 'John#1234', + }, + guild: { + name: 'Test Server', + memberCount: 50, + id: 'guild123', + channels: { + cache: channelsMap, + }, + }, + }; + + mockConfig = { + welcome: { + enabled: true, + channelId: 'welcome123', + dynamic: { + enabled: true, + timezone: 'America/New_York', + highlightChannels: [], + excludeChannels: [], + activityWindowMinutes: 45, + milestoneInterval: 25, + }, + }, + }; + }); + + it('should attempt time-appropriate greeting without crashing', async () => { + // Dynamic welcome may fail with incomplete mocks + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + }); + + it('should handle different timezones gracefully', async () => { + mockConfig.welcome.dynamic.timezone = 'Europe/London'; + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + }); + + it('should handle invalid timezone gracefully', async () => { + mockConfig.welcome.dynamic.timezone = 'Invalid/Timezone'; + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + }); + + it('should handle member count without crashing', async () => { + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + }); + + it('should handle milestone counts without crashing', async () => { + mockMember.guild.memberCount = 100; + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + }); + + it('should handle interval milestones without crashing', async () => { + mockConfig.welcome.dynamic.milestoneInterval = 25; + mockMember.guild.memberCount = 75; // Multiple of 25 + await expect(sendWelcomeMessage(mockMember, mockClient, mockConfig)).resolves.not.toThrow(); + }); + }); +}); \ No newline at end of file