@monoes/monomindcli 1.10.12 → 1.10.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/graphify-freshen.cjs +20 -3
- package/.claude/helpers/hook-handler.cjs +48 -36
- package/.claude/helpers/statusline.cjs +14 -8
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +8 -6
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -81,11 +81,28 @@ try {
|
|
|
81
81
|
// Write lock file; the build process removes it on completion
|
|
82
82
|
try { fs.writeFileSync(lockPath, String(process.pid)); } catch { /* non-fatal */ }
|
|
83
83
|
|
|
84
|
-
// Spawn a detached node process to run buildAsync from @monoes/monograph (ESM)
|
|
84
|
+
// Spawn a detached node process to run buildAsync from @monoes/monograph (ESM).
|
|
85
|
+
// After the build, VACUUM the DB if it has >50% bloat (reclaim space from
|
|
86
|
+
// delete/insert churn; opens are ~5x faster on a tight DB).
|
|
87
|
+
const dbPathStr = JSON.stringify(path.join(projectDir, '.monomind', 'monograph.db'));
|
|
85
88
|
const script = `
|
|
86
89
|
import { buildAsync } from ${JSON.stringify(pathToFileURL(entryPoint).href)};
|
|
87
|
-
import { unlinkSync } from 'fs';
|
|
88
|
-
|
|
90
|
+
import { unlinkSync, statSync } from 'fs';
|
|
91
|
+
import { execSync } from 'child_process';
|
|
92
|
+
try {
|
|
93
|
+
await buildAsync(${JSON.stringify(projectDir)});
|
|
94
|
+
// Vacuum if bloat ratio is high — keeps openDb fast over time.
|
|
95
|
+
try {
|
|
96
|
+
const dbPath = ${dbPathStr};
|
|
97
|
+
const fileMB = statSync(dbPath).size / 1024 / 1024;
|
|
98
|
+
const liveMB = parseInt(
|
|
99
|
+
execSync('sqlite3 "' + dbPath + '" "SELECT SUM(pgsize)/1024/1024 FROM dbstat;"',
|
|
100
|
+
{ encoding: 'utf-8' }).trim(), 10);
|
|
101
|
+
if (fileMB > 100 && liveMB / fileMB < 0.5) {
|
|
102
|
+
execSync('sqlite3 "' + dbPath + '" "VACUUM;"', { timeout: 120000 });
|
|
103
|
+
}
|
|
104
|
+
} catch (_) {}
|
|
105
|
+
} finally {
|
|
89
106
|
try { unlinkSync(${JSON.stringify(lockPath)}); } catch {}
|
|
90
107
|
}`;
|
|
91
108
|
const child = spawn(process.execPath, ['--input-type=module', '--eval', script], {
|
|
@@ -29,14 +29,20 @@ function _requireMonograph() {
|
|
|
29
29
|
// Used by route (pre-resolve), pre-search (Grep/Glob redirect), and post-read
|
|
30
30
|
// (neighbor footer). All calls are best-effort; failures are silent.
|
|
31
31
|
|
|
32
|
+
// Memoized at module scope — opening a multi-GB monograph.db can take 7-10s,
|
|
33
|
+
// and we call this 3+ times per route hook. Cache for the lifetime of this
|
|
34
|
+
// hook process. Callers should NOT close the returned handle.
|
|
35
|
+
var _cachedMonographDb = undefined;
|
|
32
36
|
function _openMonographDb() {
|
|
37
|
+
if (_cachedMonographDb !== undefined) return _cachedMonographDb;
|
|
33
38
|
try {
|
|
34
39
|
var dbPath = path.join(CWD, '.monomind', 'monograph.db');
|
|
35
|
-
if (!fs.existsSync(dbPath)) return null;
|
|
40
|
+
if (!fs.existsSync(dbPath)) { _cachedMonographDb = null; return null; }
|
|
36
41
|
var mod = _requireMonograph();
|
|
37
|
-
if (!mod || !mod.openDb) return null;
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
if (!mod || !mod.openDb) { _cachedMonographDb = null; return null; }
|
|
43
|
+
_cachedMonographDb = mod.openDb(dbPath);
|
|
44
|
+
return _cachedMonographDb;
|
|
45
|
+
} catch (e) { _cachedMonographDb = null; return null; }
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
function getMonographSuggestions(taskText, limit) {
|
|
@@ -91,7 +97,7 @@ function getMonographSuggestions(taskText, limit) {
|
|
|
91
97
|
}
|
|
92
98
|
return rows || [];
|
|
93
99
|
} catch (e) { return []; }
|
|
94
|
-
finally {
|
|
100
|
+
finally { /* db is shared/cached; do not close */ }
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
function getMonographNeighbors(filePath) {
|
|
@@ -119,7 +125,7 @@ function getMonographNeighbors(filePath) {
|
|
|
119
125
|
|
|
120
126
|
return { imports: imports, importedBy: importedBy };
|
|
121
127
|
} catch (e) { return null; }
|
|
122
|
-
finally {
|
|
128
|
+
finally { /* db is shared/cached; do not close */ }
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
// Rough per-event token + USD cost estimates. Tuned to Sonnet input pricing
|
|
@@ -184,7 +190,7 @@ function _injectCompactGraphMap() {
|
|
|
184
190
|
}
|
|
185
191
|
console.log(' Use mcp__monomind__monograph_suggest first when navigating.');
|
|
186
192
|
}
|
|
187
|
-
} finally {
|
|
193
|
+
} finally { /* db is shared/cached; do not close */ }
|
|
188
194
|
} catch (e) {}
|
|
189
195
|
}
|
|
190
196
|
|
|
@@ -264,7 +270,7 @@ function _findAffectedTests(filePath) {
|
|
|
264
270
|
).all(filePath, rel);
|
|
265
271
|
return rows.map(function(r) { return r.file_path; });
|
|
266
272
|
} catch (e) { return []; }
|
|
267
|
-
finally {
|
|
273
|
+
finally { /* db is shared/cached; do not close */ }
|
|
268
274
|
}
|
|
269
275
|
|
|
270
276
|
// ── Hook latency tracking ─────────────────────────────────────────────────────
|
|
@@ -629,9 +635,8 @@ function _autoIndexKnowledge(knowledgeDir) {
|
|
|
629
635
|
|
|
630
636
|
if (fs.existsSync(mgDbPath2)) {
|
|
631
637
|
try {
|
|
632
|
-
var
|
|
633
|
-
if (
|
|
634
|
-
var sumDb = mgMod2.openDb(mgDbPath2);
|
|
638
|
+
var sumDb = _openMonographDb();
|
|
639
|
+
if (sumDb) {
|
|
635
640
|
try {
|
|
636
641
|
var nodeC = sumDb.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
637
642
|
var edgeC = sumDb.prepare('SELECT COUNT(*) AS c FROM edges').get().c;
|
|
@@ -661,9 +666,7 @@ function _autoIndexKnowledge(knowledgeDir) {
|
|
|
661
666
|
' mcp__monomind__monograph_impact({ name: "<file>" }) — upstream + downstream blast radius',
|
|
662
667
|
].join('\n');
|
|
663
668
|
summaryMeta = { label: 'monograph-graph-summary', source: 'monograph.db', nodes: nodeC, edges: edgeC };
|
|
664
|
-
}
|
|
665
|
-
try { sumDb.close(); } catch (_) {}
|
|
666
|
-
}
|
|
669
|
+
} catch (e) { /* keep summaryText if partial */ }
|
|
667
670
|
}
|
|
668
671
|
} catch (e) { /* fall through to legacy */ }
|
|
669
672
|
}
|
|
@@ -830,11 +833,22 @@ const handlers = {
|
|
|
830
833
|
var output = [];
|
|
831
834
|
output.push('[INFO] Routing task: ' + (prompt.substring(0, 80) || '(no prompt)'));
|
|
832
835
|
output.push('');
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
836
|
+
// Suppress the agent recommendation panel for low-confidence routes on
|
|
837
|
+
// short prompts — the recommendation is almost always wrong (e.g.
|
|
838
|
+
// "what else can we do" → marketing → China E-Commerce). Saves ~150
|
|
839
|
+
// tokens per prompt. Skill matches and specific agents still render
|
|
840
|
+
// below when confidence is decent.
|
|
841
|
+
var conf = result.confidence != null ? result.confidence : 0;
|
|
842
|
+
var promptShort = (prompt || '').trim().length < 60;
|
|
843
|
+
var lowConf = conf < 0.70;
|
|
844
|
+
var suppressPanel = lowConf && promptShort;
|
|
845
|
+
if (!suppressPanel) {
|
|
846
|
+
output.push('+------------- monomind | Primary Recommendation --------------+');
|
|
847
|
+
output.push('| Agent: ' + (result.agent || 'unknown').substring(0, 54).padEnd(54) + '|');
|
|
848
|
+
output.push('| Confidence: ' + ((result.confidence != null ? (result.confidence * 100).toFixed(1) : '?') + '%').padEnd(49) + '|');
|
|
849
|
+
output.push('| Reason: ' + (result.reason || '').substring(0, 53).padEnd(53) + '|');
|
|
850
|
+
output.push('+--------------------------------------------------------------+');
|
|
851
|
+
}
|
|
838
852
|
|
|
839
853
|
// ── Persist routing result for statusline display ─────────────
|
|
840
854
|
try {
|
|
@@ -913,8 +927,9 @@ const handlers = {
|
|
|
913
927
|
}
|
|
914
928
|
|
|
915
929
|
// ── Specific agent panel ──────────────────────────────────────────────────
|
|
930
|
+
// Skip entirely on suppressed (low-confidence + short) prompts.
|
|
916
931
|
var specificAgents = result.specificAgents || [];
|
|
917
|
-
if (specificAgents.length > 0) {
|
|
932
|
+
if (specificAgents.length > 0 && !suppressPanel) {
|
|
918
933
|
output.push('');
|
|
919
934
|
var saHdr = '------- Specific Agents (' + specificAgents.length + ' available) ';
|
|
920
935
|
output.push('+' + saHdr + '-'.repeat(Math.max(1, 62 - saHdr.length)) + '+');
|
|
@@ -934,7 +949,7 @@ const handlers = {
|
|
|
934
949
|
// ── Specialist agents (non-dev domain) — only shown when specificAgents panel wasn't shown ──
|
|
935
950
|
var extras = result.extrasMatches || [];
|
|
936
951
|
var specificAgentsShown = (result.specificAgents || []).length > 0;
|
|
937
|
-
if (extras.length > 0 && !specificAgentsShown) {
|
|
952
|
+
if (extras.length > 0 && !specificAgentsShown && !suppressPanel) {
|
|
938
953
|
output.push('');
|
|
939
954
|
var spHdr = '------- Specialist Agents (' + extras.length + ' matched) ';
|
|
940
955
|
output.push('+' + spHdr + '-'.repeat(Math.max(1, 62 - spHdr.length)) + '+');
|
|
@@ -1007,14 +1022,9 @@ const handlers = {
|
|
|
1007
1022
|
var nodeCount = 0;
|
|
1008
1023
|
if (fs.existsSync(monographDb)) {
|
|
1009
1024
|
try {
|
|
1010
|
-
var
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1013
|
-
try {
|
|
1014
|
-
nodeCount = hintDb.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
1015
|
-
} finally {
|
|
1016
|
-
try { hintDb.close(); } catch (_) {}
|
|
1017
|
-
}
|
|
1025
|
+
var hintDb = _openMonographDb();
|
|
1026
|
+
if (hintDb) {
|
|
1027
|
+
nodeCount = hintDb.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
1018
1028
|
}
|
|
1019
1029
|
} catch (e) { /* ignore — fall back to legacy */ }
|
|
1020
1030
|
}
|
|
@@ -1161,6 +1171,7 @@ const handlers = {
|
|
|
1161
1171
|
if (clean.length >= 3) {
|
|
1162
1172
|
var hits = getMonographSuggestions(clean, 5);
|
|
1163
1173
|
if (hits.length > 0) {
|
|
1174
|
+
_recordGraphTelemetry('graph_assist_search');
|
|
1164
1175
|
console.log('[MONOGRAPH_HIT] Graph has ' + hits.length + ' file(s) for "' + clean.slice(0, 40) + '" — consider monograph_query instead of shell grep:');
|
|
1165
1176
|
for (var j = 0; j < hits.length; j++) {
|
|
1166
1177
|
var h = hits[j];
|
|
@@ -1185,7 +1196,6 @@ const handlers = {
|
|
|
1185
1196
|
var clean = pattern.replace(/[\\^$.*+?()\[\]{}|]/g, ' ').trim();
|
|
1186
1197
|
if (clean.length < 3) return;
|
|
1187
1198
|
|
|
1188
|
-
// Loop drift detection
|
|
1189
1199
|
var sig = (toolName || 'Search') + ':' + clean.slice(0, 60);
|
|
1190
1200
|
var count = _recordToolCall(sig);
|
|
1191
1201
|
if (count >= 3) {
|
|
@@ -1193,6 +1203,9 @@ const handlers = {
|
|
|
1193
1203
|
}
|
|
1194
1204
|
var suggestions = getMonographSuggestions(clean, 5);
|
|
1195
1205
|
if (suggestions.length === 0) return;
|
|
1206
|
+
// Successful intercept — count as a "graph assist" so the ratio reflects
|
|
1207
|
+
// server-side wins, not just LLM-initiated MCP calls.
|
|
1208
|
+
_recordGraphTelemetry('graph_assist_search');
|
|
1196
1209
|
console.log('[MONOGRAPH_HIT] Graph already knows ' + suggestions.length + ' file(s) matching "' + clean.slice(0, 40) + '":');
|
|
1197
1210
|
for (var i = 0; i < suggestions.length; i++) {
|
|
1198
1211
|
var s = suggestions[i];
|
|
@@ -1214,6 +1227,7 @@ const handlers = {
|
|
|
1214
1227
|
if (n.importedBy.length > 0) parts.push('imported-by: ' + n.importedBy.slice(0, 4).join(', '));
|
|
1215
1228
|
if (n.imports.length > 0) parts.push('imports: ' + n.imports.slice(0, 4).join(', '));
|
|
1216
1229
|
if (parts.length === 0) return;
|
|
1230
|
+
_recordGraphTelemetry('graph_assist_neighbors');
|
|
1217
1231
|
console.log('[MONOGRAPH_NEIGHBORS] ' + parts.join(' · '));
|
|
1218
1232
|
} catch (e) { /* non-fatal */ }
|
|
1219
1233
|
},
|
|
@@ -1447,10 +1461,8 @@ const handlers = {
|
|
|
1447
1461
|
try {
|
|
1448
1462
|
var mgDbPath = path.join(CWD, '.monomind', 'monograph.db');
|
|
1449
1463
|
if (fs.existsSync(mgDbPath)) {
|
|
1450
|
-
var
|
|
1451
|
-
|
|
1452
|
-
if (mgMod && mgMod.openDb) {
|
|
1453
|
-
var mgDb = mgMod.openDb(mgDbPath);
|
|
1464
|
+
var mgDb = _openMonographDb();
|
|
1465
|
+
if (mgDb) {
|
|
1454
1466
|
try {
|
|
1455
1467
|
var mgNodeCount = mgDb.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
1456
1468
|
var mgEdgeCount = mgDb.prepare('SELECT COUNT(*) AS c FROM edges').get().c;
|
|
@@ -1489,7 +1501,7 @@ const handlers = {
|
|
|
1489
1501
|
fs.writeFileSync(mgChunksFile, mgExisting.join('\n') + '\n');
|
|
1490
1502
|
} catch(e) {}
|
|
1491
1503
|
}
|
|
1492
|
-
}
|
|
1504
|
+
} catch(e) { /* non-fatal */ }
|
|
1493
1505
|
}
|
|
1494
1506
|
}
|
|
1495
1507
|
} catch(e) { /* non-fatal */ }
|
|
@@ -2222,7 +2234,7 @@ const handlers = {
|
|
|
2222
2234
|
} catch (_) {}
|
|
2223
2235
|
console.log(' Use mcp__monomind__monograph_suggest / monograph_query in this subagent before grepping.');
|
|
2224
2236
|
}
|
|
2225
|
-
}
|
|
2237
|
+
} catch (e) { /* non-fatal */ }
|
|
2226
2238
|
}
|
|
2227
2239
|
} catch (e) { /* non-fatal */ }
|
|
2228
2240
|
|
|
@@ -720,21 +720,27 @@ function getHookLatency() {
|
|
|
720
720
|
} catch { return null; }
|
|
721
721
|
}
|
|
722
722
|
|
|
723
|
-
// Graph usage telemetry —
|
|
723
|
+
// Graph usage telemetry — counts ALL graph wins (MCP calls + silent assists)
|
|
724
|
+
// vs greps that got no graph help. "graph %" reflects how often the graph
|
|
725
|
+
// actually touched the LLM's flow, not just how often the LLM invoked MCP.
|
|
724
726
|
function getGraphUsage() {
|
|
725
727
|
const usagePath = path.join(CWD, '.monomind', 'metrics', 'graph-usage.json');
|
|
726
728
|
try {
|
|
727
729
|
if (!fs.existsSync(usagePath)) return null;
|
|
728
730
|
const d = JSON.parse(fs.readFileSync(usagePath, 'utf-8'));
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
731
|
+
const graphWins =
|
|
732
|
+
(d.monograph_call || 0) // LLM-initiated MCP monograph tool call
|
|
733
|
+
+ (d.preresolve_hit || 0) // route hook injected pre-resolved files
|
|
734
|
+
+ (d.graph_assist_search || 0) // pre-search / pre-bash injected hits
|
|
735
|
+
+ (d.graph_assist_neighbors || 0); // post-read neighbor footer
|
|
736
|
+
const searches = (d.grep_call || 0) + (d.glob_call || 0)
|
|
737
|
+
+ (d.bash_grep_call || 0) + (d.bash_find_call || 0);
|
|
738
|
+
const total = graphWins + searches;
|
|
733
739
|
if (total === 0) return null;
|
|
734
740
|
return {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
pct: Math.round((
|
|
741
|
+
graphWins,
|
|
742
|
+
searches,
|
|
743
|
+
pct: Math.round((graphWins / total) * 100),
|
|
738
744
|
dollarsSaved: d.dollars_saved || 0,
|
|
739
745
|
};
|
|
740
746
|
} catch { return null; }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusline-generator.d.ts","sourceRoot":"","sources":["../../../src/init/statusline-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"statusline-generator.d.ts","sourceRoot":"","sources":["../../../src/init/statusline-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CA8qCrE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CA8BnE"}
|
|
@@ -849,18 +849,20 @@ function getHookLatency() {
|
|
|
849
849
|
} catch { return null; }
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
-
// Graph usage telemetry —
|
|
852
|
+
// Graph usage telemetry — counts ALL graph wins (MCP calls + silent assists)
|
|
853
|
+
// vs greps that got no graph help.
|
|
853
854
|
function getGraphUsage() {
|
|
854
855
|
const usagePath = path.join(CWD, '.monomind', 'metrics', 'graph-usage.json');
|
|
855
856
|
try {
|
|
856
857
|
if (!fs.existsSync(usagePath)) return null;
|
|
857
858
|
const d = JSON.parse(fs.readFileSync(usagePath, 'utf-8'));
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
859
|
+
const graphWins = (d.monograph_call || 0) + (d.preresolve_hit || 0)
|
|
860
|
+
+ (d.graph_assist_search || 0) + (d.graph_assist_neighbors || 0);
|
|
861
|
+
const searches = (d.grep_call || 0) + (d.glob_call || 0)
|
|
862
|
+
+ (d.bash_grep_call || 0) + (d.bash_find_call || 0);
|
|
863
|
+
const total = graphWins + searches;
|
|
862
864
|
if (total === 0) return null;
|
|
863
|
-
return {
|
|
865
|
+
return { graphWins: graphWins, searches: searches, pct: Math.round((graphWins / total) * 100), dollarsSaved: d.dollars_saved || 0 };
|
|
864
866
|
} catch { return null; }
|
|
865
867
|
}
|
|
866
868
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusline-generator.js","sourceRoot":"","sources":["../../../src/init/statusline-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAoB;IAC3D,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;IAC5C,OAAO;;;;;;;;;;;;;;;;;;;;;;;eAuBM,SAAS
|
|
1
|
+
{"version":3,"file":"statusline-generator.js","sourceRoot":"","sources":["../../../src/init/statusline-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAoB;IAC3D,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;IAC5C,OAAO;;;;;;;;;;;;;;;;;;;;;;;eAuBM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAopCvB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAoB;IACzD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QAChC,OAAO,sCAAsC,CAAC;IAChD,CAAC;IAED,OAAO;;;;;;;;;;;;;;;;;;;;;;;;CAwBR,CAAC;AACF,CAAC"}
|