@clear-capabilities/agentic-security-scanner 0.184.0 → 0.186.1

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 (228) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/agentic-security.js +111 -1
  3. package/dist/1128.index.js +912 -0
  4. package/dist/1665.index.js +828 -0
  5. package/dist/2584.index.js +1 -1
  6. package/dist/4171.index.js +2 -2
  7. package/dist/5752.index.js +466 -0
  8. package/dist/7405.index.js +1 -1
  9. package/dist/8158.index.js +1713 -0
  10. package/dist/8798.index.js +1 -1
  11. package/dist/9382.index.js +2 -2
  12. package/dist/9579.index.js +1 -1
  13. package/dist/agentic-security.mjs +29 -29
  14. package/dist/agentic-security.mjs.sha256 +1 -1
  15. package/dist/{dad58d1113a12c23a142.json → ca62ee93079be4c24eee.json} +29 -12
  16. package/dist/frontend/src/shell.js +12 -2
  17. package/package.json +29 -12
  18. package/src/dataflow/CLAUDE.md +46 -1
  19. package/src/dataflow/access-paths.js +12 -2
  20. package/src/dataflow/backward.js +42 -7
  21. package/src/dataflow/cross-service-taint.js +83 -1
  22. package/src/dataflow/demand-query.js +1168 -0
  23. package/src/dataflow/dep-fingerprint.js +718 -0
  24. package/src/dataflow/engine.js +48 -6
  25. package/src/dataflow/exploit-prover.js +6 -2
  26. package/src/dataflow/ifds-precise.js +22 -0
  27. package/src/dataflow/incremental-replay.js +118 -0
  28. package/src/dataflow/incremental-session.js +257 -0
  29. package/src/dataflow/incremental.js +237 -35
  30. package/src/dataflow/index.js +25 -94
  31. package/src/dataflow/ir-cache.js +268 -0
  32. package/src/dataflow/privacy-taint.js +7 -0
  33. package/src/dataflow/summaries.js +72 -6
  34. package/src/dataflow/tabulation.js +80 -24
  35. package/src/engine.js +113 -8
  36. package/src/graph/CLAUDE.md +31 -0
  37. package/src/graph/adapters/artifact.js +81 -0
  38. package/src/graph/adapters/crossrepo.js +75 -0
  39. package/src/graph/adapters/dataflow.js +76 -0
  40. package/src/graph/adapters/provenance.js +92 -0
  41. package/src/graph/adapters/semantic.js +89 -0
  42. package/src/graph/crosswalk-builders.js +152 -0
  43. package/src/graph/crosswalk.js +146 -0
  44. package/src/graph/index.js +15 -0
  45. package/src/graph/observation.js +57 -0
  46. package/src/graph/refs.js +105 -0
  47. package/src/graph/registry.js +129 -0
  48. package/src/graph/relations.js +81 -0
  49. package/src/graph/scc.js +241 -0
  50. package/src/graph/view.js +254 -0
  51. package/src/ir/CLAUDE.md +4 -0
  52. package/src/ir/callgraph.js +36 -8
  53. package/src/ir/index.js +18 -4
  54. package/src/ir/parser-js.js +10 -0
  55. package/src/language/CLAUDE.md +13 -0
  56. package/src/language/nix-option-types.js +170 -0
  57. package/src/language/nix-script-taint.js +46 -3
  58. package/src/language/nix-secrets.js +20 -2
  59. package/src/lineage/CLAUDE.md +72 -8
  60. package/src/lineage/DESIGN_GRAPH_BUILDER.md +1 -1
  61. package/src/lineage/DESIGN_PATH_PROVENANCE.md +6 -0
  62. package/src/lineage/boundary-contract.js +273 -0
  63. package/src/lineage/completion.js +205 -0
  64. package/src/lineage/continuation.js +140 -0
  65. package/src/lineage/control-credit.js +313 -0
  66. package/src/lineage/correlation.js +199 -0
  67. package/src/lineage/coverage.js +8 -1
  68. package/src/lineage/cross-repo-link.js +22 -0
  69. package/src/lineage/cut-planner.js +548 -0
  70. package/src/lineage/driver.js +219 -66
  71. package/src/lineage/engine.js +166 -21
  72. package/src/lineage/export-json.js +5 -0
  73. package/src/lineage/graph-builder.js +84 -7
  74. package/src/lineage/haskell-view.js +59 -0
  75. package/src/lineage/index.js +3 -0
  76. package/src/lineage/nix-view.js +35 -0
  77. package/src/lineage/owned-build.js +77 -0
  78. package/src/lineage/owned-worker-entry.js +22 -0
  79. package/src/lineage/owned-worker.js +95 -0
  80. package/src/lineage/path-evidence.js +123 -0
  81. package/src/lineage/path-predicates.js +529 -0
  82. package/src/lineage/path-query.js +189 -20
  83. package/src/lineage/path-store.js +191 -17
  84. package/src/lineage/scc-solver.js +186 -0
  85. package/src/lineage/smt-orchestrator.js +174 -0
  86. package/src/lineage/smt-z3-backend.js +294 -0
  87. package/src/lineage/source-seeding.js +36 -2
  88. package/src/lineage/summaries.js +66 -4
  89. package/src/lineage/value-versions.js +130 -0
  90. package/src/lineage/witness-selection.js +95 -0
  91. package/src/pipeline/scan-health.js +18 -0
  92. package/src/posture/CLAUDE.md +17 -0
  93. package/src/posture/business-logic.js +16 -7
  94. package/src/posture/dag-commands.js +442 -0
  95. package/src/posture/evidence-bundle.js +1 -1
  96. package/src/posture/finding-defaults.js +19 -0
  97. package/src/posture/git-history.js +91 -1
  98. package/src/posture/witness-bundle.js +573 -0
  99. package/src/posture/witness-runtime.js +276 -0
  100. package/src/report/index.js +2 -0
  101. package/src/sast/CLAUDE.md +2 -0
  102. package/src/sast/_call-args.js +70 -0
  103. package/src/sast/init-scope.js +87 -0
  104. package/src/sast/listen-bind-address.js +49 -0
  105. package/src/sast/redirect-target.js +106 -0
  106. package/src/sast/reflected-input.js +76 -0
  107. package/src/sast/send-file-root.js +27 -0
  108. package/src/sast/sensitive-data-logging.js +38 -1
  109. package/dist/119.index.js +0 -197
  110. package/dist/128.index.js +0 -129
  111. package/dist/1293.index.js +0 -5689
  112. package/dist/1336.index.js +0 -16182
  113. package/dist/1351.index.js +0 -133
  114. package/dist/1387.index.js +0 -271
  115. package/dist/1437.index.js +0 -23
  116. package/dist/1445.index.js +0 -110
  117. package/dist/1505.index.js +0 -151
  118. package/dist/1557.index.js +0 -293
  119. package/dist/1621.index.js +0 -311
  120. package/dist/1751.index.js +0 -160
  121. package/dist/1765.index.js +0 -727
  122. package/dist/1861.index.js +0 -690
  123. package/dist/1906.index.js +0 -433
  124. package/dist/208.index.js +0 -1631
  125. package/dist/2223.index.js +0 -159
  126. package/dist/2238.index.js +0 -295
  127. package/dist/2530.index.js +0 -152
  128. package/dist/2565.index.js +0 -116
  129. package/dist/2574.index.js +0 -127
  130. package/dist/2620.index.js +0 -345
  131. package/dist/2773.index.js +0 -98
  132. package/dist/2775.index.js +0 -32
  133. package/dist/2966.index.js +0 -171
  134. package/dist/2972.index.js +0 -87
  135. package/dist/2991.index.js +0 -131
  136. package/dist/3063.index.js +0 -833
  137. package/dist/3075.index.js +0 -227
  138. package/dist/3121.index.js +0 -297
  139. package/dist/3314.index.js +0 -263
  140. package/dist/3480.index.js +0 -1822
  141. package/dist/3481.index.js +0 -24
  142. package/dist/3506.index.js +0 -268
  143. package/dist/3533.index.js +0 -591
  144. package/dist/3653.index.js +0 -450
  145. package/dist/3740.index.js +0 -722
  146. package/dist/3822.index.js +0 -131
  147. package/dist/3868.index.js +0 -292
  148. package/dist/3918.index.js +0 -192
  149. package/dist/4090.index.js +0 -288
  150. package/dist/4113.index.js +0 -1286
  151. package/dist/4121.index.js +0 -1190
  152. package/dist/4142.index.js +0 -690
  153. package/dist/4168.index.js +0 -509
  154. package/dist/4281.index.js +0 -993
  155. package/dist/4336.index.js +0 -436
  156. package/dist/4387.index.js +0 -532
  157. package/dist/4413.index.js +0 -302
  158. package/dist/4476.index.js +0 -4510
  159. package/dist/4491.index.js +0 -133
  160. package/dist/4538.index.js +0 -185
  161. package/dist/4684.index.js +0 -319
  162. package/dist/4740.index.js +0 -165
  163. package/dist/4780.index.js +0 -209
  164. package/dist/4802.index.js +0 -225
  165. package/dist/4812.index.js +0 -379
  166. package/dist/4835.index.js +0 -126
  167. package/dist/494.index.js +0 -226
  168. package/dist/4991.index.js +0 -549
  169. package/dist/5199.index.js +0 -305
  170. package/dist/5216.index.js +0 -109
  171. package/dist/5439.index.js +0 -301
  172. package/dist/5443.index.js +0 -97
  173. package/dist/5496.index.js +0 -377
  174. package/dist/5738.index.js +0 -240
  175. package/dist/5801.index.js +0 -588
  176. package/dist/5817.index.js +0 -714
  177. package/dist/5858.index.js +0 -223
  178. package/dist/5933.index.js +0 -143
  179. package/dist/601.index.js +0 -23
  180. package/dist/6173.index.js +0 -273
  181. package/dist/6291.index.js +0 -1871
  182. package/dist/6312.index.js +0 -666
  183. package/dist/634.index.js +0 -16182
  184. package/dist/644.index.js +0 -87
  185. package/dist/6508.index.js +0 -283
  186. package/dist/6560.index.js +0 -645
  187. package/dist/6576.index.js +0 -422
  188. package/dist/6579.index.js +0 -271
  189. package/dist/6655.index.js +0 -3221
  190. package/dist/6745.index.js +0 -305
  191. package/dist/6818.index.js +0 -21642
  192. package/dist/6932.index.js +0 -1190
  193. package/dist/6972.index.js +0 -167
  194. package/dist/6995.index.js +0 -163
  195. package/dist/7249.index.js +0 -191
  196. package/dist/7372.index.js +0 -1197
  197. package/dist/7546.index.js +0 -541
  198. package/dist/76.index.js +0 -448
  199. package/dist/7879.index.js +0 -163
  200. package/dist/8025.index.js +0 -130
  201. package/dist/8054.index.js +0 -248
  202. package/dist/808.index.js +0 -1330
  203. package/dist/8097.index.js +0 -201
  204. package/dist/8126.index.js +0 -191
  205. package/dist/8178.index.js +0 -78
  206. package/dist/8382.index.js +0 -93
  207. package/dist/8408.index.js +0 -261
  208. package/dist/8540.index.js +0 -174
  209. package/dist/8635.index.js +0 -802
  210. package/dist/8688.index.js +0 -410
  211. package/dist/8699.index.js +0 -779
  212. package/dist/8891.index.js +0 -457
  213. package/dist/8916.index.js +0 -885
  214. package/dist/9146.index.js +0 -170
  215. package/dist/9168.index.js +0 -841
  216. package/dist/9292.index.js +0 -369
  217. package/dist/9422.index.js +0 -866
  218. package/dist/9440.index.js +0 -453
  219. package/dist/9479.index.js +0 -152
  220. package/dist/9481.index.js +0 -220
  221. package/dist/9539.index.js +0 -100
  222. package/dist/9547.index.js +0 -103
  223. package/dist/9692.index.js +0 -90
  224. package/dist/9800.index.js +0 -462
  225. package/dist/9899.index.js +0 -138
  226. package/dist/9981.index.js +0 -206
  227. package/dist/999.index.js +0 -117
  228. package/dist/9999.index.js +0 -1698
