@munin-cli/core 0.0.0-stage → 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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/dist/analyze/build.d.ts +13 -0
  3. package/dist/analyze/build.d.ts.map +1 -0
  4. package/dist/analyze/build.js +223 -0
  5. package/dist/analyze/build.js.map +1 -0
  6. package/dist/analyze/cache.d.ts +18 -0
  7. package/dist/analyze/cache.d.ts.map +1 -0
  8. package/dist/analyze/cache.js +198 -0
  9. package/dist/analyze/cache.js.map +1 -0
  10. package/dist/analyze/day.d.ts +11 -0
  11. package/dist/analyze/day.d.ts.map +1 -0
  12. package/dist/analyze/day.js +33 -0
  13. package/dist/analyze/day.js.map +1 -0
  14. package/dist/analyze/findings/index.d.ts +3 -0
  15. package/dist/analyze/findings/index.d.ts.map +1 -0
  16. package/dist/analyze/findings/index.js +11 -0
  17. package/dist/analyze/findings/index.js.map +1 -0
  18. package/dist/analyze/findings/repeat-tool-calls.d.ts +16 -0
  19. package/dist/analyze/findings/repeat-tool-calls.d.ts.map +1 -0
  20. package/dist/analyze/findings/repeat-tool-calls.js +168 -0
  21. package/dist/analyze/findings/repeat-tool-calls.js.map +1 -0
  22. package/dist/analyze/findings/repeated-context.d.ts +22 -0
  23. package/dist/analyze/findings/repeated-context.d.ts.map +1 -0
  24. package/dist/analyze/findings/repeated-context.js +113 -0
  25. package/dist/analyze/findings/repeated-context.js.map +1 -0
  26. package/dist/analyze/findings.d.ts +64 -0
  27. package/dist/analyze/findings.d.ts.map +1 -0
  28. package/dist/analyze/findings.js +288 -0
  29. package/dist/analyze/findings.js.map +1 -0
  30. package/dist/analyze/index.d.ts +18 -0
  31. package/dist/analyze/index.d.ts.map +1 -0
  32. package/dist/analyze/index.js +18 -0
  33. package/dist/analyze/index.js.map +1 -0
  34. package/dist/analyze/projection.d.ts +21 -0
  35. package/dist/analyze/projection.d.ts.map +1 -0
  36. package/dist/analyze/projection.js +33 -0
  37. package/dist/analyze/projection.js.map +1 -0
  38. package/dist/analyze/recommend.d.ts +12 -0
  39. package/dist/analyze/recommend.d.ts.map +1 -0
  40. package/dist/analyze/recommend.js +78 -0
  41. package/dist/analyze/recommend.js.map +1 -0
  42. package/dist/analyze/results.d.ts +70 -0
  43. package/dist/analyze/results.d.ts.map +1 -0
  44. package/dist/analyze/results.js +189 -0
  45. package/dist/analyze/results.js.map +1 -0
  46. package/dist/analyze/rules/apply.d.ts +113 -0
  47. package/dist/analyze/rules/apply.d.ts.map +1 -0
  48. package/dist/analyze/rules/apply.js +251 -0
  49. package/dist/analyze/rules/apply.js.map +1 -0
  50. package/dist/analyze/rules/confidence.d.ts +20 -0
  51. package/dist/analyze/rules/confidence.d.ts.map +1 -0
  52. package/dist/analyze/rules/confidence.js +44 -0
  53. package/dist/analyze/rules/confidence.js.map +1 -0
  54. package/dist/analyze/rules/ported.d.ts +17 -0
  55. package/dist/analyze/rules/ported.d.ts.map +1 -0
  56. package/dist/analyze/rules/ported.js +701 -0
  57. package/dist/analyze/rules/ported.js.map +1 -0
  58. package/dist/analyze/rules/replay.d.ts +53 -0
  59. package/dist/analyze/rules/replay.d.ts.map +1 -0
  60. package/dist/analyze/rules/replay.js +179 -0
  61. package/dist/analyze/rules/replay.js.map +1 -0
  62. package/dist/analyze/rules.d.ts +32 -0
  63. package/dist/analyze/rules.d.ts.map +1 -0
  64. package/dist/analyze/rules.js +220 -0
  65. package/dist/analyze/rules.js.map +1 -0
  66. package/dist/analyze/sessions.d.ts +20 -0
  67. package/dist/analyze/sessions.d.ts.map +1 -0
  68. package/dist/analyze/sessions.js +131 -0
  69. package/dist/analyze/sessions.js.map +1 -0
  70. package/dist/analyze/snapshot-diff.d.ts +4 -0
  71. package/dist/analyze/snapshot-diff.d.ts.map +1 -0
  72. package/dist/analyze/snapshot-diff.js +63 -0
  73. package/dist/analyze/snapshot-diff.js.map +1 -0
  74. package/dist/analyze/summary.d.ts +25 -0
  75. package/dist/analyze/summary.d.ts.map +1 -0
  76. package/dist/analyze/summary.js +227 -0
  77. package/dist/analyze/summary.js.map +1 -0
  78. package/dist/analyze/turns.d.ts +23 -0
  79. package/dist/analyze/turns.d.ts.map +1 -0
  80. package/dist/analyze/turns.js +46 -0
  81. package/dist/analyze/turns.js.map +1 -0
  82. package/dist/analyze/verification.d.ts +29 -0
  83. package/dist/analyze/verification.d.ts.map +1 -0
  84. package/dist/analyze/verification.js +116 -0
  85. package/dist/analyze/verification.js.map +1 -0
  86. package/dist/diagnostics.d.ts +65 -0
  87. package/dist/diagnostics.d.ts.map +1 -0
  88. package/dist/diagnostics.js +59 -0
  89. package/dist/diagnostics.js.map +1 -0
  90. package/dist/domain/analysis.d.ts +243 -0
  91. package/dist/domain/analysis.d.ts.map +1 -0
  92. package/dist/domain/analysis.js +2 -0
  93. package/dist/domain/analysis.js.map +1 -0
  94. package/dist/domain/cost.d.ts +17 -0
  95. package/dist/domain/cost.d.ts.map +1 -0
  96. package/dist/domain/cost.js +17 -0
  97. package/dist/domain/cost.js.map +1 -0
  98. package/dist/domain/finding.d.ts +216 -0
  99. package/dist/domain/finding.d.ts.map +1 -0
  100. package/dist/domain/finding.js +8 -0
  101. package/dist/domain/finding.js.map +1 -0
  102. package/dist/domain/index.d.ts +10 -0
  103. package/dist/domain/index.d.ts.map +1 -0
  104. package/dist/domain/index.js +10 -0
  105. package/dist/domain/index.js.map +1 -0
  106. package/dist/domain/ledger.d.ts +87 -0
  107. package/dist/domain/ledger.d.ts.map +1 -0
  108. package/dist/domain/ledger.js +2 -0
  109. package/dist/domain/ledger.js.map +1 -0
  110. package/dist/domain/recommendation.d.ts +30 -0
  111. package/dist/domain/recommendation.d.ts.map +1 -0
  112. package/dist/domain/recommendation.js +2 -0
  113. package/dist/domain/recommendation.js.map +1 -0
  114. package/dist/domain/report.d.ts +193 -0
  115. package/dist/domain/report.d.ts.map +1 -0
  116. package/dist/domain/report.js +2 -0
  117. package/dist/domain/report.js.map +1 -0
  118. package/dist/domain/source.d.ts +96 -0
  119. package/dist/domain/source.d.ts.map +1 -0
  120. package/dist/domain/source.js +2 -0
  121. package/dist/domain/source.js.map +1 -0
  122. package/dist/domain/usage.d.ts +141 -0
  123. package/dist/domain/usage.d.ts.map +1 -0
  124. package/dist/domain/usage.js +9 -0
  125. package/dist/domain/usage.js.map +1 -0
  126. package/dist/domain/verification.d.ts +14 -0
  127. package/dist/domain/verification.d.ts.map +1 -0
  128. package/dist/domain/verification.js +2 -0
  129. package/dist/domain/verification.js.map +1 -0
  130. package/dist/index.d.ts +17 -0
  131. package/dist/index.d.ts.map +1 -0
  132. package/dist/index.js +19 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/ledger/cursor.d.ts +16 -0
  135. package/dist/ledger/cursor.d.ts.map +1 -0
  136. package/dist/ledger/cursor.js +43 -0
  137. package/dist/ledger/cursor.js.map +1 -0
  138. package/dist/ledger/schemas.d.ts +8 -0
  139. package/dist/ledger/schemas.d.ts.map +1 -0
  140. package/dist/ledger/schemas.js +179 -0
  141. package/dist/ledger/schemas.js.map +1 -0
  142. package/dist/ledger/slices.d.ts +9 -0
  143. package/dist/ledger/slices.d.ts.map +1 -0
  144. package/dist/ledger/slices.js +234 -0
  145. package/dist/ledger/slices.js.map +1 -0
  146. package/dist/normalize/merge.d.ts +11 -0
  147. package/dist/normalize/merge.d.ts.map +1 -0
  148. package/dist/normalize/merge.js +47 -0
  149. package/dist/normalize/merge.js.map +1 -0
  150. package/dist/normalize/opencode-v1.d.ts +10 -0
  151. package/dist/normalize/opencode-v1.d.ts.map +1 -0
  152. package/dist/normalize/opencode-v1.js +144 -0
  153. package/dist/normalize/opencode-v1.js.map +1 -0
  154. package/dist/normalize/opencode-v2.d.ts +20 -0
  155. package/dist/normalize/opencode-v2.d.ts.map +1 -0
  156. package/dist/normalize/opencode-v2.js +217 -0
  157. package/dist/normalize/opencode-v2.js.map +1 -0
  158. package/dist/normalize/reconcile.d.ts +20 -0
  159. package/dist/normalize/reconcile.d.ts.map +1 -0
  160. package/dist/normalize/reconcile.js +72 -0
  161. package/dist/normalize/reconcile.js.map +1 -0
  162. package/dist/normalize/rows.d.ts +81 -0
  163. package/dist/normalize/rows.d.ts.map +1 -0
  164. package/dist/normalize/rows.js +8 -0
  165. package/dist/normalize/rows.js.map +1 -0
  166. package/dist/normalize/text.d.ts +2 -0
  167. package/dist/normalize/text.d.ts.map +1 -0
  168. package/dist/normalize/text.js +13 -0
  169. package/dist/normalize/text.js.map +1 -0
  170. package/dist/normalize/time.d.ts +2 -0
  171. package/dist/normalize/time.d.ts.map +1 -0
  172. package/dist/normalize/time.js +7 -0
  173. package/dist/normalize/time.js.map +1 -0
  174. package/dist/normalize/tools.d.ts +4 -0
  175. package/dist/normalize/tools.d.ts.map +1 -0
  176. package/dist/normalize/tools.js +149 -0
  177. package/dist/normalize/tools.js.map +1 -0
  178. package/dist/pricing/catalogue/anthropic.d.ts +8 -0
  179. package/dist/pricing/catalogue/anthropic.d.ts.map +1 -0
  180. package/dist/pricing/catalogue/anthropic.js +169 -0
  181. package/dist/pricing/catalogue/anthropic.js.map +1 -0
  182. package/dist/pricing/catalogue/google.d.ts +9 -0
  183. package/dist/pricing/catalogue/google.d.ts.map +1 -0
  184. package/dist/pricing/catalogue/google.js +48 -0
  185. package/dist/pricing/catalogue/google.js.map +1 -0
  186. package/dist/pricing/catalogue/ollama.d.ts +9 -0
  187. package/dist/pricing/catalogue/ollama.d.ts.map +1 -0
  188. package/dist/pricing/catalogue/ollama.js +17 -0
  189. package/dist/pricing/catalogue/ollama.js.map +1 -0
  190. package/dist/pricing/catalogue/open.d.ts +9 -0
  191. package/dist/pricing/catalogue/open.d.ts.map +1 -0
  192. package/dist/pricing/catalogue/open.js +108 -0
  193. package/dist/pricing/catalogue/open.js.map +1 -0
  194. package/dist/pricing/catalogue/openai.d.ts +8 -0
  195. package/dist/pricing/catalogue/openai.d.ts.map +1 -0
  196. package/dist/pricing/catalogue/openai.js +291 -0
  197. package/dist/pricing/catalogue/openai.js.map +1 -0
  198. package/dist/pricing/catalogue/shared.d.ts +29 -0
  199. package/dist/pricing/catalogue/shared.d.ts.map +1 -0
  200. package/dist/pricing/catalogue/shared.js +86 -0
  201. package/dist/pricing/catalogue/shared.js.map +1 -0
  202. package/dist/pricing/cost.d.ts +42 -0
  203. package/dist/pricing/cost.d.ts.map +1 -0
  204. package/dist/pricing/cost.js +180 -0
  205. package/dist/pricing/cost.js.map +1 -0
  206. package/dist/pricing/index.d.ts +10 -0
  207. package/dist/pricing/index.d.ts.map +1 -0
  208. package/dist/pricing/index.js +11 -0
  209. package/dist/pricing/index.js.map +1 -0
  210. package/dist/pricing/providers.d.ts +19 -0
  211. package/dist/pricing/providers.d.ts.map +1 -0
  212. package/dist/pricing/providers.js +35 -0
  213. package/dist/pricing/providers.js.map +1 -0
  214. package/dist/pricing/resolve.d.ts +20 -0
  215. package/dist/pricing/resolve.d.ts.map +1 -0
  216. package/dist/pricing/resolve.js +79 -0
  217. package/dist/pricing/resolve.js.map +1 -0
  218. package/dist/pricing/types.d.ts +158 -0
  219. package/dist/pricing/types.d.ts.map +1 -0
  220. package/dist/pricing/types.js +2 -0
  221. package/dist/pricing/types.js.map +1 -0
  222. package/dist/report/report.d.ts +32 -0
  223. package/dist/report/report.d.ts.map +1 -0
  224. package/dist/report/report.js +269 -0
  225. package/dist/report/report.js.map +1 -0
  226. package/package.json +25 -4
  227. package/README.md +0 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 semideus-dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ import type { PricedDataset, Window } from "../domain/analysis.js";
