@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.
Files changed (257) hide show
  1. package/LICENSE +21 -0
  2. package/MAST_SPEC.md +2926 -0
  3. package/README.md +842 -0
  4. package/assets/skill.md +45 -0
  5. package/dist/ast/extract.d.ts +65 -0
  6. package/dist/ast/extract.d.ts.map +1 -0
  7. package/dist/ast/extract.js +147 -0
  8. package/dist/ast/extract.js.map +1 -0
  9. package/dist/ast/extractor.d.ts +85 -0
  10. package/dist/ast/extractor.d.ts.map +1 -0
  11. package/dist/ast/extractor.js +2 -0
  12. package/dist/ast/extractor.js.map +1 -0
  13. package/dist/ast/extractors/markdown.d.ts +40 -0
  14. package/dist/ast/extractors/markdown.d.ts.map +1 -0
  15. package/dist/ast/extractors/markdown.js +175 -0
  16. package/dist/ast/extractors/markdown.js.map +1 -0
  17. package/dist/ast/extractors/typescript.d.ts +179 -0
  18. package/dist/ast/extractors/typescript.d.ts.map +1 -0
  19. package/dist/ast/extractors/typescript.js +1439 -0
  20. package/dist/ast/extractors/typescript.js.map +1 -0
  21. package/dist/ast/parser.d.ts +5 -0
  22. package/dist/ast/parser.d.ts.map +1 -0
  23. package/dist/ast/parser.js +21 -0
  24. package/dist/ast/parser.js.map +1 -0
  25. package/dist/ast/types.d.ts +586 -0
  26. package/dist/ast/types.d.ts.map +1 -0
  27. package/dist/ast/types.js +3 -0
  28. package/dist/ast/types.js.map +1 -0
  29. package/dist/cli/docs-cmd.d.ts +22 -0
  30. package/dist/cli/docs-cmd.d.ts.map +1 -0
  31. package/dist/cli/docs-cmd.js +100 -0
  32. package/dist/cli/docs-cmd.js.map +1 -0
  33. package/dist/cli/index-cmd.d.ts +72 -0
  34. package/dist/cli/index-cmd.d.ts.map +1 -0
  35. package/dist/cli/index-cmd.js +263 -0
  36. package/dist/cli/index-cmd.js.map +1 -0
  37. package/dist/cli/index.d.ts +3 -0
  38. package/dist/cli/index.d.ts.map +1 -0
  39. package/dist/cli/index.js +4 -0
  40. package/dist/cli/index.js.map +1 -0
  41. package/dist/cli/init.d.ts +14 -0
  42. package/dist/cli/init.d.ts.map +1 -0
  43. package/dist/cli/init.js +64 -0
  44. package/dist/cli/init.js.map +1 -0
  45. package/dist/cli/install-hooks.d.ts +3 -0
  46. package/dist/cli/install-hooks.d.ts.map +1 -0
  47. package/dist/cli/install-hooks.js +34 -0
  48. package/dist/cli/install-hooks.js.map +1 -0
  49. package/dist/cli/metrics-cmd.d.ts +3 -0
  50. package/dist/cli/metrics-cmd.d.ts.map +1 -0
  51. package/dist/cli/metrics-cmd.js +164 -0
  52. package/dist/cli/metrics-cmd.js.map +1 -0
  53. package/dist/cli/program.d.ts +14 -0
  54. package/dist/cli/program.d.ts.map +1 -0
  55. package/dist/cli/program.js +44 -0
  56. package/dist/cli/program.js.map +1 -0
  57. package/dist/cli/query.d.ts +46 -0
  58. package/dist/cli/query.d.ts.map +1 -0
  59. package/dist/cli/query.js +153 -0
  60. package/dist/cli/query.js.map +1 -0
  61. package/dist/cli/search-cmd.d.ts +33 -0
  62. package/dist/cli/search-cmd.d.ts.map +1 -0
  63. package/dist/cli/search-cmd.js +114 -0
  64. package/dist/cli/search-cmd.js.map +1 -0
  65. package/dist/cli/serve.d.ts +3 -0
  66. package/dist/cli/serve.d.ts.map +1 -0
  67. package/dist/cli/serve.js +19 -0
  68. package/dist/cli/serve.js.map +1 -0
  69. package/dist/cli/skill-install.d.ts +39 -0
  70. package/dist/cli/skill-install.d.ts.map +1 -0
  71. package/dist/cli/skill-install.js +57 -0
  72. package/dist/cli/skill-install.js.map +1 -0
  73. package/dist/cli/status.d.ts +35 -0
  74. package/dist/cli/status.d.ts.map +1 -0
  75. package/dist/cli/status.js +117 -0
  76. package/dist/cli/status.js.map +1 -0
  77. package/dist/cli/upgrade-cmd.d.ts +48 -0
  78. package/dist/cli/upgrade-cmd.d.ts.map +1 -0
  79. package/dist/cli/upgrade-cmd.js +129 -0
  80. package/dist/cli/upgrade-cmd.js.map +1 -0
  81. package/dist/cli/version.d.ts +3 -0
  82. package/dist/cli/version.d.ts.map +1 -0
  83. package/dist/cli/version.js +19 -0
  84. package/dist/cli/version.js.map +1 -0
  85. package/dist/env.d.ts +17 -0
  86. package/dist/env.d.ts.map +1 -0
  87. package/dist/env.js +17 -0
  88. package/dist/env.js.map +1 -0
  89. package/dist/graph/checker-resolver.d.ts +156 -0
  90. package/dist/graph/checker-resolver.d.ts.map +1 -0
  91. package/dist/graph/checker-resolver.js +458 -0
  92. package/dist/graph/checker-resolver.js.map +1 -0
  93. package/dist/graph/db.d.ts +318 -0
  94. package/dist/graph/db.d.ts.map +1 -0
  95. package/dist/graph/db.js +266 -0
  96. package/dist/graph/db.js.map +1 -0
  97. package/dist/graph/index.d.ts +4 -0
  98. package/dist/graph/index.d.ts.map +1 -0
  99. package/dist/graph/index.js +5 -0
  100. package/dist/graph/index.js.map +1 -0
  101. package/dist/graph/local-type-env.d.ts +45 -0
  102. package/dist/graph/local-type-env.d.ts.map +1 -0
  103. package/dist/graph/local-type-env.js +59 -0
  104. package/dist/graph/local-type-env.js.map +1 -0
  105. package/dist/graph/path-range.d.ts +40 -0
  106. package/dist/graph/path-range.d.ts.map +1 -0
  107. package/dist/graph/path-range.js +42 -0
  108. package/dist/graph/path-range.js.map +1 -0
  109. package/dist/graph/populate.d.ts +263 -0
  110. package/dist/graph/populate.d.ts.map +1 -0
  111. package/dist/graph/populate.js +984 -0
  112. package/dist/graph/populate.js.map +1 -0
  113. package/dist/graph/queries.d.ts +114 -0
  114. package/dist/graph/queries.d.ts.map +1 -0
  115. package/dist/graph/queries.js +415 -0
  116. package/dist/graph/queries.js.map +1 -0
  117. package/dist/graph/sqliteBatch.d.ts +48 -0
  118. package/dist/graph/sqliteBatch.d.ts.map +1 -0
  119. package/dist/graph/sqliteBatch.js +93 -0
  120. package/dist/graph/sqliteBatch.js.map +1 -0
  121. package/dist/indexer/freshness.d.ts +48 -0
  122. package/dist/indexer/freshness.d.ts.map +1 -0
  123. package/dist/indexer/freshness.js +72 -0
  124. package/dist/indexer/freshness.js.map +1 -0
  125. package/dist/indexer/import-resolver.d.ts +50 -0
  126. package/dist/indexer/import-resolver.d.ts.map +1 -0
  127. package/dist/indexer/import-resolver.js +318 -0
  128. package/dist/indexer/import-resolver.js.map +1 -0
  129. package/dist/indexer/index.d.ts +249 -0
  130. package/dist/indexer/index.d.ts.map +1 -0
  131. package/dist/indexer/index.js +560 -0
  132. package/dist/indexer/index.js.map +1 -0
  133. package/dist/indexer/walker.d.ts +60 -0
  134. package/dist/indexer/walker.d.ts.map +1 -0
  135. package/dist/indexer/walker.js +128 -0
  136. package/dist/indexer/walker.js.map +1 -0
  137. package/dist/indexer/watcher.d.ts +80 -0
  138. package/dist/indexer/watcher.d.ts.map +1 -0
  139. package/dist/indexer/watcher.js +151 -0
  140. package/dist/indexer/watcher.js.map +1 -0
  141. package/dist/mcp/context.d.ts +23 -0
  142. package/dist/mcp/context.d.ts.map +1 -0
  143. package/dist/mcp/context.js +2 -0
  144. package/dist/mcp/context.js.map +1 -0
  145. package/dist/mcp/register-tools.d.ts +21 -0
  146. package/dist/mcp/register-tools.d.ts.map +1 -0
  147. package/dist/mcp/register-tools.js +42 -0
  148. package/dist/mcp/register-tools.js.map +1 -0
  149. package/dist/mcp/server.d.ts +58 -0
  150. package/dist/mcp/server.d.ts.map +1 -0
  151. package/dist/mcp/server.js +177 -0
  152. package/dist/mcp/server.js.map +1 -0
  153. package/dist/mcp/staleness.d.ts +72 -0
  154. package/dist/mcp/staleness.d.ts.map +1 -0
  155. package/dist/mcp/staleness.js +213 -0
  156. package/dist/mcp/staleness.js.map +1 -0
  157. package/dist/mcp/startup.d.ts +40 -0
  158. package/dist/mcp/startup.d.ts.map +1 -0
  159. package/dist/mcp/startup.js +114 -0
  160. package/dist/mcp/startup.js.map +1 -0
  161. package/dist/mcp/tools/_helpers.d.ts +26 -0
  162. package/dist/mcp/tools/_helpers.d.ts.map +1 -0
  163. package/dist/mcp/tools/_helpers.js +54 -0
  164. package/dist/mcp/tools/_helpers.js.map +1 -0
  165. package/dist/mcp/tools/callers.d.ts +4 -0
  166. package/dist/mcp/tools/callers.d.ts.map +1 -0
  167. package/dist/mcp/tools/callers.js +112 -0
  168. package/dist/mcp/tools/callers.js.map +1 -0
  169. package/dist/mcp/tools/dependencies.d.ts +4 -0
  170. package/dist/mcp/tools/dependencies.d.ts.map +1 -0
  171. package/dist/mcp/tools/dependencies.js +36 -0
  172. package/dist/mcp/tools/dependencies.js.map +1 -0
  173. package/dist/mcp/tools/efficiency.d.ts +4 -0
  174. package/dist/mcp/tools/efficiency.d.ts.map +1 -0
  175. package/dist/mcp/tools/efficiency.js +39 -0
  176. package/dist/mcp/tools/efficiency.js.map +1 -0
  177. package/dist/mcp/tools/exports.d.ts +4 -0
  178. package/dist/mcp/tools/exports.d.ts.map +1 -0
  179. package/dist/mcp/tools/exports.js +56 -0
  180. package/dist/mcp/tools/exports.js.map +1 -0
  181. package/dist/mcp/tools/implementors.d.ts +4 -0
  182. package/dist/mcp/tools/implementors.d.ts.map +1 -0
  183. package/dist/mcp/tools/implementors.js +47 -0
  184. package/dist/mcp/tools/implementors.js.map +1 -0
  185. package/dist/mcp/tools/project-skeleton.d.ts +4 -0
  186. package/dist/mcp/tools/project-skeleton.d.ts.map +1 -0
  187. package/dist/mcp/tools/project-skeleton.js +61 -0
  188. package/dist/mcp/tools/project-skeleton.js.map +1 -0
  189. package/dist/mcp/tools/reindex.d.ts +14 -0
  190. package/dist/mcp/tools/reindex.d.ts.map +1 -0
  191. package/dist/mcp/tools/reindex.js +32 -0
  192. package/dist/mcp/tools/reindex.js.map +1 -0
  193. package/dist/mcp/tools/rename-impact.d.ts +4 -0
  194. package/dist/mcp/tools/rename-impact.d.ts.map +1 -0
  195. package/dist/mcp/tools/rename-impact.js +124 -0
  196. package/dist/mcp/tools/rename-impact.js.map +1 -0
  197. package/dist/mcp/tools/search.d.ts +4 -0
  198. package/dist/mcp/tools/search.d.ts.map +1 -0
  199. package/dist/mcp/tools/search.js +69 -0
  200. package/dist/mcp/tools/search.js.map +1 -0
  201. package/dist/mcp/tools/signature.d.ts +4 -0
  202. package/dist/mcp/tools/signature.d.ts.map +1 -0
  203. package/dist/mcp/tools/signature.js +127 -0
  204. package/dist/mcp/tools/signature.js.map +1 -0
  205. package/dist/mcp/tools/status.d.ts +4 -0
  206. package/dist/mcp/tools/status.d.ts.map +1 -0
  207. package/dist/mcp/tools/status.js +30 -0
  208. package/dist/mcp/tools/status.js.map +1 -0
  209. package/dist/search/declex.d.ts +109 -0
  210. package/dist/search/declex.d.ts.map +1 -0
  211. package/dist/search/declex.js +242 -0
  212. package/dist/search/declex.js.map +1 -0
  213. package/dist/search/fts.d.ts +77 -0
  214. package/dist/search/fts.d.ts.map +1 -0
  215. package/dist/search/fts.js +223 -0
  216. package/dist/search/fts.js.map +1 -0
  217. package/dist/search/fused.d.ts +110 -0
  218. package/dist/search/fused.d.ts.map +1 -0
  219. package/dist/search/fused.js +323 -0
  220. package/dist/search/fused.js.map +1 -0
  221. package/dist/search/potential-matches.d.ts +88 -0
  222. package/dist/search/potential-matches.d.ts.map +1 -0
  223. package/dist/search/potential-matches.js +83 -0
  224. package/dist/search/potential-matches.js.map +1 -0
  225. package/dist/search/scope.d.ts +66 -0
  226. package/dist/search/scope.d.ts.map +1 -0
  227. package/dist/search/scope.js +71 -0
  228. package/dist/search/scope.js.map +1 -0
  229. package/dist/store/config.d.ts +57 -0
  230. package/dist/store/config.d.ts.map +1 -0
  231. package/dist/store/config.js +208 -0
  232. package/dist/store/config.js.map +1 -0
  233. package/dist/store/lock.d.ts +101 -0
  234. package/dist/store/lock.d.ts.map +1 -0
  235. package/dist/store/lock.js +182 -0
  236. package/dist/store/lock.js.map +1 -0
  237. package/dist/store/lockMetrics.d.ts +52 -0
  238. package/dist/store/lockMetrics.d.ts.map +1 -0
  239. package/dist/store/lockMetrics.js +29 -0
  240. package/dist/store/lockMetrics.js.map +1 -0
  241. package/dist/store/sqliteChunkStore.d.ts +44 -0
  242. package/dist/store/sqliteChunkStore.d.ts.map +1 -0
  243. package/dist/store/sqliteChunkStore.js +135 -0
  244. package/dist/store/sqliteChunkStore.js.map +1 -0
  245. package/dist/telemetry/lockMetricsSummary.d.ts +42 -0
  246. package/dist/telemetry/lockMetricsSummary.d.ts.map +1 -0
  247. package/dist/telemetry/lockMetricsSummary.js +139 -0
  248. package/dist/telemetry/lockMetricsSummary.js.map +1 -0
  249. package/dist/telemetry/metrics.d.ts +140 -0
  250. package/dist/telemetry/metrics.d.ts.map +1 -0
  251. package/dist/telemetry/metrics.js +312 -0
  252. package/dist/telemetry/metrics.js.map +1 -0
  253. package/dist/telemetry/tokenizer.d.ts +125 -0
  254. package/dist/telemetry/tokenizer.d.ts.map +1 -0
  255. package/dist/telemetry/tokenizer.js +174 -0
  256. package/dist/telemetry/tokenizer.js.map +1 -0
  257. 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":"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"}