@@ -1,151 +0,0 @@
1
- export const id = 1505;
2
- export const ids = [1505];
3
- export const modules = {
4
-
5
- /***/ 61505:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ computeRuntimeAssurance: () => (/* binding */ computeRuntimeAssurance)
10
- /* harmony export */ });
11
- /* unused harmony export RUNTIME_STATUS_VALUES */
12
- // runtime-assurance.js — PRD "Runtime-Corroborated Security Twin" P0-8
13
- // (Runtime Assurance). A pure aggregation over already-loaded observation
14
- // imports and an already-computed `correlateObservations` result — never
15
- // re-reads a file, never re-runs the match ladder. Honest by construction:
16
- // the one rule this module exists to enforce is that a MISSING or STALE
17
- // telemetry source can never collapse into "no runtime risk" — the
18
- // `runtimeStatus` field is always exactly one of `'complete'`, `'partial'`,
19
- // or `'unavailable'`, and `'unavailable'` is the answer whenever no
20
- // observation store was consulted at all, never a silent empty report.
21
- //
22
- // `parseFailures` is always reported as 0, with an explicit note why:
23
- // `dataflow observations import` REFUSES the whole import the moment any
24
- // record fails to parse or validate (AC-29 clause 5) — a failed import is
25
- // never persisted, so there is no on-disk history of parse failures for
26
- // this module to read. This is a real, disclosed limitation, not a false
27
- // "zero failures ever happened" claim.
28
-
29
- const RUNTIME_STATUS_VALUES = Object.freeze(['complete', 'partial', 'unavailable']);
30
-
31
- function _isPlainObject(v) {
32
- return typeof v === 'object' && v !== null && !Array.isArray(v);
33
- }
34
-
35
- function _maxIso(list) {
36
- let best = null;
37
- let bestT = -Infinity;
38
- for (const s of list) {
39
- const t = Date.parse(s);
40
- if (Number.isFinite(t) && t > bestT) { bestT = t; best = s; }
41
- }
42
- return best;
43
- }
44
-
45
- function _minIso(list) {
46
- let best = null;
47
- let bestT = Infinity;
48
- for (const s of list) {
49
- const t = Date.parse(s);
50
- if (Number.isFinite(t) && t < bestT) { bestT = t; best = s; }
51
- }
52
- return best;
53
- }
54
-
55
- /**
56
- * `computeRuntimeAssurance(opts) -> RuntimeAssurance`
57
- *
58
- * `opts`:
59
- * - `importRecords`: the `ObservationImport[]` read back from the store
60
- * (`loadObservationImports`), or `null`/`undefined` when the store was
61
- * never consulted at all (mirrors `correlateObservations`'s own
62
- * `observations === null` convention).
63
- * - `correlation`: the `CorrelationResult` from `correlateObservations`
64
- * for the SAME graph/observations/window this call is reporting on.
65
- * - `now`: an ISO-8601 string (defaults to `new Date().toISOString()`,
66
- * injectable for deterministic tests).
67
- * - `staleAfterHours`: how old the most recent observed event can be
68
- * before freshness is reported stale (default 24 * 30 = 720, i.e. 30
69
- * days — telemetry windows in this package's own fixtures/CLI default
70
- * to month-long exports, so a 30-day staleness bar is the honest
71
- * "this is still describing roughly the current system" line, not an
72
- * arbitrary short window).
73
- *
74
- * Never throws.
75
- */
76
- function computeRuntimeAssurance(opts) {
77
- const o = opts && typeof opts === 'object' ? opts : {};
78
- const now = typeof o.now === 'string' && Number.isFinite(Date.parse(o.now)) ? o.now : new Date().toISOString();
79
- const staleAfterHours = Number.isFinite(o.staleAfterHours) ? o.staleAfterHours : 720;
80
-
81
- const importRecords = Array.isArray(o.importRecords) ? o.importRecords : null;
82
- const correlation = _isPlainObject(o.correlation) ? o.correlation : null;
83
-
84
- const base = {
85
- runtimeStatus: 'unavailable',
86
- telemetryWindow: { start: null, end: null },
87
- freshness: { lastObservedAt: null, lastImportedAt: null, staleHours: null, stale: true },
88
- parseFailures: 0,
89
- parseFailuresNote: 'A failed import is refused in full and never persisted (AC-29 clause 5) — '
90
- + 'this count reflects the import store, not the history of every attempt. Check `runtime ingest`\'s '
91
- + 'own exit code/stderr at import time for parse failures.',
92
- mappingPercentage: null,
93
- unmatchedObservationCount: 0,
94
- totalObservationCount: 0,
95
- missingTelemetry: true,
96
- limitations: [],
97
- };
98
-
99
- // No store was ever consulted — the honest 'unavailable' case, never a
100
- // fabricated empty-but-fine report.
101
- if (importRecords === null || importRecords.length === 0 || !correlation || correlation.evaluated !== true) {
102
- base.limitations.push(
103
- 'No runtime observation import exists for this scan — runtimeStatus is \'unavailable\', never \'complete\' '
104
- + 'or a silent absence of risk. Import telemetry with `runtime ingest` before relying on this report.',
105
- );
106
- return base;
107
- }
108
-
109
- const windowStart = _minIso(importRecords.map((r) => r?.windowStart));
110
- const windowEnd = _maxIso(importRecords.map((r) => r?.windowEnd));
111
- base.telemetryWindow = { start: windowStart, end: windowEnd };
112
- base.missingTelemetry = false;
113
-
114
- const allObservations = importRecords.flatMap((r) => (Array.isArray(r?.observations) ? r.observations : []));
115
- const total = allObservations.length;
116
- const unmatched = allObservations.filter((obs) => obs && obs.matchMethod === 'unmatched').length;
117
- base.totalObservationCount = total;
118
- base.unmatchedObservationCount = unmatched;
119
- base.mappingPercentage = total > 0 ? Math.round(((total - unmatched) / total) * 10000) / 100 : null;
120
-
121
- const lastObservedAt = _maxIso(allObservations.map((obs) => obs?.lastObservedAt));
122
- const lastImportedAt = _maxIso(importRecords.map((r) => r?.importedAt));
123
- base.freshness.lastObservedAt = lastObservedAt;
124
- base.freshness.lastImportedAt = lastImportedAt;
125
- if (lastObservedAt !== null) {
126
- const ageMs = Date.parse(now) - Date.parse(lastObservedAt);
127
- const ageHours = Math.round((ageMs / 3600000) * 100) / 100;
128
- base.freshness.staleHours = ageHours;
129
- base.freshness.stale = ageHours > staleAfterHours;
130
- }
131
-
132
- if (total === 0) {
133
- base.limitations.push('The import store exists but carries zero observation records.');
134
- }
135
- if (unmatched > 0) {
136
- base.limitations.push(`${unmatched} of ${total} imported observation(s) never matched any graph node (matchMethod 'unmatched') — these contribute no corroboration to any flow.`);
137
- }
138
- if (base.freshness.stale) {
139
- base.limitations.push(`The most recent observed event is ${base.freshness.staleHours} hour(s) old (beyond the ${staleAfterHours}-hour staleness bar) — this twin may no longer describe current production behavior.`);
140
- }
141
-
142
- const complete = total > 0 && unmatched === 0 && !base.freshness.stale;
143
- base.runtimeStatus = complete ? 'complete' : 'partial';
144
-
145
- return base;
146
- }
147
-
148
-
149
- /***/ })
150
-
151
- };
@@ -1,293 +0,0 @@
1
- export const id = 1557;
2
- export const ids = [1557];
3
- export const modules = {
4
-
5
- /***/ 51557:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ analyzeTranscript: () => (/* binding */ analyzeTranscript),
10
- /* harmony export */ formatCacheReport: () => (/* binding */ formatCacheReport),
11
- /* harmony export */ renderCacheStatusLine: () => (/* binding */ renderCacheStatusLine)
12
- /* harmony export */ });
13
- /* unused harmony export _internal */
14
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
15
- /* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(48161);
16
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(76760);
17
- // Prompt-cache economics — turn Claude Code's own transcript usage into a
18
- // dollarized report: how much prompt caching saved, how much was wasted on
19
- // avoidable cache misses, and what invalidated the cache.
20
- //
21
- // Source of truth: the Claude Code transcript at
22
- // ~/.claude/projects/<enc>/<session>.jsonl
23
- // where <enc> is CLAUDE_PROJECT_DIR with `/` and `.` replaced by `-`. Each
24
- // assistant turn carries `message.usage` with input/output/cache_read/
25
- // cache_creation token counts (and a 5m/1h write split). We price those against
26
- // per-model rates to compute real economics — no estimates, no network.
27
- //
28
- // Pure compute on parsed records; only `locateTranscript`/`parseTranscriptUsage`
29
- // touch the filesystem. ESM (scanner tree). A trimmed CJS twin lives at
30
- // hooks/lib/transcript.js for the CJS hooks; test/cache-economics.test.js asserts
31
- // the two agree.
32
-
33
-
34
-
35
-
36
- // Cents-scale money formatter (fmtUsd in risk-dollars.js targets five-figure
37
- // breach costs and won't round sub-dollar values).
38
- function money(n) {
39
- const v = Number(n) || 0;
40
- return Math.abs(v) >= 1 ? `$${v.toFixed(2)}` : `$${v.toFixed(4)}`;
41
- }
42
-
43
- // Per-1M-token rates (input / output). Mirror hooks/model-cost-advisor.js MODELS.
44
- const MODEL_RATES = {
45
- fable: { label: 'Fable 5', in: 10, out: 50 },
46
- opus: { label: 'Opus 4.8', in: 5, out: 25 },
47
- sonnet5: { label: 'Sonnet 5', in: 3, out: 15 },
48
- sonnet: { label: 'Sonnet 4.6', in: 3, out: 15 },
49
- haiku: { label: 'Haiku 4.5', in: 1, out: 5 },
50
- };
51
- const CACHE_READ_MULT = 0.1; // cache read ≈ 0.1× input
52
- const CACHE_WRITE_MULT = 1.25; // 5-minute cache write ≈ 1.25× input
53
- const CACHE_WRITE_1H_MULT = 2.0; // 1-hour cache write ≈ 2× input
54
- const TTL_MS = 5 * 60 * 1000;
55
-
56
- // Map any model string to a rate family. Returns null for unpriceable models
57
- // (e.g. "<synthetic>" sidechain/compaction turns) so they're skipped.
58
- function rateFor(model) {
59
- if (typeof model !== 'string') return null;
60
- const s = model.toLowerCase();
61
- if (s.includes('fable') || s.includes('mythos')) return MODEL_RATES.fable;
62
- if (s.includes('haiku')) return MODEL_RATES.haiku;
63
- if (s.includes('sonnet')) return (s.includes('sonnet-5') || s.includes('sonnet 5')) ? MODEL_RATES.sonnet5 : MODEL_RATES.sonnet;
64
- if (s.includes('opus')) return MODEL_RATES.opus;
65
- return null;
66
- }
67
-
68
- // ── Transcript discovery + parse ─────────────────────────────────────────────
69
-
70
- function encodeProjectDir(dir) {
71
- return String(dir).replace(/[/.]/g, '-');
72
- }
73
-
74
- // Locate the session transcript. Prefer an explicit (hook-provided) path; else
75
- // derive the project's transcript dir and take the most-recently-modified jsonl.
76
- function locateTranscript({ transcriptPath, projectDir } = {}) {
77
- try {
78
- if (transcriptPath && node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(transcriptPath)) return transcriptPath;
79
- } catch { /* fall through */ }
80
- try {
81
- const dir = node_path__WEBPACK_IMPORTED_MODULE_2__.join(node_os__WEBPACK_IMPORTED_MODULE_1__.homedir(), '.claude', 'projects', encodeProjectDir(projectDir || process.cwd()));
82
- if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(dir)) return null;
83
- const files = node_fs__WEBPACK_IMPORTED_MODULE_0__.readdirSync(dir)
84
- .filter(f => f.endsWith('.jsonl'))
85
- .map(f => ({ f: node_path__WEBPACK_IMPORTED_MODULE_2__.join(dir, f), m: node_fs__WEBPACK_IMPORTED_MODULE_0__.statSync(node_path__WEBPACK_IMPORTED_MODULE_2__.join(dir, f)).mtimeMs }))
86
- .sort((a, b) => b.m - a.m);
87
- return files.length ? files[0].f : null;
88
- } catch { return null; }
89
- }
90
-
91
- // Parse a transcript jsonl into per-assistant-turn usage records. Skips lines
92
- // that aren't priceable assistant turns.
93
- function parseTranscriptUsage(jsonlPath) {
94
- let raw;
95
- try { raw = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(jsonlPath, 'utf8'); } catch { return []; }
96
- const records = [];
97
- for (const line of raw.split('\n')) {
98
- const t = line.trim();
99
- if (!t) continue;
100
- let o;
101
- try { o = JSON.parse(t); } catch { continue; }
102
- if (o.type !== 'assistant') continue;
103
- const msg = o.message;
104
- const u = msg && msg.usage;
105
- if (!u || !msg.model || !rateFor(msg.model)) continue;
106
- const cc = u.cache_creation || {};
107
- records.push({
108
- model: msg.model,
109
- input: u.input_tokens || 0,
110
- output: u.output_tokens || 0,
111
- cacheRead: u.cache_read_input_tokens || 0,
112
- cacheCreate: u.cache_creation_input_tokens || 0,
113
- cacheCreate5m: cc.ephemeral_5m_input_tokens || 0,
114
- cacheCreate1h: cc.ephemeral_1h_input_tokens || 0,
115
- ts: o.timestamp ? Date.parse(o.timestamp) : null,
116
- });
117
- }
118
- return records;
119
- }
120
-
121
- // ── Pure economics ───────────────────────────────────────────────────────────
122
-
123
- function writeCostUsd(r, inRate) {
124
- const m5 = r.cacheCreate5m || 0, m1 = r.cacheCreate1h || 0;
125
- if (m5 + m1 > 0) return (m5 * CACHE_WRITE_MULT + m1 * CACHE_WRITE_1H_MULT) * inRate;
126
- return (r.cacheCreate || 0) * CACHE_WRITE_MULT * inRate; // breakdown absent
127
- }
128
-
129
- // Aggregate economics over parsed records.
130
- function computeCacheEconomics(records) {
131
- let turns = 0, inTok = 0, outTok = 0, cacheRead = 0, cacheCreate = 0;
132
- let actualUsd = 0, uncachedUsd = 0, writePremiumUsd = 0;
133
- const perModel = {};
134
-
135
- for (const r of records) {
136
- const rate = rateFor(r.model);
137
- if (!rate) continue;
138
- turns++;
139
- const inRate = rate.in / 1e6, outRate = rate.out / 1e6;
140
-
141
- const readCost = r.cacheRead * inRate * CACHE_READ_MULT;
142
- const writeCost = writeCostUsd(r, inRate);
143
- const inCost = r.input * inRate;
144
- const outCost = r.output * outRate;
145
- const turnActual = readCost + writeCost + inCost + outCost;
146
- // What this turn would have cost with NO caching: every input-side token full price.
147
- const turnUncached = (r.cacheRead + r.cacheCreate + r.input) * inRate + outCost;
148
-
149
- actualUsd += turnActual;
150
- uncachedUsd += turnUncached;
151
- writePremiumUsd += writeCost - (r.cacheCreate * inRate); // the >1× premium paid to cache
152
-
153
- inTok += r.input; outTok += r.output; cacheRead += r.cacheRead; cacheCreate += r.cacheCreate;
154
-
155
- const key = rate.label;
156
- const pm = perModel[key] || (perModel[key] = { turns: 0, actualUsd: 0, cacheRead: 0, inputSide: 0 });
157
- pm.turns++; pm.actualUsd += turnActual; pm.cacheRead += r.cacheRead;
158
- pm.inputSide += r.cacheRead + r.cacheCreate + r.input;
159
- }
160
-
161
- const inputSide = cacheRead + cacheCreate + inTok;
162
- return {
163
- turns,
164
- tokens: { input: inTok, output: outTok, cacheRead, cacheCreate },
165
- actualUsd,
166
- uncachedUsd,
167
- savedUsd: uncachedUsd - actualUsd, // net $ caching saved (can dip negative early)
168
- writePremiumUsd, // $ invested establishing caches
169
- cacheHitRatio: inputSide ? cacheRead / inputSide : 0,
170
- costPerTurnUsd: turns ? actualUsd / turns : 0,
171
- perModel,
172
- };
173
- }
174
-
175
- // Attribute cache drops: a turn that re-ingests a large prefix cold after a warm
176
- // prior turn. Cause = model-switch | cache-expired | prefix-change.
177
- function detectInvalidators(records) {
178
- const leaks = [];
179
- const MIN_WARM = 2000;
180
- for (let i = 1; i < records.length; i++) {
181
- const prev = records[i - 1], cur = records[i];
182
- const prevWarm = prev.cacheRead + prev.input + prev.cacheCreate;
183
- if (prevWarm < MIN_WARM) continue;
184
- const curFresh = cur.input + cur.cacheCreate;
185
- const coldish = cur.cacheRead < prevWarm * 0.25 && curFresh > prevWarm * 0.5;
186
- if (!coldish) continue;
187
-
188
- let cause;
189
- if (cur.model !== prev.model) cause = 'model-switch';
190
- else if (cur.ts && prev.ts && (cur.ts - prev.ts) > TTL_MS) cause = 'cache-expired';
191
- else cause = 'prefix-change';
192
-
193
- const rate = rateFor(cur.model);
194
- const inRate = rate ? rate.in / 1e6 : 0;
195
- // Extra paid vs. having kept the prefix as a cheap cache read.
196
- const wastedUsd = prevWarm * inRate * (1 - CACHE_READ_MULT);
197
- leaks.push({ turn: i, cause, wastedUsd, model: cur.model });
198
- }
199
- return leaks;
200
- }
201
-
202
- // Convenience: locate → parse → compute → detect. Returns { ok:false } when no
203
- // transcript is available.
204
- function analyzeTranscript(opts = {}) {
205
- const transcript = locateTranscript(opts);
206
- if (!transcript) return { ok: false, reason: 'no-transcript' };
207
- const records = parseTranscriptUsage(transcript);
208
- if (!records.length) return { ok: false, reason: 'no-priceable-turns', transcript };
209
- return {
210
- ok: true,
211
- transcript,
212
- metrics: computeCacheEconomics(records),
213
- leaks: detectInvalidators(records),
214
- };
215
- }
216
-
217
- // ── Report formatting ────────────────────────────────────────────────────────
218
-
219
- const CAUSE_LABEL = {
220
- 'model-switch': 'model switch (cache is model-scoped)',
221
- 'cache-expired': 'cache expired (gap > 5-min TTL)',
222
- 'prefix-change': 'prefix changed (system prompt / tools / context edit)',
223
- };
224
-
225
- // F6 — one-line HUD for a Claude Code statusLine command (mirrors
226
- // watch-mode.js renderStatusLine). Takes the metrics from computeCacheEconomics.
227
- function renderCacheStatusLine(metrics) {
228
- if (!metrics || !metrics.turns) return 'agentic-security: no session cost yet';
229
- const hit = Math.round(metrics.cacheHitRatio * 100);
230
- return `agentic-security: ${money(metrics.actualUsd)} · ${hit}% cached · ${money(metrics.costPerTurnUsd)}/turn`;
231
- }
232
-
233
- function formatCacheReport(result) {
234
- if (!result.ok) {
235
- return result.reason === 'no-transcript'
236
- ? 'agentic-security: no Claude Code transcript found for this project yet.'
237
- : 'agentic-security: transcript has no priceable model turns yet.';
238
- }
239
- const m = result.metrics;
240
- const lines = [];
241
- lines.push('');
242
- lines.push(' Prompt-cache economics — this session');
243
- lines.push(` ${result.turns ?? m.turns} model turns\n`);
244
- lines.push(` cache hit ratio ${(m.cacheHitRatio * 100).toFixed(1)}% (input-side tokens served from cache)`);
245
- lines.push(` spent ${money(m.actualUsd)} (~${money(m.costPerTurnUsd)}/turn)`);
246
- lines.push(` ▶ saved by caching ${money(m.savedUsd)} vs. ${money(m.uncachedUsd)} with no cache`);
247
- lines.push(` invested in caches ${money(m.writePremiumUsd)} (write premium over base input)`);
248
- lines.push('');
249
- lines.push(' tokens: '
250
- + `${m.tokens.cacheRead.toLocaleString()} cached-read · `
251
- + `${m.tokens.cacheCreate.toLocaleString()} cache-write · `
252
- + `${m.tokens.input.toLocaleString()} fresh-in · `
253
- + `${m.tokens.output.toLocaleString()} out`);
254
-
255
- const models = Object.keys(m.perModel);
256
- if (models.length > 1) {
257
- lines.push('\n by model:');
258
- for (const k of models.sort()) {
259
- const pm = m.perModel[k];
260
- const hr = pm.inputSide ? (pm.cacheRead / pm.inputSide * 100).toFixed(0) : '0';
261
- lines.push(` ${k.padEnd(12)} ${pm.turns} turns · ${money(pm.actualUsd)} · ${hr}% cached`);
262
- }
263
- }
264
-
265
- if (result.leaks && result.leaks.length) {
266
- const wasted = result.leaks.reduce((s, l) => s + l.wastedUsd, 0);
267
- lines.push(`\n ⚠ cache leaks (${result.leaks.length}, ~${money(wasted)} wasted re-ingesting context):`);
268
- const byCause = {};
269
- for (const l of result.leaks) {
270
- (byCause[l.cause] || (byCause[l.cause] = { n: 0, usd: 0 })).n++;
271
- byCause[l.cause].usd += l.wastedUsd;
272
- }
273
- for (const c of Object.keys(byCause).sort()) {
274
- lines.push(` · ${byCause[c].n}× ${CAUSE_LABEL[c] || c} — ~${money(byCause[c].usd)}`);
275
- }
276
- lines.push(' Keep one model + a stable system prompt within a working window to avoid these.');
277
- } else {
278
- lines.push('\n ✓ no cache leaks detected — your context stayed warm.');
279
- }
280
- lines.push('');
281
- return lines.join('\n');
282
- }
283
-
284
- // Test surface (underscore export is exempt from the dead-module gate).
285
- const _internal = {
286
- MODEL_RATES, CACHE_READ_MULT, CACHE_WRITE_MULT, CACHE_WRITE_1H_MULT,
287
- rateFor, locateTranscript, parseTranscriptUsage, computeCacheEconomics, detectInvalidators,
288
- };
289
-
290
-
291
- /***/ })
292
-
293
- };