2
+ import type { Dataset } from "../domain/source.js";
3
+ import type { UsageEvent } from "../domain/usage.js";
4
+ /** Stable source ordering shared by batch and ledger report builds. */
5
+ export declare function sortUsageEvents(events: readonly UsageEvent[]): UsageEvent[];
6
+ /** First event time to the last end (or start) time; never less than one day. */
7
+ export declare function windowOf(events: UsageEvent[]): Window;
8
+ /**
9
+ * Turn a dataset into priced events, every attempt, a window, and warnings.
10
+ * The input dataset is never mutated.
11
+ */
12
+ export declare function buildEvents(dataset: Dataset): PricedDataset;
13
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/analyze/build.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EACV,OAAO,EAKR,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAiB,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAkIpE,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,EAAE,CAE3E;AAQD,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAiCrD;AA2BD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAoC3D"}
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Build priced events from a Phase 2 dataset.
3
+ *
4
+ * This is the seam between what the source recorded and what reports read. It
5
+ * is pure: no filesystem, network, or clock access. Every price is selected
6
+ * from the call's own timestamp.
7
+ */
8
+ import { WARNING_CODES } from "../diagnostics.js";
9
+ import { billableTokens, estimateCost } from "../pricing/cost.js";
10
+ import { resolvePrice } from "../pricing/resolve.js";
11
+ import { messageContexts } from "./turns.js";
12
+ function isModelCall(message) {
13
+ return message.type === "assistant" || message.type === "compaction";
14
+ }
15
+ function schemaFor(dataset, session) {
16
+ if (session !== undefined)
17
+ return session.schema;
18
+ if (dataset.schema === "v1" || dataset.schema === "v2")
19
+ return dataset.schema;
20
+ // A message without its session should not happen. When it does, keep the
21
+ // event usable rather than throwing; the missing session is already visible
22
+ // in data health.
23
+ return "v2";
24
+ }
25
+ function latencyMs(start, end) {
26
+ if (end === undefined)
27
+ return undefined;
28
+ const startMs = Date.parse(start);
29
+ const endMs = Date.parse(end);
30
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs))
31
+ return undefined;
32
+ const delta = endMs - startMs;
33
+ return delta >= 0 ? delta : undefined;
34
+ }
35
+ function attemptFrom(message, session) {
36
+ const model = message.model ?? session?.model;
37
+ const agent = message.agent ?? session?.agent;
38
+ return {
39
+ id: message.id,
40
+ sessionId: message.sessionId,
41
+ ts: message.timeCreated,
42
+ type: message.type === "compaction" ? "compaction" : "assistant",
43
+ ...(model === undefined ? {} : { model: { ...model } }),
44
+ ...(agent === undefined ? {} : { agent }),
45
+ ...(message.usage === undefined
46
+ ? {}
47
+ : {
48
+ usage: {
49
+ tokens: { ...message.usage.tokens },
50
+ ...(message.usage.observedUsd === undefined
51
+ ? {}
52
+ : { observedUsd: message.usage.observedUsd }),
53
+ },
54
+ }),
55
+ ...(message.usage?.observedUsd === undefined ? {} : { observedUsd: message.usage.observedUsd }),
56
+ ...(message.stopReason === undefined ? {} : { stopReason: message.stopReason }),
57
+ ...(message.errorType === undefined ? {} : { errorType: message.errorType }),
58
+ };
59
+ }
60
+ function eventFrom(dataset, message, session, context) {
61
+ // The caller guarantees usage; the assertion keeps the map below total.
62
+ const usage = message.usage;
63
+ const billed = billableTokens(usage.tokens);
64
+ const model = message.model ?? session?.model;
65
+ const variant = message.model?.variant ?? session?.model?.variant;
66
+ const agent = message.agent ?? session?.agent;
67
+ const modelId = model?.model ?? "unknown";
68
+ const instructions = session?.instructions;
69
+ const endTs = message.completedAt;
70
+ const latency = latencyMs(message.timeCreated, endTs);
71
+ const turn = context ?? { contextEpoch: 0 };
72
+ return {
73
+ id: message.id,
74
+ source: "opencode",
75
+ schema: schemaFor(dataset, session),
76
+ provider: model?.provider ?? "unknown",
77
+ model: modelId,
78
+ ...(variant === undefined ? {} : { variant }),
79
+ ...(agent === undefined ? {} : { agent }),
80
+ ts: message.timeCreated,
81
+ sessionId: message.sessionId,
82
+ ...(session?.parentId === undefined ? {} : { parentSessionId: session.parentId }),
83
+ ...(session?.projectId === undefined ? {} : { projectId: session.projectId }),
84
+ project: session?.project ?? session?.projectId ?? "unknown",
85
+ ...(turn.turnId === undefined ? {} : { turnId: turn.turnId }),
86
+ ...(turn.turnStartedAt === undefined ? {} : { turnStartedAt: turn.turnStartedAt }),
87
+ ...(endTs === undefined ? {} : { endTs }),
88
+ contextEpoch: turn.contextEpoch,
89
+ inputTokens: billed.input,
90
+ // Priced output includes reasoning, which the provider bills as output.
91
+ outputTokens: billed.output,
92
+ thinkingTokens: billed.thinking,
93
+ cacheReadTokens: billed.cacheRead,
94
+ cacheWrite5mTokens: billed.cacheWrite,
95
+ cacheWrite1hTokens: 0,
96
+ ...(latency === undefined ? {} : { latencyMs: latency }),
97
+ ...(message.stopReason === undefined ? {} : { stopReason: message.stopReason }),
98
+ isSubagent: session?.parentId !== undefined,
99
+ tools: (message.toolCalls ?? []).map((tool) => ({ ...tool })),
100
+ ...(instructions === undefined
101
+ ? {}
102
+ : {
103
+ instructionKeys: [...instructions.keys],
104
+ instructionChars: instructions.chars,
105
+ prefixHash: instructions.prefixHash,
106
+ }),
107
+ cost: estimateCost({
108
+ tokens: billed,
109
+ model: modelId,
110
+ ...(usage.observedUsd === undefined ? {} : { observedUsd: usage.observedUsd }),
111
+ at: message.timeCreated,
112
+ }),
113
+ };
114
+ }
115
+ function compareEvents(a, b) {
116
+ if (a.ts !== b.ts)
117
+ return a.ts.localeCompare(b.ts);
118
+ if (a.sessionId !== b.sessionId)
119
+ return a.sessionId.localeCompare(b.sessionId);
120
+ return a.id.localeCompare(b.id);
121
+ }
122
+ /** Stable source ordering shared by batch and ledger report builds. */
123
+ export function sortUsageEvents(events) {
124
+ return [...events].sort(compareEvents);
125
+ }
126
+ function compareAttempts(a, b) {
127
+ if (a.ts !== b.ts)
128
+ return a.ts.localeCompare(b.ts);
129
+ if (a.sessionId !== b.sessionId)
130
+ return a.sessionId.localeCompare(b.sessionId);
131
+ return a.id.localeCompare(b.id);
132
+ }
133
+ /** First event time to the last end (or start) time; never less than one day. */
134
+ export function windowOf(events) {
135
+ const first = events[0];
136
+ if (first === undefined)
137
+ return { start: null, end: null, days: 0 };
138
+ let startMs = Date.parse(first.ts);
139
+ let start = first.ts;
140
+ let end = first.endTs ?? first.ts;
141
+ let endMs = Date.parse(end);
142
+ for (const event of events) {
143
+ const startCandidateMs = Date.parse(event.ts);
144
+ if (Number.isFinite(startCandidateMs) && startCandidateMs < startMs) {
145
+ // Same rule as sorted input: the earliest start wins. buildEvents() output
146
+ // is sorted, so this matches its window exactly and survives unsorted input.
147
+ startMs = startCandidateMs;
148
+ start = event.ts;
149
+ }
150
+ const candidate = event.endTs ?? event.ts;
151
+ const candidateMs = Date.parse(candidate);
152
+ if (Number.isFinite(candidateMs) && (!Number.isFinite(endMs) || candidateMs > endMs)) {
153
+ end = candidate;
154
+ endMs = candidateMs;
155
+ }
156
+ }
157
+ // startMs is always the earliest finite start: initialised from the first
158
+ // event and only ever lowered by a valid timestamp.
159
+ const days = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs >= startMs
160
+ ? Math.max(1, (endMs - startMs) / 86_400_000)
161
+ : 1;
162
+ return { start, end, days };
163
+ }
164
+ /**
165
+ * One `price.unpriced-model` per model with no card, aggregated with its call
166
+ * count and observed spend. An observed charge does not make a model priced: it
167
+ * makes that call's headline observed, but the model still cannot be estimated.
168
+ */
169
+ function addPricingWarnings(events, warnings) {
170
+ const unpriced = new Map();
171
+ for (const event of events) {
172
+ if (resolvePrice(event.model) !== null)
173
+ continue;
174
+ const row = unpriced.get(event.model) ?? { calls: 0, observedUsd: 0 };
175
+ row.calls += 1;
176
+ row.observedUsd += event.cost.observedUsd ?? 0;
177
+ unpriced.set(event.model, row);
178
+ }
179
+ for (const [model, row] of [...unpriced.entries()].sort(([a], [b]) => a.localeCompare(b))) {
180
+ warnings.push({
181
+ code: WARNING_CODES.priceUnpricedModel,
182
+ message: `No price card for ${model}: ${row.calls} call(s), observed spend $${row.observedUsd}`,
183
+ count: row.calls,
184
+ });
185
+ }
186
+ }
187
+ /**
188
+ * Turn a dataset into priced events, every attempt, a window, and warnings.
189
+ * The input dataset is never mutated.
190
+ */
191
+ export function buildEvents(dataset) {
192
+ const sessions = new Map(dataset.sessions.map((session) => [session.id, session]));
193
+ const contexts = messageContexts(dataset.messages);
194
+ const events = [];
195
+ const attempts = [];
196
+ for (const message of dataset.messages) {
197
+ if (!isModelCall(message))
198
+ continue;
199
+ const session = sessions.get(message.sessionId);
200
+ attempts.push(attemptFrom(message, session));
201
+ if (message.usage === undefined)
202
+ continue;
203
+ events.push(eventFrom(dataset, message, session, contexts.get(message.id)));
204
+ }
205
+ events.sort(compareEvents);
206
+ attempts.sort(compareAttempts);
207
+ const warnings = [...dataset.warnings];
208
+ addPricingWarnings(events, warnings);
209
+ if (events.length === 0) {
210
+ warnings.push({
211
+ code: WARNING_CODES.analysisNoEvents,
212
+ message: "No usage-bearing model calls were found",
213
+ });
214
+ }
215
+ return {
216
+ schema: dataset.schema,
217
+ events,
218
+ attempts,
219
+ window: windowOf(events),
220
+ warnings,
221
+ };
222
+ }
223
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/analyze/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Cache read/write analysis.
3
+ *
4
+ * Descriptive only: numbers here describe what happened, and nothing claims a
5
+ * low hit rate is waste (that judgement is a detector in Phase 4). Cache costs
6
+ * are always the list-price estimate; the observed total is never rebuilt from
7
+ * cache categories.
8
+ */
9
+ import type { CacheStats, CacheStatsOptions } from "../domain/analysis.js";
10
+ import type { UsageEvent } from "../domain/usage.js";
11
+ /**
12
+ * Cache read/write behaviour over priced events.
13
+ *
14
+ * Events are consumed in the order given; buildEvents() output is already in a
15
+ * stable order.
16
+ */
17
+ export declare function cacheStats(events: UsageEvent[], options?: CacheStatsOptions): CacheStats;
18
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/analyze/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAEV,UAAU,EACV,iBAAiB,EAIlB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAqGrD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,OAAO,GAAE,iBAAsB,GAAG,UAAU,CAqC5F"}
@@ -0,0 +1,198 @@
1
+ import { dayKey } from "./day.js";
2
+ /** Codepoint order, so results never depend on the runtime locale. */
3
+ function asc(a, b) {
4
+ return a < b ? -1 : a > b ? 1 : 0;
5
+ }
6
+ function emptyAcc() {
7
+ return {
8
+ calls: 0,
9
+ inputTokens: 0,
10
+ readTokens: 0,
11
+ writeTokens: 0,
12
+ write5mTokens: 0,
13
+ write1hTokens: 0,
14
+ estimatedReadUsd: 0,
15
+ estimatedWriteUsd: 0,
16
+ };
17
+ }
18
+ function addEvent(acc, event) {
19
+ acc.calls += 1;
20
+ acc.inputTokens += event.inputTokens;
21
+ acc.readTokens += event.cacheReadTokens;
22
+ acc.write5mTokens += event.cacheWrite5mTokens;
23
+ acc.write1hTokens += event.cacheWrite1hTokens;
24
+ acc.writeTokens += event.cacheWrite5mTokens + event.cacheWrite1hTokens;
25
+ acc.estimatedReadUsd += event.cost.cacheRead;
26
+ acc.estimatedWriteUsd += event.cost.cacheWrite;
27
+ }
28
+ function hitRate(acc) {
29
+ const inputSide = acc.inputTokens + acc.readTokens + acc.writeTokens;
30
+ return inputSide === 0 ? null : acc.readTokens / inputSide;
31
+ }
32
+ function accumulate(events) {
33
+ const acc = emptyAcc();
34
+ for (const event of events)
35
+ addEvent(acc, event);
36
+ return acc;
37
+ }
38
+ function toBreakdown(key, events) {
39
+ const acc = accumulate(events);
40
+ // Change over the window: the later half of the calls against the earlier
41
+ // half, in event order. With an odd count the middle call is in neither.
42
+ const mid = Math.floor(events.length / 2);
43
+ let hitRateChange = null;
44
+ if (mid > 0) {
45
+ const earlier = accumulate(events.slice(0, mid));
46
+ const later = accumulate(events.slice(events.length - mid));
47
+ const earlierRate = hitRate(earlier);
48
+ const laterRate = hitRate(later);
49
+ if (earlierRate !== null && laterRate !== null)
50
+ hitRateChange = laterRate - earlierRate;
51
+ }
52
+ return {
53
+ key,
54
+ calls: acc.calls,
55
+ inputTokens: acc.inputTokens,
56
+ readTokens: acc.readTokens,
57
+ writeTokens: acc.writeTokens,
58
+ write5mTokens: acc.write5mTokens,
59
+ write1hTokens: acc.write1hTokens,
60
+ hitRate: hitRate(acc),
61
+ estimatedReadUsd: acc.estimatedReadUsd,
62
+ estimatedWriteUsd: acc.estimatedWriteUsd,
63
+ hitRateChange,
64
+ };
65
+ }
66
+ function byCacheSpendDescThenKey(a, b) {
67
+ const aCache = a.estimatedReadUsd + a.estimatedWriteUsd;
68
+ const bCache = b.estimatedReadUsd + b.estimatedWriteUsd;
69
+ if (aCache !== bCache)
70
+ return bCache - aCache;
71
+ return asc(a.key, b.key);
72
+ }
73
+ function breakdown(map) {
74
+ const rows = [];
75
+ for (const [key, events] of map)
76
+ rows.push(toBreakdown(key, events));
77
+ return rows.sort(byCacheSpendDescThenKey);
78
+ }
79
+ /**
80
+ * Cache read/write behaviour over priced events.
81
+ *
82
+ * Events are consumed in the order given; buildEvents() output is already in a
83
+ * stable order.
84
+ */
85
+ export function cacheStats(events, options = {}) {
86
+ const zone = options.zone ?? "UTC";
87
+ const totals = emptyAcc();
88
+ const byModel = new Map();
89
+ const byProvider = new Map();
90
+ const bySession = new Map();
91
+ const byDay = new Map();
92
+ for (const event of events) {
93
+ addEvent(totals, event);
94
+ pushTo(byModel, `${event.provider}/${event.model}`, event);
95
+ pushTo(byProvider, event.provider, event);
96
+ pushTo(bySession, event.sessionId, event);
97
+ pushTo(byDay, dayKey(event.ts, zone), event);
98
+ }
99
+ const estimatedTotal = events.reduce((sum, event) => sum + (event.cost.estimatedUsd ?? 0), 0);
100
+ const cacheUsd = totals.estimatedReadUsd + totals.estimatedWriteUsd;
101
+ return {
102
+ inputTokens: totals.inputTokens,
103
+ readTokens: totals.readTokens,
104
+ writeTokens: totals.writeTokens,
105
+ write5mTokens: totals.write5mTokens,
106
+ write1hTokens: totals.write1hTokens,
107
+ hitRate: hitRate(totals),
108
+ estimatedReadUsd: totals.estimatedReadUsd,
109
+ estimatedWriteUsd: totals.estimatedWriteUsd,
110
+ shareOfEstimate: estimatedTotal === 0 ? 0 : cacheUsd / estimatedTotal,
111
+ writesRecorded: totals.writeTokens > 0,
112
+ byModel: breakdown(byModel),
113
+ byProvider: breakdown(byProvider),
114
+ bySession: breakdown(bySession),
115
+ byDay: breakdown(byDay),
116
+ instructionContext: instructionContextOf(events),
117
+ };
118
+ }
119
+ function pushTo(map, key, event) {
120
+ const list = map.get(key);
121
+ if (list === undefined)
122
+ map.set(key, [event]);
123
+ else
124
+ list.push(event);
125
+ }
126
+ /**
127
+ * Instruction context, grouped by the cache identity
128
+ * `prefixHash + agent + model + variant`, never the prefix hash alone: two
129
+ * agents on one model share nothing at request time.
130
+ */
131
+ function instructionContextOf(events) {
132
+ const seenSessions = new Set();
133
+ const prefixHashes = new Set();
134
+ let totalChars = 0;
135
+ const groups = new Map();
136
+ for (const event of events) {
137
+ const hasInstructions = event.instructionChars !== undefined || event.prefixHash !== undefined;
138
+ if (hasInstructions && !seenSessions.has(event.sessionId)) {
139
+ // Instruction size and prefix are session properties; count them on the
140
+ // first observed event of the session, never per call.
141
+ seenSessions.add(event.sessionId);
142
+ if (event.instructionChars !== undefined)
143
+ totalChars += event.instructionChars;
144
+ if (event.prefixHash !== undefined)
145
+ prefixHashes.add(event.prefixHash);
146
+ }
147
+ if (event.prefixHash !== undefined) {
148
+ const agent = event.agent ?? "unknown";
149
+ const model = `${event.provider}/${event.model}`;
150
+ const variant = event.variant ?? undefined;
151
+ const key = `${event.prefixHash}\u0000${agent}\u0000${model}\u0000${variant ?? "-"}`;
152
+ const group = groups.get(key);
153
+ if (group === undefined) {
154
+ groups.set(key, {
155
+ prefixHash: event.prefixHash,
156
+ agent,
157
+ model,
158
+ variant,
159
+ sessions: new Set([event.sessionId]),
160
+ calls: 1,
161
+ uncachedInputTokens: event.inputTokens,
162
+ });
163
+ }
164
+ else {
165
+ group.sessions.add(event.sessionId);
166
+ group.calls += 1;
167
+ group.uncachedInputTokens += event.inputTokens;
168
+ }
169
+ }
170
+ }
171
+ const sharedPrefixes = [];
172
+ for (const group of groups.values()) {
173
+ if (group.sessions.size < 2)
174
+ continue;
175
+ sharedPrefixes.push({
176
+ prefixHash: group.prefixHash,
177
+ agent: group.agent,
178
+ model: group.model,
179
+ ...(group.variant === undefined ? {} : { variant: group.variant }),
180
+ sessions: group.sessions.size,
181
+ calls: group.calls,
182
+ uncachedInputTokens: group.uncachedInputTokens,
183
+ });
184
+ }
185
+ sharedPrefixes.sort((a, b) => {
186
+ if (a.uncachedInputTokens !== b.uncachedInputTokens) {
187
+ return b.uncachedInputTokens - a.uncachedInputTokens;
188
+ }
189
+ return asc(a.prefixHash, b.prefixHash);
190
+ });
191
+ return {
192
+ sessions: seenSessions.size,
193
+ totalChars,
194
+ distinctPrefixHashes: prefixHashes.size,
195
+ sharedPrefixes,
196
+ };
197
+ }
198
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/analyze/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Day bucketing.
3
+ *
4
+ * UTC is the default. For non-UTC zones the local calendar day comes from
5
+ * `Intl.DateTimeFormat.formatToParts`, never from slicing the ISO string: a
6
+ * slice is only correct when the string is already in the bucketing zone.
7
+ */
8
+ import type { DayZone } from "../domain/analysis.js";
9
+ /** The calendar day one timestamp falls on in `zone`, as `YYYY-MM-DD`. */
10
+ export declare function dayKey(iso: string, zone?: DayZone): string;
11
+ //# sourceMappingURL=day.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"day.d.ts","sourceRoot":"","sources":["../../src/analyze/day.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAuBrD,0EAA0E;AAC1E,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,OAAe,GAAG,MAAM,CAejE"}
@@ -0,0 +1,33 @@
1
+ const formatters = new Map();
2
+ function pad(part) {
3
+ return part < 10 ? `0${part}` : String(part);
4
+ }
5
+ function formatterFor(zone) {
6
+ const cached = formatters.get(zone);
7
+ if (cached !== undefined)
8
+ return cached;
9
+ // en-CA renders year-month-day, so the parts are unambiguous.
10
+ const created = new Intl.DateTimeFormat("en-CA", {
11
+ timeZone: zone === "local" ? undefined : zone,
12
+ year: "numeric",
13
+ month: "2-digit",
14
+ day: "2-digit",
15
+ });
16
+ formatters.set(zone, created);
17
+ return created;
18
+ }
19
+ /** The calendar day one timestamp falls on in `zone`, as `YYYY-MM-DD`. */
20
+ export function dayKey(iso, zone = "UTC") {
21
+ const ms = Date.parse(iso);
22
+ if (!Number.isFinite(ms))
23
+ return "unknown";
24
+ if (zone === "UTC") {
25
+ // The UTC day comes straight from the parsed instant.
26
+ const date = new Date(ms);
27
+ return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}`;
28
+ }
29
+ const parts = formatterFor(zone).formatToParts(ms);
30
+ const read = (type) => parts.find((part) => part.type === type)?.value ?? "";
31
+ return `${read("year")}-${read("month")}-${read("day")}`;
32
+ }
33
+ //# sourceMappingURL=day.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"day.js","sourceRoot":"","sources":["../../src/analyze/day.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,GAAG,IAAI,GAAG,EAA+B,CAAC;AAE1D,SAAS,GAAG,CAAC,IAAY;IACvB,OAAO,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAExC,8DAA8D;IAC9D,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;QAC/C,QAAQ,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;QAC7C,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,SAAS;KACf,CAAC,CAAC;IACH,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,MAAM,CAAC,GAAW,EAAE,OAAgB,KAAK;IACvD,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IAE3C,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,sDAAsD;QACtD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1B,OAAO,GAAG,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC;IAC7F,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,CAAC,IAAkC,EAAU,EAAE,CAC1D,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;IAExD,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { detectRepeatToolCalls } from "./repeat-tool-calls.js";
2
+ export { detectRepeatedContext, REPEATED_CONTEXT_THRESHOLDS } from "./repeated-context.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analyze/findings/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Finding detectors and thresholds shared by tests and the rule registry.
3
+ *
4
+ * The detector list itself now lives in `analyze/rules.ts` so rule metadata and
5
+ * detector wiring stay together.
6
+ */
7
+ import { detectRepeatToolCalls } from "./repeat-tool-calls.js";
8
+ import { detectRepeatedContext, REPEATED_CONTEXT_THRESHOLDS } from "./repeated-context.js";
9
+ export { detectRepeatToolCalls } from "./repeat-tool-calls.js";
10
+ export { detectRepeatedContext, REPEATED_CONTEXT_THRESHOLDS } from "./repeated-context.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analyze/findings/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAE3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The repeated-read detector: the same read-only tool call made again in one
3
+ * context, with nothing changed in between.
4
+ *
5
+ * A duplicate is not one extra read: it is written into the cacheable prefix
6
+ * and re-read by every later call in the context, so it compounds. An edit to
7
+ * the file, or any change at all for a search, makes the next read fresh. A
8
+ * subagent starts with an empty context, so its reads are not repeats of the
9
+ * parent; neither is a read after a compaction.
10
+ *
11
+ * Unknown tools count as mutating, so duplicates are under-counted, never
12
+ * over-counted (Phase 3 plan, Step 6).
13
+ */
14
+ import type { DetectionContext, FindingDraft } from "../../domain/finding.js";
15
+ export declare function detectRepeatToolCalls(context: DetectionContext): readonly FindingDraft[];
16
+ //# sourceMappingURL=repeat-tool-calls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repeat-tool-calls.d.ts","sourceRoot":"","sources":["../../../src/analyze/findings/repeat-tool-calls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAqF9E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,YAAY,EAAE,CA+GxF"}