@spikedpunch/mast 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/MAST_SPEC.md +2926 -0
- package/README.md +842 -0
- package/assets/skill.md +45 -0
- package/dist/ast/extract.d.ts +65 -0
- package/dist/ast/extract.d.ts.map +1 -0
- package/dist/ast/extract.js +147 -0
- package/dist/ast/extract.js.map +1 -0
- package/dist/ast/extractor.d.ts +85 -0
- package/dist/ast/extractor.d.ts.map +1 -0
- package/dist/ast/extractor.js +2 -0
- package/dist/ast/extractor.js.map +1 -0
- package/dist/ast/extractors/markdown.d.ts +40 -0
- package/dist/ast/extractors/markdown.d.ts.map +1 -0
- package/dist/ast/extractors/markdown.js +175 -0
- package/dist/ast/extractors/markdown.js.map +1 -0
- package/dist/ast/extractors/typescript.d.ts +179 -0
- package/dist/ast/extractors/typescript.d.ts.map +1 -0
- package/dist/ast/extractors/typescript.js +1439 -0
- package/dist/ast/extractors/typescript.js.map +1 -0
- package/dist/ast/parser.d.ts +5 -0
- package/dist/ast/parser.d.ts.map +1 -0
- package/dist/ast/parser.js +21 -0
- package/dist/ast/parser.js.map +1 -0
- package/dist/ast/types.d.ts +586 -0
- package/dist/ast/types.d.ts.map +1 -0
- package/dist/ast/types.js +3 -0
- package/dist/ast/types.js.map +1 -0
- package/dist/cli/docs-cmd.d.ts +22 -0
- package/dist/cli/docs-cmd.d.ts.map +1 -0
- package/dist/cli/docs-cmd.js +100 -0
- package/dist/cli/docs-cmd.js.map +1 -0
- package/dist/cli/index-cmd.d.ts +72 -0
- package/dist/cli/index-cmd.d.ts.map +1 -0
- package/dist/cli/index-cmd.js +263 -0
- package/dist/cli/index-cmd.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +14 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +64 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/install-hooks.d.ts +3 -0
- package/dist/cli/install-hooks.d.ts.map +1 -0
- package/dist/cli/install-hooks.js +34 -0
- package/dist/cli/install-hooks.js.map +1 -0
- package/dist/cli/metrics-cmd.d.ts +3 -0
- package/dist/cli/metrics-cmd.d.ts.map +1 -0
- package/dist/cli/metrics-cmd.js +164 -0
- package/dist/cli/metrics-cmd.js.map +1 -0
- package/dist/cli/program.d.ts +14 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +44 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/query.d.ts +46 -0
- package/dist/cli/query.d.ts.map +1 -0
- package/dist/cli/query.js +153 -0
- package/dist/cli/query.js.map +1 -0
- package/dist/cli/search-cmd.d.ts +33 -0
- package/dist/cli/search-cmd.d.ts.map +1 -0
- package/dist/cli/search-cmd.js +114 -0
- package/dist/cli/search-cmd.js.map +1 -0
- package/dist/cli/serve.d.ts +3 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +19 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/skill-install.d.ts +39 -0
- package/dist/cli/skill-install.d.ts.map +1 -0
- package/dist/cli/skill-install.js +57 -0
- package/dist/cli/skill-install.js.map +1 -0
- package/dist/cli/status.d.ts +35 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +117 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/upgrade-cmd.d.ts +48 -0
- package/dist/cli/upgrade-cmd.d.ts.map +1 -0
- package/dist/cli/upgrade-cmd.js +129 -0
- package/dist/cli/upgrade-cmd.js.map +1 -0
- package/dist/cli/version.d.ts +3 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +19 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/env.d.ts +17 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +17 -0
- package/dist/env.js.map +1 -0
- package/dist/graph/checker-resolver.d.ts +156 -0
- package/dist/graph/checker-resolver.d.ts.map +1 -0
- package/dist/graph/checker-resolver.js +458 -0
- package/dist/graph/checker-resolver.js.map +1 -0
- package/dist/graph/db.d.ts +318 -0
- package/dist/graph/db.d.ts.map +1 -0
- package/dist/graph/db.js +266 -0
- package/dist/graph/db.js.map +1 -0
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +5 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/local-type-env.d.ts +45 -0
- package/dist/graph/local-type-env.d.ts.map +1 -0
- package/dist/graph/local-type-env.js +59 -0
- package/dist/graph/local-type-env.js.map +1 -0
- package/dist/graph/path-range.d.ts +40 -0
- package/dist/graph/path-range.d.ts.map +1 -0
- package/dist/graph/path-range.js +42 -0
- package/dist/graph/path-range.js.map +1 -0
- package/dist/graph/populate.d.ts +263 -0
- package/dist/graph/populate.d.ts.map +1 -0
- package/dist/graph/populate.js +984 -0
- package/dist/graph/populate.js.map +1 -0
- package/dist/graph/queries.d.ts +114 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +415 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/sqliteBatch.d.ts +48 -0
- package/dist/graph/sqliteBatch.d.ts.map +1 -0
- package/dist/graph/sqliteBatch.js +93 -0
- package/dist/graph/sqliteBatch.js.map +1 -0
- package/dist/indexer/freshness.d.ts +48 -0
- package/dist/indexer/freshness.d.ts.map +1 -0
- package/dist/indexer/freshness.js +72 -0
- package/dist/indexer/freshness.js.map +1 -0
- package/dist/indexer/import-resolver.d.ts +50 -0
- package/dist/indexer/import-resolver.d.ts.map +1 -0
- package/dist/indexer/import-resolver.js +318 -0
- package/dist/indexer/import-resolver.js.map +1 -0
- package/dist/indexer/index.d.ts +249 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +560 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/walker.d.ts +60 -0
- package/dist/indexer/walker.d.ts.map +1 -0
- package/dist/indexer/walker.js +128 -0
- package/dist/indexer/walker.js.map +1 -0
- package/dist/indexer/watcher.d.ts +80 -0
- package/dist/indexer/watcher.d.ts.map +1 -0
- package/dist/indexer/watcher.js +151 -0
- package/dist/indexer/watcher.js.map +1 -0
- package/dist/mcp/context.d.ts +23 -0
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/context.js +2 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/register-tools.d.ts +21 -0
- package/dist/mcp/register-tools.d.ts.map +1 -0
- package/dist/mcp/register-tools.js +42 -0
- package/dist/mcp/register-tools.js.map +1 -0
- package/dist/mcp/server.d.ts +58 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +177 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/staleness.d.ts +72 -0
- package/dist/mcp/staleness.d.ts.map +1 -0
- package/dist/mcp/staleness.js +213 -0
- package/dist/mcp/staleness.js.map +1 -0
- package/dist/mcp/startup.d.ts +40 -0
- package/dist/mcp/startup.d.ts.map +1 -0
- package/dist/mcp/startup.js +114 -0
- package/dist/mcp/startup.js.map +1 -0
- package/dist/mcp/tools/_helpers.d.ts +26 -0
- package/dist/mcp/tools/_helpers.d.ts.map +1 -0
- package/dist/mcp/tools/_helpers.js +54 -0
- package/dist/mcp/tools/_helpers.js.map +1 -0
- package/dist/mcp/tools/callers.d.ts +4 -0
- package/dist/mcp/tools/callers.d.ts.map +1 -0
- package/dist/mcp/tools/callers.js +112 -0
- package/dist/mcp/tools/callers.js.map +1 -0
- package/dist/mcp/tools/dependencies.d.ts +4 -0
- package/dist/mcp/tools/dependencies.d.ts.map +1 -0
- package/dist/mcp/tools/dependencies.js +36 -0
- package/dist/mcp/tools/dependencies.js.map +1 -0
- package/dist/mcp/tools/efficiency.d.ts +4 -0
- package/dist/mcp/tools/efficiency.d.ts.map +1 -0
- package/dist/mcp/tools/efficiency.js +39 -0
- package/dist/mcp/tools/efficiency.js.map +1 -0
- package/dist/mcp/tools/exports.d.ts +4 -0
- package/dist/mcp/tools/exports.d.ts.map +1 -0
- package/dist/mcp/tools/exports.js +56 -0
- package/dist/mcp/tools/exports.js.map +1 -0
- package/dist/mcp/tools/implementors.d.ts +4 -0
- package/dist/mcp/tools/implementors.d.ts.map +1 -0
- package/dist/mcp/tools/implementors.js +47 -0
- package/dist/mcp/tools/implementors.js.map +1 -0
- package/dist/mcp/tools/project-skeleton.d.ts +4 -0
- package/dist/mcp/tools/project-skeleton.d.ts.map +1 -0
- package/dist/mcp/tools/project-skeleton.js +61 -0
- package/dist/mcp/tools/project-skeleton.js.map +1 -0
- package/dist/mcp/tools/reindex.d.ts +14 -0
- package/dist/mcp/tools/reindex.d.ts.map +1 -0
- package/dist/mcp/tools/reindex.js +32 -0
- package/dist/mcp/tools/reindex.js.map +1 -0
- package/dist/mcp/tools/rename-impact.d.ts +4 -0
- package/dist/mcp/tools/rename-impact.d.ts.map +1 -0
- package/dist/mcp/tools/rename-impact.js +124 -0
- package/dist/mcp/tools/rename-impact.js.map +1 -0
- package/dist/mcp/tools/search.d.ts +4 -0
- package/dist/mcp/tools/search.d.ts.map +1 -0
- package/dist/mcp/tools/search.js +69 -0
- package/dist/mcp/tools/search.js.map +1 -0
- package/dist/mcp/tools/signature.d.ts +4 -0
- package/dist/mcp/tools/signature.d.ts.map +1 -0
- package/dist/mcp/tools/signature.js +127 -0
- package/dist/mcp/tools/signature.js.map +1 -0
- package/dist/mcp/tools/status.d.ts +4 -0
- package/dist/mcp/tools/status.d.ts.map +1 -0
- package/dist/mcp/tools/status.js +30 -0
- package/dist/mcp/tools/status.js.map +1 -0
- package/dist/search/declex.d.ts +109 -0
- package/dist/search/declex.d.ts.map +1 -0
- package/dist/search/declex.js +242 -0
- package/dist/search/declex.js.map +1 -0
- package/dist/search/fts.d.ts +77 -0
- package/dist/search/fts.d.ts.map +1 -0
- package/dist/search/fts.js +223 -0
- package/dist/search/fts.js.map +1 -0
- package/dist/search/fused.d.ts +110 -0
- package/dist/search/fused.d.ts.map +1 -0
- package/dist/search/fused.js +323 -0
- package/dist/search/fused.js.map +1 -0
- package/dist/search/potential-matches.d.ts +88 -0
- package/dist/search/potential-matches.d.ts.map +1 -0
- package/dist/search/potential-matches.js +83 -0
- package/dist/search/potential-matches.js.map +1 -0
- package/dist/search/scope.d.ts +66 -0
- package/dist/search/scope.d.ts.map +1 -0
- package/dist/search/scope.js +71 -0
- package/dist/search/scope.js.map +1 -0
- package/dist/store/config.d.ts +57 -0
- package/dist/store/config.d.ts.map +1 -0
- package/dist/store/config.js +208 -0
- package/dist/store/config.js.map +1 -0
- package/dist/store/lock.d.ts +101 -0
- package/dist/store/lock.d.ts.map +1 -0
- package/dist/store/lock.js +182 -0
- package/dist/store/lock.js.map +1 -0
- package/dist/store/lockMetrics.d.ts +52 -0
- package/dist/store/lockMetrics.d.ts.map +1 -0
- package/dist/store/lockMetrics.js +29 -0
- package/dist/store/lockMetrics.js.map +1 -0
- package/dist/store/sqliteChunkStore.d.ts +44 -0
- package/dist/store/sqliteChunkStore.d.ts.map +1 -0
- package/dist/store/sqliteChunkStore.js +135 -0
- package/dist/store/sqliteChunkStore.js.map +1 -0
- package/dist/telemetry/lockMetricsSummary.d.ts +42 -0
- package/dist/telemetry/lockMetricsSummary.d.ts.map +1 -0
- package/dist/telemetry/lockMetricsSummary.js +139 -0
- package/dist/telemetry/lockMetricsSummary.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +140 -0
- package/dist/telemetry/metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics.js +312 -0
- package/dist/telemetry/metrics.js.map +1 -0
- package/dist/telemetry/tokenizer.d.ts +125 -0
- package/dist/telemetry/tokenizer.d.ts.map +1 -0
- package/dist/telemetry/tokenizer.js +174 -0
- package/dist/telemetry/tokenizer.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { computeDurationPercentiles } from './metrics.js';
|
|
5
|
+
import { LOCK_METRICS_FILENAME } from '../store/lockMetrics.js';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Lock-hold summarizer (D6, IMPLEMENTATION_PLAN.md Stage 4 "D6 RESCOPE"
|
|
8
|
+
// deliverable 2) — `mast metrics --locks`.
|
|
9
|
+
//
|
|
10
|
+
// Generalizes eval/baseline-locks.json's one-off capture-and-hand-compute
|
|
11
|
+
// into a repeatable report over store/lockMetrics.ts's JSONL sink
|
|
12
|
+
// (`<stateDir>/lock-metrics.jsonl`). That module's `LockEvent` union is the
|
|
13
|
+
// source of truth for the shape summarized here: 'acquired' {waitMs},
|
|
14
|
+
// 'released' {holdMs}, 'failed' {waitMs}, each carrying {type, caller,
|
|
15
|
+
// timestamp}. `structure` lock hold-by-caller "SURVIVES, narrowed" per the
|
|
16
|
+
// D6 rescope table — this is that surviving row's standing instrument.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Validated at the boundary (§3.2): a hand-appended JSONL file on disk is
|
|
19
|
+
// untrusted input, not a value already known to match `LockEvent` — a
|
|
20
|
+
// truncated write (process killed mid-`appendFileSync`) or a future event
|
|
21
|
+
// kind this summarizer doesn't know about must be skipped, not crash the
|
|
22
|
+
// report.
|
|
23
|
+
const LockEventSchema = z.discriminatedUnion('kind', [
|
|
24
|
+
z.object({
|
|
25
|
+
kind: z.literal('acquired'),
|
|
26
|
+
type: z.string(),
|
|
27
|
+
caller: z.string(),
|
|
28
|
+
waitMs: z.number(),
|
|
29
|
+
timestamp: z.number(),
|
|
30
|
+
}),
|
|
31
|
+
z.object({
|
|
32
|
+
kind: z.literal('released'),
|
|
33
|
+
type: z.string(),
|
|
34
|
+
caller: z.string(),
|
|
35
|
+
holdMs: z.number(),
|
|
36
|
+
timestamp: z.number(),
|
|
37
|
+
}),
|
|
38
|
+
z.object({
|
|
39
|
+
kind: z.literal('failed'),
|
|
40
|
+
type: z.string(),
|
|
41
|
+
caller: z.string(),
|
|
42
|
+
waitMs: z.number(),
|
|
43
|
+
timestamp: z.number(),
|
|
44
|
+
}),
|
|
45
|
+
]);
|
|
46
|
+
/**
|
|
47
|
+
* Summarize raw `lock-metrics.jsonl` content into per-caller count/p50/p95/max
|
|
48
|
+
* for both hold duration (`released` events) and wait/acquire duration
|
|
49
|
+
* (`acquired` events), plus a failed-acquisition count (`failed` events).
|
|
50
|
+
*
|
|
51
|
+
* Pure — takes the file's text directly rather than a path, so it is
|
|
52
|
+
* unit-testable against fixture strings without touching the filesystem.
|
|
53
|
+
* Malformed lines (bad JSON, or JSON that doesn't match a known `LockEvent`
|
|
54
|
+
* shape) are skipped and counted rather than thrown, matching
|
|
55
|
+
* store/lockMetrics.ts's own best-effort discipline for this data.
|
|
56
|
+
*/
|
|
57
|
+
export function summarizeLockMetricsJsonl(jsonlContent) {
|
|
58
|
+
const holdsByCaller = new Map();
|
|
59
|
+
const waitsByCaller = new Map();
|
|
60
|
+
const failedCountByCaller = new Map();
|
|
61
|
+
let malformedLineCount = 0;
|
|
62
|
+
const lines = jsonlContent.split('\n').filter((line) => line.trim().length > 0);
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(line);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
malformedLineCount++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const result = LockEventSchema.safeParse(parsed);
|
|
73
|
+
if (!result.success) {
|
|
74
|
+
malformedLineCount++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const event = result.data;
|
|
78
|
+
switch (event.kind) {
|
|
79
|
+
case 'released': {
|
|
80
|
+
const holds = holdsByCaller.get(event.caller) ?? [];
|
|
81
|
+
holds.push(event.holdMs);
|
|
82
|
+
holdsByCaller.set(event.caller, holds);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case 'acquired': {
|
|
86
|
+
const waits = waitsByCaller.get(event.caller) ?? [];
|
|
87
|
+
waits.push(event.waitMs);
|
|
88
|
+
waitsByCaller.set(event.caller, waits);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case 'failed': {
|
|
92
|
+
failedCountByCaller.set(event.caller, (failedCountByCaller.get(event.caller) ?? 0) + 1);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const allCallers = new Set([
|
|
98
|
+
...holdsByCaller.keys(),
|
|
99
|
+
...waitsByCaller.keys(),
|
|
100
|
+
...failedCountByCaller.keys(),
|
|
101
|
+
]);
|
|
102
|
+
const callers = [...allCallers].sort().map((caller) => {
|
|
103
|
+
const holds = holdsByCaller.get(caller) ?? [];
|
|
104
|
+
const waits = waitsByCaller.get(caller) ?? [];
|
|
105
|
+
const holdPercentiles = computeDurationPercentiles(holds);
|
|
106
|
+
const waitPercentiles = computeDurationPercentiles(waits);
|
|
107
|
+
return {
|
|
108
|
+
caller,
|
|
109
|
+
count: holds.length,
|
|
110
|
+
hold_p50_ms: holdPercentiles.p50,
|
|
111
|
+
hold_p95_ms: holdPercentiles.p95,
|
|
112
|
+
hold_max_ms: holds.length > 0 ? Math.max(...holds) : 0,
|
|
113
|
+
wait_p50_ms: waitPercentiles.p50,
|
|
114
|
+
wait_p95_ms: waitPercentiles.p95,
|
|
115
|
+
wait_max_ms: waits.length > 0 ? Math.max(...waits) : 0,
|
|
116
|
+
failed_count: failedCountByCaller.get(caller) ?? 0,
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
return { callers, malformed_line_count: malformedLineCount };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Read and summarize `<stateDir>/lock-metrics.jsonl`.
|
|
123
|
+
*
|
|
124
|
+
* Returns `null` when the file is missing or empty — the "no lock metrics
|
|
125
|
+
* recorded" case `mast metrics --locks` reports distinctly from a
|
|
126
|
+
* zero-caller summary, so callers can tell "never ran a coarse write" apart
|
|
127
|
+
* from "ran, but every event was malformed" (the latter still returns a
|
|
128
|
+
* summary object, with `malformed_line_count > 0` and `callers: []`).
|
|
129
|
+
*/
|
|
130
|
+
export function readLockMetricsSummary(stateDir) {
|
|
131
|
+
const path = join(stateDir, LOCK_METRICS_FILENAME);
|
|
132
|
+
if (!existsSync(path))
|
|
133
|
+
return null;
|
|
134
|
+
const content = readFileSync(path, 'utf-8');
|
|
135
|
+
if (content.trim().length === 0)
|
|
136
|
+
return null;
|
|
137
|
+
return summarizeLockMetricsJsonl(content);
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=lockMetricsSummary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lockMetricsSummary.js","sourceRoot":"","sources":["../../src/telemetry/lockMetricsSummary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { Db } from '../graph/db.js';
|
|
2
|
+
import type { ToolStats } from '../ast/types.js';
|
|
3
|
+
import type { DeclexTelemetry } from '../search/fused.js';
|
|
4
|
+
export interface RecordToolCallOptions {
|
|
5
|
+
readonly toolName: string;
|
|
6
|
+
readonly tokensReturned: number;
|
|
7
|
+
readonly tokensFullFileBound: number;
|
|
8
|
+
readonly durationMs: number;
|
|
9
|
+
readonly sessionId: string;
|
|
10
|
+
readonly status: 'ok' | 'stale_returned' | 'error';
|
|
11
|
+
/** Pre-serialised via {@link buildArgsJson}. Omitted for tools that don't yet record argument identity. */
|
|
12
|
+
readonly argsJson?: string;
|
|
13
|
+
/** Pre-serialised via {@link buildResultsJson}. Omitted for tools that don't yet record result identity. */
|
|
14
|
+
readonly resultsJson?: string;
|
|
15
|
+
/** Pre-serialised via {@link buildDeclexJson}. Omitted when ranker D did not fire or the flag was off. */
|
|
16
|
+
readonly declexJson?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Persist a single tool-call record to `metrics` and upsert the
|
|
20
|
+
* `metrics_daily` roll-up row for today.
|
|
21
|
+
*
|
|
22
|
+
* The write is fire-and-forget from the caller's perspective; any DB error
|
|
23
|
+
* is swallowed so a metrics failure never breaks a tool response.
|
|
24
|
+
*/
|
|
25
|
+
export declare function recordToolCall(db: Db, options: RecordToolCallOptions): Promise<void>;
|
|
26
|
+
export interface MetricsSummary {
|
|
27
|
+
readonly tool_name: string;
|
|
28
|
+
readonly calls: number;
|
|
29
|
+
readonly tokens_returned_total: number;
|
|
30
|
+
readonly tokens_full_file_total: number;
|
|
31
|
+
readonly avg_duration_ms: number;
|
|
32
|
+
readonly efficiency_ratio: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Aggregate `metrics` rows within the given time window.
|
|
36
|
+
*
|
|
37
|
+
* @param db - open database
|
|
38
|
+
* @param sinceMs - unix epoch milliseconds; rows older than this are excluded
|
|
39
|
+
* @param toolName - when provided, restrict to one tool
|
|
40
|
+
*/
|
|
41
|
+
export declare function queryMetricsSummary(db: Db, sinceMs: number, toolName?: string): Promise<MetricsSummary[]>;
|
|
42
|
+
/** A tool's p50/p95 call duration, in milliseconds. */
|
|
43
|
+
export interface DurationPercentiles {
|
|
44
|
+
readonly p50: number;
|
|
45
|
+
readonly p95: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Nearest-rank percentile (no interpolation): sort ascending, then for
|
|
49
|
+
* percentile P take the value at rank `ceil(P/100 * N)` (1-indexed).
|
|
50
|
+
*
|
|
51
|
+
* Chosen over interpolated definitions (linear/R-7 etc.) because §14.9
|
|
52
|
+
* explicitly declines to assert an SLA on this data — the interpolation
|
|
53
|
+
* precision those methods buy is not needed for a human-readable CLI table,
|
|
54
|
+
* and nearest-rank is the simplest definition that avoids an
|
|
55
|
+
* interpolation-method debate. Returns `{p50: 0, p95: 0}` for an empty input
|
|
56
|
+
* rather than throwing — matches this module's fire-and-forget discipline
|
|
57
|
+
* (a metrics computation must never crash a caller).
|
|
58
|
+
*/
|
|
59
|
+
export declare function computeDurationPercentiles(durationsMs: readonly number[]): DurationPercentiles;
|
|
60
|
+
export interface MetricsSummaryWithPercentiles extends MetricsSummary {
|
|
61
|
+
readonly p50_duration_ms: number;
|
|
62
|
+
readonly p95_duration_ms: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Same aggregation as {@link queryMetricsSummary}, plus per-tool p50/p95
|
|
66
|
+
* duration percentiles.
|
|
67
|
+
*
|
|
68
|
+
* better-sqlite3's default build has no SQL percentile aggregate, so this
|
|
69
|
+
* fetches the window's raw `duration_ms` values (one extra query) and
|
|
70
|
+
* computes percentiles in JS per tool group via
|
|
71
|
+
* {@link computeDurationPercentiles}. Safe at the row counts this table
|
|
72
|
+
* actually holds: §14.4's rotation policy bounds the raw `metrics` table to
|
|
73
|
+
* roughly 1,500 rows/day pre-rollup, i.e. thousands of rows at most for any
|
|
74
|
+
* realistic `--since` window — well within "fetch and sort in JS" territory.
|
|
75
|
+
*/
|
|
76
|
+
export declare function queryMetricsSummaryWithPercentiles(db: Db, sinceMs: number, toolName?: string): Promise<readonly MetricsSummaryWithPercentiles[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Aggregate `metrics` rows for a single session.
|
|
79
|
+
*/
|
|
80
|
+
export declare function querySessionSummary(db: Db, sessionId: string): Promise<MetricsSummary[]>;
|
|
81
|
+
/**
|
|
82
|
+
* Collapse raw `metrics` rows older than `keepDays` into `metrics_daily`
|
|
83
|
+
* (the roll-up already exists) and delete the raw rows.
|
|
84
|
+
*
|
|
85
|
+
* Safe to call repeatedly — idempotent.
|
|
86
|
+
*/
|
|
87
|
+
export declare function rollupMetrics(db: Db, keepDays?: number): Promise<number>;
|
|
88
|
+
/**
|
|
89
|
+
* Delete `metrics_daily` rows older than `keepDays` days.
|
|
90
|
+
*/
|
|
91
|
+
export declare function vacuumMetrics(db: Db, keepDays?: number): Promise<number>;
|
|
92
|
+
/**
|
|
93
|
+
* Build a `ToolStats` block from raw measurements.
|
|
94
|
+
*
|
|
95
|
+
* Attached to every read-tool response so consumers can track token savings.
|
|
96
|
+
*/
|
|
97
|
+
export declare function buildToolStats(toolName: string, tokensReturned: number, tokensFullFileBound: number, filesReferenced: readonly string[], durationMs: number): ToolStats;
|
|
98
|
+
/** A single `{file_path, symbol_name}` identity pair returned by a read tool, in rank order. */
|
|
99
|
+
export interface ResultIdentity {
|
|
100
|
+
readonly file_path: string;
|
|
101
|
+
readonly symbol_name: string | null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Serialise the salient tool arguments for `metrics.args_json`, capped at
|
|
105
|
+
* {@link ARGS_JSON_CHAR_CAP} characters.
|
|
106
|
+
*
|
|
107
|
+
* `JSON.stringify` already drops `undefined`-valued properties, so passing
|
|
108
|
+
* the tool's raw zod-parsed input captures "query + filters" (or
|
|
109
|
+
* "symbol/file_path") without extra bookkeeping at each call site.
|
|
110
|
+
*
|
|
111
|
+
* When the serialised form exceeds the cap, the truncation is stated
|
|
112
|
+
* honestly in the payload itself (`_truncated`, `original_length`) rather
|
|
113
|
+
* than silently cutting the string — an un-labelled truncated JSON string
|
|
114
|
+
* would also risk being invalid JSON.
|
|
115
|
+
*/
|
|
116
|
+
export declare function buildArgsJson(args: Record<string, unknown>, capChars?: number): string;
|
|
117
|
+
/**
|
|
118
|
+
* Serialise result identity pairs for `metrics.results_json`, capped at
|
|
119
|
+
* {@link RESULTS_JSON_ENTRY_CAP} entries.
|
|
120
|
+
*
|
|
121
|
+
* Rank order is preserved. When the list exceeds the cap, a final
|
|
122
|
+
* `{"_truncated": N}` element (N = the number of dropped entries) is
|
|
123
|
+
* appended rather than silently dropping them — mirrors {@link buildArgsJson}'s
|
|
124
|
+
* honesty rule.
|
|
125
|
+
*/
|
|
126
|
+
export declare function buildResultsJson(identities: readonly ResultIdentity[], cap?: number): string;
|
|
127
|
+
/**
|
|
128
|
+
* Serialise ranker D's fire telemetry for `metrics.declex_json`, capping
|
|
129
|
+
* `window_effects` at {@link DECLEX_JSON_ENTRY_CAP} entries.
|
|
130
|
+
*
|
|
131
|
+
* Truncation shape: a top-level `_truncated: <dropped count>` field is set
|
|
132
|
+
* (nothing appended to `window_effects` itself) — {@link buildArgsJson}'s
|
|
133
|
+
* convention of a top-level marker rather than {@link buildResultsJson}'s
|
|
134
|
+
* appended-sentinel-element, because `window_effects` here is one field of a
|
|
135
|
+
* larger diagnostics object (fired/top_match_channel/candidate_count sit
|
|
136
|
+
* alongside it), not a bare list — a marker mixed into the array would be
|
|
137
|
+
* indistinguishable from a real (malformed) effect on parse.
|
|
138
|
+
*/
|
|
139
|
+
export declare function buildDeclexJson(telemetry: DeclexTelemetry, capEntries?: number): string;
|
|
140
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/telemetry/metrics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { sql } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Persist a single tool-call record to `metrics` and upsert the
|
|
4
|
+
* `metrics_daily` roll-up row for today.
|
|
5
|
+
*
|
|
6
|
+
* The write is fire-and-forget from the caller's perspective; any DB error
|
|
7
|
+
* is swallowed so a metrics failure never breaks a tool response.
|
|
8
|
+
*/
|
|
9
|
+
export async function recordToolCall(db, options) {
|
|
10
|
+
const now = Date.now() / 1000; // unix epoch seconds
|
|
11
|
+
const day = new Date().toISOString().slice(0, 10); // 'YYYY-MM-DD'
|
|
12
|
+
await db
|
|
13
|
+
.insertInto('metrics')
|
|
14
|
+
.values({
|
|
15
|
+
tool_name: options.toolName,
|
|
16
|
+
call_timestamp: now,
|
|
17
|
+
tokens_returned: options.tokensReturned,
|
|
18
|
+
tokens_full_file_upper_bound: options.tokensFullFileBound,
|
|
19
|
+
duration_ms: options.durationMs,
|
|
20
|
+
// Stage 7.2 (IMPLEMENTATION_PLAN.md "Stage 7: Vector-store deletion"):
|
|
21
|
+
// no code produces a search mode any more (the vector leg it used to
|
|
22
|
+
// distinguish from lexical is gone), so every new row writes NULL. The
|
|
23
|
+
// COLUMN stays — pre-Stage-7 rows carry 'hybrid'/'lexical' and remain
|
|
24
|
+
// readable for historical queries.
|
|
25
|
+
mode: null,
|
|
26
|
+
session_id: options.sessionId,
|
|
27
|
+
status: options.status,
|
|
28
|
+
args_json: options.argsJson ?? null,
|
|
29
|
+
results_json: options.resultsJson ?? null,
|
|
30
|
+
declex_json: options.declexJson ?? null,
|
|
31
|
+
})
|
|
32
|
+
.execute();
|
|
33
|
+
// Upsert today's roll-up row.
|
|
34
|
+
await db
|
|
35
|
+
.insertInto('metrics_daily')
|
|
36
|
+
.values({
|
|
37
|
+
day,
|
|
38
|
+
tool_name: options.toolName,
|
|
39
|
+
calls: 1,
|
|
40
|
+
tokens_returned_total: options.tokensReturned,
|
|
41
|
+
tokens_full_file_total: options.tokensFullFileBound,
|
|
42
|
+
avg_duration_ms: options.durationMs,
|
|
43
|
+
})
|
|
44
|
+
.onConflict((oc) => oc.columns(['day', 'tool_name']).doUpdateSet((eb) => ({
|
|
45
|
+
calls: sql `${eb.ref('excluded.calls')} + metrics_daily.calls`,
|
|
46
|
+
tokens_returned_total: sql `${eb.ref('excluded.tokens_returned_total')} + metrics_daily.tokens_returned_total`,
|
|
47
|
+
tokens_full_file_total: sql `${eb.ref('excluded.tokens_full_file_total')} + metrics_daily.tokens_full_file_total`,
|
|
48
|
+
// Incremental running average: (old_avg * old_n + new_val) / (old_n + 1)
|
|
49
|
+
avg_duration_ms: sql `(metrics_daily.avg_duration_ms * metrics_daily.calls + ${eb.ref('excluded.avg_duration_ms')}) / (metrics_daily.calls + 1)`,
|
|
50
|
+
})))
|
|
51
|
+
.execute();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Aggregate `metrics` rows within the given time window.
|
|
55
|
+
*
|
|
56
|
+
* @param db - open database
|
|
57
|
+
* @param sinceMs - unix epoch milliseconds; rows older than this are excluded
|
|
58
|
+
* @param toolName - when provided, restrict to one tool
|
|
59
|
+
*/
|
|
60
|
+
export async function queryMetricsSummary(db, sinceMs, toolName) {
|
|
61
|
+
const sinceEpochSec = sinceMs / 1000;
|
|
62
|
+
let q = db
|
|
63
|
+
.selectFrom('metrics')
|
|
64
|
+
.select((eb) => [
|
|
65
|
+
'tool_name',
|
|
66
|
+
eb.fn.count('id').as('calls'),
|
|
67
|
+
eb.fn.sum('tokens_returned').as('tokens_returned_total'),
|
|
68
|
+
eb.fn.sum('tokens_full_file_upper_bound').as('tokens_full_file_total'),
|
|
69
|
+
eb.fn.avg('duration_ms').as('avg_duration_ms'),
|
|
70
|
+
])
|
|
71
|
+
.where('call_timestamp', '>=', sinceEpochSec)
|
|
72
|
+
.groupBy('tool_name')
|
|
73
|
+
.orderBy('tool_name', 'asc');
|
|
74
|
+
if (toolName !== undefined) {
|
|
75
|
+
q = q.where('tool_name', '=', toolName);
|
|
76
|
+
}
|
|
77
|
+
const rows = await q.execute();
|
|
78
|
+
return rows.map((r) => ({
|
|
79
|
+
tool_name: r.tool_name,
|
|
80
|
+
calls: Number(r.calls),
|
|
81
|
+
tokens_returned_total: Number(r.tokens_returned_total),
|
|
82
|
+
tokens_full_file_total: Number(r.tokens_full_file_total),
|
|
83
|
+
avg_duration_ms: Number(r.avg_duration_ms),
|
|
84
|
+
efficiency_ratio: Number(r.tokens_full_file_total) > 0
|
|
85
|
+
? 1 - Number(r.tokens_returned_total) / Number(r.tokens_full_file_total)
|
|
86
|
+
: 0,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Nearest-rank percentile (no interpolation): sort ascending, then for
|
|
91
|
+
* percentile P take the value at rank `ceil(P/100 * N)` (1-indexed).
|
|
92
|
+
*
|
|
93
|
+
* Chosen over interpolated definitions (linear/R-7 etc.) because §14.9
|
|
94
|
+
* explicitly declines to assert an SLA on this data — the interpolation
|
|
95
|
+
* precision those methods buy is not needed for a human-readable CLI table,
|
|
96
|
+
* and nearest-rank is the simplest definition that avoids an
|
|
97
|
+
* interpolation-method debate. Returns `{p50: 0, p95: 0}` for an empty input
|
|
98
|
+
* rather than throwing — matches this module's fire-and-forget discipline
|
|
99
|
+
* (a metrics computation must never crash a caller).
|
|
100
|
+
*/
|
|
101
|
+
export function computeDurationPercentiles(durationsMs) {
|
|
102
|
+
if (durationsMs.length === 0)
|
|
103
|
+
return { p50: 0, p95: 0 };
|
|
104
|
+
const sorted = [...durationsMs].sort((a, b) => a - b);
|
|
105
|
+
const rankValue = (percentile) => {
|
|
106
|
+
const rank = Math.ceil((percentile / 100) * sorted.length);
|
|
107
|
+
const index = Math.min(Math.max(rank - 1, 0), sorted.length - 1);
|
|
108
|
+
return sorted[index];
|
|
109
|
+
};
|
|
110
|
+
return { p50: rankValue(50), p95: rankValue(95) };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Same aggregation as {@link queryMetricsSummary}, plus per-tool p50/p95
|
|
114
|
+
* duration percentiles.
|
|
115
|
+
*
|
|
116
|
+
* better-sqlite3's default build has no SQL percentile aggregate, so this
|
|
117
|
+
* fetches the window's raw `duration_ms` values (one extra query) and
|
|
118
|
+
* computes percentiles in JS per tool group via
|
|
119
|
+
* {@link computeDurationPercentiles}. Safe at the row counts this table
|
|
120
|
+
* actually holds: §14.4's rotation policy bounds the raw `metrics` table to
|
|
121
|
+
* roughly 1,500 rows/day pre-rollup, i.e. thousands of rows at most for any
|
|
122
|
+
* realistic `--since` window — well within "fetch and sort in JS" territory.
|
|
123
|
+
*/
|
|
124
|
+
export async function queryMetricsSummaryWithPercentiles(db, sinceMs, toolName) {
|
|
125
|
+
const base = await queryMetricsSummary(db, sinceMs, toolName);
|
|
126
|
+
if (base.length === 0)
|
|
127
|
+
return [];
|
|
128
|
+
const sinceEpochSec = sinceMs / 1000;
|
|
129
|
+
let durationQuery = db
|
|
130
|
+
.selectFrom('metrics')
|
|
131
|
+
.select(['tool_name', 'duration_ms'])
|
|
132
|
+
.where('call_timestamp', '>=', sinceEpochSec);
|
|
133
|
+
if (toolName !== undefined) {
|
|
134
|
+
durationQuery = durationQuery.where('tool_name', '=', toolName);
|
|
135
|
+
}
|
|
136
|
+
const durationRows = await durationQuery.execute();
|
|
137
|
+
const durationsByTool = new Map();
|
|
138
|
+
for (const row of durationRows) {
|
|
139
|
+
const existing = durationsByTool.get(row.tool_name);
|
|
140
|
+
if (existing !== undefined)
|
|
141
|
+
existing.push(row.duration_ms);
|
|
142
|
+
else
|
|
143
|
+
durationsByTool.set(row.tool_name, [row.duration_ms]);
|
|
144
|
+
}
|
|
145
|
+
return base.map((summary) => {
|
|
146
|
+
const { p50, p95 } = computeDurationPercentiles(durationsByTool.get(summary.tool_name) ?? []);
|
|
147
|
+
return { ...summary, p50_duration_ms: p50, p95_duration_ms: p95 };
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Aggregate `metrics` rows for a single session.
|
|
152
|
+
*/
|
|
153
|
+
export async function querySessionSummary(db, sessionId) {
|
|
154
|
+
const rows = await db
|
|
155
|
+
.selectFrom('metrics')
|
|
156
|
+
.select((eb) => [
|
|
157
|
+
'tool_name',
|
|
158
|
+
eb.fn.count('id').as('calls'),
|
|
159
|
+
eb.fn.sum('tokens_returned').as('tokens_returned_total'),
|
|
160
|
+
eb.fn.sum('tokens_full_file_upper_bound').as('tokens_full_file_total'),
|
|
161
|
+
eb.fn.avg('duration_ms').as('avg_duration_ms'),
|
|
162
|
+
])
|
|
163
|
+
.where('session_id', '=', sessionId)
|
|
164
|
+
.groupBy('tool_name')
|
|
165
|
+
.orderBy('tool_name', 'asc')
|
|
166
|
+
.execute();
|
|
167
|
+
return rows.map((r) => ({
|
|
168
|
+
tool_name: r.tool_name,
|
|
169
|
+
calls: Number(r.calls),
|
|
170
|
+
tokens_returned_total: Number(r.tokens_returned_total),
|
|
171
|
+
tokens_full_file_total: Number(r.tokens_full_file_total),
|
|
172
|
+
avg_duration_ms: Number(r.avg_duration_ms),
|
|
173
|
+
efficiency_ratio: Number(r.tokens_full_file_total) > 0
|
|
174
|
+
? 1 - Number(r.tokens_returned_total) / Number(r.tokens_full_file_total)
|
|
175
|
+
: 0,
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Rotation helpers
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
/**
|
|
182
|
+
* Collapse raw `metrics` rows older than `keepDays` into `metrics_daily`
|
|
183
|
+
* (the roll-up already exists) and delete the raw rows.
|
|
184
|
+
*
|
|
185
|
+
* Safe to call repeatedly — idempotent.
|
|
186
|
+
*/
|
|
187
|
+
export async function rollupMetrics(db, keepDays = 7) {
|
|
188
|
+
const cutoffSec = (Date.now() - keepDays * 86_400_000) / 1000;
|
|
189
|
+
const result = await db
|
|
190
|
+
.deleteFrom('metrics')
|
|
191
|
+
.where('call_timestamp', '<', cutoffSec)
|
|
192
|
+
.executeTakeFirst();
|
|
193
|
+
return Number(result.numDeletedRows);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Delete `metrics_daily` rows older than `keepDays` days.
|
|
197
|
+
*/
|
|
198
|
+
export async function vacuumMetrics(db, keepDays = 90) {
|
|
199
|
+
const cutoff = new Date(Date.now() - keepDays * 86_400_000)
|
|
200
|
+
.toISOString()
|
|
201
|
+
.slice(0, 10);
|
|
202
|
+
const result = await db
|
|
203
|
+
.deleteFrom('metrics_daily')
|
|
204
|
+
.where('day', '<', cutoff)
|
|
205
|
+
.executeTakeFirst();
|
|
206
|
+
return Number(result.numDeletedRows);
|
|
207
|
+
}
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// ToolStats builder
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
/**
|
|
212
|
+
* Build a `ToolStats` block from raw measurements.
|
|
213
|
+
*
|
|
214
|
+
* Attached to every read-tool response so consumers can track token savings.
|
|
215
|
+
*/
|
|
216
|
+
export function buildToolStats(toolName, tokensReturned, tokensFullFileBound, filesReferenced, durationMs) {
|
|
217
|
+
const efficiency = tokensFullFileBound > 0
|
|
218
|
+
? 1 - tokensReturned / tokensFullFileBound
|
|
219
|
+
: 0;
|
|
220
|
+
return {
|
|
221
|
+
tool: toolName,
|
|
222
|
+
tokens_returned: tokensReturned,
|
|
223
|
+
tokens_full_file_upper_bound: tokensFullFileBound,
|
|
224
|
+
files_referenced: filesReferenced,
|
|
225
|
+
efficiency_ratio: efficiency,
|
|
226
|
+
duration_ms: durationMs,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const ARGS_JSON_CHAR_CAP = 1_000;
|
|
230
|
+
const RESULTS_JSON_ENTRY_CAP = 20;
|
|
231
|
+
/**
|
|
232
|
+
* Serialise the salient tool arguments for `metrics.args_json`, capped at
|
|
233
|
+
* {@link ARGS_JSON_CHAR_CAP} characters.
|
|
234
|
+
*
|
|
235
|
+
* `JSON.stringify` already drops `undefined`-valued properties, so passing
|
|
236
|
+
* the tool's raw zod-parsed input captures "query + filters" (or
|
|
237
|
+
* "symbol/file_path") without extra bookkeeping at each call site.
|
|
238
|
+
*
|
|
239
|
+
* When the serialised form exceeds the cap, the truncation is stated
|
|
240
|
+
* honestly in the payload itself (`_truncated`, `original_length`) rather
|
|
241
|
+
* than silently cutting the string — an un-labelled truncated JSON string
|
|
242
|
+
* would also risk being invalid JSON.
|
|
243
|
+
*/
|
|
244
|
+
export function buildArgsJson(args, capChars = ARGS_JSON_CHAR_CAP) {
|
|
245
|
+
const full = JSON.stringify(args);
|
|
246
|
+
if (full.length <= capChars)
|
|
247
|
+
return full;
|
|
248
|
+
const wrap = (previewLen) => JSON.stringify({
|
|
249
|
+
_truncated: true,
|
|
250
|
+
original_length: full.length,
|
|
251
|
+
preview: full.slice(0, previewLen),
|
|
252
|
+
});
|
|
253
|
+
// `preview` is a slice of an already-JSON-encoded string, so it can contain
|
|
254
|
+
// quote/backslash characters that need escaping once re-embedded as a JSON
|
|
255
|
+
// string value — a fixed-length guess can overshoot the cap. Shrink
|
|
256
|
+
// iteratively by the observed overflow instead; the max() clamp guarantees
|
|
257
|
+
// termination even if a shrink step's escaping happens to net zero.
|
|
258
|
+
let previewLen = Math.max(0, capChars - wrap(0).length);
|
|
259
|
+
let wrapped = wrap(previewLen);
|
|
260
|
+
while (wrapped.length > capChars && previewLen > 0) {
|
|
261
|
+
previewLen = Math.max(0, previewLen - (wrapped.length - capChars));
|
|
262
|
+
wrapped = wrap(previewLen);
|
|
263
|
+
}
|
|
264
|
+
return wrapped;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Serialise result identity pairs for `metrics.results_json`, capped at
|
|
268
|
+
* {@link RESULTS_JSON_ENTRY_CAP} entries.
|
|
269
|
+
*
|
|
270
|
+
* Rank order is preserved. When the list exceeds the cap, a final
|
|
271
|
+
* `{"_truncated": N}` element (N = the number of dropped entries) is
|
|
272
|
+
* appended rather than silently dropping them — mirrors {@link buildArgsJson}'s
|
|
273
|
+
* honesty rule.
|
|
274
|
+
*/
|
|
275
|
+
export function buildResultsJson(identities, cap = RESULTS_JSON_ENTRY_CAP) {
|
|
276
|
+
if (identities.length <= cap)
|
|
277
|
+
return JSON.stringify(identities);
|
|
278
|
+
const kept = identities.slice(0, cap);
|
|
279
|
+
const droppedCount = identities.length - cap;
|
|
280
|
+
return JSON.stringify([...kept, { _truncated: droppedCount }]);
|
|
281
|
+
}
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// D-fire telemetry — `metrics.declex_json` (Stage 6.3, M2 decision memo
|
|
284
|
+
// condition 3: the input signal for the F18 kill-switch and re-entry
|
|
285
|
+
// criteria).
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
const DECLEX_JSON_ENTRY_CAP = 10;
|
|
288
|
+
/**
|
|
289
|
+
* Serialise ranker D's fire telemetry for `metrics.declex_json`, capping
|
|
290
|
+
* `window_effects` at {@link DECLEX_JSON_ENTRY_CAP} entries.
|
|
291
|
+
*
|
|
292
|
+
* Truncation shape: a top-level `_truncated: <dropped count>` field is set
|
|
293
|
+
* (nothing appended to `window_effects` itself) — {@link buildArgsJson}'s
|
|
294
|
+
* convention of a top-level marker rather than {@link buildResultsJson}'s
|
|
295
|
+
* appended-sentinel-element, because `window_effects` here is one field of a
|
|
296
|
+
* larger diagnostics object (fired/top_match_channel/candidate_count sit
|
|
297
|
+
* alongside it), not a bare list — a marker mixed into the array would be
|
|
298
|
+
* indistinguishable from a real (malformed) effect on parse.
|
|
299
|
+
*/
|
|
300
|
+
export function buildDeclexJson(telemetry, capEntries = DECLEX_JSON_ENTRY_CAP) {
|
|
301
|
+
const { window_effects, ...rest } = telemetry;
|
|
302
|
+
if (window_effects.length <= capEntries) {
|
|
303
|
+
return JSON.stringify({ ...rest, window_effects });
|
|
304
|
+
}
|
|
305
|
+
const droppedCount = window_effects.length - capEntries;
|
|
306
|
+
return JSON.stringify({
|
|
307
|
+
...rest,
|
|
308
|
+
window_effects: window_effects.slice(0, capEntries),
|
|
309
|
+
_truncated: droppedCount,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/telemetry/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAwB7B;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,EAAM,EACN,OAA8B;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,qBAAqB;IACpD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe;IAElE,MAAM,EAAE;SACL,UAAU,CAAC,SAAS,CAAC;SACrB,MAAM,CAAC;QACN,SAAS,EAAqB,OAAO,CAAC,QAAQ;QAC9C,cAAc,EAAgB,GAAG;QACjC,eAAe,EAAe,OAAO,CAAC,cAAc;QACpD,4BAA4B,EAAE,OAAO,CAAC,mBAAmB;QACzD,WAAW,EAAmB,OAAO,CAAC,UAAU;QAChD,uEAAuE;QACvE,qEAAqE;QACrE,uEAAuE;QACvE,sEAAsE;QACtE,mCAAmC;QACnC,IAAI,EAA0B,IAAI;QAClC,UAAU,EAAoB,OAAO,CAAC,SAAS;QAC/C,MAAM,EAAwB,OAAO,CAAC,MAAM;QAC5C,SAAS,EAAqB,OAAO,CAAC,QAAQ,IAAI,IAAI;QACtD,YAAY,EAAkB,OAAO,CAAC,WAAW,IAAI,IAAI;QACzD,WAAW,EAAmB,OAAO,CAAC,UAAU,IAAI,IAAI;KACzD,CAAC;SACD,OAAO,EAAE,CAAC;IAEb,8BAA8B;IAC9B,MAAM,EAAE;SACL,UAAU,CAAC,eAAe,CAAC;SAC3B,MAAM,CAAC;QACN,GAAG;QACH,SAAS,EAAc,OAAO,CAAC,QAAQ;QACvC,KAAK,EAAkB,CAAC;QACxB,qBAAqB,EAAE,OAAO,CAAC,cAAc;QAC7C,sBAAsB,EAAE,OAAO,CAAC,mBAAmB;QACnD,eAAe,EAAQ,OAAO,CAAC,UAAU;KAC1C,CAAC;SACD,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,CACjB,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACpD,KAAK,EAAkB,GAAG,CAAA,GAAG,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,wBAAwB;QAC7E,qBAAqB,EAAE,GAAG,CAAA,GAAG,EAAE,CAAC,GAAG,CAAC,gCAAgC,CAAC,wCAAwC;QAC7G,sBAAsB,EAAE,GAAG,CAAA,GAAG,EAAE,CAAC,GAAG,CAAC,iCAAiC,CAAC,yCAAyC;QAChH,yEAAyE;QACzE,eAAe,EAAE,GAAG,CAAA,0DAA0D,EAAE,CAAC,GAAG,CAAC,0BAA0B,CAAC,+BAA+B;KAChJ,CAAC,CAAC,CACJ;SACA,OAAO,EAAE,CAAC;AACf,CAAC;AAeD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,EAAM,EACN,OAAe,EACf,QAAiB;IAEjB,MAAM,aAAa,GAAG,OAAO,GAAG,IAAI,CAAC;IAErC,IAAI,CAAC,GAAG,EAAE;SACP,UAAU,CAAC,SAAS,CAAC;SACrB,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;QACd,WAAW;QACX,EAAE,CAAC,EAAE,CAAC,KAAK,CAAS,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC;QACrC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAS,iBAAiB,CAAC,CAAC,EAAE,CAAC,uBAAuB,CAAC;QAChE,EAAE,CAAC,EAAE,CAAC,GAAG,CAAS,8BAA8B,CAAC,CAAC,EAAE,CAAC,wBAAwB,CAAC;QAC9E,EAAE,CAAC,EAAE,CAAC,GAAG,CAAS,aAAa,CAAC,CAAC,EAAE,CAAC,iBAAiB,CAAC;KACvD,CAAC;SACD,KAAK,CAAC,gBAAgB,EAAE,IAAI,EAAE,aAAa,CAAC;SAC5C,OAAO,CAAC,WAAW,CAAC;SACpB,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAE/B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;IAE/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtB,SAAS,EAAc,CAAC,CAAC,SAAS;QAClC,KAAK,EAAkB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QACtC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC;QACtD,sBAAsB,EAAE,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC;QACxD,eAAe,EAAQ,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC;QAChD,gBAAgB,EACd,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC;YAClC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC;YACxE,CAAC,CAAC,CAAC;KACR,CAAC,CAAC,CAAC;AACN,CAAC;AAeD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CAAC,WAA8B;IACvE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IAExD,MAAM,MAAM,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,CAAC,UAAkB,EAAU,EAAE;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACjE,OAAO,MAAM,CAAC,KAAK,CAAE,CAAC;IACxB,CAAC,CAAC;IAEF,OAAO,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;AACpD,CAAC;AAOD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,kCAAkC,CACtD,EAAM,EACN,OAAe,EACf,QAAiB;IAEjB,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC9D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEjC,MAAM,aAAa,GAAG,OAAO,GAAG,IAAI,CAAC;IACrC,IAAI,aAAa,GAAG,EAAE;SACnB,UAAU,CAAC,SAAS,CAAC;SACrB,MAAM,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;SACpC,KAAK,CAAC,gBAAgB,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IAChD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,aAAa,GAAG,aAAa,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC;IAEnD,MAAM,eAAe,GAAG,IAAI,GAAG,EAAoB,CAAC;IACpD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,QAAQ,KAAK,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;;YACtD,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC1B,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,0BAA0B,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9F,OAAO,EAAE,GAAG,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,EAAM,EACN,SAAiB;IAEjB,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,UAAU,CAAC,SAAS,CAAC;SACrB,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;QACd,WAAW;QACX,EAAE,CAAC,EAAE,CAAC,KAAK,CAAS,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC;QACrC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAS,iBAAiB,CAAC,CAAC,EAAE,CAAC,uBAAuB,CAAC;QAChE,EAAE,CAAC,EAAE,CAAC,GAAG,CAAS,8BAA8B,CAAC,CAAC,EAAE,CAAC,wBAAwB,CAAC;QAC9E,EAAE,CAAC,EAAE,CAAC,GAAG,CAAS,aAAa,CAAC,CAAC,EAAE,CAAC,iBAAiB,CAAC;KACvD,CAAC;SACD,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,SAAS,CAAC;SACnC,OAAO,CAAC,WAAW,CAAC;SACpB,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC;SAC3B,OAAO,EAAE,CAAC;IAEb,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtB,SAAS,EAAc,CAAC,CAAC,SAAS;QAClC,KAAK,EAAkB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QACtC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC;QACtD,sBAAsB,EAAE,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC;QACxD,eAAe,EAAQ,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC;QAChD,gBAAgB,EACd,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC;YAClC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC;YACxE,CAAC,CAAC,CAAC;KACR,CAAC,CAAC,CAAC;AACN,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAM,EAAE,QAAQ,GAAG,CAAC;IACtD,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC;IAC9D,MAAM,MAAM,GAAG,MAAM,EAAE;SACpB,UAAU,CAAC,SAAS,CAAC;SACrB,KAAK,CAAC,gBAAgB,EAAE,GAAG,EAAE,SAAS,CAAC;SACvC,gBAAgB,EAAE,CAAC;IACtB,OAAO,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAM,EAAE,QAAQ,GAAG,EAAE;IACvD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,GAAG,UAAU,CAAC;SACxD,WAAW,EAAE;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,MAAM,MAAM,GAAG,MAAM,EAAE;SACpB,UAAU,CAAC,eAAe,CAAC;SAC3B,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC;SACzB,gBAAgB,EAAE,CAAC;IACtB,OAAO,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;AACvC,CAAC;AAED,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAgB,EAChB,cAAsB,EACtB,mBAA2B,EAC3B,eAAkC,EAClC,UAAkB;IAElB,MAAM,UAAU,GACd,mBAAmB,GAAG,CAAC;QACrB,CAAC,CAAC,CAAC,GAAG,cAAc,GAAG,mBAAmB;QAC1C,CAAC,CAAC,CAAC,CAAC;IAER,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,eAAe,EAAE,cAAc;QAC/B,4BAA4B,EAAE,mBAAmB;QACjD,gBAAgB,EAAE,eAAe;QACjC,gBAAgB,EAAE,UAAU;QAC5B,WAAW,EAAE,UAAU;KACxB,CAAC;AACJ,CAAC;AAiBD,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACjC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAElC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,IAA6B,EAAE,QAAQ,GAAG,kBAAkB;IACxF,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEzC,MAAM,IAAI,GAAG,CAAC,UAAkB,EAAU,EAAE,CAC1C,IAAI,CAAC,SAAS,CAAC;QACb,UAAU,EAAE,IAAI;QAChB,eAAe,EAAE,IAAI,CAAC,MAAM;QAC5B,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;KACnC,CAAC,CAAC;IAEL,4EAA4E;IAC5E,2EAA2E;IAC3E,oEAAoE;IACpE,2EAA2E;IAC3E,oEAAoE;IACpE,IAAI,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/B,OAAO,OAAO,CAAC,MAAM,GAAG,QAAQ,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACnD,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;QACnE,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAAqC,EACrC,GAAG,GAAG,sBAAsB;IAE5B,IAAI,UAAU,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAEhE,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC;IAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,8EAA8E;AAC9E,wEAAwE;AACxE,qEAAqE;AACrE,aAAa;AACb,8EAA8E;AAE9E,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,SAA0B,EAAE,UAAU,GAAG,qBAAqB;IAC5F,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC;IAC9C,IAAI,cAAc,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,GAAG,UAAU,CAAC;IACxD,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,GAAG,IAAI;QACP,cAAc,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;QACnD,UAAU,EAAE,YAAY;KACzB,CAAC,CAAC;AACL,CAAC"}
|