tokentrace 0.21.1 → 0.21.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to TokenTrace are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## [0.21.2] - 2026-06-26
8
+
9
+ ### Fixed
10
+
11
+ - **ProjScan circular-import warnings removed.** Shared scan-health, analytics,
12
+ evidence, review-queue, project-signal, session-comparison, and unknown-cost
13
+ repair contracts now live in leaf type modules so the import graph stays
14
+ acyclic. Added a regression test for these architecture boundaries.
15
+
16
+ ### Changed
17
+
18
+ - **Low-risk dependency refresh.** Updated patch and minor dependencies within
19
+ the existing major-version ranges after audit and package-security checks.
20
+
7
21
  ## [0.21.1] - 2026-06-26
8
22
 
9
23
  ### Changed
package/README.md CHANGED
@@ -417,7 +417,7 @@ Stop the server with `Ctrl+C` in the terminal where `tokentrace` is running.
417
417
  - `npm run security:ioc` scans lockfiles, workflows, and local Claude/VS Code hook files for high-signal supply-chain compromise indicators.
418
418
  - Public npm publishing is configured through GitHub Actions Trusted Publishing and provenance from version tags.
419
419
  - Socket GitHub checks and ProjScan are used as release guardrails, alongside `npm audit --audit-level=moderate`.
420
- - `npm run release:check` fails when a gate command exits non-zero. ProjScan warning-level architecture findings, such as circular imports, are reported as release risk and tracked as architecture debt; they do not fail the release unless ProjScan exits non-zero.
420
+ - `npm run release:check` fails when a gate command exits non-zero. The import graph is expected to remain free of circular dependencies; `tests/import-boundaries.test.ts` protects shared type/helper modules from importing higher-level orchestration barrels.
421
421
  - Release notes are published directly in GitHub Releases from the relevant changelog section, not as a link-only summary.
422
422
 
423
423
  See [SECURITY.md](SECURITY.md) for the full security and privacy model.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokentrace",
3
- "version": "0.21.1",
3
+ "version": "0.21.2",
4
4
  "mcpName": "io.github.abhiyoheswaran1/tokentrace",
5
5
  "description": "Local-first dashboard for AI CLI token, cost, and session analytics.",
6
6
  "author": {
package/server.json CHANGED
@@ -8,12 +8,12 @@
8
8
  "url": "https://github.com/abhiyoheswaran1/tokentrace",
9
9
  "source": "github"
10
10
  },
