@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.
@@ -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
- try { await buildAsync(${JSON.stringify(projectDir)}); } finally {
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
- return mod.openDb(dbPath);
39
- } catch (e) { return null; }
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 { try { db.close(); } catch (_) {} }
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 { try { db.close(); } catch (_) {} }
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 { try { db.close(); } catch (_) {} }
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 { try { db.close(); } catch (_) {} }
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 mgMod2 = _requireMonograph();
633
- if (mgMod2 && mgMod2.openDb) {
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
- } finally {
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
- output.push('+------------- monomind | Primary Recommendation --------------+');
834
- output.push('| Agent: ' + (result.agent || 'unknown').substring(0, 54).padEnd(54) + '|');
835
- output.push('| Confidence: ' + ((result.confidence != null ? (result.confidence * 100).toFixed(1) : '?') + '%').padEnd(49) + '|');
836
- output.push('| Reason: ' + (result.reason || '').substring(0, 53).padEnd(53) + '|');
837
- output.push('+--------------------------------------------------------------+');
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 mgMod = _requireMonograph();
1011
- if (mgMod && mgMod.openDb) {
1012
- var hintDb = mgMod.openDb(monographDb);
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 mgMod = null;
1451
- mgMod = _requireMonograph();
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
- } finally { if (mgMod.closeDb) mgMod.closeDb(mgDb); }
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
- } finally { try { subDb.close(); } catch (_) {} }
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 — ratio of monograph_* vs Grep/Glob, plus $ saved
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 monograph = d.monograph_call || 0;
730
- const search = (d.grep_call || 0) + (d.glob_call || 0)
731
- + (d.bash_grep_call || 0) + (d.bash_find_call || 0);
732
- const total = monograph + search;
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
- monograph,
736
- search,
737
- pct: Math.round((monograph / total) * 100),
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,CA4qCrE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CA8BnE"}
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 — ratio of monograph_* vs Grep/Glob/Bash-grep, + $ saved
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 monograph = d.monograph_call || 0;
859
- const search = (d.grep_call || 0) + (d.glob_call || 0)
860
- + (d.bash_grep_call || 0) + (d.bash_find_call || 0);
861
- const total = monograph + search;
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 { monograph: monograph, search: search, pct: Math.round((monograph / total) * 100), dollarsSaved: d.dollars_saved || 0 };
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkpCvB,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"}
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"}