agentwrangler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -0
- package/README.md +116 -0
- package/dist/apply/jobs.js +429 -0
- package/dist/apply/open-terminal-child.mjs +98 -0
- package/dist/apply/open-terminal.js +221 -0
- package/dist/apply/settings-gen.js +35 -0
- package/dist/cli/agentwrangler.js +18 -0
- package/dist/daemon/config.js +51 -0
- package/dist/daemon/http.js +258 -0
- package/dist/daemon/index.js +372 -0
- package/dist/daemon/outcomes-pass.js +82 -0
- package/dist/daemon/readiness.js +15 -0
- package/dist/daemon/router.js +756 -0
- package/dist/daemon/static.js +146 -0
- package/dist/db/migrate.js +72 -0
- package/dist/db/migrations/001_observe.sql +196 -0
- package/dist/db/migrations/002_indexes.sql +6 -0
- package/dist/db/migrations/003_context_inventory_history.sql +20 -0
- package/dist/db/migrations/004_apply_jobs.sql +17 -0
- package/dist/db/migrations/005_tool_event_metadata.sql +17 -0
- package/dist/db/migrations/006_d7_query_indexes.sql +9 -0
- package/dist/db/migrations/007_work_item_branch_keys.sql +11 -0
- package/dist/db/migrations/008_thinking_tokens.sql +1 -0
- package/dist/db/migrations/009_user_turn_count.sql +1 -0
- package/dist/db/migrations/010_workspace_cwd.sql +1 -0
- package/dist/db/migrations/011_reports.sql +1 -0
- package/dist/db/migrations/012_reconcile_indexes.sql +2 -0
- package/dist/db/migrations/013_friction_fields.sql +5 -0
- package/dist/db/migrations/014_session_churn.sql +11 -0
- package/dist/db/migrations/015_gap_aggregates.sql +6 -0
- package/dist/db/open.js +30 -0
- package/dist/detector/benchmark-anchors.js +36 -0
- package/dist/detector/calibration.js +302 -0
- package/dist/detector/context-history-retention.js +312 -0
- package/dist/detector/context-probe.js +574 -0
- package/dist/detector/d1-source-identity.js +25 -0
- package/dist/detector/detectors/d10_catalog_footprint.js +146 -0
- package/dist/detector/detectors/d1_ctx_always_loaded.js +203 -0
- package/dist/detector/detectors/d2_session_long_full_context.js +119 -0
- package/dist/detector/detectors/d4_model_mismatch.js +258 -0
- package/dist/detector/detectors/d5_limit_burn_forecast.js +138 -0
- package/dist/detector/detectors/d6_tool_result_bloat.js +301 -0
- package/dist/detector/detectors/d7_loop_retry_waste.js +345 -0
- package/dist/detector/detectors/d8_cache_write_churn.js +201 -0
- package/dist/detector/detectors/d9_idle_background_session.js +101 -0
- package/dist/detector/engine.js +88 -0
- package/dist/detector/index.js +17 -0
- package/dist/detector/measurement.js +426 -0
- package/dist/detector/practice-registry.js +259 -0
- package/dist/detector/registry.js +32 -0
- package/dist/detector/savings.js +249 -0
- package/dist/detector/types.js +14 -0
- package/dist/evidence/common/approved-input.js +632 -0
- package/dist/evidence/common/boundary.js +84 -0
- package/dist/evidence/common/canonical.js +55 -0
- package/dist/evidence/common/redaction.js +321 -0
- package/dist/evidence/common/sqlite.js +25 -0
- package/dist/evidence/common/state.js +29 -0
- package/dist/evidence/cond1/cli.js +289 -0
- package/dist/evidence/cond1/packet.js +407 -0
- package/dist/evidence/cond1/prepare.js +295 -0
- package/dist/evidence/cond1/score.js +349 -0
- package/dist/evidence/cond1/types.js +1 -0
- package/dist/evidence/create-approval.js +365 -0
- package/dist/evidence/create-scratch.js +542 -0
- package/dist/evidence/d7/cli.js +113 -0
- package/dist/evidence/d7/measure.js +193 -0
- package/dist/evidence/d7/types.js +1 -0
- package/dist/evidence/discover-approval.js +492 -0
- package/dist/evidence/g2/adjudicate.js +20 -0
- package/dist/evidence/g2/cli.js +207 -0
- package/dist/evidence/g2/kappa.js +39 -0
- package/dist/evidence/g2/pipeline.js +92 -0
- package/dist/evidence/g2/store.js +14 -0
- package/dist/evidence/github/client.js +1 -0
- package/dist/evidence/github/gh-cli-client.js +301 -0
- package/dist/evidence/r3/cli.js +209 -0
- package/dist/evidence/r3/evaluate.js +417 -0
- package/dist/evidence/r3/packet.js +162 -0
- package/dist/evidence/r3/prepare.js +405 -0
- package/dist/evidence/r3/score.js +341 -0
- package/dist/evidence/r3/transcript.js +155 -0
- package/dist/evidence/r3/types.js +4 -0
- package/dist/hook/context-budget-hook.mjs +138 -0
- package/dist/hook/danger-guard-denylist.json +27 -0
- package/dist/hook/danger-guard-hook.mjs +167 -0
- package/dist/hook/install.js +0 -0
- package/dist/hook/limit-burn-hook.mjs +127 -0
- package/dist/hook/loop-guard-hook.mjs +104 -0
- package/dist/hook/precompact-checkpoint-hook.mjs +123 -0
- package/dist/ingest/churn-collector.js +122 -0
- package/dist/ingest/detector-hook.js +52 -0
- package/dist/ingest/discovery.js +207 -0
- package/dist/ingest/health.js +43 -0
- package/dist/ingest/index.js +28 -0
- package/dist/ingest/ingestor.js +509 -0
- package/dist/ingest/parser.js +344 -0
- package/dist/ingest/pricing.js +153 -0
- package/dist/ingest/reconcile.js +52 -0
- package/dist/ingest/tail.js +152 -0
- package/dist/ingest/types.js +24 -0
- package/dist/ingest/workspace-mapping.js +114 -0
- package/dist/oauth/anthropic-api-key.js +88 -0
- package/dist/oauth/count-tokens.js +86 -0
- package/dist/oauth/credentials.js +171 -0
- package/dist/oauth/judge-g2-client.js +154 -0
- package/dist/oauth/usage.js +167 -0
- package/dist/outcomes/branch-key.js +49 -0
- package/dist/outcomes/conclusions.js +45 -0
- package/dist/outcomes/derive.js +94 -0
- package/dist/outcomes/finding-extractors.js +131 -0
- package/dist/outcomes/findings.js +237 -0
- package/dist/outcomes/github/client.js +367 -0
- package/dist/outcomes/github/credential.js +195 -0
- package/dist/outcomes/github/gh-cli-client.js +340 -0
- package/dist/outcomes/linker.js +486 -0
- package/dist/outcomes/pool.js +24 -0
- package/dist/outcomes/sync.js +276 -0
- package/dist/query/api/agents-liveness.js +182 -0
- package/dist/query/api/burn-status.js +50 -0
- package/dist/query/api/context-budget.js +114 -0
- package/dist/query/api/context-composition.js +67 -0
- package/dist/query/api/cost-per-success.js +104 -0
- package/dist/query/api/delivery.js +92 -0
- package/dist/query/api/effectiveness.js +254 -0
- package/dist/query/api/efficiency-headroom.js +74 -0
- package/dist/query/api/headroom-trend.js +105 -0
- package/dist/query/api/hook-config.js +75 -0
- package/dist/query/api/hook-install.js +8 -0
- package/dist/query/api/hot-sessions.js +17 -0
- package/dist/query/api/idle-sessions.js +52 -0
- package/dist/query/api/index.js +40 -0
- package/dist/query/api/loop-guard.js +90 -0
- package/dist/query/api/offload-share.js +41 -0
- package/dist/query/api/outcomes.js +218 -0
- package/dist/query/api/overview.js +535 -0
- package/dist/query/api/rec-prompt.js +138 -0
- package/dist/query/api/recommendations-ledger.js +111 -0
- package/dist/query/api/recommendations.js +514 -0
- package/dist/query/api/reports.js +78 -0
- package/dist/query/api/self-churn.js +77 -0
- package/dist/query/api/self-percentiles.js +109 -0
- package/dist/query/api/session-drivers.js +153 -0
- package/dist/query/api/settings.js +85 -0
- package/dist/query/api/spend-flavor.js +234 -0
- package/dist/query/api/trends.js +155 -0
- package/dist/query/cap-weighted.js +119 -0
- package/dist/query/db-context.js +42 -0
- package/dist/query/envelope.js +71 -0
- package/dist/query/forecast.js +191 -0
- package/dist/query/settings-store.js +441 -0
- package/dist/query/spend.js +171 -0
- package/dist/query/trends.js +194 -0
- package/dist/ui/assets/index-DnRKgc21.css +1 -0
- package/dist/ui/assets/index-h1Q1wWq5.js +168 -0
- package/dist/ui/index.html +39 -0
- package/package.json +59 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/query/spend.ts — spend-path metric SQL (metric_definition_version = 'observe-1').
|
|
3
|
+
*
|
|
4
|
+
* Pure query building blocks over a better-sqlite3 handle. Each takes explicit
|
|
5
|
+
* window / cutoff parameters so callers (and tests) control time boundaries with
|
|
6
|
+
* no hidden `now()`. The API-method wrappers in api/overview.ts resolve the
|
|
7
|
+
* window/cutoff and attach the response envelope.
|
|
8
|
+
*
|
|
9
|
+
* Rules encoded here (Data Model v2 §3):
|
|
10
|
+
* - cost figures keep micro-USD integer precision; only per-turn/USD outputs
|
|
11
|
+
* divide by 1e6.
|
|
12
|
+
* - `cost_claim` kinds are surfaced, never silently mixed (COUNT(DISTINCT ...)).
|
|
13
|
+
* - reconciled (spend) aggregates exclude provisional turns; context-per-turn
|
|
14
|
+
* and the live strip deliberately include them (a documented difference).
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Spend grouped by workspace over [tFrom, tTo), reconciled turns only.
|
|
18
|
+
* ORDER BY cost_equiv_usd DESC (i.e., SUM(cost_equiv_u) DESC).
|
|
19
|
+
*/
|
|
20
|
+
export function spendByWorkspace(db, tFrom, tTo) {
|
|
21
|
+
return db
|
|
22
|
+
.prepare(`SELECT w.workspace_id AS workspace_id,
|
|
23
|
+
w.project_slug AS project_slug,
|
|
24
|
+
w.repo_path AS repo_path,
|
|
25
|
+
w.repo_owner AS repo_owner,
|
|
26
|
+
w.repo_name AS repo_name,
|
|
27
|
+
COALESCE(SUM(t.cost_equiv_u), 0) AS cost_equiv_u,
|
|
28
|
+
COALESCE(SUM(t.input_tokens), 0) AS input_tok,
|
|
29
|
+
COALESCE(SUM(t.output_tokens), 0) AS output_tok,
|
|
30
|
+
COALESCE(SUM(t.cache_read_tokens), 0) AS cache_read_tok,
|
|
31
|
+
COALESCE(SUM(t.cache_write_5m + t.cache_write_1h
|
|
32
|
+
+ t.cache_write_other), 0) AS cache_write_tok,
|
|
33
|
+
COUNT(*) AS turns,
|
|
34
|
+
SUM(t.cost_equiv_u IS NULL) AS unpriced_turns,
|
|
35
|
+
COUNT(DISTINCT t.cost_claim) AS claim_kinds
|
|
36
|
+
FROM turns t JOIN workspaces w USING (workspace_id)
|
|
37
|
+
WHERE t.ts >= ? AND t.ts < ? AND t.provisional = 0
|
|
38
|
+
GROUP BY w.workspace_id
|
|
39
|
+
ORDER BY cost_equiv_u DESC, w.workspace_id ASC`)
|
|
40
|
+
.all(tFrom, tTo);
|
|
41
|
+
}
|
|
42
|
+
/** Global spend aggregate over [tFrom, tTo). Reconciled unless noted per column. */
|
|
43
|
+
export function globalSpend(db, tFrom, tTo) {
|
|
44
|
+
const row = db
|
|
45
|
+
.prepare(`SELECT COALESCE(SUM(CASE WHEN provisional = 0 THEN cost_equiv_u END), 0) AS cost_equiv_u,
|
|
46
|
+
SUM(CASE WHEN provisional = 0 THEN 1 ELSE 0 END) AS turns,
|
|
47
|
+
COUNT(*) AS turns_total,
|
|
48
|
+
SUM(CASE WHEN provisional = 0 AND cost_equiv_u IS NULL
|
|
49
|
+
THEN 1 ELSE 0 END) AS unpriced_turns,
|
|
50
|
+
COUNT(DISTINCT CASE WHEN provisional = 0 THEN cost_claim END) AS claim_kinds
|
|
51
|
+
FROM turns
|
|
52
|
+
WHERE ts >= ? AND ts < ?`)
|
|
53
|
+
.get(tFrom, tTo);
|
|
54
|
+
return row;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether any reconciled turn in the window carries a stale claim.
|
|
58
|
+
* Used to choose the response's claim_kind (LIST_EQUIV vs LIST_EQUIV_STALE).
|
|
59
|
+
*/
|
|
60
|
+
export function hasStaleClaim(db, tFrom, tTo, workspaceId) {
|
|
61
|
+
const base = `SELECT 1 FROM turns
|
|
62
|
+
WHERE ts >= ? AND ts < ? AND provisional = 0
|
|
63
|
+
AND cost_claim = 'LIST_EQUIV_STALE'`;
|
|
64
|
+
const row = workspaceId
|
|
65
|
+
? db.prepare(`${base} AND workspace_id = ? LIMIT 1`).get(tFrom, tTo, workspaceId)
|
|
66
|
+
: db.prepare(`${base} LIMIT 1`).get(tFrom, tTo);
|
|
67
|
+
return row !== undefined;
|
|
68
|
+
}
|
|
69
|
+
/** Count LIVE sessions whose last_turn_at is at or after the activity cutoff. */
|
|
70
|
+
export function liveSessionCount(db, activityCutoff) {
|
|
71
|
+
const row = db
|
|
72
|
+
.prepare("SELECT COUNT(*) AS n FROM sessions WHERE state = 'LIVE' AND last_turn_at >= ?")
|
|
73
|
+
.get(activityCutoff);
|
|
74
|
+
return row.n;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Context per turn grouped by model over [tFrom, tTo).
|
|
78
|
+
* GUARD: window only — deliberately does NOT filter provisional (differs from
|
|
79
|
+
* spend). Claim kind OBS_PROXY. Ordered by turn count desc for stable output.
|
|
80
|
+
*/
|
|
81
|
+
export function contextPerTurnByModel(db, tFrom, tTo) {
|
|
82
|
+
return db
|
|
83
|
+
.prepare(`SELECT model AS model,
|
|
84
|
+
COUNT(*) AS n,
|
|
85
|
+
AVG(context_tokens) AS avg_context_per_turn,
|
|
86
|
+
AVG(output_tokens) AS avg_output_per_turn,
|
|
87
|
+
SUM(cost_equiv_u) * 1.0 / COUNT(*) / 1e6 AS usd_per_turn
|
|
88
|
+
FROM turns
|
|
89
|
+
WHERE ts >= ? AND ts < ?
|
|
90
|
+
GROUP BY model
|
|
91
|
+
ORDER BY n DESC, model ASC`)
|
|
92
|
+
.all(tFrom, tTo);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* LIVE sessions active at/after `activityCutoff`.
|
|
96
|
+
*
|
|
97
|
+
* running_usd_u is computed by an on-demand SUM over the session's turns at
|
|
98
|
+
* query time — NOT read from sessions.cost_equiv_u, which WP1 only maintains at
|
|
99
|
+
* reconcile/close and is therefore unreliable for mid-flight LIVE sessions.
|
|
100
|
+
* Provisional turns are intentionally included here (this is the live view).
|
|
101
|
+
*/
|
|
102
|
+
export function liveSessions(db, activityCutoff) {
|
|
103
|
+
return db
|
|
104
|
+
.prepare(`SELECT s.session_id AS session_id,
|
|
105
|
+
s.workspace_id AS workspace_id,
|
|
106
|
+
w.project_slug AS project_slug,
|
|
107
|
+
w.repo_path AS repo_path,
|
|
108
|
+
w.repo_owner AS repo_owner,
|
|
109
|
+
w.repo_name AS repo_name,
|
|
110
|
+
COALESCE((SELECT SUM(t.cost_equiv_u) FROM turns t
|
|
111
|
+
WHERE t.session_id = s.session_id), 0) AS running_usd_u,
|
|
112
|
+
COALESCE((SELECT tt.context_tokens FROM turns tt
|
|
113
|
+
WHERE tt.session_id = s.session_id
|
|
114
|
+
ORDER BY tt.ts DESC LIMIT 1), 0) AS current_context_tokens,
|
|
115
|
+
COALESCE((SELECT tm.model FROM turns tm
|
|
116
|
+
WHERE tm.session_id = s.session_id
|
|
117
|
+
ORDER BY tm.ts DESC LIMIT 1), '') AS model,
|
|
118
|
+
s.first_turn_at AS started_at
|
|
119
|
+
FROM sessions s JOIN workspaces w USING (workspace_id)
|
|
120
|
+
WHERE s.state = 'LIVE' AND s.last_turn_at >= ?
|
|
121
|
+
ORDER BY s.last_turn_at DESC, s.session_id ASC`)
|
|
122
|
+
.all(activityCutoff);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Sessions ranked by total list-equivalent cost across all turns.
|
|
126
|
+
*
|
|
127
|
+
* This deliberately includes provisional turns and applies no turn-count or
|
|
128
|
+
* average-context threshold: the view is a cost ranking, not a thresholded
|
|
129
|
+
* context-pressure detector.
|
|
130
|
+
*/
|
|
131
|
+
export function hotSessionsByCost(db, limit = 20, window) {
|
|
132
|
+
// Optional window bounds the JOINed turns to [from, to); absent = all-time
|
|
133
|
+
// (preserves the /api/hot-sessions default). Matches the `t.ts >= ? AND t.ts < ?`
|
|
134
|
+
// string-bound convention used by globalSpend and the other spend queries.
|
|
135
|
+
const turnWindow = window === undefined ? "" : "AND t.ts >= @from AND t.ts < @to";
|
|
136
|
+
const rows = db
|
|
137
|
+
.prepare(`SELECT s.session_id AS session_id,
|
|
138
|
+
s.workspace_id AS workspace_id,
|
|
139
|
+
COUNT(*) AS turns,
|
|
140
|
+
COALESCE(SUM(t.cost_equiv_u), 0) AS cost_equiv_u,
|
|
141
|
+
COALESCE(SUM(t.output_tokens), 0) AS total_output_tokens,
|
|
142
|
+
ROUND(COALESCE(SUM(t.output_tokens), 0) * 1.0 / COUNT(*)) AS avg_output_tokens,
|
|
143
|
+
COALESCE(SUM(t.context_tokens), 0) AS total_context_tokens,
|
|
144
|
+
ROUND(COALESCE(SUM(t.context_tokens), 0) * 1.0 / COUNT(*)) AS avg_context_tokens,
|
|
145
|
+
COALESCE((SELECT tm.model FROM turns tm
|
|
146
|
+
WHERE tm.session_id = s.session_id
|
|
147
|
+
GROUP BY tm.model
|
|
148
|
+
ORDER BY COUNT(*) DESC, tm.model ASC LIMIT 1), '') AS model,
|
|
149
|
+
COALESCE(s.last_turn_at, MAX(t.ts)) AS last_turn_at,
|
|
150
|
+
s.api_error_count AS api_error_count,
|
|
151
|
+
s.compaction_count AS compaction_count,
|
|
152
|
+
s.interrupt_count AS interrupt_count,
|
|
153
|
+
s.user_turn_count AS user_turn_count,
|
|
154
|
+
(SELECT COUNT(*) FROM tool_events te
|
|
155
|
+
WHERE te.session_id = s.session_id
|
|
156
|
+
AND te.exit_class = 'ERROR') AS tool_error_count,
|
|
157
|
+
(SELECT COUNT(*) FROM tool_events te
|
|
158
|
+
WHERE te.session_id = s.session_id
|
|
159
|
+
AND te.exit_class = 'TEST_FAIL') AS test_fail_count,
|
|
160
|
+
s.gap_median_s AS gap_median_s,
|
|
161
|
+
s.gap_p90_s AS gap_p90_s,
|
|
162
|
+
s.long_gap_count AS long_gap_count,
|
|
163
|
+
s.gap_n AS gap_n
|
|
164
|
+
FROM sessions s JOIN turns t ON t.session_id = s.session_id
|
|
165
|
+
WHERE 1 = 1 ${turnWindow}
|
|
166
|
+
GROUP BY s.session_id, s.workspace_id, s.last_turn_at
|
|
167
|
+
ORDER BY cost_equiv_u DESC, s.session_id ASC
|
|
168
|
+
LIMIT @limit`)
|
|
169
|
+
.all(window === undefined ? { limit } : { limit, from: window.from, to: window.to });
|
|
170
|
+
return rows;
|
|
171
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/query/trends.ts — spend-over-time bucketed queries.
|
|
3
|
+
*
|
|
4
|
+
* Pure query building blocks over a better-sqlite3 handle. No hidden `now()`.
|
|
5
|
+
* All timestamp parameters are explicit so callers (and tests) control boundaries.
|
|
6
|
+
*
|
|
7
|
+
* Reconciliation guarantee: SUM(SpendBucketRow.cost_equiv_u) over all buckets
|
|
8
|
+
* in a window EQUALS globalSpend(db, tFrom, tTo).cost_equiv_u for the same
|
|
9
|
+
* window. Both use `provisional = 0` and the same [tFrom, tTo) half-open interval.
|
|
10
|
+
*
|
|
11
|
+
* Bucket key SQL expressions (document verbatim — do not change without bumping
|
|
12
|
+
* metric_definition_version):
|
|
13
|
+
* day → date(ts, 'localtime') SQLite 'localtime' modifier converts
|
|
14
|
+
* stored UTC ISO-8601 ts to the process's local calendar day.
|
|
15
|
+
* week → strftime('%Y-%W', ts) UTC-based ISO year-week (no localtime).
|
|
16
|
+
* month → strftime('%Y-%m', ts) UTC-based year-month (no localtime).
|
|
17
|
+
*
|
|
18
|
+
* The week/month bucket keys are UTC-based intentionally: cross-day rounding at
|
|
19
|
+
* midnight matters most for the daily bucket; weekly/monthly boundaries are
|
|
20
|
+
* wide enough that UTC vs local is rarely material.
|
|
21
|
+
*/
|
|
22
|
+
import { capWeightExprSql } from "./cap-weighted.js";
|
|
23
|
+
/** The SQL expression for the given bucket size (verbatim). */
|
|
24
|
+
function bucketExpr(bucket) {
|
|
25
|
+
if (bucket === "day")
|
|
26
|
+
return "date(ts, 'localtime')";
|
|
27
|
+
if (bucket === "week")
|
|
28
|
+
return "strftime('%Y-%W', ts)";
|
|
29
|
+
return "strftime('%Y-%m', ts)";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Like bucketExpr but for an aliased `turns t` reference (used in cap-weighted
|
|
33
|
+
* queries that require the `t.` alias for capWeightExprSql compatibility).
|
|
34
|
+
*/
|
|
35
|
+
function aliasedBucketExpr(bucket) {
|
|
36
|
+
if (bucket === "day")
|
|
37
|
+
return "date(t.ts, 'localtime')";
|
|
38
|
+
if (bucket === "week")
|
|
39
|
+
return "strftime('%Y-%W', t.ts)";
|
|
40
|
+
return "strftime('%Y-%m', t.ts)";
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Spend grouped by time bucket over [tFrom, tTo), reconciled turns only.
|
|
44
|
+
*
|
|
45
|
+
* SUM(cost_equiv_u) across all returned rows equals globalSpend for the
|
|
46
|
+
* same window (both filter provisional=0 and the same [tFrom, tTo)).
|
|
47
|
+
*
|
|
48
|
+
* Optionally scoped to one workspace.
|
|
49
|
+
*/
|
|
50
|
+
export function spendByBucket(db, tFrom, tTo, bucket, workspaceId) {
|
|
51
|
+
const bk = bucketExpr(bucket);
|
|
52
|
+
const base = `SELECT ${bk} AS bucket,
|
|
53
|
+
COALESCE(SUM(cost_equiv_u), 0) AS cost_equiv_u,
|
|
54
|
+
COUNT(*) AS turns
|
|
55
|
+
FROM turns
|
|
56
|
+
WHERE ts >= ? AND ts < ? AND provisional = 0`;
|
|
57
|
+
const tail = "GROUP BY bucket ORDER BY bucket ASC";
|
|
58
|
+
if (workspaceId !== undefined) {
|
|
59
|
+
return db
|
|
60
|
+
.prepare(`${base} AND workspace_id = ? ${tail}`)
|
|
61
|
+
.all(tFrom, tTo, workspaceId);
|
|
62
|
+
}
|
|
63
|
+
return db.prepare(`${base} ${tail}`).all(tFrom, tTo);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Spend grouped by bucket × model over [tFrom, tTo), reconciled turns only.
|
|
67
|
+
* Used to build the stacked-by-model bar series on TrendChart.
|
|
68
|
+
*/
|
|
69
|
+
export function spendByBucketAndModel(db, tFrom, tTo, bucket, workspaceId) {
|
|
70
|
+
const bk = bucketExpr(bucket);
|
|
71
|
+
const base = `SELECT ${bk} AS bucket,
|
|
72
|
+
model,
|
|
73
|
+
COALESCE(SUM(cost_equiv_u), 0) AS cost_equiv_u,
|
|
74
|
+
COUNT(*) AS turns
|
|
75
|
+
FROM turns
|
|
76
|
+
WHERE ts >= ? AND ts < ? AND provisional = 0`;
|
|
77
|
+
const tail = "GROUP BY bucket, model ORDER BY bucket ASC, model ASC";
|
|
78
|
+
if (workspaceId !== undefined) {
|
|
79
|
+
return db
|
|
80
|
+
.prepare(`${base} AND workspace_id = ? ${tail}`)
|
|
81
|
+
.all(tFrom, tTo, workspaceId);
|
|
82
|
+
}
|
|
83
|
+
return db.prepare(`${base} ${tail}`).all(tFrom, tTo);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Spend grouped by bucket × workspace over [tFrom, tTo), reconciled turns only.
|
|
87
|
+
* Used to build the stacked-by-workspace bar series on TrendChart.
|
|
88
|
+
*/
|
|
89
|
+
export function spendByBucketAndWorkspace(db, tFrom, tTo, bucket) {
|
|
90
|
+
const bk = bucketExpr(bucket);
|
|
91
|
+
return db
|
|
92
|
+
.prepare(`SELECT ${bk} AS bucket,
|
|
93
|
+
w.workspace_id,
|
|
94
|
+
w.project_slug,
|
|
95
|
+
COALESCE(SUM(t.cost_equiv_u), 0) AS cost_equiv_u,
|
|
96
|
+
COUNT(*) AS turns
|
|
97
|
+
FROM turns t JOIN workspaces w USING (workspace_id)
|
|
98
|
+
WHERE t.ts >= ? AND t.ts < ? AND t.provisional = 0
|
|
99
|
+
GROUP BY bucket, w.workspace_id
|
|
100
|
+
ORDER BY bucket ASC, w.workspace_id ASC`)
|
|
101
|
+
.all(tFrom, tTo);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Cap-weighted token total grouped by time bucket over [tFrom, tTo).
|
|
105
|
+
*
|
|
106
|
+
* Reconciliation guarantee: SUM(cap_weighted_tokens) over all returned buckets
|
|
107
|
+
* equals capWeightedTokens(db, { fromIso: tFrom, toIso: tTo, coeff }).cap_weighted_tokens
|
|
108
|
+
* for the same window + coeff (both filter provisional=0 and the same half-open
|
|
109
|
+
* [tFrom, tTo) interval). Single-bucket windows are exactly equal; multi-bucket
|
|
110
|
+
* windows may differ by at most ±bucketCount due to per-bucket Math.round.
|
|
111
|
+
*
|
|
112
|
+
* Uses an aliased `turns t` so capWeightExprSql("t", coeff) inlines cleanly.
|
|
113
|
+
* Does NOT reference context_tokens (frozen column — see cap-weighted.ts header).
|
|
114
|
+
*/
|
|
115
|
+
export function capWeightedByBucket(db, tFrom, tTo, bucket, coeff, workspaceId) {
|
|
116
|
+
const bk = aliasedBucketExpr(bucket);
|
|
117
|
+
const expr = capWeightExprSql("t", coeff);
|
|
118
|
+
const base = `SELECT ${bk} AS bucket,
|
|
119
|
+
CAST(ROUND(SUM(${expr})) AS INTEGER) AS cap_weighted_tokens,
|
|
120
|
+
COUNT(*) AS turns
|
|
121
|
+
FROM turns t
|
|
122
|
+
WHERE t.ts >= ? AND t.ts < ? AND t.provisional = 0`;
|
|
123
|
+
const tail = "GROUP BY bucket ORDER BY bucket ASC";
|
|
124
|
+
if (workspaceId !== undefined) {
|
|
125
|
+
return db
|
|
126
|
+
.prepare(`${base} AND t.workspace_id = ? ${tail}`)
|
|
127
|
+
.all(tFrom, tTo, workspaceId);
|
|
128
|
+
}
|
|
129
|
+
return db.prepare(`${base} ${tail}`).all(tFrom, tTo);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Cache write + read tokens grouped by time bucket over [tFrom, tTo).
|
|
133
|
+
*
|
|
134
|
+
* Reconciliation guarantee: SUM(cache_creation_tokens) over all returned buckets
|
|
135
|
+
* equals cache_creation_tokens from capWeightedTokens(db, { fromIso: tFrom, toIso: tTo })
|
|
136
|
+
* for the same window (both filter provisional=0, same [tFrom, tTo) interval).
|
|
137
|
+
*
|
|
138
|
+
* efficiency_ratio is computed in TypeScript to avoid SQLite division edge cases.
|
|
139
|
+
*/
|
|
140
|
+
export function cacheWriteByBucket(db, tFrom, tTo, bucket, workspaceId) {
|
|
141
|
+
const bk = aliasedBucketExpr(bucket);
|
|
142
|
+
const base = `SELECT ${bk} AS bucket,
|
|
143
|
+
COALESCE(SUM(t.cache_write_5m + t.cache_write_1h
|
|
144
|
+
+ t.cache_write_other), 0) AS cache_creation_tokens,
|
|
145
|
+
COALESCE(SUM(t.cache_read_tokens), 0) AS cache_read_tokens,
|
|
146
|
+
COUNT(*) AS turns
|
|
147
|
+
FROM turns t
|
|
148
|
+
WHERE t.ts >= ? AND t.ts < ? AND t.provisional = 0`;
|
|
149
|
+
const tail = "GROUP BY bucket ORDER BY bucket ASC";
|
|
150
|
+
let rawRows;
|
|
151
|
+
if (workspaceId !== undefined) {
|
|
152
|
+
rawRows = db
|
|
153
|
+
.prepare(`${base} AND t.workspace_id = ? ${tail}`)
|
|
154
|
+
.all(tFrom, tTo, workspaceId);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
rawRows = db.prepare(`${base} ${tail}`).all(tFrom, tTo);
|
|
158
|
+
}
|
|
159
|
+
return rawRows.map((r) => ({
|
|
160
|
+
...r,
|
|
161
|
+
efficiency_ratio: r.cache_creation_tokens + r.cache_read_tokens > 0
|
|
162
|
+
? r.cache_read_tokens / (r.cache_read_tokens + r.cache_creation_tokens)
|
|
163
|
+
: null,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Per-session cost series — from sessions table
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
/**
|
|
170
|
+
* Per-session cost over time, RECONCILED sessions only (state='RECONCILED').
|
|
171
|
+
*
|
|
172
|
+
* The sessions.cost_equiv_u column is only reliable after reconciliation at
|
|
173
|
+
* close; LIVE sessions are intentionally excluded (their cost is computed
|
|
174
|
+
* on-demand from turns in the live strip — see liveSessions() in spend.ts).
|
|
175
|
+
*
|
|
176
|
+
* Optionally scoped to one workspace.
|
|
177
|
+
*/
|
|
178
|
+
export function sessionCostSeries(db, tFrom, tTo, workspaceId) {
|
|
179
|
+
const base = `SELECT s.session_id,
|
|
180
|
+
s.workspace_id,
|
|
181
|
+
w.project_slug,
|
|
182
|
+
s.first_turn_at,
|
|
183
|
+
s.cost_equiv_u
|
|
184
|
+
FROM sessions s JOIN workspaces w USING (workspace_id)
|
|
185
|
+
WHERE s.state = 'RECONCILED'
|
|
186
|
+
AND s.first_turn_at >= ? AND s.first_turn_at < ?`;
|
|
187
|
+
const tail = "ORDER BY s.first_turn_at ASC, s.session_id ASC";
|
|
188
|
+
if (workspaceId !== undefined) {
|
|
189
|
+
return db
|
|
190
|
+
.prepare(`${base} AND s.workspace_id = ? ${tail}`)
|
|
191
|
+
.all(tFrom, tTo, workspaceId);
|
|
192
|
+
}
|
|
193
|
+
return db.prepare(`${base} ${tail}`).all(tFrom, tTo);
|
|
194
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg: #0b0f1a;--panel: #111827;--panel2: #1a2233;--line: rgba(255, 255, 255, .07);--sidebar: var(--bg);--text: #e2e8f0;--text-2: #94a3b8;--text-muted: #64748b;--soft: var(--text-2);--muted: var(--text-muted);--cyan: #22d3ee;--violet: #a78bfa;--green: #34d399;--amber: #fbbf24;--rose: #fb7185;--purple: var(--violet);--red: var(--rose);--blue: #60a5fa;--orange: var(--amber);--teal: #2dd4bf;--series-1: var(--cyan);--series-2: var(--violet);--series-3: var(--blue);--series-4: var(--green);--series-5: var(--amber);--accent-ink: #06151a;--r: 15px;--shadow: 0 16px 38px rgba(0, 0, 0, .22)}:root[data-theme=light]{--bg: #f8fafc;--panel: #ffffff;--panel2: #f1f5f9;--line: rgba(15, 23, 42, .12);--sidebar: var(--bg);--text: #0f172a;--text-2: #475569;--text-muted: #64748b;--soft: var(--text-2);--muted: var(--text-muted);--cyan: #0891b2;--violet: #7c3aed;--green: #059669;--amber: #b45309;--rose: #e11d48;--purple: var(--violet);--red: var(--rose);--blue: #2563eb;--orange: var(--amber);--teal: #0d9488;--series-1: var(--cyan);--series-2: var(--violet);--series-3: var(--blue);--series-4: var(--green);--series-5: var(--amber);--accent-ink: #f8fafc;--shadow: 0 8px 24px rgba(15, 23, 42, .1);color-scheme:light}*,*:before,*:after{box-sizing:border-box}html{background:var(--bg)}body{margin:0;color:var(--text);background:var(--bg);font:14px/1.45 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}button,select,input{font:inherit}button{color:inherit}.shell{display:grid;grid-template-columns:238px 1fr;min-height:100vh}.sidebar{position:sticky;top:0;height:100vh;background:var(--sidebar);border-right:1px solid var(--line);padding:22px 15px;z-index:5;overflow-y:auto;display:flex;flex-direction:column}.brand{display:flex;align-items:center;gap:11px;font-weight:780;font-size:17px;margin:0 7px 24px;flex-shrink:0}.brandmark{width:30px;height:30px;border:1px solid var(--line);border-radius:9px;display:grid;place-items:center;background:var(--panel);flex-shrink:0}.brandmark:before{content:"";width:13px;height:13px;border:2px solid var(--cyan);border-top-color:var(--purple);border-radius:4px;transform:rotate(12deg)}.navgroup{margin:13px 0 5px 10px;color:var(--text-muted);font-size:11px;text-transform:uppercase;letter-spacing:.08em}.nav-btn{width:100%;border:0;background:transparent;color:var(--muted);text-align:left;padding:10px 11px;border-radius:9px;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:9px}.nav-btn:hover:not(:disabled),.nav-btn.active{background:var(--panel2);color:var(--text)}.nav-btn:disabled{cursor:default;opacity:.5}.theme-toggle{width:100%;margin-top:6px;border:1px solid var(--line);background:transparent;color:var(--muted);text-align:left;padding:8px 11px;border-radius:9px;cursor:pointer;font-size:12px}.theme-toggle:hover{background:var(--panel2);color:var(--text)}.nav-badge{font-size:11px;padding:2px 6px;border-radius:99px;border:1px solid var(--line);color:var(--text-muted)}.nav-dot{width:7px;height:7px;border-radius:50%;background:var(--amber);flex-shrink:0}.sidebox{margin-top:auto;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:12px}.sidebox b{font-size:12px}.sidebox-sub{font-size:11px;color:var(--muted);margin-top:4px}.main-content{padding:23px 28px 48px;max-width:1720px;width:100%;margin:auto;min-width:0}.page-top{display:flex;justify-content:space-between;align-items:flex-start;gap:18px;margin-bottom:16px}.page-title h1{margin:0 0 4px;font-size:24px;letter-spacing:-.025em}.page-sub{color:var(--muted);font-size:12px}.banner{padding:10px 14px;border-radius:11px;margin-bottom:14px;display:flex;align-items:center;gap:10px;font-size:12px}.banner-info{border:1px solid var(--line);background:var(--panel2);color:var(--soft)}.banner-error{border:1px solid color-mix(in srgb,var(--rose) 45%,transparent);background:color-mix(in srgb,var(--rose) 9%,var(--bg));color:var(--rose)}.banner-retry{margin-left:auto;border:1px solid color-mix(in srgb,var(--rose) 45%,transparent);background:transparent;color:var(--rose);border-radius:7px;padding:4px 10px;font-size:11px;cursor:pointer;flex-shrink:0}.banner-retry:hover{background:color-mix(in srgb,var(--rose) 14%,var(--bg))}.date-range{display:flex;gap:4px;align-items:center}.date-preset-btn{border:1px solid var(--line);background:transparent;color:var(--muted);border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}.date-preset-btn.active,.date-preset-btn:hover{background:var(--panel2);border-color:var(--cyan);color:var(--text)}.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);min-width:0}.kpi-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:13px;margin-bottom:13px}.kpi{padding:14px}.kpi-label{color:var(--muted);font-size:11px;margin-bottom:6px}.kpi-value{font-size:24px;font-weight:780;letter-spacing:-.035em}.kpi-value-sm{font-size:16px;font-weight:700;letter-spacing:-.02em}.kpi-subval{font-size:11px;color:var(--soft);margin-top:4px}.kpi-off{font-size:18px;font-weight:700;color:var(--muted)}.kpi-off-hint{font-size:11px;color:var(--muted);margin-top:4px;line-height:1.4}.chips{margin-top:7px;display:flex;flex-wrap:wrap;gap:4px}.kpi-fn{font-size:11px;color:var(--text-muted);margin-top:4px}.ctx-rows{display:grid;gap:6px;margin-top:4px}.ctx-row{display:grid;grid-template-columns:1fr auto;gap:6px;font-size:11px}.ctx-model{color:var(--soft);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ctx-value{color:var(--text);font-weight:600;text-align:right;white-space:nowrap}.chip{display:inline-flex;align-items:center;gap:5px;border-radius:99px;padding:3px 7px;font-size:11px;font-weight:700;border:1px solid transparent;white-space:nowrap}.chip-exact{background:#71d89b17;color:var(--green);border-color:#71d89b38}.chip-list-equiv{background:#4ecdc417;color:var(--teal);border-color:#4ecdc438}.chip-stale,.chip-partial{background:#f4c76617;color:var(--amber);border-color:#f4c76638}.chip-proxy{background:#ff858517;color:var(--red);border-color:#ff858538}.chip-obs-proxy{background:#65d3ff17;color:var(--cyan);border-color:#65d3ff38}.chip-experimental{background:#7fb3ff17;color:var(--blue);border-color:#7fb3ff38}.chip-na{background:#8492a614;color:var(--text-2);border-color:var(--line)}.chip-live{background:#ffad5517;color:var(--orange);border-color:#ffad5538}.chip-modeled{background:#b8a0ff17;color:var(--purple);border-color:#b8a0ff38}.chip-directional{background:#b8a0ff12;color:var(--purple);border-color:#b8a0ff2e}.chip-observed{background:#4ecdc417;color:var(--teal);border-color:#4ecdc438}.impact-ledger{margin-top:10px;display:grid;gap:8px}.ledger-entry{padding:8px 10px}.ledger-head{display:flex;justify-content:space-between;gap:8px;font-size:12px;align-items:baseline}.ledger-row{display:grid;grid-template-columns:1fr auto;gap:6px;font-size:11px;margin-top:5px}.ledger-key{color:var(--soft)}.ledger-val{text-align:right}.ledger-banner{margin-top:6px}.ledger-note{margin-top:5px}.detstatus-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.detstatus-item{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}.detstatus-name{font-weight:600;font-size:12px}.detstatus-note{color:var(--text-2);font-size:11px}.detstatus-active{background:#71d89b17;color:var(--green);border-color:#71d89b38}.detstatus-inactive{background:#8492a614;color:var(--text-2);border-color:var(--line)}.detstatus-blocked{background:#f4c76617;color:var(--amber);border-color:#f4c76638}.detstatus-not-evaluated{background:#8492a614;color:var(--text-2);border-color:var(--line)}.rec-badge{display:inline-block;padding:2px 8px;border-radius:6px;background:#7fb3ff1f;color:var(--blue);font-weight:700;margin-right:6px}.rec-section{margin-bottom:12px}.rec-section-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);margin:0 0 4px}.rec-evidence,.rec-formula-inputs{list-style:none;margin:0;padding:0;font-size:12px}.rec-evidence-key{color:var(--cyan)}.rec-savings-value{font-size:20px;font-weight:700;display:flex;align-items:center;gap:8px}.rec-formula-expr code{font-size:11px;color:var(--purple)}.rec-modeled-note{font-size:11px;color:var(--text-2);margin-top:6px}.rec-unvalidated-note{font-size:11px;color:var(--amber);margin:4px 0 0}.rec-ranking-explainer{max-width:860px;margin:4px 0 13px;color:var(--muted);font-size:12px;line-height:1.5}.rec-rank-explanation{padding:10px 12px;border-left:3px solid rgba(101,211,255,.45);border-radius:0 6px 6px 0;background:#65d3ff0d}.rec-rank-explanation p,.rec-success-measure{margin:0;color:var(--soft);font-size:12px;line-height:1.5}.rec-observations{margin:0;padding-left:20px;color:var(--soft);font-size:12px;line-height:1.5}.rec-observations li+li{margin-top:4px}.rec-collapsed-row{padding:14px 16px}.rec-group-header{padding:14px 16px 10px}.rec-group-detector,.rec-group-count{font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}.rec-group-detector{font-weight:750;letter-spacing:.04em}.rec-group-count{white-space:nowrap}.rec-group-summary{display:flex;flex-wrap:wrap;gap:8px 16px;margin:2px 0 10px;color:var(--soft);font-size:11px;font-variant-numeric:tabular-nums}.rec-group-header .rec-rank-explanation{margin:8px 0 0}.rec-group-header .rec-actions-hint{text-align:left;margin-bottom:0}.rec-group-members{border-top:1px solid var(--line)}.rec-session-row{padding:12px 16px 2px;border-bottom:1px solid var(--line)}.rec-session-row:last-child{border-bottom:0}.rec-session-row .rec-collapsed-row{padding:0 0 10px}.rec-session-links{display:flex;flex-wrap:wrap;gap:5px 12px;list-style:none;margin:8px 0 0;padding:0;color:var(--muted);font-size:11px}.rec-session-links a{color:var(--cyan);text-decoration:underline;text-underline-offset:2px}.rec-session-links a:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}.rec-session-label{color:var(--muted)}.rec-minor-items>summary{cursor:pointer;padding:10px 16px 12px;color:var(--cyan);font-size:11px;font-weight:650}.rec-minor-items>summary:focus-visible{outline:2px solid var(--cyan);outline-offset:-2px}.rec-details{padding:14px 16px;border-top:1px solid var(--line)}.rec-header-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px}.rec-title{font-size:15px;font-weight:650;margin:0;color:var(--text);flex:1;min-width:0}.rec-headline{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.rec-headline-text{font-size:13px;font-weight:600;color:var(--soft)}.rec-confidence-tier{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;border:1px solid;border-radius:99px;padding:2px 7px;flex-shrink:0}.rec-confidence-tier--modeled{color:var(--cyan);background:#65d3ff1a;border-color:#65d3ff59}.rec-confidence-tier--advisory{color:var(--amber);background:#f4c7661a;border-color:#f4c76659}.rec-confidence-tier--directional{color:var(--purple);background:#b8a0ff1a;border-color:#b8a0ff59}.rec-confidence-tier--warning{color:var(--red);background:#ff85851a;border-color:#ff858559}.rec-rank-badge{font-size:11px;font-weight:700;color:var(--muted);background:#8492a61a;border:1px solid var(--line);border-radius:5px;padding:2px 6px;flex-shrink:0}.rec-category-chip,.rec-cross-ws-badge{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);background:#8492a614;border:1px solid var(--line);border-radius:99px;padding:2px 7px;flex-shrink:0}.rec-adopted-pill{font-size:11px;font-weight:700;color:var(--green);background:#71d89b1a;border:1px solid rgba(113,216,155,.25);border-radius:99px;padding:2px 7px;flex-shrink:0}.rec-flagship-banner{display:flex;align-items:center;gap:10px;padding:8px 16px;background:#65d3ff0d;border-bottom:1px solid rgba(101,211,255,.15);border-radius:var(--r) var(--r) 0 0}.rec-flagship-badge{font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--cyan);background:#65d3ff1a;border:1px solid rgba(101,211,255,.3);border-radius:5px;padding:2px 7px;flex-shrink:0}.rec-flagship-rationale{font-size:11px;color:var(--text-2)}.rec-action-btn{display:inline-block;border-radius:7px;padding:6px 12px;font:inherit;font-size:12px;font-weight:550;cursor:pointer;white-space:nowrap;border:1px solid var(--line);background:var(--panel2);color:var(--soft);transition:background .12s,border-color .12s}.rec-action-btn:hover:not(:disabled){background:var(--panel2);border-color:var(--cyan);color:var(--text)}.rec-action-btn:disabled{opacity:.4;cursor:default}.rec-action-btn--primary{background:#65d3ff1f;border-color:#65d3ff59;color:var(--cyan);font-weight:650}.rec-action-btn--primary:hover:not(:disabled){background:#65d3ff33;border-color:#65d3ff80;color:var(--cyan)}.rec-action-btn--ghost{background:transparent;border-color:var(--line);color:var(--muted)}.rec-action-btn--ghost:hover:not(:disabled){background:#8492a612;border-color:var(--cyan);color:var(--soft)}a.rec-action-btn{text-decoration:none}.rec-primary-action{margin:10px 16px;display:flex;flex-direction:column;align-items:flex-start;gap:8px}.rec-guided-prompt{width:100%}.rec-guided-toggle{font-size:11px}.rec-guided-prompt .rec-prompt-artifact{margin:8px 0 0}.rec-actions{display:flex;gap:8px;margin-top:12px;justify-content:flex-end;flex-wrap:wrap}.rec-actions-hint{font-size:11px;color:var(--muted);margin:6px 0 0;text-align:right}.rec-apply-panel{margin:10px 16px 14px;padding:10px 12px;border:1px solid var(--line);border-radius:7px;background:#ffffff08}.rec-apply-preview{max-height:220px;overflow:auto;margin:8px 0 10px;padding:10px;border-radius:6px;border:1px solid var(--line);background:var(--bg);color:var(--soft);font-size:11px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.rec-prompt-artifact{margin:10px 16px;padding:10px 12px;border:1px solid var(--line);border-radius:7px;background:#ffffff08}.rec-prompt-artifact textarea{display:block;box-sizing:border-box;width:100%;min-height:120px;margin:8px 0 10px;padding:10px;border:1px solid var(--line);border-radius:6px;background:var(--bg);color:var(--soft);font:inherit;font-size:11px;line-height:1.45;resize:vertical}.rec-apply-status{margin:8px 16px 14px;font-size:12px;color:var(--soft)}.rec-apply-status--error{color:var(--rose)}.rec-diagnostics{margin:16px 0 12px;border:1px solid var(--line);border-radius:7px;background:#ffffff05;color:var(--muted);font-size:11px}.rec-diagnostics>summary{padding:9px 11px;cursor:pointer;color:var(--soft);font-weight:650}.rec-diagnostics[open]>summary{border-bottom:1px solid var(--line)}.rec-diagnostics-body{padding:11px}.rec-diagnostics-body .rec-section:last-child{margin-bottom:0}.rec-evidence-raw li,.rec-diagnostics-body code{overflow-wrap:anywhere;word-break:break-word}.skeleton{display:inline-block;background:linear-gradient(90deg,var(--panel) 25%,var(--panel2) 50%,var(--panel) 75%);background-size:200% 100%;animation:skeleton-pulse 1.5s infinite;animation-delay:.1s;border-radius:6px}.kpi-grid>.skeleton-kpi{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow)}.skeleton-chart{display:block;width:100%}.skeleton-line{height:14px;display:block;margin-bottom:6px}.skeleton-value{height:28px;display:block;margin-bottom:8px;width:60%}.skeleton-chip{height:18px;width:70px;display:inline-block}@keyframes skeleton-pulse{0%{background-position:200% 0}to{background-position:-200% 0}}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px 16px;color:var(--muted);text-align:center;gap:8px}.empty-state-icon{font-size:28px;opacity:.4}.empty-state-text{font-size:12px;line-height:1.5}.section-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:15px 16px 0}.section-head h2{font-size:14px;margin:0}.section-meta{font-size:11px;color:var(--muted)}.table-wrap{overflow-x:auto;padding:0 16px 16px;margin-top:12px}table{width:100%;border-collapse:collapse}th{text-align:left;color:var(--muted);font-size:11px;font-weight:650;padding:8px 9px;border-bottom:1px solid var(--line)}td{padding:9px;border-bottom:1px solid var(--line);color:var(--soft);font-size:11px;font-variant-numeric:tabular-nums}td:first-child{color:var(--text)}tr.total-row td{color:var(--text);font-weight:600;border-top:1px solid var(--line);border-bottom:0}.table-footnotes{padding:8px 16px 14px;font-size:11px;color:var(--text-muted);line-height:1.6}.live-strip-wrap{margin-bottom:13px}.live-strip{border:1px solid var(--line);border-radius:11px;background:var(--panel2);padding:12px 15px}.live-strip-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;font-size:12px;font-weight:600}.live-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--text-muted);margin-right:6px}.live-dot.on{background:var(--orange);animation:pulse 1.5s infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.4}}.live-strip-empty{color:var(--muted);font-size:11px;padding:8px 0}.live-strip-freshness{font-size:11px;color:var(--muted);font-weight:400}.live-row-live td:first-child{border-left:3px solid var(--orange);padding-left:6px;transition:border-color .4s ease}.live-row-reconciled td:first-child{border-left:3px solid transparent;padding-left:6px;transition:border-color .4s ease}.live-session-link{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;padding:0;background:none;color:var(--cyan);cursor:pointer;font:inherit;text-decoration:underline;text-underline-offset:2px}.live-session-link:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}.context-workspace-select{display:block;width:min(100%,28rem);margin-top:8px;padding:7px 10px;color:var(--text);color-scheme:dark;background:var(--panel2);border:1px solid var(--line);border-radius:8px}:root[data-theme=light] .context-workspace-select{color-scheme:light}.context-workspace-select:focus-visible{outline:2px solid var(--cyan);outline-offset:1px}.session-detail{max-width:1000px}.session-back{margin-bottom:14px}.session-kpis{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:14px}.session-kpis>div{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:13px;display:grid;gap:5px;min-width:0;box-sizing:border-box}.session-kpis span{color:var(--muted);font-size:11px}.session-kpis b{font-size:14px;color:var(--text);font-variant-numeric:tabular-nums}.session-load-more{padding:0 16px 16px}.session-detail-empty{min-height:200px}.hot-session-row{cursor:pointer}.hot-session-row:hover>td{background:var(--panel2)}.settings-page{max-width:820px}.settings-section{margin-bottom:13px}.settings-body{padding:16px;display:flex;flex-direction:column;gap:18px}.settings-row{display:flex;align-items:flex-start;gap:16px}.settings-label{min-width:200px;font-size:13px;color:var(--text);display:flex;flex-direction:column;gap:3px;padding-top:6px}.settings-hint{font-size:11px;color:var(--muted);font-weight:400}.settings-field-wrap{flex:1;display:flex;flex-direction:column;gap:4px}.settings-input{flex:1;background:var(--panel2);border:1px solid var(--line);border-radius:8px;color:var(--text);padding:7px 10px;font:inherit;font-size:13px;min-width:0;width:100%}.settings-input:focus{outline:2px solid var(--cyan);outline-offset:1px}.settings-input[readonly]{color:var(--muted);cursor:default}.settings-input-sm{width:240px}.settings-textarea{background:var(--panel2);border:1px solid var(--line);border-radius:8px;color:var(--text);padding:7px 10px;font:inherit;font-size:13px;width:100%;resize:vertical;min-height:72px}.settings-input-error{border-color:var(--red)}.settings-error{font-size:11px;color:var(--red);margin:0}.settings-actions{display:flex;gap:8px;align-items:center}.settings-danger-zone{border-color:#ff858540}.ws-slug{font-family:ui-monospace,Cascadia Code,Fira Code,monospace;font-size:11px;color:var(--text);white-space:nowrap}.health-grid{padding:12px 16px 16px;display:flex;flex-direction:column;gap:8px}.health-row{display:flex;align-items:baseline;gap:12px;font-size:12px}.health-label{min-width:200px;color:var(--muted)}.health-value{color:var(--text);font-weight:600;font-variant-numeric:tabular-nums}.health-warn{color:var(--amber)}.btn-primary{background:var(--cyan);color:var(--accent-ink);border:0;border-radius:8px;padding:8px 18px;font:inherit;font-size:13px;font-weight:650;cursor:pointer}.btn-primary:hover{filter:brightness(1.1)}.btn-secondary{background:transparent;color:var(--soft);border:1px solid var(--line);border-radius:8px;padding:8px 16px;font:inherit;font-size:13px;cursor:pointer}.btn-secondary:hover{background:var(--panel2)}.btn-destructive{background:transparent;color:var(--red);border:1px solid rgba(255,133,133,.35);border-radius:8px;padding:8px 16px;font:inherit;font-size:13px;cursor:pointer}.btn-destructive:hover:not(:disabled){background:#ff858514}.btn-destructive:disabled{opacity:.4;cursor:default}.modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#000000a6;display:flex;align-items:center;justify-content:center;z-index:100}.modal-content{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:28px;width:min(480px,calc(100vw - 32px));display:flex;flex-direction:column;gap:14px;box-shadow:var(--shadow)}.modal-title{margin:0;font-size:18px;letter-spacing:-.02em}.modal-body{margin:0;font-size:13px;color:var(--soft);line-height:1.55}.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}.settings-field{display:flex;flex-direction:column;gap:5px;margin-bottom:14px}.settings-field:last-of-type{margin-bottom:0}.settings-inline-error{font-size:12px;color:var(--red);border:1px solid rgba(255,133,133,.3);background:#ff85850f;border-radius:7px;padding:7px 11px;margin-top:8px}.settings-save-btn{background:var(--cyan);color:var(--accent-ink);border:0;border-radius:8px;padding:8px 18px;font:inherit;font-size:13px;font-weight:650;cursor:pointer;margin-top:14px}.settings-save-btn:hover:not(:disabled){filter:brightness(1.1)}.settings-save-btn:disabled{opacity:.6;cursor:default}.settings-calibrate-primary-btn{width:100%;background:var(--panel2);border:1.5px solid var(--cyan);border-radius:8px;color:var(--cyan);padding:10px 18px;font:inherit;font-size:13px;font-weight:650;cursor:pointer;text-align:center}.settings-calibrate-primary-btn:hover:not(:disabled){background:#00c8dc14}.settings-calibrate-primary-btn:disabled{opacity:.6;cursor:default}.settings-calibrate-desc{font-size:11px;color:var(--muted);margin:2px 0 6px}.settings-calibrate-result{font-size:12px;color:var(--text);background:#00c8dc0f;border:1px solid rgba(0,200,220,.2);border-radius:6px;padding:6px 10px;margin-bottom:6px}.settings-advanced-override{border-top:1px solid var(--line);padding-top:10px;margin-top:6px;display:flex;flex-direction:column;gap:2px}.settings-table-input{width:100%;background:var(--panel2);border:1px solid var(--line);border-radius:6px;color:var(--text);padding:5px 8px;font:inherit;font-size:12px}.settings-table-input:focus{outline:2px solid var(--cyan);outline-offset:1px}.settings-health-grid{display:flex;flex-direction:column;gap:8px}.settings-health-row{display:flex;align-items:baseline;gap:12px;font-size:13px}.settings-health-label{min-width:200px;color:var(--muted)}.settings-health-value{color:var(--text);font-weight:600;font-variant-numeric:tabular-nums}.settings-reset-btn{background:transparent;color:var(--red);border:1px solid rgba(255,133,133,.35);border-radius:8px;padding:8px 16px;font:inherit;font-size:13px;cursor:pointer}.settings-reset-btn:hover{background:#ff858514}.settings-modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#000000b3;display:flex;align-items:center;justify-content:center;z-index:200}.settings-modal{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:24px 28px;width:min(440px,calc(100vw - 32px));box-shadow:var(--shadow);margin:0;color:inherit}.settings-cancel-btn{background:transparent;color:var(--soft);border:1px solid var(--line);border-radius:8px;padding:8px 16px;font:inherit;font-size:13px;cursor:pointer}.settings-cancel-btn:hover:not(:disabled){background:var(--panel2)}.settings-reset-confirm-btn{background:transparent;color:var(--red);border:1px solid rgba(255,133,133,.35);border-radius:8px;padding:8px 16px;font:inherit;font-size:13px;cursor:pointer}.settings-reset-confirm-btn:hover:not(:disabled){background:#ff858514}.settings-reset-confirm-btn:disabled{opacity:.4;cursor:default}.settings-skeleton{height:120px;border-radius:var(--r);background:linear-gradient(90deg,var(--panel) 25%,var(--panel2) 50%,var(--panel) 75%);background-size:400% 100%;animation:shimmer 1.4s ease infinite;animation-delay:.1s;margin-bottom:16px;border:1px solid var(--line)}@media(max-width:1280px){.kpi-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:800px){.shell{display:block}.sidebar{position:static;height:auto}.page-top{display:block}.kpi-grid{grid-template-columns:1fr 1fr}.main-content{padding:16px}}.page-top-controls{display:flex;flex-direction:column;align-items:flex-end;gap:10px;min-width:0}.page-top-controls .banner-info{margin-bottom:0;max-width:min(760px,100%)}.banner-dismiss{flex-shrink:0;border:0;background:transparent;color:inherit;padding:0 2px;font:inherit;font-size:12px;line-height:1;cursor:pointer}.banner-dismiss:hover{color:var(--text)}.chip-legend{margin-bottom:14px}.chip-legend-toggle{border:1px solid var(--line);border-radius:7px;background:transparent;color:var(--soft);padding:5px 9px;font:inherit;font-size:11px;cursor:pointer}.chip-legend-toggle:hover{color:var(--text);background:var(--panel2)}.chip-legend-panel{margin-top:8px;padding:10px 12px;border:1px solid var(--line);border-radius:var(--r);background:var(--panel)}.chip-legend-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:8px 16px;margin:0;padding:0;list-style:none}.chip-legend-item{display:flex;align-items:center;gap:8px;min-width:0;color:var(--soft);font-size:11px;line-height:1.35}.chip-legend-item .chip{flex-shrink:0}@media(max-width:800px){.page-top-controls{align-items:stretch;margin-top:12px}.page-top-controls .date-range{justify-content:flex-start}}.settings-onboarding-card{padding:16px 20px;margin-bottom:16px;border:1px solid rgba(0,200,220,.28);border-radius:var(--r);background:var(--panel)}.settings-onboarding-card h2{margin:0 0 10px;font-size:15px}.settings-onboarding-card ol{display:grid;gap:8px;margin:0;padding-left:22px;color:var(--soft);font-size:12px;line-height:1.45}.settings-onboarding-card strong{color:var(--text)}.daemon-unreachable{display:grid;min-height:100vh;place-items:center;padding:24px;background:var(--bg)}.daemon-unreachable-card{width:min(100%,440px);padding:32px;border:1px solid var(--line);border-radius:14px;background:var(--panel);box-shadow:var(--shadow)}.daemon-unreachable-brand{display:flex;align-items:center;gap:10px;color:var(--text);font-size:13px;font-weight:700;letter-spacing:.02em}.daemon-unreachable-mark{display:grid;width:30px;height:30px;place-items:center;border-radius:8px;background:var(--cyan);color:var(--accent-ink);font-size:11px;font-weight:800}.daemon-unreachable-card h1{margin:26px 0 10px;font-size:23px;letter-spacing:-.02em}.daemon-unreachable-card p{margin:0 0 8px;color:var(--soft);font-size:13px;line-height:1.55}.daemon-unreachable-command{display:block;margin:14px 0 22px;padding:10px 12px;overflow-x:auto;border:1px solid var(--line);border-radius:8px;background:var(--panel2);color:var(--cyan);font-family:ui-monospace,Cascadia Code,Fira Code,monospace;font-size:13px}.daemon-unreachable-retry{min-width:148px}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-delay:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-delay:.01ms!important;transition-duration:.01ms!important}}.verdict-band{display:grid;grid-template-columns:minmax(230px,1.15fr) minmax(180px,.85fr);gap:14px 24px;padding:18px 20px;margin-bottom:14px;background:linear-gradient(120deg,var(--panel),var(--panel2))}.verdict-band-summary{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.verdict-band-label,.verdict-band-caption{color:var(--text-muted);font-size:11px;letter-spacing:.07em}.verdict-band-value{margin:2px 0;color:var(--text);font-size:clamp(32px,3vw,36px);font-weight:780;letter-spacing:-.045em;font-variant-numeric:tabular-nums}.window-delta{display:inline-flex;align-items:center;width:fit-content;border:1px solid var(--line);border-radius:999px;padding:3px 7px;color:var(--text-2);background:var(--panel);font-size:11px;font-weight:700;white-space:nowrap}.window-delta-up{color:var(--rose)}.window-delta-down{color:var(--green)}.window-delta-new{color:var(--amber)}.verdict-band-trend{display:grid;align-content:center;gap:5px;color:var(--series-1);min-width:0}.verdict-band-trend span,.trend-sparkline-empty{color:var(--text-muted);font-size:11px}.trend-sparkline{width:100%;height:42px;overflow:visible}.trend-sparkline-empty{display:block;min-height:20px}.verdict-band-detail,.verdict-band-action{color:var(--text-2);font-size:12px}.verdict-band-detail strong{color:var(--text)}.verdict-band-action{justify-self:start;color:var(--cyan);text-decoration:none}.verdict-band-action:hover{color:var(--text);text-decoration:underline}.verdict-band-claim{color:var(--text-2)}.verdict-band-actions{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.verdict-band-copy{border:1px solid var(--line);border-radius:7px;padding:5px 10px;color:var(--text);background:var(--panel);font-size:12px;font-weight:600;cursor:pointer}.verdict-band-copy:hover{border-color:var(--cyan);color:var(--cyan)}.rec-focus-highlight{outline:2px solid var(--cyan);outline-offset:2px}.rec-focus-banner{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px;padding:5px 10px;border-radius:7px;background:color-mix(in srgb,var(--cyan) 12%,transparent);color:var(--text-2);font-size:12px}.rec-focus-banner-label{font-weight:600}.rec-focus-dismiss,.recs-focus-notice-dismiss{border:1px solid var(--line);border-radius:6px;padding:2px 8px;color:var(--text-2);background:var(--panel);font-size:12px;cursor:pointer}.rec-focus-dismiss:hover,.recs-focus-notice-dismiss:hover{color:var(--text);border-color:var(--cyan)}.recs-focus-notice{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px;padding:8px 12px;border:1px solid var(--line);border-radius:8px;color:var(--text-2);background:var(--panel);font-size:12px}.section-subtitle{margin:2px 16px 8px;color:var(--text-muted);font-size:12px}.overview-secondary-kpis .kpi{padding:12px 14px;box-shadow:none}.overview-secondary-kpis .kpi-value{font-size:21px}.kpi-label-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.kpi-label-row .kpi-label{margin-bottom:6px}.overview-secondary-kpis .trend-sparkline{height:22px;margin-top:5px;color:var(--series-2)}@media(max-width:800px){.verdict-band{grid-template-columns:1fr}}.chip,.rec-confidence-tier{height:20px;padding:1px 7px;border:1px solid;font-size:11px;line-height:1}.rec-chip-row{display:inline-flex;flex-wrap:wrap;align-items:center;gap:6px}.rec-chip-overflow{display:inline-flex;align-items:center;gap:6px}.rec-chip-expander{min-height:20px;padding:1px 6px;border:1px solid rgba(148,163,184,.25);border-radius:999px;background:#94a3b81a;color:#bccadc;font-size:11px;font-weight:700;line-height:1;cursor:pointer}.rec-chip-expander:hover{background:#94a3b82e}.chip-exact,.chip-list-equiv,.chip-obs-proxy,.chip-modeled,.rec-confidence-tier--modeled{background:#22d3ee1a;border-color:#22d3ee40;color:#a1ecf7}.chip-proxy,.chip-stale,.chip-live,.rec-confidence-tier--warning,.rec-confidence-tier--advisory{background:#fbbf241a;border-color:#fbbf2440;color:#fce19c}.chip-experimental,.chip-directional,.rec-confidence-tier--directional{background:#a78bfa1a;border-color:#a78bfa40;color:#b29efa}.chip-na{background:#94a3b81a;border-color:#94a3b840;color:#bccadc}.rec-pending-action-pill{color:var(--amber);font-size:12px;font-weight:700}.rec-action-toast{display:inline-flex;align-items:center;gap:10px;margin:8px 0 0;padding:8px 10px;border:1px solid var(--line);border-radius:6px;background:var(--panel2);color:var(--text);font-size:12px}.rec-action-toast-undo{border:0;padding:0;background:transparent;color:var(--cyan);font:inherit;font-weight:700;cursor:pointer}.rec-action-toast-undo:hover{color:var(--text);text-decoration:underline}.briefs-controls{display:flex;align-items:end;justify-content:space-between;gap:12px;margin-bottom:13px}.briefs-scope{display:grid;gap:5px;color:var(--muted);font-size:12px;font-weight:700}.briefs-scope select{min-width:220px}.briefs-prompt{margin:8px 0 0;padding:10px;overflow-x:auto;border:1px solid var(--line);border-radius:6px;background:var(--panel2);color:var(--text);font:12px / 1.45 var(--mono);white-space:pre-wrap}.briefs-lever+.briefs-lever{margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}.context-composition-bar{height:1.5rem;margin-top:.25rem;overflow:hidden;background:var(--panel2)}.context-composition-segment{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text);font-size:11px;font-weight:700}.context-composition-segment-always_loaded{background:var(--accent)}.context-composition-segment-session_residual{background:var(--muted)}.context-composition-interpretation{margin:.75rem 0 0;color:var(--text)}.recommendations-summary-bar{display:flex;flex-wrap:wrap;gap:4px 10px;margin:0 0 10px;color:var(--text-2);font-size:12px}.recommendations-summary-bar span+span:before{margin-right:10px;color:var(--text-muted);content:"·"}.detector-status-details{margin-bottom:13px}.detector-status-details>summary{margin-bottom:8px;color:var(--text-2);font-size:12px;font-weight:700;cursor:pointer}.detector-status-details>.card{margin-bottom:0!important}.recs-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 20px;padding:12px 14px;margin-bottom:16px;background:var(--panel);border:1px solid var(--line);border-radius:var(--r)}.recs-toolbar-group{display:flex;flex-wrap:wrap;align-items:center;gap:7px;min-width:0;margin:0;padding:0;border:0}.recs-toolbar-legend{float:none;width:auto;padding:0;margin-right:4px;color:var(--text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em}.recs-chip{display:inline-flex;align-items:center;gap:5px;border-radius:99px;padding:4px 11px;font-size:12px;font-weight:700;color:var(--text-2);background:var(--panel2);border:1px solid var(--line);cursor:pointer;white-space:nowrap;transition:color .12s ease,border-color .12s ease,background .12s ease}.recs-chip:hover{color:var(--text);border-color:var(--cyan)}.recs-chip:focus-visible{outline:2px solid var(--cyan);outline-offset:1px}.recs-chip--active{color:var(--cyan);background:#22d3ee1a;border-color:var(--cyan)}.recs-chip-count{color:var(--text-muted);font-weight:600}.recs-chip--active .recs-chip-count{color:var(--cyan)}.recs-toolbar-row{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;margin-left:auto}.recs-toolbar-label{display:inline-flex;align-items:center;gap:7px;color:var(--text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em}.recs-toolbar-select{padding:6px 9px;color:var(--text);color-scheme:dark;background:var(--panel2);border:1px solid var(--line);border-radius:8px;font-weight:600;letter-spacing:normal;text-transform:none;cursor:pointer}:root[data-theme=light] .recs-toolbar-select{color-scheme:light}.recs-toolbar-select:focus-visible{outline:2px solid var(--cyan);outline-offset:1px}.bfc-headline{font-size:22px;font-weight:700;letter-spacing:-.01em;margin:6px 0 4px}.bfc-meter{position:relative;height:8px;border-radius:4px;background:#8492a62e;overflow:visible;margin:8px 0 4px}.bfc-meter-fill{height:100%;border-radius:4px 0 0 4px;max-width:100%}.bfc-fill-green{background:var(--green)}.bfc-fill-amber{background:var(--amber)}.bfc-fill-red{background:var(--red)}.bfc-tick{position:absolute;top:-3px;width:2px;height:14px;background:var(--muted);border-radius:1px;transform:translate(-50%);pointer-events:none}.bfc-caption{font-size:11px;color:var(--muted);margin:2px 0 6px}.bfc-state-chip{font-size:10px;font-weight:700;letter-spacing:.07em}.bfc-chip-green{background:#34d3991f;color:var(--green);border-color:#34d39947}.bfc-chip-amber{background:#fbbf241f;color:var(--amber);border-color:#fbbf2447}.bfc-chip-red{background:#fb71851f;color:var(--red);border-color:#fb718547}.bfc-delta-chip{font-size:10px;font-weight:600}.bfc-delta-amber{background:#fbbf241f;color:var(--amber);border-color:#fbbf2447}.bfc-delta-green{background:#34d3991f;color:var(--green);border-color:#34d39947}.bfc-delta-neutral{background:#8492a61a;color:var(--muted);border-color:transparent}.bfc-chip-low-confidence{background:#a78bfa1f;color:var(--violet);border-color:#a78bfa47;font-size:10px;font-weight:700;letter-spacing:.06em}.scope-strip{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px;padding:8px 0 4px}.scope-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:var(--r);border:1px solid var(--line);background:var(--panel2);color:var(--text-2);font-size:12px;font-weight:600;cursor:pointer;transition:background .12s,border-color .12s,color .12s}.scope-chip:hover{background:var(--panel);border-color:var(--muted);color:var(--text)}.scope-chip--active{background:var(--panel);border-color:var(--text-muted);color:var(--text)}.scope-chip--global{border-color:color-mix(in srgb,var(--violet) 40%,transparent);color:var(--violet)}.scope-chip--global.scope-chip--active{background:color-mix(in srgb,var(--violet) 12%,var(--panel2));border-color:var(--violet);color:var(--violet)}.scope-chip--global:hover{background:color-mix(in srgb,var(--violet) 8%,var(--panel2));border-color:var(--violet);color:var(--violet)}.scope-chip--workspace{border-color:color-mix(in srgb,var(--blue) 40%,transparent);color:var(--blue)}.scope-chip--workspace.scope-chip--active{background:color-mix(in srgb,var(--blue) 12%,var(--panel2));border-color:var(--blue);color:var(--blue)}.scope-chip--workspace:hover{background:color-mix(in srgb,var(--blue) 8%,var(--panel2));border-color:var(--blue);color:var(--blue)}.scope-chip-count{font-weight:400;opacity:.75}:root[data-theme=light] .scope-chip--global{border-color:color-mix(in srgb,var(--violet) 50%,transparent)}:root[data-theme=light] .scope-chip--workspace{border-color:color-mix(in srgb,var(--blue) 50%,transparent)}@media(prefers-color-scheme:light){:root:not([data-theme=dark]) .scope-chip--global{border-color:color-mix(in srgb,var(--violet) 50%,transparent)}:root:not([data-theme=dark]) .scope-chip--workspace{border-color:color-mix(in srgb,var(--blue) 50%,transparent)}}.rec-scope-badge{display:inline-flex;align-items:center;padding:2px 7px;border-radius:99px;font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;line-height:1.4;flex-shrink:0}.rec-scope-badge--global{background:color-mix(in srgb,var(--violet) 15%,transparent);color:var(--violet);border:1px solid color-mix(in srgb,var(--violet) 35%,transparent)}.rec-scope-badge--workspace{background:color-mix(in srgb,var(--blue) 15%,transparent);color:var(--blue);border:1px solid color-mix(in srgb,var(--blue) 35%,transparent)}.rec-prompt-scope-caption{display:block;margin-top:4px;font-size:11px;color:var(--muted);font-style:italic}.recs-summary-header{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px 10px;margin-bottom:13px}.recs-summary-main{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 16px;margin-bottom:8px}.recs-summary-count{display:flex;align-items:baseline;gap:4px}.recs-summary-count-number{font-size:1.6rem;font-weight:700;color:var(--fg);line-height:1}.recs-summary-count-label{font-size:.9rem;color:var(--muted)}.recs-summary-lever{margin:0;font-size:.85rem;color:var(--fg)}.recs-summary-trio{display:flex;flex-wrap:wrap;gap:4px 16px;justify-content:flex-end;font-size:.8rem;color:var(--muted)}.recs-summary-trio strong{color:var(--fg);font-weight:600}.recs-summary-header .scope-strip{margin-bottom:10px}.fb5-panel-header{display:flex;align-items:center;gap:10px;margin-bottom:0}.fb5-status-pill{display:inline-block;padding:2px 10px;border-radius:99px;font-size:11px;font-weight:700}.fb5-status-installed{background:#22c55e26;color:var(--green, #22c55e);border:1px solid var(--green, #22c55e)}.fb5-status-not-installed{background:var(--panel2);color:var(--text-muted);border:1px solid var(--line)}@media(prefers-color-scheme:dark){:root:not([data-theme=light]) .fb5-status-installed{background:#22c55e1f}}.fb5-guard-cards{display:flex;flex-direction:column;gap:10px;margin-bottom:10px}.fb5-guard-card{background:var(--panel2);border:1px solid var(--line);border-radius:var(--r);padding:10px 14px}.fb5-guard-card-title{font-size:12px;font-weight:700;margin-bottom:8px;color:var(--text)}.fb5-guard-inputs{display:flex;flex-wrap:wrap;gap:8px}.fb5-guard-label{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--text-2)}.fb5-guard-input{width:64px!important;padding:3px 6px!important;font-size:12px!important}.fb5-field-hint{font-size:10px;color:var(--text-muted);margin:0 0 12px;font-family:monospace}.fb5-actions{display:flex;flex-wrap:wrap;gap:10px 18px;margin-bottom:6px}.fb5-action-group{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.fb5-secondary-btn{background:transparent;border:1px solid var(--line);border-radius:var(--r);color:var(--text-2);padding:6px 12px;font-size:12px;cursor:pointer;transition:border-color .12s,color .12s}.fb5-secondary-btn:hover:not(:disabled){border-color:var(--cyan);color:var(--text)}.fb5-secondary-btn:disabled{opacity:.5;cursor:not-allowed}.fb5-takes-effect{font-size:11px;color:var(--text-muted);margin:0}.fb6-end-btn{padding:3px 10px;background:transparent;border:1px solid var(--line);border-radius:var(--r);color:var(--text-2);font-size:11px;cursor:pointer;transition:border-color .12s,color .12s;white-space:nowrap}.fb6-end-btn:hover:not(:disabled){border-color:var(--red, #ef4444);color:var(--red, #ef4444)}.fb6-end-btn:disabled{opacity:.5;cursor:not-allowed}.fb6-bg-cta{font-size:11px;color:var(--text-muted);display:inline-flex;align-items:center;gap:4px;white-space:nowrap}.fb6-code{font-family:monospace;font-size:11px;background:var(--panel2);border:1px solid var(--line);border-radius:4px;padding:1px 4px}.fb6-copy-btn{background:transparent;border:none;color:var(--cyan);font-size:11px;cursor:pointer;padding:0 4px}.fb6-bulk-end-btn{background:transparent;border:1px solid var(--line);border-radius:var(--r);color:var(--text-2);padding:6px 14px;font-size:12px;cursor:pointer;transition:border-color .12s,color .12s}.fb6-bulk-end-btn:hover{border-color:var(--red, #ef4444);color:var(--red, #ef4444)}.fb6-ended-line{font-size:12px;color:var(--text-muted);margin:8px 0 0}.briefs-window{color:var(--muted);font-size:12px;font-weight:700}.briefs-verdict{padding:14px 16px}.briefs-verdict-line{margin:0;font-size:15px;font-weight:600;line-height:1.5}.briefs-verdict-line strong{font-size:18px;font-weight:800}.briefs-verdict-sep{color:var(--line)}.briefs-friction{font-weight:700;text-transform:uppercase;letter-spacing:.04em;font-size:12px}.briefs-friction-LOW{color:var(--green)}.briefs-friction-ELEVATED{color:var(--amber)}.briefs-friction-HIGH{color:var(--red)}.brief-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin:13px 0}.brief-tile{padding:14px 16px;border:1px solid var(--line);border-radius:var(--r);background:var(--panel)}.brief-tile-label{color:var(--muted);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.brief-tile-value{margin:6px 0 4px;font-size:24px;font-weight:800;letter-spacing:-.01em}.brief-tile-delta{font-size:12px;font-weight:700}.brief-tile-delta-up{color:var(--red)}.brief-tile-delta-down{color:var(--green)}.brief-tile-delta-flat,.brief-tile-delta-none{color:var(--text-muted)}.briefs-action-list{margin:0;padding:0 0 0 20px;display:grid;gap:12px}.briefs-action{display:flex;align-items:start;justify-content:space-between;gap:12px}.briefs-action-main{display:grid;gap:3px}.briefs-action-copy{flex-shrink:0}.briefs-details{margin-top:13px}.briefs-details>summary{cursor:pointer;padding:10px 14px;border:1px solid var(--line);border-radius:var(--r);background:var(--panel);color:var(--muted);font-size:13px;font-weight:700}.briefs-details[open]>summary{margin-bottom:12px}.first-run-step-hint{margin:2px 0 0 22px;font-size:11.5px;color:var(--muted);line-height:1.4}.first-run-activation{margin-top:14px;padding-top:12px;border-top:1px solid var(--line)}.first-run-activation-title{margin:0 0 8px;font-size:12px;font-weight:700;color:var(--soft);text-transform:uppercase;letter-spacing:.03em}.first-run-activation ul{display:grid;gap:8px;margin:0;padding-left:22px;color:var(--soft);font-size:12px;line-height:1.45}.first-run-activation a{color:var(--teal);white-space:nowrap}.first-run-glossary-link{margin:12px 0 0;font-size:12px;color:var(--muted)}.first-run-glossary-link a{color:var(--teal)}.nav-btn-small{font-size:12px;opacity:.85}.glossary-section p{margin:0 0 8px;font-size:13px;line-height:1.55;color:var(--soft)}.glossary-section p:last-child{margin-bottom:0}.glossary-chip-list{margin:0}.glossary-chip-row{display:grid;grid-template-columns:160px 1fr;gap:12px;align-items:start;padding:6px 0;border-top:1px solid var(--line)}.glossary-chip-row:first-child{border-top:none}.glossary-chip-row dt{margin:0}.glossary-chip-row dd{margin:0;font-size:12.5px;line-height:1.5;color:var(--soft)}
|