11
- "version": "0.21.1",
11
+ "version": "0.21.2",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "tokentrace",
16
- "version": "0.21.1",
16
+ "version": "0.21.2",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {
@@ -1,10 +1,7 @@
1
1
  import type { DataConfidenceGrade, DataConfidenceScore } from "@/src/lib/data-confidence";
2
- import type { EvidenceMetric } from "@/src/lib/evidence-trail";
2
+ import type { EvidenceMetric } from "@/src/lib/evidence/metrics";
3
3
  import type { LocalRecommendation } from "@/src/lib/recommendations";
4
- import type { ProjectSignalRow } from "@/src/lib/project-signals";
5
- import type { ReviewQueueItem } from "@/src/lib/review-queue";
6
- import type { ScanConfidenceSummary, ScanHealth } from "@/src/lib/scan-health";
7
- import type { SessionComparisonRow } from "@/src/lib/session-comparison";
4
+ import type { ScanConfidenceSummary, ScanHealth } from "@/src/lib/scan-health-types";
8
5
  import type { UsageGuardrailProgress } from "@/src/lib/usage-guardrails";
9
6
 
10
7
  export type TrendPoint = {
@@ -172,6 +169,60 @@ export type ModelAliasSuggestion = {
172
169
  parserHref: string;
173
170
  };
174
171
 
172
+ export type ReviewQueueCategory =
173
+ | "guardrail"
174
+ | "cost-repair"
175
+ | "session"
176
+ | "project"
177
+ | "model"
178
+ | "cache"
179
+ | "baseline";
180
+
181
+ export type ReviewQueueItem = {
182
+ id: string;
183
+ severity: "high" | "medium" | "low";
184
+ category: ReviewQueueCategory;
185
+ title: string;
186
+ evidence: string;
187
+ action: string;
188
+ href: string;
189
+ impactLabel: string;
190
+ impactValue: string;
191
+ };
192
+
193
+ export type SessionComparisonRow = {
194
+ sessionId: string;
195
+ title: string;
196
+ project: string;
197
+ tool: string;
198
+ models: string;
199
+ totalTokens: number;
200
+ cost: number | null;
201
+ peerSessions: number;
202
+ peerMedianTokens: number;
203
+ peerMedianCost: number | null;
204
+ tokenMultiple: number;
205
+ costMultiple: number | null;
206
+ severity: "high" | "medium" | "low";
207
+ flag: "token outlier" | "cost outlier";
208
+ evidence: string;
209
+ action: string;
210
+ href: string;
211
+ };
212
+
213
+ export type ProjectSignalRow = {
214
+ id: string;
215
+ severity: "high" | "medium" | "low";
216
+ project: string;
217
+ path: string;
218
+ signal: "dominant usage" | "unknown cost" | "estimated tokens" | "model concentration";
219
+ evidence: string;
220
+ action: string;
221
+ href: string;
222
+ metricLabel: string;
223
+ metricValue: string;
224
+ };
225
+
175
226
  export type DebugScanFile = {
176
227
  id: string;
177
228
  scanRunId: string;
@@ -1,5 +1,5 @@
1
1
  import { sqlite } from "@/src/db/client";
2
- import type { AnalyticsFilters } from "@/src/lib/analytics";
2
+ import type { AnalyticsFilters } from "@/src/lib/analytics-types";
3
3
  import type { EvidenceMetric } from "@/src/lib/evidence/metrics";
4
4
 
5
5
  export type EvidenceSessionRow = {
@@ -1,4 +1,4 @@
1
- import type { AnalyticsFilters } from "@/src/lib/analytics";
1
+ import type { AnalyticsFilters } from "@/src/lib/analytics-types";
2
2
  import type { EvidenceMetric } from "@/src/lib/evidence/metrics";
3
3
  import {
4
4
  mapEvidenceTrail,
@@ -1,18 +1,7 @@
1
- import type { ProjectAnalyticsRow, SessionRow } from "@/src/lib/analytics";
1
+ import type { ProjectAnalyticsRow, ProjectSignalRow, SessionRow } from "@/src/lib/analytics-types";
2
2
  import { formatTokens } from "@/src/lib/format";
3
3
 
4
- export type ProjectSignalRow = {
5
- id: string;
6
- severity: "high" | "medium" | "low";
7
- project: string;
8
- path: string;
9
- signal: "dominant usage" | "unknown cost" | "estimated tokens" | "model concentration";
10
- evidence: string;
11
- action: string;
12
- href: string;
13
- metricLabel: string;
14
- metricValue: string;
15
- };
4
+ export type { ProjectSignalRow } from "@/src/lib/analytics-types";
16
5
 
17
6
  type ProjectSignalInput = {
18
7
  totalTokens: number;
@@ -2,33 +2,16 @@ import { formatCurrency, formatTokens } from "@/src/lib/format";
2
2
  import type {
3
3
  ModelAnalyticsRow,
4
4
  ProjectAnalyticsRow,
5
+ ReviewQueueCategory,
6
+ ReviewQueueItem,
5
7
  SessionRow,
6
8
  SummaryMetrics,
7
9
  ToolComparisonRow,
8
10
  UnknownCostQueueRow
9
- } from "@/src/lib/analytics";
11
+ } from "@/src/lib/analytics-types";
10
12
  import type { UsageGuardrailProgress, UsageGuardrailMetric } from "@/src/lib/usage-guardrails";
11
13
 
12
- export type ReviewQueueCategory =
13
- | "guardrail"
14
- | "cost-repair"
15
- | "session"
16
- | "project"
17
- | "model"
18
- | "cache"
19
- | "baseline";
20
-
21
- export type ReviewQueueItem = {
22
- id: string;
23
- severity: "high" | "medium" | "low";
24
- category: ReviewQueueCategory;
25
- title: string;
26
- evidence: string;
27
- action: string;
28
- href: string;
29
- impactLabel: string;
30
- impactValue: string;
31
- };
14
+ export type { ReviewQueueCategory, ReviewQueueItem } from "@/src/lib/analytics-types";
32
15
 
33
16
  type ReviewQueueInput = {
34
17
  summary: Pick<SummaryMetrics, "totalTokens" | "totalCost" | "inputTokens" | "cachedTokens" | "unknownCostInteractions">;
@@ -6,7 +6,7 @@ import type {
6
6
  ScanHealthNoteGroup,
7
7
  ScanHealthRun,
8
8
  SupplyChainHealth
9
- } from "@/src/lib/scan-health";
9
+ } from "@/src/lib/scan-health-types";
10
10
 
11
11
  export function incrementCount(target: Record<string, number>, key: string, amount = 1) {
12
12
  target[key] = (target[key] ?? 0) + amount;
@@ -0,0 +1,104 @@
1
+ export type ScanHealthRun = {
2
+ id: string;
3
+ startedAt: number;
4
+ completedAt: number | null;
5
+ filesScanned: number;
6
+ recordsImported: number;
7
+ warnings: string[];
8
+ errors: string[];
9
+ };
10
+
11
+ export type ScanHealthFile = {
12
+ id: string;
13
+ scanRunId: string;
14
+ path: string;
15
+ modifiedTime: number | null;
16
+ sizeBytes: number;
17
+ parser: string | null;
18
+ status: string;
19
+ recordsImported: number;
20
+ warnings: string[];
21
+ errors: string[];
22
+ rawMetadata: Record<string, unknown>;
23
+ };
24
+
25
+ export type ScanConfidenceSummary = {
26
+ interactions: number;
27
+ exactTokenInteractions: number;
28
+ tokenizerEstimateInteractions?: number;
29
+ simpleEstimateInteractions?: number;
30
+ highConfidenceTokenInteractions: number;
31
+ lowConfidenceTokenInteractions: number;
32
+ unknownTokenInteractions: number;
33
+ estimatedTokenInteractions: number;
34
+ exactCostInteractions: number;
35
+ estimatedCostInteractions: number;
36
+ unknownCostInteractions: number;
37
+ unknownCostCauses: {
38
+ missingModelName: number;
39
+ missingPricing: number;
40
+ missingTokenCount: number;
41
+ other: number;
42
+ };
43
+ };
44
+
45
+ export type ScanHealthAction = {
46
+ label: string;
47
+ href: string;
48
+ reason: string;
49
+ tone: "default" | "warning" | "destructive";
50
+ };
51
+
52
+ export type SupplyChainHealth = {
53
+ status: "passed" | "failed" | "not-run";
54
+ checkedAt: number | null;
55
+ findings: number;
56
+ summary: string;
57
+ };
58
+
59
+ export type ScanHealthNoteGroup = {
60
+ severity: "warning" | "error";
61
+ message: string;
62
+ count: number;
63
+ examples: string[];
64
+ };
65
+
66
+ export type ScanHealth = {
67
+ latestRun: ScanHealthRun | null;
68
+ lastSuccessfulRun: ScanHealthRun | null;
69
+ latestFiles: ScanHealthFile[];
70
+ headline: string;
71
+ description: string;
72
+ tone: "success" | "warning" | "destructive" | "secondary";
73
+ latestStatusCounts: Record<string, number>;
74
+ parserCounts: Record<string, number>;
75
+ latestWarnings: string[];
76
+ latestErrors: string[];
77
+ latestNoteGroups: ScanHealthNoteGroup[];
78
+ freshness: {
79
+ state: "no-scan" | "no-successful-scan" | "fresh" | "stale";
80
+ lastSuccessfulCompletedAt: number | null;
81
+ staleAfterMs: number;
82
+ description: string;
83
+ };
84
+ tokenCoverage: {
85
+ exact: number;
86
+ tokenizerEstimate: number;
87
+ simpleEstimate: number;
88
+ highConfidenceEstimate: number;
89
+ lowConfidenceEstimate: number;
90
+ unknown: number;
91
+ estimated: number;
92
+ total: number;
93
+ };
94
+ costCoverage: {
95
+ priced: number;
96
+ exact: number;
97
+ estimated: number;
98
+ unknown: number;
99
+ unknownCauses: ScanConfidenceSummary["unknownCostCauses"];
100
+ total: number;
101
+ };
102
+ supplyChain: SupplyChainHealth;
103
+ actions: ScanHealthAction[];
104
+ };
@@ -8,111 +8,23 @@ import {
8
8
  uniqueMessages,
9
9
  type ScanHealthRuleStats
10
10
  } from "@/src/lib/scan-health-rules";
11
+ import type {
12
+ ScanConfidenceSummary,
13
+ ScanHealth,
14
+ ScanHealthFile,
15
+ ScanHealthRun,
16
+ SupplyChainHealth
17
+ } from "@/src/lib/scan-health-types";
11
18
 
12
- export type ScanHealthRun = {
13
- id: string;
14
- startedAt: number;
15
- completedAt: number | null;
16
- filesScanned: number;
17
- recordsImported: number;
18
- warnings: string[];
19
- errors: string[];
20
- };
21
-
22
- export type ScanHealthFile = {
23
- id: string;
24
- scanRunId: string;
25
- path: string;
26
- modifiedTime: number | null;
27
- sizeBytes: number;
28
- parser: string | null;
29
- status: string;
30
- recordsImported: number;
31
- warnings: string[];
32
- errors: string[];
33
- rawMetadata: Record<string, unknown>;
34
- };
35
-
36
- export type ScanConfidenceSummary = {
37
- interactions: number;
38
- exactTokenInteractions: number;
39
- tokenizerEstimateInteractions?: number;
40
- simpleEstimateInteractions?: number;
41
- highConfidenceTokenInteractions: number;
42
- lowConfidenceTokenInteractions: number;
43
- unknownTokenInteractions: number;
44
- estimatedTokenInteractions: number;
45
- exactCostInteractions: number;
46
- estimatedCostInteractions: number;
47
- unknownCostInteractions: number;
48
- unknownCostCauses: {
49
- missingModelName: number;
50
- missingPricing: number;
51
- missingTokenCount: number;
52
- other: number;
53
- };
54
- };
55
-
56
- export type ScanHealthAction = {
57
- label: string;
58
- href: string;
59
- reason: string;
60
- tone: "default" | "warning" | "destructive";
61
- };
62
-
63
- export type SupplyChainHealth = {
64
- status: "passed" | "failed" | "not-run";
65
- checkedAt: number | null;
66
- findings: number;
67
- summary: string;
68
- };
69
-
70
- export type ScanHealthNoteGroup = {
71
- severity: "warning" | "error";
72
- message: string;
73
- count: number;
74
- examples: string[];
75
- };
76
-
77
- export type ScanHealth = {
78
- latestRun: ScanHealthRun | null;
79
- lastSuccessfulRun: ScanHealthRun | null;
80
- latestFiles: ScanHealthFile[];
81
- headline: string;
82
- description: string;
83
- tone: "success" | "warning" | "destructive" | "secondary";
84
- latestStatusCounts: Record<string, number>;
85
- parserCounts: Record<string, number>;
86
- latestWarnings: string[];
87
- latestErrors: string[];
88
- latestNoteGroups: ScanHealthNoteGroup[];
89
- freshness: {
90
- state: "no-scan" | "no-successful-scan" | "fresh" | "stale";
91
- lastSuccessfulCompletedAt: number | null;
92
- staleAfterMs: number;
93
- description: string;
94
- };
95
- tokenCoverage: {
96
- exact: number;
97
- tokenizerEstimate: number;
98
- simpleEstimate: number;
99
- highConfidenceEstimate: number;
100
- lowConfidenceEstimate: number;
101
- unknown: number;
102
- estimated: number;
103
- total: number;
104
- };
105
- costCoverage: {
106
- priced: number;
107
- exact: number;
108
- estimated: number;
109
- unknown: number;
110
- unknownCauses: ScanConfidenceSummary["unknownCostCauses"];
111
- total: number;
112
- };
113
- supplyChain: SupplyChainHealth;
114
- actions: ScanHealthAction[];
115
- };
19
+ export type {
20
+ ScanConfidenceSummary,
21
+ ScanHealth,
22
+ ScanHealthAction,
23
+ ScanHealthFile,
24
+ ScanHealthNoteGroup,
25
+ ScanHealthRun,
26
+ SupplyChainHealth
27
+ } from "@/src/lib/scan-health-types";
116
28
 
117
29
  export function buildScanHealth({
118
30
  scanRuns,
@@ -1,25 +1,7 @@
1
- import type { SessionRow } from "@/src/lib/analytics";
1
+ import type { SessionComparisonRow, SessionRow } from "@/src/lib/analytics-types";
2
2
  import { formatCurrency, formatTokens } from "@/src/lib/format";
3
3
 
4
- export type SessionComparisonRow = {
5
- sessionId: string;
6
- title: string;
7
- project: string;
8
- tool: string;
9
- models: string;
10
- totalTokens: number;
11
- cost: number | null;
12
- peerSessions: number;
13
- peerMedianTokens: number;
14
- peerMedianCost: number | null;
15
- tokenMultiple: number;
16
- costMultiple: number | null;
17
- severity: "high" | "medium" | "low";
18
- flag: "token outlier" | "cost outlier";
19
- evidence: string;
20
- action: string;
21
- href: string;
22
- };
4
+ export type { SessionComparisonRow } from "@/src/lib/analytics-types";
23
5
 
24
6
  function median(values: number[]) {
25
7
  if (!values.length) return 0;
@@ -1,46 +1,21 @@
1
1
  import { prepareCached } from "@/src/db/prepared";
2
2
  import { modelNameCandidates } from "@/src/lib/model-aliases";
3
- import type { UnknownCostRepairWorkbenchGroup } from "@/src/lib/unknown-cost-repair/types";
4
-
5
- export type AutoClassificationRule = "exact-model" | "family-fragment" | "parser-source" | "none";
6
-
7
- export type AutoClassification = {
8
- suggestedModel: string | null;
9
- suggestedProvider: string | null;
10
- confidence: number;
11
- rule: AutoClassificationRule;
12
- evidence: {
13
- matchedRows: number;
14
- sampleSourceFile: string | null;
15
- };
16
- };
17
-
18
- export type ClassifyInput = {
19
- cause: UnknownCostRepairWorkbenchGroup["cause"];
20
- providerId: string;
21
- model: string;
22
- sourceFile: string;
23
- };
24
-
25
- export type PricedModelRow = {
26
- providerId: string;
27
- providerName: string;
28
- modelName: string;
29
- usageCount: number;
30
- };
31
-
32
- export type SourcePricedRow = {
33
- sourceFile: string;
34
- providerId: string;
35
- providerName: string;
36
- modelName: string;
37
- usageCount: number;
38
- };
39
-
40
- export type ClassificationLookups = {
41
- pricedByProvider: Map<string, PricedModelRow[]>;
42
- pricedBySource: Map<string, SourcePricedRow>;
43
- };
3
+ import type {
4
+ AutoClassification,
5
+ ClassificationLookups,
6
+ ClassifyInput,
7
+ PricedModelRow,
8
+ SourcePricedRow
9
+ } from "@/src/lib/unknown-cost-repair/classification-types";
10
+
11
+ export type {
12
+ AutoClassification,
13
+ AutoClassificationRule,
14
+ ClassificationLookups,
15
+ ClassifyInput,
16
+ PricedModelRow,
17
+ SourcePricedRow
18
+ } from "@/src/lib/unknown-cost-repair/classification-types";
44
19
 
45
20
  export function buildClassificationLookups(): ClassificationLookups {
46
21
  const pricedRows = prepareCached(
@@ -0,0 +1,41 @@
1
+ import type { UnknownCostRepairCause } from "@/src/lib/repair-actions";
2
+
3
+ export type AutoClassificationRule = "exact-model" | "family-fragment" | "parser-source" | "none";
4
+
5
+ export type AutoClassification = {
6
+ suggestedModel: string | null;
7
+ suggestedProvider: string | null;
8
+ confidence: number;
9
+ rule: AutoClassificationRule;
10
+ evidence: {
11
+ matchedRows: number;
12
+ sampleSourceFile: string | null;
13
+ };
14
+ };
15
+
16
+ export type ClassifyInput = {
17
+ cause: UnknownCostRepairCause;
18
+ providerId: string;
19
+ model: string;
20
+ sourceFile: string;
21
+ };
22
+
23
+ export type PricedModelRow = {
24
+ providerId: string;
25
+ providerName: string;
26
+ modelName: string;
27
+ usageCount: number;
28
+ };
29
+
30
+ export type SourcePricedRow = {
31
+ sourceFile: string;
32
+ providerId: string;
33
+ providerName: string;
34
+ modelName: string;
35
+ usageCount: number;
36
+ };
37
+
38
+ export type ClassificationLookups = {
39
+ pricedByProvider: Map<string, PricedModelRow[]>;
40
+ pricedBySource: Map<string, SourcePricedRow>;
41
+ };
@@ -1,4 +1,4 @@
1
- import type { AutoClassification } from "@/src/lib/unknown-cost-repair/auto-classify";
1
+ import type { AutoClassification } from "@/src/lib/unknown-cost-repair/classification-types";
2
2
  import type { UnknownCostRepairAction, UnknownCostRepairCause } from "@/src/lib/repair-actions";
3
3
 
4
4
  export type UnknownCostRepairStatus = "unresolved" | "ignored" | "resolved" | "needs-parser-review";