@akasecurity/ai-tc-claude-code 0.9.4 → 0.9.5

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.
@@ -492,15 +492,14 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // src/apply-suppressions.ts
495
- import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
495
+ import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
496
496
  import { userInfo } from "os";
497
- import { dirname as dirname5, join as join15 } from "path";
498
- import { fileURLToPath as fileURLToPath3 } from "url";
497
+ import { dirname as dirname6, join as join17 } from "path";
498
+ import { fileURLToPath as fileURLToPath4 } from "url";
499
499
 
500
500
  // ../../packages/persistence/src/database.ts
501
- import { randomUUID as randomUUID9 } from "crypto";
502
- import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
503
- import { join, sep } from "path";
501
+ import { randomUUID as randomUUID10 } from "crypto";
502
+ import { join as join2, sep } from "path";
504
503
  import { DatabaseSync } from "node:sqlite";
505
504
 
506
505
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
@@ -580,6 +579,14 @@ var SQLITE_MIGRATIONS = [
580
579
  {
581
580
  tag: "0018_serious_tana_nile",
582
581
  sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
582
+ },
583
+ {
584
+ tag: "0019_audit_started_at_index",
585
+ sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
586
+ },
587
+ {
588
+ tag: "0020_secret_vault_pagination_indexes",
589
+ sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
583
590
  }
584
591
  ];
585
592
 
@@ -15317,7 +15324,17 @@ var Finding = external_exports.object({
15317
15324
  }).meta({ id: "Finding" });
15318
15325
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15319
15326
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15320
- var FindingProvider = external_exports.enum(["claudecode", "claudedesktop", "cursor", "copilot", "chatgpt", "api"]).meta({ id: "FindingProvider" });
15327
+ var FindingProvider = external_exports.enum([
15328
+ "claudecode",
15329
+ "claudedesktop",
15330
+ "cursor",
15331
+ "copilot",
15332
+ "chatgpt",
15333
+ "claudeai",
15334
+ "codex",
15335
+ "antigravity",
15336
+ "api"
15337
+ ]).meta({ id: "FindingProvider" });
15321
15338
  var FindingCategory = external_exports.enum([
15322
15339
  "secret",
15323
15340
  "pii",
@@ -15371,7 +15388,16 @@ var FindingInstance = external_exports.object({
15371
15388
  confidence: external_exports.number().min(0).max(1),
15372
15389
  // Lifecycle status (see FindingStatus). Optional so legacy callers/rows
15373
15390
  // that predate the resolution feature stay valid.
15374
- status: FindingStatus.optional()
15391
+ status: FindingStatus.optional(),
15392
+ // The audit event this finding was captured from. Optional so callers that
15393
+ // do not project it stay valid. An at-rest finding is content-addressed by
15394
+ // finding_key and its row is upserted on re-detection, so this names the
15395
+ // MOST RECENT detection event, not the first.
15396
+ eventId: external_exports.string().optional(),
15397
+ // The session that event belongs to, when it has one — the seam a
15398
+ // per-instance "view session" link needs. Absent for events captured
15399
+ // outside a session.
15400
+ sessionId: external_exports.string().optional()
15375
15401
  }).meta({ id: "FindingInstance" });
15376
15402
  var FindingGroup = external_exports.object({
15377
15403
  id: external_exports.string(),
@@ -15415,7 +15441,11 @@ var FindingFacets = external_exports.object({
15415
15441
  // for every instance, so every group lands in a bucket; a status-less
15416
15442
  // group (possible only for callers whose rows carry no statuses) is
15417
15443
  // counted under no value.
15418
- status: external_exports.array(FindingFacetItem)
15444
+ status: external_exports.array(FindingFacetItem),
15445
+ // Host tool (attributes.tool_name). Present only on the instance-level
15446
+ // reads, which can filter by it; the grouped read omits the dimension
15447
+ // because a group spans tools.
15448
+ tool: external_exports.array(FindingFacetItem).optional()
15419
15449
  }).meta({ id: "FindingFacets" });
15420
15450
  var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
15421
15451
  var ListGroupedFindingsQuery = external_exports.object({
@@ -15433,6 +15463,16 @@ var ListGroupedFindingsQuery = external_exports.object({
15433
15463
  // Scope to findings whose event carries this session id (the Activity page's
15434
15464
  // session → findings drilldown). Findings without a session never match.
15435
15465
  sessionId: external_exports.string().optional(),
15466
+ // Inclusive lower bound on the parent event's timestamp, so a caller arriving
15467
+ // from a time-scoped page (Activity's range) can carry that scope. Absent
15468
+ // means all time — this list has no default window.
15469
+ from: external_exports.iso.datetime().optional(),
15470
+ // A group or instance id that must appear in the page even when the cursor
15471
+ // has already advanced past its sort position. This is what keeps the
15472
+ // Findings page's one-shot ?finding= deep link resolving once the list
15473
+ // paginates: the target group is appended out of sort order rather than
15474
+ // scanning forward for it. Never affects totals, facets or the cursor.
15475
+ includeId: external_exports.string().optional(),
15436
15476
  groupBy: external_exports.literal("type").optional(),
15437
15477
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
15438
15478
  cursor: external_exports.string().optional()
@@ -15477,15 +15517,110 @@ var FindingInstanceDetail = FindingInstance.extend({
15477
15517
  detection: FindingDetectionRef,
15478
15518
  policy: FindingPolicyRef
15479
15519
  }).meta({ id: "FindingInstanceDetail" });
15520
+ var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15521
+ var ListFindingInstancesQuery = external_exports.object({
15522
+ severity: external_exports.array(Severity).optional(),
15523
+ // Rule ids, the same vocabulary the grouped list's `subtype` carries.
15524
+ subtype: external_exports.array(external_exports.string()).optional(),
15525
+ provider: external_exports.array(FindingProvider).optional(),
15526
+ action: external_exports.array(FindingAction).optional(),
15527
+ // Matches each instance's OWN derived status (deriveFindingStatus), unlike
15528
+ // the grouped query's group-level fold.
15529
+ status: external_exports.array(FindingStatus).optional(),
15530
+ // Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
15531
+ // where the free-text `q` can only match the rendered "via Bash" label.
15532
+ tool: external_exports.array(external_exports.string()).optional(),
15533
+ // Exact repository / file-path matches, for the drill-down out of the
15534
+ // locations view. A row whose event carries no repo/file matches neither.
15535
+ repo: external_exports.string().optional(),
15536
+ file: external_exports.string().optional(),
15537
+ q: external_exports.string().optional(),
15538
+ sessionId: external_exports.string().optional(),
15539
+ from: external_exports.iso.datetime().optional(),
15540
+ limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15541
+ cursor: external_exports.string().optional()
15542
+ });
15543
+ var ListFindingInstancesResponse = external_exports.object({
15544
+ // Instances matching the filters across the whole scope, not just this
15545
+ // page — cursor-independent, like the grouped list's totals.
15546
+ totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
15547
+ // Counts in INSTANCES here, where the grouped response counts groups. Each
15548
+ // dimension still excludes its own filter.
15549
+ facets: FindingFacets,
15550
+ items: external_exports.array(FindingInstanceDetail),
15551
+ nextCursor: external_exports.string().nullable()
15552
+ }).meta({ id: "ListFindingInstancesResponse" });
15553
+ var FindingLocationFile = external_exports.object({
15554
+ // Empty when the instances carried no file path (a prompt or a tool call
15555
+ // with no file attribution).
15556
+ file: external_exports.string(),
15557
+ instanceCount: external_exports.number().int().nonnegative(),
15558
+ maxSeverity: Severity,
15559
+ latestDetectedAt: external_exports.iso.datetime(),
15560
+ // Folded from the instances' derived statuses with the same
15561
+ // open-dominates precedence a group uses.
15562
+ status: FindingStatus.optional(),
15563
+ // Distinct rules seen at this location, capped — the row shows them as
15564
+ // chips, and the count is what conveys scale.
15565
+ ruleIds: external_exports.array(external_exports.string())
15566
+ }).meta({ id: "FindingLocationFile" });
15567
+ var FindingLocationRepo = external_exports.object({
15568
+ /** Empty when the instances carried no repo attribute. */
15569
+ repo: external_exports.string(),
15570
+ instanceCount: external_exports.number().int().nonnegative(),
15571
+ maxSeverity: Severity,
15572
+ latestDetectedAt: external_exports.iso.datetime(),
15573
+ status: FindingStatus.optional(),
15574
+ files: external_exports.array(FindingLocationFile)
15575
+ }).meta({ id: "FindingLocationRepo" });
15576
+ var ListFindingLocationsQuery = external_exports.object({
15577
+ severity: external_exports.array(Severity).optional(),
15578
+ subtype: external_exports.array(external_exports.string()).optional(),
15579
+ provider: external_exports.array(FindingProvider).optional(),
15580
+ action: external_exports.array(FindingAction).optional(),
15581
+ // Per-instance, as in ListFindingInstancesQuery: a location keeps the
15582
+ // instances that match, and folds its status from those.
15583
+ status: external_exports.array(FindingStatus).optional(),
15584
+ tool: external_exports.array(external_exports.string()).optional(),
15585
+ q: external_exports.string().optional(),
15586
+ sessionId: external_exports.string().optional(),
15587
+ from: external_exports.iso.datetime().optional(),
15588
+ limit: external_exports.coerce.number().int().min(1).max(500).optional()
15589
+ });
15590
+ var ListFindingLocationsResponse = external_exports.object({
15591
+ totals: external_exports.object({
15592
+ findings: external_exports.number().int().nonnegative(),
15593
+ repos: external_exports.number().int().nonnegative(),
15594
+ files: external_exports.number().int().nonnegative()
15595
+ }),
15596
+ /** Sorted by max severity, then most recent. */
15597
+ items: external_exports.array(FindingLocationRepo),
15598
+ /** Whether `limit` truncated the repo list. */
15599
+ hasMore: external_exports.boolean()
15600
+ }).meta({ id: "ListFindingLocationsResponse" });
15480
15601
 
15481
15602
  // ../../packages/schema/src/zod/harness-map.ts
15482
- var Harness = external_exports.enum(["claudecode", "cursor", "copilot", "codex", "windsurf", "claudedesktop", "chatgpt", "api"]).meta({ id: "Harness" });
15603
+ var Harness = external_exports.enum([
15604
+ "claudecode",
15605
+ "cursor",
15606
+ "copilot",
15607
+ "codex",
15608
+ "antigravity",
15609
+ "windsurf",
15610
+ "claudedesktop",
15611
+ "chatgpt",
15612
+ "claudeai",
15613
+ "api"
15614
+ ]).meta({ id: "Harness" });
15483
15615
  var TOOL_TO_HARNESS = {
15484
15616
  "claude-code": "claudecode",
15485
15617
  "claude-desktop": "claudedesktop",
15486
15618
  "github-copilot": "copilot",
15487
15619
  cursor: "cursor",
15488
- chatgpt: "chatgpt"
15620
+ chatgpt: "chatgpt",
15621
+ codex: "codex",
15622
+ antigravity: "antigravity",
15623
+ "claude-ai": "claudeai"
15489
15624
  };
15490
15625
 
15491
15626
  // ../../packages/schema/src/zod/meta.ts
@@ -15943,7 +16078,18 @@ var ActivityOverviewResponse = external_exports.object({
15943
16078
  // ../../packages/schema/src/zod/event.ts
15944
16079
  var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
15945
16080
  var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
15946
- var SourceTool = external_exports.enum(["claude-code", "claude-desktop", "cursor", "chatgpt", "github-copilot", "cli", "unknown"]).meta({ id: "SourceTool" });
16081
+ var SourceTool = external_exports.enum([
16082
+ "claude-code",
16083
+ "claude-desktop",
16084
+ "cursor",
16085
+ "chatgpt",
16086
+ "claude-ai",
16087
+ "github-copilot",
16088
+ "codex",
16089
+ "antigravity",
16090
+ "cli",
16091
+ "unknown"
16092
+ ]).meta({ id: "SourceTool" });
15947
16093
  var EventMetadata = external_exports.object({
15948
16094
  sessionId: external_exports.string().optional(),
15949
16095
  repo: external_exports.string().optional(),
@@ -16014,7 +16160,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
16014
16160
  var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16015
16161
  var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16016
16162
  var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16017
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
16163
+ var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16018
16164
  var AccessCounts = external_exports.object({
16019
16165
  open: external_exports.number().int().nonnegative(),
16020
16166
  approved: external_exports.number().int().nonnegative(),
@@ -16283,6 +16429,7 @@ var ExceptionBundleEntry = DetectionException.pick({
16283
16429
  useCount: true,
16284
16430
  conditions: true
16285
16431
  });
16432
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16286
16433
 
16287
16434
  // ../../packages/schema/src/zod/rule.ts
16288
16435
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
@@ -17098,6 +17245,35 @@ var EgressWriteSummary = external_exports.object({
17098
17245
  droppedFiles: external_exports.array(external_exports.string()).default([])
17099
17246
  }).meta({ id: "EgressWriteSummary" });
17100
17247
 
17248
+ // ../../packages/schema/src/zod/exception-action.ts
17249
+ var confirmation = external_exports.string().optional();
17250
+ var ApproveBlockedInput = external_exports.object({
17251
+ reference: external_exports.string(),
17252
+ scope: external_exports.string(),
17253
+ reason: external_exports.string(),
17254
+ confirmation
17255
+ });
17256
+ var AddExceptionInput = external_exports.object({
17257
+ ruleId: external_exports.string(),
17258
+ value: external_exports.string(),
17259
+ scope: external_exports.string(),
17260
+ reason: external_exports.string(),
17261
+ confirmation
17262
+ });
17263
+ var GrantRevealInput = external_exports.object({
17264
+ pointer: external_exports.string(),
17265
+ scope: external_exports.string(),
17266
+ justification: external_exports.string(),
17267
+ confirmation
17268
+ });
17269
+ var RevokeExceptionInput = external_exports.object({
17270
+ id: external_exports.string(),
17271
+ reason: external_exports.string()
17272
+ });
17273
+ var RotateKeyInput = external_exports.object({
17274
+ confirmation: external_exports.string()
17275
+ });
17276
+
17101
17277
  // ../../packages/schema/src/zod/findings-group-build.ts
17102
17278
  function toApiAction(dbVal) {
17103
17279
  const map2 = {
@@ -17153,6 +17329,8 @@ function buildFindingGroups(rows, opts = {}) {
17153
17329
  repo: r.repo,
17154
17330
  file: r.file,
17155
17331
  ...r.toolName === void 0 ? {} : { toolName: r.toolName },
17332
+ ...r.eventId === void 0 ? {} : { eventId: r.eventId },
17333
+ ...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
17156
17334
  action: toApiAction(effectiveDbAction),
17157
17335
  detectedAt: r.occurredAt,
17158
17336
  confidence: r.confidence,
@@ -17284,14 +17462,17 @@ function applyFindingFilters(groups, opts) {
17284
17462
  }
17285
17463
  var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
17286
17464
  var SEVERITY_RANK = SEVERITY_ORDER;
17465
+ function compareFindingGroupOrder(a, b) {
17466
+ const rankA = SEVERITY_RANK[a.severity] ?? -1;
17467
+ const rankB = SEVERITY_RANK[b.severity] ?? -1;
17468
+ const severityDiff = rankA - rankB;
17469
+ if (severityDiff !== 0) return severityDiff;
17470
+ const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17471
+ if (recencyDiff !== 0) return recencyDiff;
17472
+ return a.id.localeCompare(b.id);
17473
+ }
17287
17474
  function sortFindingGroups(groups) {
17288
- return [...groups].sort((a, b) => {
17289
- const rankA = SEVERITY_RANK[a.severity] ?? -1;
17290
- const rankB = SEVERITY_RANK[b.severity] ?? -1;
17291
- const severityDiff = rankA - rankB;
17292
- if (severityDiff !== 0) return severityDiff;
17293
- return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17294
- });
17475
+ return [...groups].sort(compareFindingGroupOrder);
17295
17476
  }
17296
17477
  function computeFindingFacets(allGroups, opts) {
17297
17478
  const forSeverity = applyFindingFilters(allGroups, {
@@ -17347,15 +17528,158 @@ function computeFindingFacets(allGroups, opts) {
17347
17528
  for (const g of forStatus) {
17348
17529
  if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
17349
17530
  }
17350
- const toItems = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17531
+ const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17532
+ return {
17533
+ severity: toItems2(severityMap),
17534
+ provider: toItems2(providerMap),
17535
+ action: toItems2(actionMap),
17536
+ subtype: toItems2(subtypeMap),
17537
+ status: toItems2(statusMap)
17538
+ };
17539
+ }
17540
+
17541
+ // ../../packages/schema/src/zod/findings-flat-build.ts
17542
+ function rowHaystack(row) {
17543
+ return [
17544
+ row.ruleId,
17545
+ row.category,
17546
+ row.maskedMatch,
17547
+ row.repo,
17548
+ row.file,
17549
+ row.toolName ? `via ${row.toolName}` : "",
17550
+ row.id
17551
+ ].join(" ").toLowerCase();
17552
+ }
17553
+ function matchesDimension(row, opts, dimension) {
17554
+ switch (dimension) {
17555
+ case "severity":
17556
+ return !opts.severity?.length || opts.severity.includes(row.severity);
17557
+ case "subtype":
17558
+ return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
17559
+ case "providers":
17560
+ return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
17561
+ case "actions":
17562
+ return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
17563
+ case "statuses":
17564
+ return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
17565
+ case "tools":
17566
+ return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
17567
+ case "repo":
17568
+ return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
17569
+ case "file":
17570
+ return opts.file === void 0 || opts.file === "" || row.file === opts.file;
17571
+ case "q":
17572
+ return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
17573
+ }
17574
+ }
17575
+ var DIMENSIONS = [
17576
+ "severity",
17577
+ "subtype",
17578
+ "providers",
17579
+ "actions",
17580
+ "statuses",
17581
+ "tools",
17582
+ "repo",
17583
+ "file",
17584
+ "q"
17585
+ ];
17586
+ function matchesInstanceFilters(row, opts, except) {
17587
+ for (const dimension of DIMENSIONS) {
17588
+ if (dimension === except) continue;
17589
+ if (!matchesDimension(row, opts, dimension)) return false;
17590
+ }
17591
+ return true;
17592
+ }
17593
+ function toItems(counts) {
17594
+ return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
17595
+ }
17596
+ function bump(counts, value) {
17597
+ counts.set(value, (counts.get(value) ?? 0) + 1);
17598
+ }
17599
+ function createInstanceFacetAccumulator(opts) {
17600
+ const severity = /* @__PURE__ */ new Map();
17601
+ const subtype = /* @__PURE__ */ new Map();
17602
+ const provider = /* @__PURE__ */ new Map();
17603
+ const action = /* @__PURE__ */ new Map();
17604
+ const status = /* @__PURE__ */ new Map();
17605
+ const tool = /* @__PURE__ */ new Map();
17351
17606
  return {
17352
- severity: toItems(severityMap),
17353
- provider: toItems(providerMap),
17354
- action: toItems(actionMap),
17355
- subtype: toItems(subtypeMap),
17356
- status: toItems(statusMap)
17607
+ add(row) {
17608
+ if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
17609
+ if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
17610
+ if (matchesInstanceFilters(row, opts, "providers")) {
17611
+ bump(provider, toApiProvider(row.sourceTool));
17612
+ }
17613
+ if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
17614
+ if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
17615
+ bump(status, row.status);
17616
+ }
17617
+ if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
17618
+ bump(tool, row.toolName);
17619
+ }
17620
+ },
17621
+ facets: () => ({
17622
+ severity: toItems(severity),
17623
+ subtype: toItems(subtype),
17624
+ provider: toItems(provider),
17625
+ action: toItems(action),
17626
+ status: toItems(status),
17627
+ tool: toItems(tool)
17628
+ })
17629
+ };
17630
+ }
17631
+ function toInstanceDetail(row) {
17632
+ const category = toApiCategory(row.category);
17633
+ return {
17634
+ id: row.id,
17635
+ provider: toApiProvider(row.sourceTool),
17636
+ repo: row.repo,
17637
+ file: row.file,
17638
+ ...row.toolName === void 0 ? {} : { toolName: row.toolName },
17639
+ eventId: row.eventId,
17640
+ ...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
17641
+ action: toApiAction(row.actionTaken),
17642
+ detectedAt: row.occurredAt,
17643
+ confidence: row.confidence,
17644
+ ...row.status === void 0 ? {} : { status: row.status },
17645
+ groupId: row.ruleId,
17646
+ category,
17647
+ subtype: row.ruleId,
17648
+ severity: row.severity,
17649
+ match: { maskedValue: row.maskedMatch, contextPrefix: "" },
17650
+ detection: { id: row.ruleId, name: null },
17651
+ policy: { id: `category:${category}`, name: category }
17652
+ };
17653
+ }
17654
+ var SEVERITY_ORDER2 = {
17655
+ critical: 0,
17656
+ high: 1,
17657
+ medium: 2,
17658
+ low: 3
17659
+ };
17660
+ function newLocationAccumulator() {
17661
+ return {
17662
+ instanceCount: 0,
17663
+ // Sorts after every known severity, so the first row always wins the
17664
+ // comparison below rather than an unknown value pinning the location.
17665
+ maxSeverityRank: Number.MAX_SAFE_INTEGER,
17666
+ maxSeverity: "low",
17667
+ latestDetectedAt: "",
17668
+ statuses: [],
17669
+ ruleIds: /* @__PURE__ */ new Set()
17357
17670
  };
17358
17671
  }
17672
+ function addToLocation(acc, row) {
17673
+ acc.instanceCount += 1;
17674
+ const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
17675
+ if (rank < acc.maxSeverityRank) {
17676
+ acc.maxSeverityRank = rank;
17677
+ acc.maxSeverity = row.severity;
17678
+ }
17679
+ if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
17680
+ acc.statuses.push(row.status);
17681
+ acc.ruleIds.add(row.ruleId);
17682
+ }
17359
17683
 
17360
17684
  // ../../packages/schema/src/zod/installed-pack.ts
17361
17685
  var InstalledPack = external_exports.object({
@@ -17492,6 +17816,50 @@ var VaultInventoryEntry = external_exports.object({
17492
17816
  revealGrantId: external_exports.string().nullable(),
17493
17817
  sightings: external_exports.array(VaultSighting)
17494
17818
  });
17819
+ var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
17820
+ var DEFAULT_VAULT_DEREFS_LIMIT = 50;
17821
+ var MAX_VAULT_PAGE_LIMIT = 200;
17822
+ var ListVaultInventoryQuery = external_exports.object({
17823
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17824
+ // Opaque; names the last row of the page just served.
17825
+ cursor: external_exports.string().optional()
17826
+ });
17827
+ var ListVaultInventoryResponse = external_exports.object({
17828
+ // Vaulted values across the whole store, not just this page — cursor-
17829
+ // independent, so paging never changes what the count claims.
17830
+ totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
17831
+ items: external_exports.array(VaultInventoryEntry),
17832
+ // `null` once the last page is reached.
17833
+ nextCursor: external_exports.string().nullable()
17834
+ });
17835
+ var ListVaultReuseQuery = external_exports.object({
17836
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17837
+ cursor: external_exports.string().optional()
17838
+ });
17839
+ var ListVaultReuseResponse = external_exports.object({
17840
+ // Reused values across the whole store — the number the section's claim
17841
+ // ("values detected in more than one place") is about.
17842
+ totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
17843
+ items: external_exports.array(VaultInventoryEntry),
17844
+ nextCursor: external_exports.string().nullable()
17845
+ });
17846
+ var ListVaultDerefsQuery = external_exports.object({
17847
+ // Include the batched, high-volume reasons (display, view-render). Omitted
17848
+ // hides them and counts them into `hiddenBatched` instead, so the model
17849
+ // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
17850
+ // over a Server Action, which preserves the type, never as a URL param.
17851
+ includeBatched: external_exports.boolean().optional(),
17852
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17853
+ cursor: external_exports.string().optional()
17854
+ });
17855
+ var ListVaultDerefsResponse = external_exports.object({
17856
+ items: external_exports.array(VaultDeref),
17857
+ nextCursor: external_exports.string().nullable(),
17858
+ // Display/view-render rows the query hid, over the WHOLE trail rather than
17859
+ // this page — it is the count the "N hidden" line and its toggle speak for.
17860
+ // Always 0 when `includeBatched` was set, since nothing was hidden.
17861
+ hiddenBatched: external_exports.number().int().nonnegative()
17862
+ });
17495
17863
  var VaultKeyCustody = external_exports.string();
17496
17864
  var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17497
17865
  var VaultConsent = external_exports.object({
@@ -17868,7 +18236,7 @@ var TopSourcesQuery = external_exports.object({
17868
18236
  // Omit for both kinds.
17869
18237
  kind: external_exports.enum(SOURCE_KINDS).optional()
17870
18238
  });
17871
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18239
+ var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
17872
18240
  var ScanCoverageProvider = external_exports.object({
17873
18241
  provider: Provider,
17874
18242
  // Percent of that provider's traffic scanned in the window. 0 when unsupported.
@@ -18110,6 +18478,138 @@ function captureId(sessionId, contentHash, filePath = null) {
18110
18478
  );
18111
18479
  }
18112
18480
 
18481
+ // ../../packages/persistence/src/internal/snapshot.ts
18482
+ import { randomUUID } from "crypto";
18483
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18484
+ import { basename, dirname, join } from "path";
18485
+
18486
+ // ../../packages/persistence/src/paths.ts
18487
+ import {
18488
+ chmodSync,
18489
+ linkSync,
18490
+ lstatSync,
18491
+ mkdirSync,
18492
+ renameSync,
18493
+ rmSync,
18494
+ writeFileSync
18495
+ } from "fs";
18496
+ import { threadId } from "worker_threads";
18497
+ var DATA_DIR_MODE = 448;
18498
+ var DATA_FILE_MODE = 384;
18499
+ var DB_FILENAME = "aka.db";
18500
+ function isSymlink(path) {
18501
+ try {
18502
+ return lstatSync(path).isSymbolicLink();
18503
+ } catch {
18504
+ return false;
18505
+ }
18506
+ }
18507
+ function chmodBestEffort(path, mode) {
18508
+ if (isSymlink(path)) return;
18509
+ try {
18510
+ chmodSync(path, mode);
18511
+ } catch {
18512
+ }
18513
+ }
18514
+ function tightenDir(dir) {
18515
+ chmodBestEffort(dir, DATA_DIR_MODE);
18516
+ }
18517
+ function ensureDataDirSync(dir) {
18518
+ mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18519
+ tightenDir(dir);
18520
+ }
18521
+ function dbSidecars(file2) {
18522
+ return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18523
+ }
18524
+ function tightenFile(file2) {
18525
+ chmodBestEffort(file2, DATA_FILE_MODE);
18526
+ }
18527
+ function tightenPerms(file2) {
18528
+ for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18529
+ }
18530
+
18531
+ // ../../packages/persistence/src/internal/snapshot.ts
18532
+ function backupPath(file2, tag) {
18533
+ return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18534
+ }
18535
+ var STALE_PARTIAL_MS = 5 * 6e4;
18536
+ function reapStalePartials(file2) {
18537
+ const dir = dirname(file2);
18538
+ const prefix = `${basename(file2)}.`;
18539
+ let entries;
18540
+ try {
18541
+ entries = readdirSync(dir);
18542
+ } catch {
18543
+ return;
18544
+ }
18545
+ for (const name of entries) {
18546
+ if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18547
+ const partial2 = join(dir, name);
18548
+ try {
18549
+ if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18550
+ rmSync2(partial2, { force: true });
18551
+ }
18552
+ } catch {
18553
+ }
18554
+ }
18555
+ }
18556
+ function snapshotStore(db, backup) {
18557
+ const partial2 = `${backup}.partial`;
18558
+ try {
18559
+ rmSync2(partial2, { force: true });
18560
+ db.prepare("VACUUM INTO ?").run(partial2);
18561
+ tightenFile(partial2);
18562
+ renameSync2(partial2, backup);
18563
+ } catch (error51) {
18564
+ try {
18565
+ rmSync2(partial2, { force: true });
18566
+ } catch {
18567
+ }
18568
+ throw error51;
18569
+ }
18570
+ }
18571
+ function moveStoreAside(file2, backup) {
18572
+ const undo = [];
18573
+ renameSync2(file2, backup);
18574
+ undo.push([backup, file2]);
18575
+ try {
18576
+ for (const sidecar of dbSidecars(file2)) {
18577
+ const moved = `${backup}${sidecar.slice(file2.length)}`;
18578
+ try {
18579
+ renameSync2(sidecar, moved);
18580
+ undo.push([moved, sidecar]);
18581
+ } catch {
18582
+ rmSync2(sidecar, { force: true });
18583
+ }
18584
+ }
18585
+ } catch (error51) {
18586
+ for (const [from, to] of undo.reverse()) {
18587
+ try {
18588
+ renameSync2(from, to);
18589
+ } catch {
18590
+ }
18591
+ }
18592
+ throw error51;
18593
+ }
18594
+ tightenPerms(backup);
18595
+ }
18596
+ function discardStore(file2, backup) {
18597
+ try {
18598
+ rmSync2(file2, { force: true });
18599
+ for (const sidecar of dbSidecars(file2)) {
18600
+ rmSync2(sidecar, { force: true });
18601
+ }
18602
+ } catch (error51) {
18603
+ if (existsSync(file2)) {
18604
+ try {
18605
+ rmSync2(backup, { force: true });
18606
+ } catch {
18607
+ }
18608
+ }
18609
+ throw error51;
18610
+ }
18611
+ }
18612
+
18113
18613
  // ../../packages/persistence/src/internal/sql-text.ts
18114
18614
  function escapeLikePattern(s) {
18115
18615
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18251,38 +18751,6 @@ function mapRowsTolerant(rows, map2) {
18251
18751
  return out;
18252
18752
  }
18253
18753
 
18254
- // ../../packages/persistence/src/paths.ts
18255
- import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
18256
- var DATA_DIR_MODE = 448;
18257
- var DATA_FILE_MODE = 384;
18258
- var DB_FILENAME = "aka.db";
18259
- function chmodBestEffort(path, mode) {
18260
- try {
18261
- chmodSync(path, mode);
18262
- } catch {
18263
- }
18264
- }
18265
- function tightenDir(dir) {
18266
- chmodBestEffort(dir, DATA_DIR_MODE);
18267
- }
18268
- function ensureDataDirSync(dir) {
18269
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18270
- tightenDir(dir);
18271
- }
18272
- function dbSidecars(file2) {
18273
- return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18274
- }
18275
- function tightenFile(file2) {
18276
- try {
18277
- if (lstatSync(file2).isSymbolicLink()) return;
18278
- } catch {
18279
- }
18280
- chmodBestEffort(file2, DATA_FILE_MODE);
18281
- }
18282
- function tightenPerms(file2) {
18283
- for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18284
- }
18285
-
18286
18754
  // ../../packages/persistence/src/migrations.ts
18287
18755
  function describeObject(object2) {
18288
18756
  return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
@@ -18398,9 +18866,9 @@ function applyLegacyDropMigration(db, file2) {
18398
18866
  }
18399
18867
  }
18400
18868
  function backupBeforeLegacyDrop(db, file2) {
18401
- const backup = `${file2}.pre-drop.${String(Date.now())}.bak`;
18402
- db.prepare("VACUUM INTO ?").run(backup);
18403
- tightenFile(backup);
18869
+ reapStalePartials(file2);
18870
+ const backup = backupPath(file2, "pre-drop");
18871
+ snapshotStore(db, backup);
18404
18872
  return backup;
18405
18873
  }
18406
18874
  var TOKEN_USAGE_COLUMNS = [
@@ -18744,6 +19212,25 @@ function parseJsonObject(s) {
18744
19212
  return void 0;
18745
19213
  }
18746
19214
 
19215
+ // ../../packages/persistence/src/internal/keyset-cursor.ts
19216
+ function encodeKeysetCursor(payload) {
19217
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
19218
+ }
19219
+ function decodeKeysetCursor(cursor) {
19220
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19221
+ if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19222
+ // resumes from is epoch millis, and a payload carrying ±Infinity or a
19223
+ // fraction binds cleanly rather than failing — returning an EMPTY page with
19224
+ // a null cursor, which a caller reads as "end of list". That is the one
19225
+ // outcome a cursor that does not decode must never produce, since the
19226
+ // documented behaviour above is to restart from the top. (`1e999` is valid
19227
+ // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19228
+ Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19229
+ return parsed;
19230
+ }
19231
+ return null;
19232
+ }
19233
+
18747
19234
  // ../../packages/persistence/src/repositories/activity.ts
18748
19235
  var DAY_MS = 864e5;
18749
19236
  var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
@@ -18789,16 +19276,6 @@ function utcWindow(nowMs) {
18789
19276
  const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
18790
19277
  return { startMs, endMs: startMs + DAY_MS };
18791
19278
  }
18792
- function encodeCursor(payload) {
18793
- return Buffer.from(JSON.stringify(payload)).toString("base64url");
18794
- }
18795
- function decodeCursor(cursor) {
18796
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
18797
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
18798
- return parsed;
18799
- }
18800
- return null;
18801
- }
18802
19279
  var DB_EVENT_TYPE_TO_KIND = {
18803
19280
  session: "session",
18804
19281
  prompt: "prompt",
@@ -18943,7 +19420,7 @@ var SqliteActivityRepository = class {
18943
19420
  return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
18944
19421
  }
18945
19422
  listSessions(query) {
18946
- const cursor = query.cursor ? decodeCursor(query.cursor) : null;
19423
+ const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
18947
19424
  const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
18948
19425
  const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
18949
19426
  const conditions = [SESSION_ROOT];
@@ -19017,7 +19494,7 @@ var SqliteActivityRepository = class {
19017
19494
  )
19018
19495
  );
19019
19496
  const last = page[page.length - 1];
19020
- const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19497
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19021
19498
  return Promise.resolve({ items, nextCursor, emptyCount });
19022
19499
  }
19023
19500
  getSession(sessionId) {
@@ -19890,7 +20367,7 @@ var SqliteEventsRepository = class {
19890
20367
  };
19891
20368
 
19892
20369
  // ../../packages/persistence/src/repositories/exceptions.ts
19893
- import { randomUUID } from "crypto";
20370
+ import { randomUUID as randomUUID2 } from "crypto";
19894
20371
 
19895
20372
  // ../../packages/persistence/src/internal/sqlite-errors.ts
19896
20373
  var SQLITE_CONSTRAINT_UNIQUE = 2067;
@@ -19926,8 +20403,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
19926
20403
  AND conditions IS NULL
19927
20404
  AND ${ACTIVE_PREDICATE}`;
19928
20405
  var SqliteExceptionsRepository = class {
19929
- constructor(db) {
20406
+ constructor(db, now = () => Date.now()) {
19930
20407
  this.db = db;
20408
+ this.now = now;
19931
20409
  this.consumeStmt = db.prepare(
19932
20410
  `UPDATE exceptions
19933
20411
  SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
@@ -19945,6 +20423,7 @@ var SqliteExceptionsRepository = class {
19945
20423
  );
19946
20424
  }
19947
20425
  db;
20426
+ now;
19948
20427
  consumeStmt;
19949
20428
  insertBlockedStmt;
19950
20429
  sweepBlockedStmt;
@@ -19971,8 +20450,8 @@ var SqliteExceptionsRepository = class {
19971
20450
  "provider conditions are not supported yet \u2014 a grant with one would never apply"
19972
20451
  );
19973
20452
  }
19974
- const id = randomUUID();
19975
- const now = Date.now();
20453
+ const id = randomUUID2();
20454
+ const now = this.now();
19976
20455
  try {
19977
20456
  this.insertExceptionRow(id, input, now);
19978
20457
  } catch (err) {
@@ -20050,7 +20529,7 @@ var SqliteExceptionsRepository = class {
20050
20529
  const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
20051
20530
  const rows = allRows(
20052
20531
  this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
20053
- opts?.includeTerminal ? {} : { now: Date.now() }
20532
+ opts?.includeTerminal ? {} : { now: this.now() }
20054
20533
  );
20055
20534
  const exceptions = mapRowsTolerant(rows, parseExceptionRow);
20056
20535
  return Promise.resolve(exceptions);
@@ -20085,7 +20564,7 @@ var SqliteExceptionsRepository = class {
20085
20564
  * already revoked.
20086
20565
  */
20087
20566
  revoke(id, revokedBy, reason) {
20088
- const now = Date.now();
20567
+ const now = this.now();
20089
20568
  const result = this.db.prepare(
20090
20569
  `UPDATE exceptions
20091
20570
  SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
@@ -20099,7 +20578,7 @@ var SqliteExceptionsRepository = class {
20099
20578
  * callers must treat identically — means it does not and the detection is
20100
20579
  * enforced as usual. Deliberately NOT wrapped in try/catch.
20101
20580
  */
20102
- consume(id, now = Date.now()) {
20581
+ consume(id, now = this.now()) {
20103
20582
  const result = this.consumeStmt.run({ id, now });
20104
20583
  return Promise.resolve(Number(result.changes) === 1);
20105
20584
  }
@@ -20108,7 +20587,7 @@ var SqliteExceptionsRepository = class {
20108
20587
  * version — what rides the policy bundle to the hook. Grants written under
20109
20588
  * a different (rotated-away) key never match, so they are excluded at read.
20110
20589
  */
20111
- activeBundleEntries(keyVersion, now = Date.now()) {
20590
+ activeBundleEntries(keyVersion, now = this.now()) {
20112
20591
  const rows = allRows(
20113
20592
  this.db.prepare(
20114
20593
  `SELECT * FROM exceptions
@@ -20140,7 +20619,7 @@ var SqliteExceptionsRepository = class {
20140
20619
  * than the retention window on every write, so the ledger self-limits.
20141
20620
  */
20142
20621
  recordBlocked(entry) {
20143
- const now = Date.now();
20622
+ const now = this.now();
20144
20623
  this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
20145
20624
  this.insertBlockedStmt.run({
20146
20625
  reference: entry.reference,
@@ -20163,7 +20642,7 @@ var SqliteExceptionsRepository = class {
20163
20642
  WHERE blocked_at > :cutoff
20164
20643
  ORDER BY blocked_at DESC, rowid DESC`
20165
20644
  ),
20166
- { cutoff: Date.now() - windowMs }
20645
+ { cutoff: this.now() - windowMs }
20167
20646
  );
20168
20647
  return Promise.resolve(
20169
20648
  rows.map((row) => ({
@@ -20191,8 +20670,9 @@ var SqliteExceptionsRepository = class {
20191
20670
  * evaluate conditions, and a narrowing clause that is ignored would WIDEN the
20192
20671
  * grant instead. Fail closed until reveal-side condition evaluation exists.
20193
20672
  */
20194
- activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
20673
+ activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
20195
20674
  try {
20675
+ const at = now ?? this.now();
20196
20676
  const row = getRow(
20197
20677
  this.db.prepare(
20198
20678
  `SELECT id FROM exceptions
@@ -20201,7 +20681,7 @@ var SqliteExceptionsRepository = class {
20201
20681
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
20202
20682
  LIMIT 1`
20203
20683
  ),
20204
- { ruleId, valueFingerprint, keyVersion, now }
20684
+ { ruleId, valueFingerprint, keyVersion, now: at }
20205
20685
  );
20206
20686
  return Promise.resolve(row ?? null);
20207
20687
  } catch (err) {
@@ -20215,7 +20695,7 @@ var SqliteExceptionsRepository = class {
20215
20695
  * predicate, so correctness never depends on this sweep; it only bounds how
20216
20696
  * long the audit evidence is kept locally. Returns the deleted count.
20217
20697
  */
20218
- sweepTerminal(retentionMs, now = Date.now()) {
20698
+ sweepTerminal(retentionMs, now = this.now()) {
20219
20699
  const result = this.db.prepare(
20220
20700
  `DELETE FROM exceptions
20221
20701
  WHERE updated_at < :cutoff
@@ -20278,6 +20758,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
20278
20758
 
20279
20759
  // ../../packages/persistence/src/repositories/findings.ts
20280
20760
  var PREVIEW_INSTANCES_PER_GROUP = 200;
20761
+ var SCAN_BATCH_ROWS = 1e3;
20762
+ var DEFAULT_LOCATIONS_LIMIT = 100;
20763
+ var LOCATION_RULE_IDS_CAP = 20;
20764
+ function compareLocationOrder(a, b) {
20765
+ return compareFindingGroupOrder(
20766
+ {
20767
+ severity: a.maxSeverity,
20768
+ latestDetectedAt: a.latestDetectedAt,
20769
+ id: ""
20770
+ },
20771
+ {
20772
+ severity: b.maxSeverity,
20773
+ latestDetectedAt: b.latestDetectedAt,
20774
+ id: ""
20775
+ }
20776
+ );
20777
+ }
20281
20778
  var CONCAT_SEP = ",";
20282
20779
  var TUPLE_SEP = "|";
20283
20780
  function splitConcat(value) {
@@ -20290,6 +20787,33 @@ function deriveInstanceStatus(row) {
20290
20787
  latestResolutionStatus: row.latest_status
20291
20788
  });
20292
20789
  }
20790
+ function encodeGroupCursor(group) {
20791
+ const payload = {
20792
+ sev: group.severity,
20793
+ t: group.latestDetectedAt,
20794
+ id: group.id
20795
+ };
20796
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
20797
+ }
20798
+ function decodeGroupCursor(cursor) {
20799
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
20800
+ if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
20801
+ return {
20802
+ severity: parsed.sev,
20803
+ latestDetectedAt: parsed.t,
20804
+ id: parsed.id
20805
+ };
20806
+ }
20807
+ return null;
20808
+ }
20809
+ function firstAfter(sorted, cursor) {
20810
+ const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
20811
+ return index === -1 ? sorted.length : index;
20812
+ }
20813
+ function findDeepLinked(sorted, page, id) {
20814
+ if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
20815
+ return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
20816
+ }
20293
20817
  var DAY_MS3 = 864e5;
20294
20818
  var SqliteFindingsRepository = class {
20295
20819
  constructor(db) {
@@ -20399,8 +20923,13 @@ var SqliteFindingsRepository = class {
20399
20923
  */
20400
20924
  listGroupedFindings(query) {
20401
20925
  const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
20402
- const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}`;
20403
- const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
20926
+ const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
20927
+ const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
20928
+ const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
20929
+ const sessionParams = {
20930
+ ...query.sessionId ? { sessionId: query.sessionId } : {},
20931
+ ...fromMs === void 0 ? {} : { fromMs }
20932
+ };
20404
20933
  const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
20405
20934
  predicate,
20406
20935
  params: sessionParams
@@ -20408,7 +20937,8 @@ var SqliteFindingsRepository = class {
20408
20937
  const rows = allRows(
20409
20938
  this.db.prepare(
20410
20939
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
20411
- occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
20940
+ occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
20941
+ kind, finding_key, latest_status
20412
20942
  FROM (
20413
20943
  SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
20414
20944
  d.severity AS severity, f.masked_match AS masked_match,
@@ -20418,6 +20948,7 @@ var SqliteFindingsRepository = class {
20418
20948
  json_extract(e.attributes, '$.repo') AS repo,
20419
20949
  json_extract(e.attributes, '$.file_path') AS file,
20420
20950
  json_extract(e.attributes, '$.tool_name') AS tool_name,
20951
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
20421
20952
  e.event_type AS kind, f.finding_key AS finding_key,
20422
20953
  latest.status AS latest_status,
20423
20954
  ROW_NUMBER() OVER (
@@ -20449,6 +20980,8 @@ var SqliteFindingsRepository = class {
20449
20980
  repo: r.repo ?? "",
20450
20981
  file: r.file ?? "",
20451
20982
  ...r.tool_name === null ? {} : { toolName: r.tool_name },
20983
+ eventId: r.event_id,
20984
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
20452
20985
  status: deriveInstanceStatus(r)
20453
20986
  }));
20454
20987
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -20472,18 +21005,23 @@ var SqliteFindingsRepository = class {
20472
21005
  groups: sorted.length
20473
21006
  };
20474
21007
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
21008
+ const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
21009
+ const start = cursor === null ? 0 : firstAfter(sorted, cursor);
21010
+ const page = sorted.slice(start, start + limit);
21011
+ const lastOnPage = page.at(-1);
21012
+ const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
21013
+ const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
20475
21014
  const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
20476
- const items = sorted.slice(0, limit).map(
20477
- (g) => statusSet ? {
20478
- ...g,
20479
- instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
20480
- } : g
20481
- );
21015
+ const narrow = (g) => statusSet ? {
21016
+ ...g,
21017
+ instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
21018
+ } : g;
21019
+ const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
20482
21020
  return Promise.resolve({
20483
21021
  totals,
20484
21022
  facets,
20485
21023
  items,
20486
- nextCursor: null,
21024
+ nextCursor,
20487
21025
  ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
20488
21026
  });
20489
21027
  }
@@ -20515,14 +21053,274 @@ var SqliteFindingsRepository = class {
20515
21053
  * request actually carries a `q`. (Substring matching is unaffected by a
20516
21054
  * path repeating across tuples.)
20517
21055
  */
20518
- groupAggregates(withSearchText, scope) {
20519
- const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
20520
- group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
20521
- group_concat(DISTINCT 'via ' || json_extract(e.attributes, '$.tool_name')) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
20522
- const rows = this.db.prepare(
20523
- `SELECT rule_id,
20524
- sum(tuple_count) AS instance_count,
20525
- max(latest_at) AS latest_at,
21056
+ /**
21057
+ * The instance-level (flat) findings list: one row per finding, newest first,
21058
+ * paged by keyset.
21059
+ *
21060
+ * SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
21061
+ * bound are SQL predicates: nothing counts them, so narrowing the scan by
21062
+ * them changes no reported number. Severity, subtype, provider, action,
21063
+ * status, tool, repo, file and `q` all stay in JS — each has a facet, and a
21064
+ * facet excludes its own filter, so a row the filter rejects still has to be
21065
+ * counted. Pushing any of them into SQL would silently empty its own facet.
21066
+ * Several could not be expressed there anyway: status comes from the one
21067
+ * shared classifier (deriveFindingStatus), and provider 'api' means "a tool
21068
+ * none of the mappers names", which no IN-list can say.
21069
+ *
21070
+ * The scan runs from the top of the scope on every request, not from the
21071
+ * cursor: `totals` and `facets` describe the whole filtered scope and must not
21072
+ * move as the caller pages. Rows are pulled in batches so memory stays flat
21073
+ * while the counting runs, and only the page itself is retained.
21074
+ */
21075
+ listFindingInstances(query) {
21076
+ const opts = {
21077
+ severity: query.severity,
21078
+ subtype: query.subtype,
21079
+ providers: query.provider,
21080
+ actions: query.action,
21081
+ statuses: query.status,
21082
+ tools: query.tool,
21083
+ repo: query.repo,
21084
+ file: query.file,
21085
+ q: query.q
21086
+ };
21087
+ const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
21088
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
21089
+ const accumulator = createInstanceFacetAccumulator(opts);
21090
+ const items = [];
21091
+ let total = 0;
21092
+ let last;
21093
+ let hasMore = false;
21094
+ for (const row of this.scanFindingRows({
21095
+ sessionId: query.sessionId,
21096
+ from: query.from
21097
+ })) {
21098
+ accumulator.add(row);
21099
+ if (!matchesInstanceFilters(row, opts)) continue;
21100
+ total += 1;
21101
+ if (items.length < limit) {
21102
+ items.push(toInstanceDetail(row));
21103
+ last = row;
21104
+ } else {
21105
+ hasMore = true;
21106
+ }
21107
+ }
21108
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
21109
+ if (cursor !== null) {
21110
+ const resumed = this.pageAfter(cursor, opts, limit, query);
21111
+ return Promise.resolve({
21112
+ totals: { findings: total },
21113
+ facets: accumulator.facets(),
21114
+ items: resumed.items,
21115
+ nextCursor: resumed.nextCursor
21116
+ });
21117
+ }
21118
+ return Promise.resolve({
21119
+ totals: { findings: total },
21120
+ facets: accumulator.facets(),
21121
+ items,
21122
+ nextCursor
21123
+ });
21124
+ }
21125
+ /**
21126
+ * The page of matching rows strictly after `cursor`. Separate from the
21127
+ * counting pass because that one starts at the top of the scope by design;
21128
+ * this one narrows the scan with the same keyset predicate the activity list
21129
+ * uses, so a later page costs less than the first rather than more.
21130
+ */
21131
+ pageAfter(cursor, opts, limit, query) {
21132
+ const items = [];
21133
+ let last;
21134
+ let hasMore = false;
21135
+ for (const row of this.scanFindingRows({
21136
+ sessionId: query.sessionId,
21137
+ from: query.from,
21138
+ after: cursor
21139
+ })) {
21140
+ if (!matchesInstanceFilters(row, opts)) continue;
21141
+ if (items.length < limit) {
21142
+ items.push(toInstanceDetail(row));
21143
+ last = row;
21144
+ } else {
21145
+ hasMore = true;
21146
+ break;
21147
+ }
21148
+ }
21149
+ return {
21150
+ items,
21151
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
21152
+ };
21153
+ }
21154
+ /**
21155
+ * The same findings folded by location: repository, then file within it.
21156
+ *
21157
+ * The grouping keys come from the capturing event's attributes, which is what
21158
+ * the local store relates a finding to — there is no finding↔asset row to
21159
+ * group by instead. A repo or file the event did not record folds into the
21160
+ * empty-string bucket, which the view renders but does not link, since no
21161
+ * filter can name it.
21162
+ */
21163
+ listFindingLocations(query) {
21164
+ const opts = {
21165
+ severity: query.severity,
21166
+ subtype: query.subtype,
21167
+ providers: query.provider,
21168
+ actions: query.action,
21169
+ statuses: query.status,
21170
+ tools: query.tool,
21171
+ q: query.q
21172
+ };
21173
+ const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
21174
+ const byRepo = /* @__PURE__ */ new Map();
21175
+ let total = 0;
21176
+ for (const row of this.scanFindingRows({
21177
+ sessionId: query.sessionId,
21178
+ from: query.from
21179
+ })) {
21180
+ if (!matchesInstanceFilters(row, opts)) continue;
21181
+ total += 1;
21182
+ let files = byRepo.get(row.repo);
21183
+ if (files === void 0) {
21184
+ files = /* @__PURE__ */ new Map();
21185
+ byRepo.set(row.repo, files);
21186
+ }
21187
+ let acc = files.get(row.file);
21188
+ if (acc === void 0) {
21189
+ acc = newLocationAccumulator();
21190
+ files.set(row.file, acc);
21191
+ }
21192
+ addToLocation(acc, row);
21193
+ }
21194
+ let fileCount = 0;
21195
+ const repos = [...byRepo.entries()].map(([repo, files]) => {
21196
+ fileCount += files.size;
21197
+ const fileRows = [...files.entries()].map(([file2, acc]) => ({
21198
+ file: file2,
21199
+ instanceCount: acc.instanceCount,
21200
+ maxSeverity: acc.maxSeverity,
21201
+ latestDetectedAt: acc.latestDetectedAt,
21202
+ ...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
21203
+ ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
21204
+ })).sort(compareLocationOrder);
21205
+ const rollup = fileRows.reduce(
21206
+ (a, f) => ({
21207
+ instanceCount: a.instanceCount + f.instanceCount,
21208
+ maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
21209
+ latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
21210
+ }),
21211
+ {
21212
+ instanceCount: 0,
21213
+ maxSeverity: fileRows[0]?.maxSeverity ?? "low",
21214
+ latestDetectedAt: ""
21215
+ }
21216
+ );
21217
+ const statuses = fileRows.map((f) => f.status);
21218
+ const folded = foldGroupStatus(statuses);
21219
+ return {
21220
+ repo,
21221
+ instanceCount: rollup.instanceCount,
21222
+ maxSeverity: rollup.maxSeverity,
21223
+ latestDetectedAt: rollup.latestDetectedAt,
21224
+ ...folded === void 0 ? {} : { status: folded },
21225
+ files: fileRows
21226
+ };
21227
+ });
21228
+ repos.sort(compareLocationOrder);
21229
+ return Promise.resolve({
21230
+ totals: { findings: total, repos: repos.length, files: fileCount },
21231
+ items: repos.slice(0, limit),
21232
+ hasMore: repos.length > limit
21233
+ });
21234
+ }
21235
+ /**
21236
+ * Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
21237
+ *
21238
+ * A generator so a caller streams the scope without it ever being an array:
21239
+ * the flat list counts and facets the whole filtered scope, which on a large
21240
+ * store is far more rows than any page. Each batch advances the same keyset
21241
+ * predicate the page read uses, so the scan is a sequence of bounded reads
21242
+ * rather than one unbounded result set.
21243
+ *
21244
+ * The latest-resolution lookup is the CORRELATED form, not the derived table
21245
+ * the grouped path joins: only `status` is needed, idx_finding_resolution_key
21246
+ * makes it a point lookup per row, and the derived table would re-materialize
21247
+ * a window over the whole resolution table once per batch.
21248
+ *
21249
+ * `scope` carries ONLY what no facet counts. A filter dimension narrowed here
21250
+ * would be missing from its own facet, which is computed by excluding that
21251
+ * dimension — see listFindingInstances.
21252
+ */
21253
+ *scanFindingRows(scope) {
21254
+ const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
21255
+ const params = [];
21256
+ if (scope.sessionId !== void 0 && scope.sessionId !== "") {
21257
+ conditions.push("e.root_session_id = ?");
21258
+ params.push(scope.sessionId);
21259
+ }
21260
+ if (scope.from !== void 0) {
21261
+ conditions.push("e.started_at >= ?");
21262
+ params.push(isoToEpochMillis(scope.from));
21263
+ }
21264
+ const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
21265
+ d.severity AS severity, f.masked_match AS masked_match,
21266
+ f.action_taken AS action_taken, f.confidence AS confidence,
21267
+ e.started_at AS occurred_at,
21268
+ json_extract(e.attributes, '$.source_tool') AS source_tool,
21269
+ json_extract(e.attributes, '$.repo') AS repo,
21270
+ json_extract(e.attributes, '$.file_path') AS file,
21271
+ json_extract(e.attributes, '$.tool_name') AS tool_name,
21272
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
21273
+ e.event_type AS kind, f.finding_key AS finding_key,
21274
+ ${latestResolutionStatusSql("f")} AS latest_status
21275
+ FROM inspection_findings f
21276
+ JOIN audit_events e ON e.id = f.audit_event_id
21277
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21278
+ WHERE ${conditions.join(" AND ")}
21279
+ AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
21280
+ ORDER BY e.started_at DESC, f.id DESC
21281
+ LIMIT ?`;
21282
+ let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
21283
+ for (; ; ) {
21284
+ const rows = allRows(this.db.prepare(sql), [
21285
+ ...params,
21286
+ after.startedAtMs,
21287
+ after.startedAtMs,
21288
+ after.id,
21289
+ SCAN_BATCH_ROWS
21290
+ ]);
21291
+ for (const r of rows) {
21292
+ yield {
21293
+ id: r.id,
21294
+ ruleId: r.rule_id,
21295
+ category: r.category,
21296
+ severity: r.severity,
21297
+ maskedMatch: r.masked_match,
21298
+ actionTaken: r.action_taken,
21299
+ confidence: r.confidence,
21300
+ occurredAt: epochMillisToIso(r.occurred_at),
21301
+ sourceTool: r.source_tool,
21302
+ repo: r.repo ?? "",
21303
+ file: r.file ?? "",
21304
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
21305
+ eventId: r.event_id,
21306
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
21307
+ status: deriveInstanceStatus(r)
21308
+ };
21309
+ }
21310
+ if (rows.length < SCAN_BATCH_ROWS) return;
21311
+ const lastRow = rows[rows.length - 1];
21312
+ if (lastRow === void 0) return;
21313
+ after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
21314
+ }
21315
+ }
21316
+ groupAggregates(withSearchText, scope) {
21317
+ const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
21318
+ group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
21319
+ group_concat(DISTINCT 'via ' || json_extract(e.attributes, '$.tool_name')) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
21320
+ const rows = this.db.prepare(
21321
+ `SELECT rule_id,
21322
+ sum(tuple_count) AS instance_count,
21323
+ max(latest_at) AS latest_at,
20526
21324
  group_concat(source_tools) AS source_tools,
20527
21325
  group_concat(actions_taken) AS actions_taken,
20528
21326
  group_concat(status_tuple || '${TUPLE_SEP}' || tuple_count) AS status_inputs,
@@ -20775,7 +21573,7 @@ var SqliteInspectionFindingsRepository = class {
20775
21573
  };
20776
21574
 
20777
21575
  // ../../packages/persistence/src/repositories/installed-packs.ts
20778
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
21576
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
20779
21577
 
20780
21578
  // ../../packages/persistence/src/semver.ts
20781
21579
  function parse3(version2) {
@@ -20926,7 +21724,7 @@ var SqliteInstalledPacksRepository = class {
20926
21724
  let behind = false;
20927
21725
  for (const row of rows) {
20928
21726
  const params = {
20929
- id: randomUUID2(),
21727
+ id: randomUUID3(),
20930
21728
  namespace: row.namespace,
20931
21729
  packId: row.packId,
20932
21730
  version: row.version,
@@ -20938,7 +21736,7 @@ var SqliteInstalledPacksRepository = class {
20938
21736
  if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
20939
21737
  this.upsertAvailableStmt.run({
20940
21738
  ...params,
20941
- id: randomUUID2(),
21739
+ id: randomUUID3(),
20942
21740
  recordedBy: meta3?.recordedBy ?? null
20943
21741
  });
20944
21742
  } else {
@@ -21261,14 +22059,15 @@ var SqliteInventoryRepository = class {
21261
22059
  };
21262
22060
 
21263
22061
  // ../../packages/persistence/src/repositories/inventory-assets.ts
21264
- import { randomUUID as randomUUID3 } from "crypto";
22062
+ import { randomUUID as randomUUID4 } from "crypto";
21265
22063
  var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
21266
22064
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
21267
22065
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
21268
22066
  var HARNESS_LABELS = {
21269
22067
  claudecode: "Claude Code",
21270
22068
  cursor: "Cursor",
21271
- codex: "Codex"
22069
+ codex: "Codex",
22070
+ antigravity: "Antigravity"
21272
22071
  };
21273
22072
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
21274
22073
  var EMPTY_PROJECT_AGG = {
@@ -21283,6 +22082,7 @@ function resolveHarnessId(attrs, row) {
21283
22082
  if (t.includes("claudecode") || t === "claude") return "claudecode";
21284
22083
  if (t.includes("cursor")) return "cursor";
21285
22084
  if (t.includes("codex")) return "codex";
22085
+ if (t.includes("antigravity")) return "antigravity";
21286
22086
  return null;
21287
22087
  }
21288
22088
  function isLiveRealClaudeCode(rows) {
@@ -21741,7 +22541,7 @@ var SqliteInventoryAssetsRepository = class {
21741
22541
  `INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
21742
22542
  VALUES (:id, :projectId, :path, :access, :now, :now)
21743
22543
  ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
21744
- ).run({ id: randomUUID3(), projectId, path, access, now: Date.now() });
22544
+ ).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
21745
22545
  }
21746
22546
  return true;
21747
22547
  }
@@ -21762,7 +22562,7 @@ var SqliteInventoryAssetsRepository = class {
21762
22562
  `INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
21763
22563
  VALUES (:id, :assetId, :trust, :now, :now)
21764
22564
  ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
21765
- ).run({ id: randomUUID3(), assetId, trust, now: Date.now() });
22565
+ ).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
21766
22566
  }
21767
22567
  this.configRowsCache = void 0;
21768
22568
  return "ok";
@@ -22059,7 +22859,7 @@ var SqliteInventoryAssetsRepository = class {
22059
22859
  };
22060
22860
 
22061
22861
  // ../../packages/persistence/src/repositories/policies.ts
22062
- import { randomUUID as randomUUID4 } from "crypto";
22862
+ import { randomUUID as randomUUID5 } from "crypto";
22063
22863
  var SqlitePoliciesRepository = class {
22064
22864
  constructor(db) {
22065
22865
  this.db = db;
@@ -22094,7 +22894,7 @@ var SqlitePoliciesRepository = class {
22094
22894
  failOpenTransaction(this.db, () => {
22095
22895
  for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
22096
22896
  stmt.run({
22097
- id: randomUUID4(),
22897
+ id: randomUUID5(),
22098
22898
  target: JSON.stringify({ category }),
22099
22899
  action,
22100
22900
  now: Date.now()
@@ -22114,7 +22914,7 @@ var SqlitePoliciesRepository = class {
22114
22914
  `INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
22115
22915
  VALUES (:id, 'global', :target, :action, 1, :now, :now)
22116
22916
  ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
22117
- ).run({ id: randomUUID4(), target: JSON.stringify({ category }), action, now });
22917
+ ).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
22118
22918
  }
22119
22919
  // Caps every global per-category policy currently set to block/redact down
22120
22920
  // to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
@@ -22182,7 +22982,7 @@ var SqlitePolicyCatalogRepository = class {
22182
22982
  };
22183
22983
 
22184
22984
  // ../../packages/persistence/src/repositories/project-files.ts
22185
- import { randomUUID as randomUUID5 } from "crypto";
22985
+ import { randomUUID as randomUUID6 } from "crypto";
22186
22986
  var SqliteProjectFilesRepository = class {
22187
22987
  constructor(db) {
22188
22988
  this.db = db;
@@ -22214,7 +23014,7 @@ var SqliteProjectFilesRepository = class {
22214
23014
  const stamp = Math.max(now, maxStamp + 1);
22215
23015
  for (const file2 of scan2.files) {
22216
23016
  this.upsertStmt.run({
22217
- id: randomUUID5(),
23017
+ id: randomUUID6(),
22218
23018
  projectId,
22219
23019
  path: file2.path,
22220
23020
  name: file2.name,
@@ -22228,7 +23028,7 @@ var SqliteProjectFilesRepository = class {
22228
23028
  };
22229
23029
 
22230
23030
  // ../../packages/persistence/src/repositories/resolutions.ts
22231
- import { randomUUID as randomUUID6 } from "crypto";
23031
+ import { randomUUID as randomUUID7 } from "crypto";
22232
23032
  var SqliteResolutionsRepository = class {
22233
23033
  constructor(db, now = () => Date.now()) {
22234
23034
  this.db = db;
@@ -22282,7 +23082,7 @@ var SqliteResolutionsRepository = class {
22282
23082
  */
22283
23083
  insertResolution(r) {
22284
23084
  this.insertStmt.run({
22285
- id: randomUUID6(),
23085
+ id: randomUUID7(),
22286
23086
  findingKey: r.findingKey,
22287
23087
  status: FindingStatus.parse(r.status),
22288
23088
  method: ResolutionMethod.parse(r.method),
@@ -22341,13 +23141,51 @@ var SqliteRuleProbeCacheRepository = class {
22341
23141
  this.readStmt = db.prepare(
22342
23142
  `SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
22343
23143
  );
23144
+ this.countQuarantinedStmt = db.prepare(
23145
+ `SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
23146
+ );
23147
+ this.clearQuarantinedStmt = db.prepare(
23148
+ `DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
23149
+ );
22344
23150
  }
22345
23151
  db;
22346
23152
  upsertStmt;
22347
23153
  readStmt;
23154
+ countQuarantinedStmt;
23155
+ clearQuarantinedStmt;
22348
23156
  getVerdict(ruleKey) {
22349
23157
  return getRow(this.readStmt, { ruleKey });
22350
23158
  }
23159
+ /** How many rules are currently excluded by a cached quarantine verdict. */
23160
+ countQuarantined() {
23161
+ return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
23162
+ }
23163
+ /**
23164
+ * Forgets every quarantine verdict, so the rules behind them are measured
23165
+ * again on the next load. This is the undo for a verdict the machine reached
23166
+ * on its own: a rule terminated mid-scan is cached forever and dropped from
23167
+ * every later scan, and a timing verdict is a wall-clock judgement that a
23168
+ * loaded or slow machine can reach about a rule that is in fact fine.
23169
+ *
23170
+ * Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
23171
+ * keeping, and dropping it would make every rule pay the battery again.
23172
+ *
23173
+ * Reports `refused` from the write's own result rather than inferring it from
23174
+ * the row count. The two are NOT the same answer: `failOpenTransaction`
23175
+ * swallows a contended DELETE (another writer holding the lock past
23176
+ * `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
23177
+ * leaves the count unchanged, which is indistinguishable from "there was
23178
+ * nothing to clear". An undo that reports success while the quarantines are
23179
+ * still in place is worse than one that fails, because the rules it claimed
23180
+ * to restore are silently still disabled.
23181
+ */
23182
+ clearQuarantined() {
23183
+ const before = this.countQuarantined();
23184
+ const committed = failOpenTransaction(this.db, () => {
23185
+ this.clearQuarantinedStmt.run();
23186
+ });
23187
+ return { refused: !committed, cleared: before - this.countQuarantined() };
23188
+ }
22351
23189
  setVerdict(ruleKey, verdict, worstProbeMs) {
22352
23190
  failOpenTransaction(this.db, () => {
22353
23191
  this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
@@ -22402,7 +23240,39 @@ var SqliteScanLedgerRepository = class {
22402
23240
  };
22403
23241
 
22404
23242
  // ../../packages/persistence/src/repositories/secret-vault.ts
22405
- import { randomUUID as randomUUID7 } from "crypto";
23243
+ import { randomUUID as randomUUID8 } from "crypto";
23244
+ function pageLimit(requested, fallback) {
23245
+ if (requested === void 0) return fallback;
23246
+ return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
23247
+ }
23248
+ function encodeReuseCursor(payload) {
23249
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
23250
+ }
23251
+ function decodeReuseCursor(cursor) {
23252
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23253
+ if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23254
+ // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23255
+ // null cursor, which the caller reads as "end of list" — the one outcome a
23256
+ // malformed cursor must never produce, since restarting from the top is the
23257
+ // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23258
+ // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23259
+ Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23260
+ return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23261
+ }
23262
+ return null;
23263
+ }
23264
+ var REUSED_PREDICATE = `(v.occurrence_count > 1
23265
+ OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
23266
+ var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
23267
+ v.occurrence_count, v.first_seen, v.last_seen`;
23268
+ function toSighting(row) {
23269
+ return {
23270
+ location: row.location,
23271
+ kind: row.kind,
23272
+ firstSeen: new Date(row.first_seen).toISOString(),
23273
+ lastSeen: new Date(row.last_seen).toISOString()
23274
+ };
23275
+ }
22406
23276
  var SELECT_COLUMNS = `
22407
23277
  pointer_id AS pointerId,
22408
23278
  value_fingerprint AS valueFingerprint,
@@ -22586,39 +23456,67 @@ var SqliteSecretVaultRepository = class {
22586
23456
  VALUES (:id, :pointerId, :location, :kind, :now, :now)
22587
23457
  ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
22588
23458
  ).run({
22589
- id: randomUUID7(),
23459
+ id: randomUUID8(),
22590
23460
  pointerId: entry.pointerId,
22591
23461
  location: entry.location,
22592
23462
  kind: entry.kind,
22593
23463
  now
22594
23464
  });
22595
23465
  }
22596
- listSightings(pointerId) {
23466
+ /**
23467
+ * Sightings for a whole page of pointers, in ONE query grouped in JS rather
23468
+ * than one query per row. A pointer with no sightings still gets an entry, so
23469
+ * the caller never has to distinguish "none" from "missing".
23470
+ *
23471
+ * The `IN` list is sized to the page, so this statement cannot be cached on
23472
+ * the instance the way the fixed-shape ones in the constructor are.
23473
+ */
23474
+ sightingsFor(pointerIds) {
23475
+ const byPointer = new Map(pointerIds.map((id) => [id, []]));
23476
+ if (pointerIds.length === 0) return byPointer;
22597
23477
  const rows = allRows(
22598
23478
  this.db.prepare(
22599
- `SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
22600
- WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
23479
+ `SELECT pointer_id, location, kind, first_seen, last_seen
23480
+ FROM secret_vault_sighting
23481
+ WHERE pointer_id IN (${placeholders(pointerIds.length)})
23482
+ ORDER BY last_seen DESC`
22601
23483
  ),
22602
- { pointerId }
23484
+ pointerIds
22603
23485
  );
23486
+ for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
23487
+ return byPointer;
23488
+ }
23489
+ /** Hydrate a page of raw inventory rows with their sightings, batched. */
23490
+ toInventoryEntries(rows) {
23491
+ const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
22604
23492
  return rows.map((r) => ({
22605
- location: r.location,
22606
- kind: r.kind,
23493
+ pointerId: r.pointer_id,
23494
+ category: r.category,
23495
+ ...r.provider === null ? {} : { provider: r.provider },
23496
+ maskedMatch: r.masked_match,
23497
+ occurrences: r.occurrence_count,
22607
23498
  firstSeen: new Date(r.first_seen).toISOString(),
22608
- lastSeen: new Date(r.last_seen).toISOString()
23499
+ lastSeen: new Date(r.last_seen).toISOString(),
23500
+ revealGrantId: r.grant_id,
23501
+ sightings: sightings.get(r.pointer_id) ?? []
22609
23502
  }));
22610
23503
  }
22611
23504
  /**
22612
- * The dashboard inventory: every vaulted value's descriptor data joined with
22613
- * its sightings and the active reveal-to-model grant when one exists.
22614
- * Raw-free by construction — neither the fingerprint nor the ciphertext
22615
- * columns are selected.
23505
+ * The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
23506
+ * value's descriptor data joined with its sightings and the active
23507
+ * reveal-to-model grant when one exists. Raw-free by construction — neither
23508
+ * the fingerprint nor the ciphertext columns are selected.
23509
+ *
23510
+ * `totals.values` counts the whole store, not the page, so the count a reader
23511
+ * sees never depends on how far they have paged.
22616
23512
  */
22617
- listInventory(now = Date.now()) {
23513
+ listInventory(query = {}, now = Date.now()) {
23514
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23515
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23516
+ const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
22618
23517
  const rows = allRows(
22619
23518
  this.db.prepare(
22620
- `SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
22621
- v.occurrence_count, v.first_seen, v.last_seen,
23519
+ `SELECT ${INVENTORY_COLUMNS},
22622
23520
  (SELECT e.id FROM exceptions e
22623
23521
  WHERE e.rule_id = v.rule_id
22624
23522
  AND e.value_fingerprint = v.value_fingerprint
@@ -22626,45 +23524,109 @@ var SqliteSecretVaultRepository = class {
22626
23524
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
22627
23525
  LIMIT 1) AS grant_id
22628
23526
  FROM secret_vault v
22629
- ORDER BY v.last_seen DESC`
23527
+ ${where}
23528
+ ORDER BY v.last_seen DESC, v.pointer_id DESC
23529
+ LIMIT :limit`
22630
23530
  ),
22631
- { now }
23531
+ bindParams({
23532
+ now,
23533
+ limit: limit + 1,
23534
+ ...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
23535
+ })
22632
23536
  );
22633
- return rows.map((r) => ({
22634
- pointerId: r.pointer_id,
22635
- category: r.category,
22636
- ...r.provider === null ? {} : { provider: r.provider },
22637
- maskedMatch: r.masked_match,
22638
- occurrences: r.occurrence_count,
22639
- firstSeen: new Date(r.first_seen).toISOString(),
22640
- lastSeen: new Date(r.last_seen).toISOString(),
22641
- revealGrantId: r.grant_id,
22642
- sightings: this.listSightings(r.pointer_id)
22643
- }));
23537
+ const hasMore = rows.length > limit;
23538
+ const page = hasMore ? rows.slice(0, limit) : rows;
23539
+ const last = page[page.length - 1];
23540
+ return {
23541
+ totals: { values: this.countEntries() },
23542
+ items: this.toInventoryEntries(page),
23543
+ // Minted from the last row of the PAGE, never the extra probe row.
23544
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
23545
+ };
23546
+ }
23547
+ /**
23548
+ * Values reused on this machine — detected more than once, or written to more
23549
+ * than one location — most-reused first, one page at a time.
23550
+ *
23551
+ * Its own read rather than a filter over an inventory page: reuse is a
23552
+ * property of the whole store, and deriving it from 50 newest rows would
23553
+ * under-report exactly the values a reader most needs to see.
23554
+ */
23555
+ listReuse(query = {}, now = Date.now()) {
23556
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23557
+ const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
23558
+ const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
23559
+ OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
23560
+ const rows = allRows(
23561
+ this.db.prepare(
23562
+ `SELECT ${INVENTORY_COLUMNS},
23563
+ (SELECT e.id FROM exceptions e
23564
+ WHERE e.rule_id = v.rule_id
23565
+ AND e.value_fingerprint = v.value_fingerprint
23566
+ AND e.key_version = v.fingerprint_key_version
23567
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
23568
+ LIMIT 1) AS grant_id
23569
+ FROM secret_vault v
23570
+ WHERE ${REUSED_PREDICATE} ${after}
23571
+ ORDER BY v.occurrence_count DESC, v.pointer_id DESC
23572
+ LIMIT :limit`
23573
+ ),
23574
+ bindParams({
23575
+ now,
23576
+ limit: limit + 1,
23577
+ ...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
23578
+ })
23579
+ );
23580
+ const hasMore = rows.length > limit;
23581
+ const page = hasMore ? rows.slice(0, limit) : rows;
23582
+ const last = page[page.length - 1];
23583
+ return {
23584
+ totals: { reused: this.countReused() },
23585
+ items: this.toInventoryEntries(page),
23586
+ nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
23587
+ };
22644
23588
  }
22645
23589
  /**
22646
- * The de-reference trail, newest first. By default the batched, high-volume
22647
- * reasons (display, view-render) are hidden and counted instead — the rows
22648
- * that matter as a signal are the model crossings, and burying them under
22649
- * render noise would defeat the audit's purpose.
23590
+ * The de-reference trail, newest first, one page at a time. By default the
23591
+ * batched, high-volume reasons (display, view-render) are hidden and counted
23592
+ * instead — the rows that matter as a signal are the model crossings, and
23593
+ * burying them under render noise would defeat the audit's purpose.
23594
+ *
23595
+ * `hiddenBatched` counts the whole trail rather than the page: it is what the
23596
+ * view's "N hidden" line and its toggle speak for, so it must not shrink as
23597
+ * the reader pages.
22650
23598
  */
22651
- listDerefs(opts) {
22652
- const limit = opts?.limit ?? 200;
22653
- const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
23599
+ listDerefs(query = {}) {
23600
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
23601
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23602
+ const conditions = [];
23603
+ if (query.includeBatched !== true) {
23604
+ conditions.push(`reason NOT IN ('display', 'view-render')`);
23605
+ }
23606
+ if (cursor !== null) {
23607
+ conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
23608
+ }
23609
+ const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
22654
23610
  const rows = allRows(
22655
23611
  this.db.prepare(
22656
23612
  `SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
22657
23613
  FROM secret_vault_deref ${where}
22658
- ORDER BY at DESC, rowid DESC LIMIT :limit`
23614
+ ORDER BY at DESC, id DESC LIMIT :limit`
22659
23615
  ),
22660
- { limit }
23616
+ bindParams({
23617
+ limit: limit + 1,
23618
+ ...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
23619
+ })
22661
23620
  );
22662
- const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
23621
+ const hasMore = rows.length > limit;
23622
+ const page = hasMore ? rows.slice(0, limit) : rows;
23623
+ const last = page[page.length - 1];
23624
+ const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
22663
23625
  this.db,
22664
23626
  `SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
22665
23627
  );
22666
23628
  return {
22667
- rows: rows.map((r) => ({
23629
+ items: page.map((r) => ({
22668
23630
  id: r.id,
22669
23631
  pointerId: r.pointer_id,
22670
23632
  at: new Date(r.at).toISOString(),
@@ -22674,12 +23636,20 @@ var SqliteSecretVaultRepository = class {
22674
23636
  ...r.grant_id === null ? {} : { grantId: r.grant_id },
22675
23637
  pointerCount: r.pointer_count
22676
23638
  })),
23639
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
22677
23640
  hiddenBatched
22678
23641
  };
22679
23642
  }
22680
23643
  countEntries() {
22681
23644
  return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
22682
23645
  }
23646
+ /** Values reused on this machine — the reuse list's page-independent total. */
23647
+ countReused() {
23648
+ return countScalar(
23649
+ this.db,
23650
+ `SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
23651
+ );
23652
+ }
22683
23653
  };
22684
23654
 
22685
23655
  // ../../packages/persistence/src/repositories/security.ts
@@ -22694,7 +23664,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
22694
23664
  var SCAN_COVERAGE = [
22695
23665
  { provider: "claudecode", coverage: 100, supported: true },
22696
23666
  { provider: "cursor", coverage: 0, supported: false },
22697
- { provider: "codex", coverage: 0, supported: false },
23667
+ { provider: "codex", coverage: 80, supported: true },
23668
+ { provider: "antigravity", coverage: 60, supported: true },
23669
+ { provider: "claudeai", coverage: 0, supported: false },
22698
23670
  { provider: "chatgpt", coverage: 0, supported: false },
22699
23671
  { provider: "copilot", coverage: 0, supported: false },
22700
23672
  { provider: "api", coverage: 0, supported: false }
@@ -23027,7 +23999,7 @@ var SqliteSecurityRepository = class {
23027
23999
  };
23028
24000
 
23029
24001
  // ../../packages/persistence/src/repositories/shares.ts
23030
- import { randomUUID as randomUUID8 } from "crypto";
24002
+ import { randomUUID as randomUUID9 } from "crypto";
23031
24003
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
23032
24004
  var IN_CHUNK = 500;
23033
24005
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -23283,7 +24255,7 @@ var SqliteSharesRepository = class {
23283
24255
  (id, destination_id, host, decision, created_at, updated_at)
23284
24256
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
23285
24257
  ).run({
23286
- id: randomUUID8(),
24258
+ id: randomUUID9(),
23287
24259
  destinationId,
23288
24260
  host: dest.host,
23289
24261
  decision,
@@ -23432,7 +24404,7 @@ var SqliteSharesRepository = class {
23432
24404
  let destinationId = destIds.get(hit.host);
23433
24405
  if (destinationId === void 0) {
23434
24406
  destStmt.run({
23435
- id: randomUUID8(),
24407
+ id: randomUUID9(),
23436
24408
  kind: hit.kind,
23437
24409
  name: hit.name,
23438
24410
  host: hit.host,
@@ -23448,7 +24420,7 @@ var SqliteSharesRepository = class {
23448
24420
  let endpointId = endpointIds.get(endpointKey);
23449
24421
  if (endpointId === void 0) {
23450
24422
  endpointStmt.run({
23451
- id: randomUUID8(),
24423
+ id: randomUUID9(),
23452
24424
  destinationId,
23453
24425
  method: hit.method,
23454
24426
  transport: hit.transport,
@@ -23461,7 +24433,7 @@ var SqliteSharesRepository = class {
23461
24433
  endpointIds.set(endpointKey, endpointId);
23462
24434
  }
23463
24435
  siteStmt.run({
23464
- id: randomUUID8(),
24436
+ id: randomUUID9(),
23465
24437
  endpointId,
23466
24438
  project: input.project,
23467
24439
  projectKey: input.projectKey,
@@ -23826,6 +24798,9 @@ function purgeSampleData(db) {
23826
24798
  }
23827
24799
 
23828
24800
  // ../../packages/persistence/src/database.ts
24801
+ var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
24802
+ "aka.persistence.unsafeTestOnlyRawHandle"
24803
+ );
23829
24804
  function linkHost(input, hostId) {
23830
24805
  return hostId ? { ...input, hostId } : input;
23831
24806
  }
@@ -23847,21 +24822,34 @@ function openWithPragmas(file2) {
23847
24822
  }
23848
24823
  return db;
23849
24824
  }
23850
- function backupLegacyStore(file2) {
23851
- const backup = `${file2}.legacy.${String(Date.now())}.bak`;
23852
- renameSync2(file2, backup);
23853
- tightenFile(backup);
23854
- for (const sidecar of dbSidecars(file2)) {
23855
- if (existsSync(sidecar)) rmSync2(sidecar);
24825
+ function backupLegacyStore(db, file2) {
24826
+ reapStalePartials(file2);
24827
+ const backup = backupPath(file2, "legacy");
24828
+ let snapshotted = false;
24829
+ let snapshotError;
24830
+ try {
24831
+ snapshotStore(db, backup);
24832
+ snapshotted = true;
24833
+ } catch (error51) {
24834
+ snapshotError = error51;
24835
+ } finally {
24836
+ db.close();
24837
+ }
24838
+ if (!snapshotted) {
24839
+ akaWarn(
24840
+ `Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
24841
+ );
24842
+ moveStoreAside(file2, backup);
24843
+ return backup;
23856
24844
  }
24845
+ discardStore(file2, backup);
23857
24846
  return backup;
23858
24847
  }
23859
24848
  function openAndInitialize(file2) {
23860
24849
  let db = openWithPragmas(file2);
23861
24850
  try {
23862
24851
  if (isForeignSqliteLineage(db)) {
23863
- db.close();
23864
- const backup = backupLegacyStore(file2);
24852
+ const backup = backupLegacyStore(db, file2);
23865
24853
  db = openWithPragmas(file2);
23866
24854
  akaWarn(
23867
24855
  `Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
@@ -23905,7 +24893,7 @@ function openAndInitialize(file2) {
23905
24893
  }
23906
24894
  function openLocalDatabase(dir) {
23907
24895
  ensureDataDirSync(dir);
23908
- const file2 = join(dir, DB_FILENAME);
24896
+ const file2 = join2(dir, DB_FILENAME);
23909
24897
  const {
23910
24898
  db,
23911
24899
  events,
@@ -24022,7 +25010,7 @@ function openLocalDatabase(dir) {
24022
25010
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
24023
25011
  if (!definitionId) continue;
24024
25012
  inspectionFindings.insertFinding({
24025
- id: randomUUID9(),
25013
+ id: randomUUID10(),
24026
25014
  auditEventId: record2.scanEvent.id,
24027
25015
  inspectionDefinitionId: definitionId,
24028
25016
  span: finding.span,
@@ -24128,35 +25116,51 @@ function openLocalDatabase(dir) {
24128
25116
  transaction,
24129
25117
  close: () => {
24130
25118
  db.close();
24131
- }
25119
+ },
25120
+ // Last, and a plain value rather than a getter, so `{ ...db }` carries it.
25121
+ [UNSAFE_TEST_ONLY_RAW_HANDLE]: db
24132
25122
  };
24133
25123
  }
24134
25124
 
25125
+ // ../../packages/persistence/src/file-lock.ts
25126
+ import { randomUUID as randomUUID11 } from "crypto";
25127
+ import {
25128
+ closeSync,
25129
+ existsSync as existsSync2,
25130
+ openSync,
25131
+ readFileSync,
25132
+ rmSync as rmSync3,
25133
+ statSync as statSync2,
25134
+ writeFileSync as writeFileSync2
25135
+ } from "fs";
25136
+ import { hostname as hostname3 } from "os";
25137
+ var PARK = new Int32Array(new SharedArrayBuffer(4));
25138
+
24135
25139
  // ../../packages/persistence/src/finding-key.ts
24136
25140
  import { createHash as createHash3 } from "crypto";
24137
25141
 
24138
25142
  // ../../packages/persistence/src/fingerprint.ts
24139
25143
  import { createHmac, randomBytes } from "crypto";
24140
- import { existsSync as existsSync2, readFileSync } from "fs";
24141
- import { join as join2 } from "path";
25144
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25145
+ import { join as join3 } from "path";
24142
25146
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
24143
25147
 
24144
25148
  // ../../packages/persistence/src/local-layout.ts
24145
25149
  import { renameSync as renameSync3 } from "fs";
24146
25150
  import { mkdir } from "fs/promises";
24147
25151
  import { homedir } from "os";
24148
- import { join as join3 } from "path";
25152
+ import { join as join4 } from "path";
24149
25153
  function defaultDataDir() {
24150
- return join3(homedir(), ".aka");
25154
+ return join4(homedir(), ".aka");
24151
25155
  }
24152
25156
  function settingsDir(base = defaultDataDir()) {
24153
- return join3(base, "settings");
25157
+ return join4(base, "settings");
24154
25158
  }
24155
25159
  function dataDir(base = defaultDataDir()) {
24156
- return join3(base, "data");
25160
+ return join4(base, "data");
24157
25161
  }
24158
25162
  function dbPath(base = defaultDataDir()) {
24159
- return join3(dataDir(base), "aka.db");
25163
+ return join4(dataDir(base), "aka.db");
24160
25164
  }
24161
25165
  function ensureLayoutDirSync(dir = defaultDataDir()) {
24162
25166
  ensureDataDirSync(dir);
@@ -24169,8 +25173,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24169
25173
  for (const { name, dest } of moves) {
24170
25174
  try {
24171
25175
  ensureDataDirSync(dest);
24172
- const moved = join3(dest, name);
24173
- renameSync3(join3(base, name), moved);
25176
+ const moved = join4(dest, name);
25177
+ renameSync3(join4(base, name), moved);
24174
25178
  tightenFile(moved);
24175
25179
  } catch {
24176
25180
  }
@@ -24178,10 +25182,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24178
25182
  }
24179
25183
 
24180
25184
  // ../../packages/persistence/src/settings.ts
24181
- import { readFileSync as readFileSync2 } from "fs";
24182
- import { join as join4 } from "path";
25185
+ import { readFileSync as readFileSync3 } from "fs";
25186
+ import { join as join5 } from "path";
25187
+ var SETTINGS_FILENAME = "settings.json";
24183
25188
  function readWorkspaceSettings(base = defaultDataDir()) {
24184
- const record2 = readJson(join4(settingsDir(base), "settings.json"));
25189
+ const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
24185
25190
  if (!record2) return defaultWorkspaceSettings();
24186
25191
  try {
24187
25192
  return WorkspaceSettings.parse(record2);
@@ -24192,7 +25197,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
24192
25197
  function readJson(file2) {
24193
25198
  let text;
24194
25199
  try {
24195
- text = readFileSync2(file2, "utf8");
25200
+ text = readFileSync3(file2, "utf8");
24196
25201
  } catch {
24197
25202
  return null;
24198
25203
  }
@@ -24214,24 +25219,24 @@ import { randomBytes as randomBytes2 } from "crypto";
24214
25219
  import {
24215
25220
  chmodSync as chmodSync2,
24216
25221
  mkdirSync as mkdirSync2,
24217
- readFileSync as readFileSync3,
25222
+ readFileSync as readFileSync4,
24218
25223
  renameSync as renameSync4,
24219
- rmSync as rmSync3,
24220
- statSync,
24221
- writeFileSync as writeFileSync2
25224
+ rmSync as rmSync4,
25225
+ statSync as statSync3,
25226
+ writeFileSync as writeFileSync3
24222
25227
  } from "fs";
24223
- import { join as join5 } from "path";
25228
+ import { join as join6 } from "path";
24224
25229
 
24225
25230
  // ../../packages/persistence/src/vault/vault.ts
24226
- import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
25231
+ import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
24227
25232
 
24228
25233
  // ../../packages/persistence/src/warn-era-cap.ts
24229
- import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
24230
- import { join as join6 } from "path";
25234
+ import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
25235
+ import { join as join7 } from "path";
24231
25236
 
24232
25237
  // ../../packages/plugin-sdk/src/config.ts
24233
- import { existsSync as existsSync4 } from "fs";
24234
- import { join as join7 } from "path";
25238
+ import { existsSync as existsSync5 } from "fs";
25239
+ import { join as join8 } from "path";
24235
25240
 
24236
25241
  // ../../packages/plugin-sdk/src/provider-env.ts
24237
25242
  var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
@@ -24282,11 +25287,11 @@ function resolveProvider() {
24282
25287
  }
24283
25288
 
24284
25289
  // ../../packages/plugin-sdk/src/config.ts
24285
- function loadConfig(base = defaultDataDir()) {
25290
+ function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
24286
25291
  try {
24287
25292
  ensureLayoutDirSync(base);
24288
- const settingsFile = join7(settingsDir(base), "settings.json");
24289
- if (existsSync4(settingsFile)) tightenFile(settingsFile);
25293
+ const settingsFile = join8(settingsDir(base), "settings.json");
25294
+ if (existsSync5(settingsFile)) tightenFile(settingsFile);
24290
25295
  } catch {
24291
25296
  }
24292
25297
  migrateLegacyLayout(base);
@@ -24297,21 +25302,21 @@ function loadConfig(base = defaultDataDir()) {
24297
25302
  dbPath: dbPath(base),
24298
25303
  settingsDir: settingsDir(base),
24299
25304
  onboarded: settings.onboardedAt != null,
24300
- provider: resolveProviderSafe()
25305
+ provider: resolveProviderSafe(resolveProviderFn)
24301
25306
  };
24302
25307
  }
24303
- function resolveProviderSafe() {
25308
+ function resolveProviderSafe(resolveProviderFn) {
24304
25309
  try {
24305
- return resolveProvider();
25310
+ return resolveProviderFn();
24306
25311
  } catch {
24307
25312
  return { provider: "anthropic" };
24308
25313
  }
24309
25314
  }
24310
25315
 
24311
25316
  // ../../packages/plugin-sdk/src/config-inventory.ts
24312
- import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
25317
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
24313
25318
  import { homedir as homedir2 } from "os";
24314
- import { basename as basename2, join as join9 } from "path";
25319
+ import { basename as basename3, join as join10 } from "path";
24315
25320
 
24316
25321
  // ../../packages/detections/src/egress/registry.ts
24317
25322
  var EXTRACTOR_VERSION = "1";
@@ -26887,7 +27892,7 @@ var gcp_service_account_default = {
26887
27892
  severity: "critical",
26888
27893
  matcher: {
26889
27894
  type: "regex",
26890
- pattern: "\\b[a-z0-9][a-z0-9-]{2,60}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
27895
+ pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
26891
27896
  flags: "g"
26892
27897
  },
26893
27898
  examples: [
@@ -27299,22 +28304,27 @@ function maskText(text) {
27299
28304
  }
27300
28305
 
27301
28306
  // ../../packages/plugin-sdk/src/repo.ts
27302
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
27303
- import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
28307
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
28308
+ import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
27304
28309
 
27305
28310
  // ../../packages/plugin-sdk/src/events.ts
27306
- import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
28311
+ import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
28312
+
28313
+ // ../../packages/plugin-sdk/src/isolated-scan.ts
28314
+ import { existsSync as existsSync7 } from "fs";
28315
+ import { fileURLToPath } from "url";
28316
+ import { Worker } from "worker_threads";
27307
28317
 
27308
28318
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
27309
- import { arch, hostname as hostname3, platform, release } from "os";
28319
+ import { arch, hostname as hostname4, platform, release } from "os";
27310
28320
 
27311
28321
  // ../../packages/plugin-sdk/src/nudge.ts
27312
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
27313
- import { join as join10 } from "path";
28322
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
28323
+ import { join as join11 } from "path";
27314
28324
 
27315
28325
  // ../../packages/plugin-sdk/src/paths.ts
27316
- import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
27317
- import { basename as basename3, dirname as dirname2, sep as sep3 } from "path";
28326
+ import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
28327
+ import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
27318
28328
 
27319
28329
  // ../../packages/plugin-sdk/src/posture.ts
27320
28330
  function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
@@ -27328,8 +28338,34 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
27328
28338
 
27329
28339
  // ../../packages/plugin-sdk/src/project-files.ts
27330
28340
  var import_ignore = __toESM(require_ignore(), 1);
27331
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
27332
- import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
28341
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
28342
+ import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
28343
+
28344
+ // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
28345
+ var optionalBaseUrl2 = external_exports.preprocess((v) => {
28346
+ if (typeof v === "string" && v.trim() === "") return void 0;
28347
+ return v;
28348
+ }, external_exports.string().optional()).catch(void 0);
28349
+ var optionalFlag = external_exports.preprocess((v) => {
28350
+ if (typeof v !== "string") return false;
28351
+ const normalized = v.trim().toLowerCase();
28352
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
28353
+ }, external_exports.boolean()).catch(false);
28354
+ var antigravityProviderEnvShape = {
28355
+ GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
28356
+ GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
28357
+ };
28358
+ var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
28359
+
28360
+ // ../../packages/plugin-sdk/src/provider-env-codex.ts
28361
+ var optionalBaseUrl3 = external_exports.preprocess((v) => {
28362
+ if (typeof v === "string" && v.trim() === "") return void 0;
28363
+ return v;
28364
+ }, external_exports.string().optional()).catch(void 0);
28365
+ var codexProviderEnvShape = {
28366
+ OPENAI_BASE_URL: optionalBaseUrl3
28367
+ };
28368
+ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
27333
28369
 
27334
28370
  // ../../packages/plugin-sdk/src/raw-egress.ts
27335
28371
  var RawEgressError = class extends Error {
@@ -27380,7 +28416,7 @@ function assertRawFree(text, rawValues) {
27380
28416
  }
27381
28417
 
27382
28418
  // ../../packages/plugin-sdk/src/runtime.ts
27383
- import { randomUUID as randomUUID12 } from "crypto";
28419
+ import { randomUUID as randomUUID14 } from "crypto";
27384
28420
 
27385
28421
  // ../../packages/plugin-sdk/src/suppressions.ts
27386
28422
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -27420,128 +28456,72 @@ async function applySetupTriageSuppressions(entries, writer, opts) {
27420
28456
  }
27421
28457
 
27422
28458
  // ../../packages/plugin-sdk/src/throttle.ts
27423
- import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
27424
- import { join as join12 } from "path";
28459
+ import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
28460
+ import { join as join13 } from "path";
27425
28461
 
27426
- // src/command-registry.ts
27427
- import { readdirSync as readdirSync4 } from "fs";
27428
- import { fileURLToPath } from "url";
27429
- var COMMAND_NAMESPACE = "aka";
27430
- var COMMANDS_DIR = fileURLToPath(new URL("../commands", import.meta.url));
27431
- function readRegisteredCommands() {
27432
- return readdirSync4(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
28462
+ // ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
28463
+ import { writeFileSync as writeFileSync7 } from "fs";
28464
+ import { join as join14 } from "path";
28465
+
28466
+ // ../../packages/setup-wizard/src/triage/dedupe.ts
28467
+ function dedupeKey(hit) {
28468
+ if (hit.valueFingerprint === void 0) return void 0;
28469
+ return `${hit.ruleId}\u2588${hit.valueFingerprint}`;
27433
28470
  }
27434
- function selectRegisteredCommands(curated, registry2) {
27435
- const registered = new Set(registry2);
27436
- const missing = curated.filter((c) => !registered.has(c));
27437
- if (missing.length > 0) {
27438
- throw new Error(
27439
- `Curated command(s) not registered in the installed plugin: ${missing.join(", ")}`
27440
- );
28471
+ function dedupeForJudge(hits) {
28472
+ const seen = /* @__PURE__ */ new Set();
28473
+ const out = [];
28474
+ for (const h of hits) {
28475
+ const key = dedupeKey(h);
28476
+ if (key === void 0) {
28477
+ out.push(h);
28478
+ continue;
28479
+ }
28480
+ if (seen.has(key)) continue;
28481
+ seen.add(key);
28482
+ out.push(h);
27441
28483
  }
27442
- return [...curated];
28484
+ return out;
27443
28485
  }
27444
28486
 
27445
- // src/setup-frame-json.ts
27446
- var FRAME_JSON_BEGIN = "<<<AKA_FRAME_JSON";
27447
- var FRAME_JSON_END = "AKA_FRAME_JSON>>>";
27448
- function frameJsonBlock(payload) {
27449
- return `${FRAME_JSON_BEGIN}
27450
- ${JSON.stringify(payload)}
27451
- ${FRAME_JSON_END}
27452
- `;
27453
- }
27454
-
27455
- // src/setup-show.ts
27456
- var SHOW_BEGIN = "<<<AKA_SHOW";
27457
- var SHOW_END = "AKA_SHOW>>>";
27458
- function showBlock(body) {
27459
- return `${SHOW_BEGIN}
27460
- ${body}
27461
- ${SHOW_END}
27462
- `;
27463
- }
27464
-
27465
- // src/present.ts
27466
- var SHADE = {
27467
- light: "\u2591",
27468
- medium: "\u2592",
27469
- dark: "\u2593",
27470
- full: "\u2588"
27471
- };
27472
- var ANSI_RE = /\x1b\[[0-9;]*m/g;
27473
- function visibleLength(text) {
27474
- return text.replace(ANSI_RE, "").length;
27475
- }
27476
- var fg = (hex3) => (text) => {
27477
- const r = Number.parseInt(hex3.slice(1, 3), 16);
27478
- const g = Number.parseInt(hex3.slice(3, 5), 16);
27479
- const b = Number.parseInt(hex3.slice(5, 7), 16);
27480
- return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
27481
- };
27482
- var paint = {
27483
- brand: fg("#33e6c6"),
27484
- // --color-brand · ▸▸ AKA wordmark (accent text)
27485
- dim: fg("#838995"),
27486
- // --color-text-3 · separators · "/100" · the "unreviewed" label
27487
- bold: (text) => `\x1B[1m${text}\x1B[0m`,
27488
- // the health score number
27489
- ok: fg("#0db15f"),
27490
- // --color-ok · healthy ● dot
27491
- critical: fg("#e63448"),
27492
- // --color-sev-critical · ■ and the open-findings flag
27493
- high: fg("#e97a0a"),
27494
- // --color-sev-high · ■ and the mid-health dot
27495
- medium: fg("#f7bd00"),
27496
- // --color-sev-medium · ■
27497
- low: fg("#0581d4")
27498
- // --color-sev-low · ■ (azure blue, not purple)
27499
- };
27500
- function padEnd(text, width) {
27501
- const pad = width - visibleLength(text);
27502
- return pad > 0 ? text + " ".repeat(pad) : text;
27503
- }
27504
- function indent(text, spaces = 2) {
27505
- const pad = " ".repeat(spaces);
27506
- return text.split("\n").map((line) => pad + line).join("\n");
27507
- }
27508
- function table(headers, rows, opts = {}) {
27509
- const gap = opts.gap ?? 3;
27510
- const widths = headers.map(
27511
- (h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
27512
- );
27513
- const sep5 = " ".repeat(gap);
27514
- const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep5);
27515
- const headerLine = fmt(headers.map((h) => h.toUpperCase()));
27516
- if (opts.rowSep === true) {
27517
- const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
27518
- const rule = "\u2500".repeat(fullWidth);
27519
- const body = [];
27520
- rows.forEach((row, i) => {
27521
- if (i > 0) body.push(rule);
27522
- body.push(fmt(row));
27523
- });
27524
- return [headerLine, rule, ...body].join("\n");
28487
+ // ../../packages/setup-wizard/src/triage/false-positive-patterns.ts
28488
+ function deriveFalsePositivePatterns(hits, rec, plan) {
28489
+ const hitById = new Map(hits.filter((h) => h.id !== void 0).map((h) => [h.id, h]));
28490
+ const seenCategory = /* @__PURE__ */ new Set();
28491
+ const markedIds = /* @__PURE__ */ new Set();
28492
+ for (const c of rec.perCategory) {
28493
+ if (seenCategory.has(c.category)) continue;
28494
+ seenCategory.add(c.category);
28495
+ if (plan.posture[c.category] === void 0) continue;
28496
+ for (const id of c.fpIds) markedIds.add(id);
27525
28497
  }
27526
- const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep5);
27527
- return [headerLine, ruleLine, ...rows.map(fmt)].join("\n");
27528
- }
27529
- function fenced(body) {
27530
- const longestRun = Math.max(0, ...[...body.matchAll(/`+/g)].map((m) => m[0].length));
27531
- const fence = "`".repeat(Math.max(3, longestRun + 1));
27532
- return [fence, body, fence].join("\n");
27533
- }
27534
- function show(body) {
27535
- return showBlock(body);
28498
+ const groups = /* @__PURE__ */ new Map();
28499
+ for (const id of markedIds) {
28500
+ const h = hitById.get(id);
28501
+ if (h === void 0) continue;
28502
+ const pattern = safeMaskedMatch(h.rawMatch);
28503
+ const group = groups.get(pattern) ?? { count: 0, values: [] };
28504
+ group.count += 1;
28505
+ if (h.valueFingerprint !== void 0 && h.keyVersion !== void 0) {
28506
+ group.values.push({
28507
+ ruleId: h.ruleId,
28508
+ category: h.category,
28509
+ valueFingerprint: h.valueFingerprint,
28510
+ keyVersion: h.keyVersion
28511
+ });
28512
+ }
28513
+ groups.set(pattern, group);
28514
+ }
28515
+ return [...groups.entries()].filter(([, g]) => g.values.length > 0).map(([pattern, g]) => ({ pattern, count: g.count, values: g.values }));
27536
28516
  }
27537
28517
 
27538
- // src/triage/gate-display.ts
27539
- function findContext(entry, join16) {
27540
- const byFingerprint = join16.find(
28518
+ // ../../packages/setup-wizard/src/triage/gate-display.ts
28519
+ function findContext(entry, join18) {
28520
+ const byFingerprint = join18.find(
27541
28521
  (j) => j.valueFingerprint !== void 0 && j.valueFingerprint === entry.valueFingerprint
27542
28522
  );
27543
28523
  if (byFingerprint) return byFingerprint.maskedContext;
27544
- const byRuleAndMask = join16.find(
28524
+ const byRuleAndMask = join18.find(
27545
28525
  (j) => j.ruleId === entry.ruleId && j.maskedMatch === entry.maskedValue
27546
28526
  );
27547
28527
  return byRuleAndMask?.maskedContext;
@@ -27612,13 +28592,13 @@ function renderShowcase(showcase) {
27612
28592
 
27613
28593
  ${blocks.join("\n\n")}`;
27614
28594
  }
27615
- function renderSuppressionGate(entries, join16) {
28595
+ function renderSuppressionGate(entries, join18) {
27616
28596
  if (entries.length === 0) {
27617
28597
  return "No false-positive suppressions to confirm \u2014 nothing will be written.";
27618
28598
  }
27619
28599
  const header = entries.length === 1 ? "This looks like a false positive \u2014 take a look before I suppress it:" : `These ${String(entries.length)} look like false positives \u2014 take a look before I suppress them:`;
27620
28600
  const blocks = entries.map((entry, i) => {
27621
- const context = findContext(entry, join16);
28601
+ const context = findContext(entry, join18);
27622
28602
  const lines = [
27623
28603
  `${String(i + 1)}. ${entry.ruleId} [${entry.category}]`,
27624
28604
  ` value: ${entry.maskedValue}`,
@@ -27632,153 +28612,7 @@ function renderSuppressionGate(entries, join16) {
27632
28612
  ${blocks.join("\n\n")}`;
27633
28613
  }
27634
28614
 
27635
- // src/render.ts
27636
- var STORE_UNAVAILABLE_NOTE = "I couldn't check my records just now \u2014 we can check again soon. Your Claude session keeps going, and I'll fill in as you work.";
27637
- var SEVERITY_GLYPH = {
27638
- critical: SHADE.full,
27639
- high: SHADE.dark,
27640
- medium: SHADE.medium,
27641
- low: SHADE.light
27642
- };
27643
- var CATEGORY_ORDER2 = DetectionCategory.options;
27644
- function categoryRank(category) {
27645
- const i = CATEGORY_ORDER2.indexOf(category);
27646
- return i === -1 ? CATEGORY_ORDER2.length : i;
27647
- }
27648
- function renderRecommendedPosture(posture) {
27649
- const rows = Object.keys(posture).map((category) => ({
27650
- category,
27651
- level: posture[category] ?? ""
27652
- }));
27653
- const width = Math.max(0, ...rows.map((r) => r.category.length));
27654
- return rows.sort((a, b) => categoryRank(a.category) - categoryRank(b.category)).map((r) => ` ${r.category.padEnd(width)} ${r.level}`).join("\n");
27655
- }
27656
- var GRID_MARK = "\u25CF";
27657
- function renderPostureGrid(posture) {
27658
- const packs2 = Object.keys(posture).sort(
27659
- (a, b) => categoryRank(a) - categoryRank(b)
27660
- );
27661
- const rows = packs2.map((category) => [
27662
- category,
27663
- ...BUILTIN_ORDER.map((level) => posture[category] === level ? GRID_MARK : "")
27664
- ]);
27665
- return indent(table(["Category", ...BUILTIN_ORDER], rows));
27666
- }
27667
- var READY_COMMANDS = ["/aka:health", "/aka:findings", "/aka:recommend"];
27668
- function renderCategoriesTuned(categoriesTuned) {
27669
- return `\u2713 Set all ${String(categoriesTuned)} detection categories`;
27670
- }
27671
- function renderApplied(categoriesTuned, dismissed, registry2) {
27672
- const routine = dismissed > 0 ? `set aside ${String(dismissed)} routine result${dismissed === 1 ? "" : "s"}` : "nothing routine to set aside";
27673
- const ready = `Ready: ${selectRegisteredCommands(READY_COMMANDS, registry2).join(" \xB7 ")}`;
27674
- return `${renderCategoriesTuned(categoriesTuned)} \xB7 ${routine} \xB7 ${ready}`;
27675
- }
27676
-
27677
- // src/calibration.ts
27678
- var SURFACED_KIND_LABEL = {
27679
- secret: "live keys",
27680
- pii: "personal data",
27681
- financial: "financial records",
27682
- phi: "health records",
27683
- code_context: "source context",
27684
- code_flaw: "code flaws",
27685
- custom: "custom matches",
27686
- config: "configuration secrets"
27687
- };
27688
- function frameCalibration(preview, maskedFindings = [], falsePositivePatterns = []) {
27689
- const important = preview.categories.reduce((n, c) => n + c.genuineCount, 0);
27690
- const routine = preview.categories.reduce((n, c) => n + c.fpCount, 0);
27691
- const total = important + routine;
27692
- const surfacedCategories = preview.categories.filter((c) => c.genuineCount > 0).map((c) => c.category);
27693
- const routineCategories = preview.categories.filter((c) => c.fpCount > 0).map((c) => c.category);
27694
- const findingKinds = preview.categories.filter((c) => c.genuineCount + c.fpCount > 0).map((c) => ({ category: c.category, count: c.genuineCount + c.fpCount, egress: c.egress }));
27695
- const frame = {
27696
- counts: { total, important, routine },
27697
- routineCategories,
27698
- surfacedCategories,
27699
- findingKinds,
27700
- posture: preview.posture,
27701
- ...maskedFindings.length > 0 ? { maskedFindings: [...maskedFindings] } : {},
27702
- ...falsePositivePatterns.length > 0 ? { falsePositivePatterns: [...falsePositivePatterns] } : {}
27703
- };
27704
- const kind = surfacedCategories.map((c) => SURFACED_KIND_LABEL[c]).join(", ");
27705
- const parenthetical = kind ? ` (${kind})` : "";
27706
- const headline = `I went through Claude's recent work \u2014 ${String(total)} detection${total === 1 ? "" : "s"}, ${String(important)} result${important === 1 ? "" : "s"} worth a look.${parenthetical}`;
27707
- const copy = headline;
27708
- return { frame, copy };
27709
- }
27710
- var SCAN_CLEAN_HEADLINE = "I looked over Claude's recent work \u2014 nothing needs your attention right now. You're starting clean; here's what I'd recommend:";
27711
- var NO_HISTORY_HEADLINE = "Nothing to learn from yet \u2014 Claude hasn't left any work on this machine. I'll start each detection category at a careful default:";
27712
- function zeroCountFrame(posture) {
27713
- return {
27714
- counts: { total: 0, important: 0, routine: 0 },
27715
- routineCategories: [],
27716
- surfacedCategories: [],
27717
- findingKinds: [],
27718
- posture
27719
- };
27720
- }
27721
- function frameEmptyState(cause, posture) {
27722
- const frame = zeroCountFrame(posture);
27723
- const copy = cause === "scan-clean" ? `${SCAN_CLEAN_HEADLINE}
27724
- ${renderRecommendedPosture(posture)}` : `${NO_HISTORY_HEADLINE}
27725
- ${renderPostureGrid(posture)}`;
27726
- return { frame, copy };
27727
- }
27728
-
27729
- // src/triage/dedupe.ts
27730
- function dedupeKey(hit) {
27731
- if (hit.valueFingerprint === void 0) return void 0;
27732
- return `${hit.ruleId}\u2588${hit.valueFingerprint}`;
27733
- }
27734
- function dedupeForJudge(hits) {
27735
- const seen = /* @__PURE__ */ new Set();
27736
- const out = [];
27737
- for (const h of hits) {
27738
- const key = dedupeKey(h);
27739
- if (key === void 0) {
27740
- out.push(h);
27741
- continue;
27742
- }
27743
- if (seen.has(key)) continue;
27744
- seen.add(key);
27745
- out.push(h);
27746
- }
27747
- return out;
27748
- }
27749
-
27750
- // src/triage/false-positive-patterns.ts
27751
- function deriveFalsePositivePatterns(hits, rec, plan) {
27752
- const hitById = new Map(hits.filter((h) => h.id !== void 0).map((h) => [h.id, h]));
27753
- const seenCategory = /* @__PURE__ */ new Set();
27754
- const markedIds = /* @__PURE__ */ new Set();
27755
- for (const c of rec.perCategory) {
27756
- if (seenCategory.has(c.category)) continue;
27757
- seenCategory.add(c.category);
27758
- if (plan.posture[c.category] === void 0) continue;
27759
- for (const id of c.fpIds) markedIds.add(id);
27760
- }
27761
- const groups = /* @__PURE__ */ new Map();
27762
- for (const id of markedIds) {
27763
- const h = hitById.get(id);
27764
- if (h === void 0) continue;
27765
- const pattern = safeMaskedMatch(h.rawMatch);
27766
- const group = groups.get(pattern) ?? { count: 0, values: [] };
27767
- group.count += 1;
27768
- if (h.valueFingerprint !== void 0 && h.keyVersion !== void 0) {
27769
- group.values.push({
27770
- ruleId: h.ruleId,
27771
- category: h.category,
27772
- valueFingerprint: h.valueFingerprint,
27773
- keyVersion: h.keyVersion
27774
- });
27775
- }
27776
- groups.set(pattern, group);
27777
- }
27778
- return [...groups.entries()].filter(([, g]) => g.values.length > 0).map(([pattern, g]) => ({ pattern, count: g.count, values: g.values }));
27779
- }
27780
-
27781
- // src/triage/merge.ts
28615
+ // ../../packages/setup-wizard/src/triage/merge.ts
27782
28616
  var RANK = { monitor: 0, warn: 1, redact: 2, block: 3 };
27783
28617
  function chunkForJudge(hits, maxBytes = 262144) {
27784
28618
  const chunks = [];
@@ -27847,10 +28681,10 @@ function mergeRecommendations(verdicts) {
27847
28681
  };
27848
28682
  }
27849
28683
 
27850
- // src/triage/plan-file.ts
27851
- import { mkdtempSync, readFileSync as readFileSync8, rmdirSync, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
28684
+ // ../../packages/setup-wizard/src/triage/plan-file.ts
28685
+ import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
27852
28686
  import { tmpdir } from "os";
27853
- import { basename as basename5, dirname as dirname3, join as join13 } from "path";
28687
+ import { basename as basename6, dirname as dirname4, join as join15 } from "path";
27854
28688
  var SuppressionEntrySchema = external_exports.object({
27855
28689
  ruleId: external_exports.string(),
27856
28690
  category: DetectionCategory,
@@ -27905,27 +28739,27 @@ function serializePlan(plan, current) {
27905
28739
  function writePlanFile(plan, current, rawValues, deps = {}) {
27906
28740
  const serialized = serializePlan(plan, current);
27907
28741
  assertRawFree(serialized, rawValues);
27908
- const dir = (deps.mkTempDir ?? (() => mkdtempSync(join13(tmpdir(), "aka-plan-"))))();
27909
- const path = join13(dir, "setup-plan.json");
27910
- writeFileSync6(path, serialized, { encoding: "utf8", mode: 384 });
28742
+ const dir = (deps.mkTempDir ?? (() => mkdtempSync(join15(tmpdir(), "aka-plan-"))))();
28743
+ const path = join15(dir, "setup-plan.json");
28744
+ writeFileSync8(path, serialized, { encoding: "utf8", mode: 384 });
27911
28745
  return path;
27912
28746
  }
27913
28747
  function readPlanFile(path) {
27914
- const text = readFileSync8(path, "utf8");
28748
+ const text = readFileSync9(path, "utf8");
27915
28749
  const json2 = JSON.parse(text);
27916
28750
  return PersistedPlanSchema.parse(json2);
27917
28751
  }
27918
28752
  function deletePlanFile(path) {
27919
- rmSync4(path, { force: true });
27920
- const dir = dirname3(path);
27921
- if (!basename5(dir).startsWith("aka-plan-")) return;
28753
+ rmSync5(path, { force: true });
28754
+ const dir = dirname4(path);
28755
+ if (!basename6(dir).startsWith("aka-plan-")) return;
27922
28756
  try {
27923
28757
  rmdirSync(dir);
27924
28758
  } catch {
27925
28759
  }
27926
28760
  }
27927
28761
 
27928
- // src/triage/surfaced-secrets.ts
28762
+ // ../../packages/setup-wizard/src/triage/surfaced-secrets.ts
27929
28763
  var DEFAULT_STATE = "unknown";
27930
28764
  var UNKNOWN_LOCATION = "(location unavailable)";
27931
28765
  function deriveProvider(ruleId) {
@@ -27957,7 +28791,7 @@ function deriveSurfacedSecretFindings(hits, rec, plan) {
27957
28791
  }));
27958
28792
  }
27959
28793
 
27960
- // src/triage/join-file.ts
28794
+ // ../../packages/setup-wizard/src/triage/join-file.ts
27961
28795
  function buildJoinEntries(hits) {
27962
28796
  const rawValues = hits.map((h) => h.rawMatch);
27963
28797
  return hits.map((h) => {
@@ -27984,9 +28818,9 @@ function buildJoinEntries(hits) {
27984
28818
  });
27985
28819
  }
27986
28820
 
27987
- // src/triage/resolve.ts
27988
- function resolveSuppressions(rec, join16) {
27989
- const byId = new Map(join16.map((e) => [e.id, e]));
28821
+ // ../../packages/setup-wizard/src/triage/resolve.ts
28822
+ function resolveSuppressions(rec, join18) {
28823
+ const byId = new Map(join18.map((e) => [e.id, e]));
27990
28824
  const entries = [];
27991
28825
  const skipped = [];
27992
28826
  for (const cat of rec.perCategory) {
@@ -28024,7 +28858,7 @@ function resolveSuppressions(rec, join16) {
28024
28858
  return { entries, skipped };
28025
28859
  }
28026
28860
 
28027
- // src/triage/writeback.ts
28861
+ // ../../packages/setup-wizard/src/triage/writeback.ts
28028
28862
  var SCRUBBED_NOTES = "[notes withheld: model text referenced a raw detected value]";
28029
28863
  var TRIAGE_STATUSES = ["complete", "complete:no-history", "skipped:no-consent"];
28030
28864
  function isSentinel(v) {
@@ -28088,7 +28922,7 @@ function parseTriageStream(text) {
28088
28922
  return { hits, status: "complete" };
28089
28923
  }
28090
28924
  function planTriageWriteback(hits, rec) {
28091
- const join16 = buildJoinEntries(hits);
28925
+ const join18 = buildJoinEntries(hits);
28092
28926
  const rawValues = hits.map((h) => h.rawMatch);
28093
28927
  const skipped = [];
28094
28928
  const posture = {};
@@ -28128,7 +28962,7 @@ function planTriageWriteback(hits, rec) {
28128
28962
  }
28129
28963
  const { entries, skipped: resolveSkips } = resolveSuppressions(
28130
28964
  { perCategory: safeCategories, notes: rec.notes },
28131
- join16
28965
+ join18
28132
28966
  );
28133
28967
  skipped.push(...resolveSkips);
28134
28968
  let notes = rec.notes;
@@ -28138,7 +28972,7 @@ function planTriageWriteback(hits, rec) {
28138
28972
  if (err instanceof RawEgressError) notes = SCRUBBED_NOTES;
28139
28973
  else throw err;
28140
28974
  }
28141
- return { entries, posture, showcase, join: join16, notes, skipped };
28975
+ return { entries, posture, showcase, join: join18, notes, skipped };
28142
28976
  }
28143
28977
  function recommendedPosture(evidence) {
28144
28978
  return { ...severityFloorPosture(), ...evidence };
@@ -28158,7 +28992,7 @@ async function performTriageWriteback(plan, writers, opts) {
28158
28992
  return { written, skippedDuplicate, categoriesWritten: tuned.size };
28159
28993
  }
28160
28994
 
28161
- // src/triage/adapter.ts
28995
+ // ../../packages/setup-wizard/src/triage/adapter.ts
28162
28996
  var DEFAULT_PLAN_IO = {
28163
28997
  write: writePlanFile,
28164
28998
  read: readPlanFile,
@@ -28184,28 +29018,31 @@ async function runApply(deps) {
28184
29018
  return confirmed ? runConfirm(deps, planIO) : runPreview(deps, planIO);
28185
29019
  }
28186
29020
  function runPreview(deps, planIO) {
29021
+ const { present } = deps;
28187
29022
  const streamPath = getFlag(deps.argv, "stream");
28188
29023
  const streamText = deps.readStream(streamPath === "" ? void 0 : streamPath);
28189
29024
  const { hits, status } = parseTriageStream(streamText);
28190
29025
  if (hits.length === 0) {
28191
29026
  if (status === "complete") {
28192
- const empty = frameEmptyState("scan-clean", severityFloorPosture());
28193
- deps.stdout(show(fenced(empty.copy)));
28194
- deps.stdout(frameJsonBlock(empty.frame));
29027
+ const empty = present.frameEmptyState("scan-clean", severityFloorPosture());
29028
+ deps.stdout(present.show(present.fenced(empty.copy)));
29029
+ deps.stdout(present.frameJsonBlock(empty.frame));
28195
29030
  return 0;
28196
29031
  }
28197
29032
  if (status === "complete:no-history") {
28198
- const empty = frameEmptyState("no-history", severityFloorPosture());
28199
- deps.stdout(show(fenced(empty.copy)));
28200
- deps.stdout(frameJsonBlock(empty.frame));
29033
+ const empty = present.frameEmptyState("no-history", severityFloorPosture());
29034
+ deps.stdout(present.show(present.fenced(empty.copy)));
29035
+ deps.stdout(present.frameJsonBlock(empty.frame));
28201
29036
  return 0;
28202
29037
  }
28203
- deps.stdout(show("I didn't review anything \u2014 historical access wasn't granted."));
29038
+ deps.stdout(present.show("I didn't review anything \u2014 historical access wasn't granted."));
28204
29039
  return 0;
28205
29040
  }
28206
29041
  if (!deps.modelJudgeConsent()) {
28207
- deps.stdout(show("I didn't send anything to the model \u2014 model-judge consent wasn't granted."));
28208
- deps.stdout(frameJsonBlock(zeroCountFrame(severityFloorPosture())));
29042
+ deps.stdout(
29043
+ present.show("I didn't send anything to the model \u2014 model-judge consent wasn't granted.")
29044
+ );
29045
+ deps.stdout(present.frameJsonBlock(present.zeroCountFrame(severityFloorPosture())));
28209
29046
  return 0;
28210
29047
  }
28211
29048
  const rawValues = hits.map((h) => h.rawMatch);
@@ -28214,7 +29051,7 @@ function runPreview(deps, planIO) {
28214
29051
  const chunks = chunkForJudge(reps, resolveMaxJudgeBytes(deps));
28215
29052
  if (chunks.length > 1) {
28216
29053
  deps.stdout(
28217
- show(
29054
+ present.show(
28218
29055
  `Reviewing ${String(reps.length)} distinct values in ${String(chunks.length)} batches \u2014 this is the large-history path, so give it a moment.`
28219
29056
  )
28220
29057
  );
@@ -28245,7 +29082,7 @@ function runPreview(deps, planIO) {
28245
29082
  }
28246
29083
  const gate = [];
28247
29084
  if (storeUnavailable) {
28248
- gate.push(STORE_UNAVAILABLE_NOTE);
29085
+ gate.push(present.storeUnavailableNote);
28249
29086
  }
28250
29087
  gate.push(renderPosturePlan(plan.posture, storeUnavailable ? {} : current));
28251
29088
  gate.push(renderShowcase(plan.showcase));
@@ -28267,11 +29104,11 @@ function runPreview(deps, planIO) {
28267
29104
  };
28268
29105
  const maskedFindings = deriveSurfacedSecretFindings(hits, rec, plan);
28269
29106
  const falsePositivePatterns = deriveFalsePositivePatterns(reps, rec, plan);
28270
- const calibration = frameCalibration(preview, maskedFindings, falsePositivePatterns);
29107
+ const calibration = present.frameCalibration(preview, maskedFindings, falsePositivePatterns);
28271
29108
  gate.push(calibration.copy);
28272
- gate.push(renderRecommendedPosture(preview.posture));
28273
- deps.stdout(show(fenced(gate.join("\n\n"))));
28274
- deps.stdout(frameJsonBlock(calibration.frame));
29109
+ gate.push(present.renderRecommendedPosture(preview.posture));
29110
+ deps.stdout(present.show(present.fenced(gate.join("\n\n"))));
29111
+ deps.stdout(present.frameJsonBlock(calibration.frame));
28275
29112
  const planPath = planIO.write(plan, storeUnavailable ? {} : current, rawValues);
28276
29113
  deps.stdout(`
28277
29114
  Plan saved to: ${planPath}
@@ -28328,7 +29165,7 @@ async function runConfirm(deps, planIO) {
28328
29165
  if (drifted.length > 0) {
28329
29166
  closeOnce();
28330
29167
  deps.stderr(
28331
- `AKA apply-suppressions failed: the detection store changed since this plan was previewed (${drifted.join(", ")}). Refusing to apply a stale plan \u2014 re-run /aka:setup to review against the current store.
29168
+ `AKA apply-suppressions failed: the detection store changed since this plan was previewed (${drifted.join(", ")}). Refusing to apply a stale plan \u2014 re-run ${deps.present.rerunHint} to review against the current store.
28332
29169
  `
28333
29170
  );
28334
29171
  return 1;
@@ -28378,7 +29215,7 @@ async function runConfirm(deps, planIO) {
28378
29215
  }
28379
29216
  try {
28380
29217
  deps.stdout(
28381
- show(renderApplied(res.categoriesWritten, res.written, readRegisteredCommands()))
29218
+ deps.present.show(deps.present.renderApplied(res.categoriesWritten, res.written))
28382
29219
  );
28383
29220
  } catch {
28384
29221
  }
@@ -28393,14 +29230,7 @@ async function runConfirm(deps, planIO) {
28393
29230
  }
28394
29231
  }
28395
29232
 
28396
- // src/triage/judge.ts
28397
- import { execFileSync as execFileSync2 } from "child_process";
28398
- import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync9, rmSync as rmSync5 } from "fs";
28399
- import { tmpdir as tmpdir2 } from "os";
28400
- import { dirname as dirname4, join as join14 } from "path";
28401
- import { fileURLToPath as fileURLToPath2 } from "url";
28402
-
28403
- // src/triage/parse-verdict.ts
29233
+ // ../../packages/setup-wizard/src/triage/parse-verdict.ts
28404
29234
  var FENCE_RE = /```json\s*([\s\S]*?)```/g;
28405
29235
  function parseRecommendation(text) {
28406
29236
  const fences = [...text.matchAll(FENCE_RE)];
@@ -28410,8 +29240,23 @@ function parseRecommendation(text) {
28410
29240
  }
28411
29241
 
28412
29242
  // src/triage/judge.ts
28413
- var TRIAGE_DIR = dirname4(fileURLToPath2(import.meta.url));
28414
- var DEFAULT_RUBRIC_PATH = join14(TRIAGE_DIR, "..", "..", "eval", "prompt.md");
29243
+ import { execFileSync as execFileSync2 } from "child_process";
29244
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync10, rmSync as rmSync6 } from "fs";
29245
+ import { tmpdir as tmpdir2 } from "os";
29246
+ import { dirname as dirname5, join as join16 } from "path";
29247
+ import { fileURLToPath as fileURLToPath2 } from "url";
29248
+ var TRIAGE_DIR = dirname5(fileURLToPath2(import.meta.url));
29249
+ var DEFAULT_RUBRIC_PATH = join16(
29250
+ TRIAGE_DIR,
29251
+ "..",
29252
+ "..",
29253
+ "..",
29254
+ "..",
29255
+ "packages",
29256
+ "setup-wizard",
29257
+ "assets",
29258
+ "triage-rubric.md"
29259
+ );
28415
29260
  function parseVerdict(stdout) {
28416
29261
  let envelope;
28417
29262
  try {
@@ -28438,7 +29283,7 @@ function judgeEnv(platform2 = process.platform) {
28438
29283
  CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
28439
29284
  };
28440
29285
  if (platform2 === "darwin") {
28441
- env.CLAUDE_CONFIG_DIR = mkdtempSync2(join14(tmpdir2(), "aka-judge-cfg-"));
29286
+ env.CLAUDE_CONFIG_DIR = mkdtempSync2(join16(tmpdir2(), "aka-judge-cfg-"));
28442
29287
  }
28443
29288
  return env;
28444
29289
  }
@@ -28470,7 +29315,7 @@ function runJudge(hits, deps) {
28470
29315
  if (typeof deps.spawn !== "function") {
28471
29316
  throw new TypeError("runJudge requires deps.spawn \u2014 there is no live-spawn fallback");
28472
29317
  }
28473
- const rubric = deps.loadRubric?.() ?? readFileSync9(DEFAULT_RUBRIC_PATH, "utf8");
29318
+ const rubric = deps.loadRubric?.() ?? readFileSync10(DEFAULT_RUBRIC_PATH, "utf8");
28474
29319
  const hitsJsonl = hits.map((h) => JSON.stringify(toJudgePayload(h))).join("\n");
28475
29320
  const fullPrompt = `${rubric}
28476
29321
 
@@ -28494,13 +29339,237 @@ ${hitsJsonl}
28494
29339
  } finally {
28495
29340
  if (platform2 === "darwin" && env.CLAUDE_CONFIG_DIR) {
28496
29341
  try {
28497
- rmSync5(env.CLAUDE_CONFIG_DIR, { recursive: true, force: true });
29342
+ rmSync6(env.CLAUDE_CONFIG_DIR, { recursive: true, force: true });
28498
29343
  } catch {
28499
29344
  }
28500
29345
  }
28501
29346
  }
28502
29347
  }
28503
29348
 
29349
+ // src/command-registry.ts
29350
+ import { readdirSync as readdirSync5 } from "fs";
29351
+ import { fileURLToPath as fileURLToPath3 } from "url";
29352
+ var COMMAND_NAMESPACE = "aka";
29353
+ var COMMANDS_DIR = fileURLToPath3(new URL("../commands", import.meta.url));
29354
+ function readRegisteredCommands() {
29355
+ return readdirSync5(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
29356
+ }
29357
+ function selectRegisteredCommands(curated, registry2) {
29358
+ const registered = new Set(registry2);
29359
+ const missing = curated.filter((c) => !registered.has(c));
29360
+ if (missing.length > 0) {
29361
+ throw new Error(
29362
+ `Curated command(s) not registered in the installed plugin: ${missing.join(", ")}`
29363
+ );
29364
+ }
29365
+ return [...curated];
29366
+ }
29367
+
29368
+ // src/setup-frame-json.ts
29369
+ var FRAME_JSON_BEGIN = "<<<AKA_FRAME_JSON";
29370
+ var FRAME_JSON_END = "AKA_FRAME_JSON>>>";
29371
+ function frameJsonBlock(payload) {
29372
+ return `${FRAME_JSON_BEGIN}
29373
+ ${JSON.stringify(payload)}
29374
+ ${FRAME_JSON_END}
29375
+ `;
29376
+ }
29377
+
29378
+ // src/setup-show.ts
29379
+ var SHOW_BEGIN = "<<<AKA_SHOW";
29380
+ var SHOW_END = "AKA_SHOW>>>";
29381
+ function showBlock(body) {
29382
+ return `${SHOW_BEGIN}
29383
+ ${body}
29384
+ ${SHOW_END}
29385
+ `;
29386
+ }
29387
+
29388
+ // src/present.ts
29389
+ var SHADE = {
29390
+ light: "\u2591",
29391
+ medium: "\u2592",
29392
+ dark: "\u2593",
29393
+ full: "\u2588"
29394
+ };
29395
+ var ANSI_RE = /\x1b\[[0-9;]*m/g;
29396
+ function visibleLength(text) {
29397
+ return text.replace(ANSI_RE, "").length;
29398
+ }
29399
+ var fg = (hex3) => (text) => {
29400
+ const r = Number.parseInt(hex3.slice(1, 3), 16);
29401
+ const g = Number.parseInt(hex3.slice(3, 5), 16);
29402
+ const b = Number.parseInt(hex3.slice(5, 7), 16);
29403
+ return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
29404
+ };
29405
+ var paint = {
29406
+ brand: fg("#33e6c6"),
29407
+ // --color-brand · ▸▸ AKA wordmark (accent text)
29408
+ dim: fg("#838995"),
29409
+ // --color-text-3 · separators · "/100" · the "unreviewed" label
29410
+ bold: (text) => `\x1B[1m${text}\x1B[0m`,
29411
+ // the health score number
29412
+ ok: fg("#0db15f"),
29413
+ // --color-ok · healthy ● dot
29414
+ critical: fg("#e63448"),
29415
+ // --color-sev-critical · ■ and the open-findings flag
29416
+ high: fg("#e97a0a"),
29417
+ // --color-sev-high · ■ and the mid-health dot
29418
+ medium: fg("#f7bd00"),
29419
+ // --color-sev-medium · ■
29420
+ low: fg("#0581d4")
29421
+ // --color-sev-low · ■ (azure blue, not purple)
29422
+ };
29423
+ function padEnd(text, width) {
29424
+ const pad = width - visibleLength(text);
29425
+ return pad > 0 ? text + " ".repeat(pad) : text;
29426
+ }
29427
+ function indent(text, spaces = 2) {
29428
+ const pad = " ".repeat(spaces);
29429
+ return text.split("\n").map((line) => pad + line).join("\n");
29430
+ }
29431
+ function table(headers, rows, opts = {}) {
29432
+ const gap = opts.gap ?? 3;
29433
+ const widths = headers.map(
29434
+ (h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
29435
+ );
29436
+ const sep5 = " ".repeat(gap);
29437
+ const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep5);
29438
+ const headerLine = fmt(headers.map((h) => h.toUpperCase()));
29439
+ if (opts.rowSep === true) {
29440
+ const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
29441
+ const rule = "\u2500".repeat(fullWidth);
29442
+ const body = [];
29443
+ rows.forEach((row, i) => {
29444
+ if (i > 0) body.push(rule);
29445
+ body.push(fmt(row));
29446
+ });
29447
+ return [headerLine, rule, ...body].join("\n");
29448
+ }
29449
+ const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep5);
29450
+ return [headerLine, ruleLine, ...rows.map(fmt)].join("\n");
29451
+ }
29452
+ function fenced(body) {
29453
+ const longestRun = Math.max(0, ...[...body.matchAll(/`+/g)].map((m) => m[0].length));
29454
+ const fence = "`".repeat(Math.max(3, longestRun + 1));
29455
+ return [fence, body, fence].join("\n");
29456
+ }
29457
+ function show(body) {
29458
+ return showBlock(body);
29459
+ }
29460
+
29461
+ // src/render.ts
29462
+ var STORE_UNAVAILABLE_NOTE = "I couldn't check my records just now \u2014 we can check again soon. Your Claude session keeps going, and I'll fill in as you work.";
29463
+ var SEVERITY_GLYPH = {
29464
+ critical: SHADE.full,
29465
+ high: SHADE.dark,
29466
+ medium: SHADE.medium,
29467
+ low: SHADE.light
29468
+ };
29469
+ var CATEGORY_ORDER2 = DetectionCategory.options;
29470
+ function categoryRank(category) {
29471
+ const i = CATEGORY_ORDER2.indexOf(category);
29472
+ return i === -1 ? CATEGORY_ORDER2.length : i;
29473
+ }
29474
+ function renderRecommendedPosture(posture) {
29475
+ const rows = Object.keys(posture).map((category) => ({
29476
+ category,
29477
+ level: posture[category] ?? ""
29478
+ }));
29479
+ const width = Math.max(0, ...rows.map((r) => r.category.length));
29480
+ return rows.sort((a, b) => categoryRank(a.category) - categoryRank(b.category)).map((r) => ` ${r.category.padEnd(width)} ${r.level}`).join("\n");
29481
+ }
29482
+ var GRID_MARK = "\u25CF";
29483
+ function renderPostureGrid(posture) {
29484
+ const packs2 = Object.keys(posture).sort(
29485
+ (a, b) => categoryRank(a) - categoryRank(b)
29486
+ );
29487
+ const rows = packs2.map((category) => [
29488
+ category,
29489
+ ...BUILTIN_ORDER.map((level) => posture[category] === level ? GRID_MARK : "")
29490
+ ]);
29491
+ return indent(table(["Category", ...BUILTIN_ORDER], rows));
29492
+ }
29493
+ var READY_COMMANDS = ["/aka:health", "/aka:findings", "/aka:recommend"];
29494
+ function renderCategoriesTuned(categoriesTuned) {
29495
+ return `\u2713 Set all ${String(categoriesTuned)} detection categories`;
29496
+ }
29497
+ function renderApplied(categoriesTuned, dismissed, registry2) {
29498
+ const routine = dismissed > 0 ? `set aside ${String(dismissed)} routine result${dismissed === 1 ? "" : "s"}` : "nothing routine to set aside";
29499
+ const ready = `Ready: ${selectRegisteredCommands(READY_COMMANDS, registry2).join(" \xB7 ")}`;
29500
+ return `${renderCategoriesTuned(categoriesTuned)} \xB7 ${routine} \xB7 ${ready}`;
29501
+ }
29502
+
29503
+ // src/calibration.ts
29504
+ var SURFACED_KIND_LABEL = {
29505
+ secret: "live keys",
29506
+ pii: "personal data",
29507
+ financial: "financial records",
29508
+ phi: "health records",
29509
+ code_context: "source context",
29510
+ code_flaw: "code flaws",
29511
+ custom: "custom matches",
29512
+ config: "configuration secrets"
29513
+ };
29514
+ function frameCalibration(preview, maskedFindings = [], falsePositivePatterns = []) {
29515
+ const important = preview.categories.reduce((n, c) => n + c.genuineCount, 0);
29516
+ const routine = preview.categories.reduce((n, c) => n + c.fpCount, 0);
29517
+ const total = important + routine;
29518
+ const surfacedCategories = preview.categories.filter((c) => c.genuineCount > 0).map((c) => c.category);
29519
+ const routineCategories = preview.categories.filter((c) => c.fpCount > 0).map((c) => c.category);
29520
+ const findingKinds = preview.categories.filter((c) => c.genuineCount + c.fpCount > 0).map((c) => ({ category: c.category, count: c.genuineCount + c.fpCount, egress: c.egress }));
29521
+ const frame = {
29522
+ counts: { total, important, routine },
29523
+ routineCategories,
29524
+ surfacedCategories,
29525
+ findingKinds,
29526
+ posture: preview.posture,
29527
+ ...maskedFindings.length > 0 ? { maskedFindings: [...maskedFindings] } : {},
29528
+ ...falsePositivePatterns.length > 0 ? { falsePositivePatterns: [...falsePositivePatterns] } : {}
29529
+ };
29530
+ const kind = surfacedCategories.map((c) => SURFACED_KIND_LABEL[c]).join(", ");
29531
+ const parenthetical = kind ? ` (${kind})` : "";
29532
+ const headline = `I went through Claude's recent work \u2014 ${String(total)} detection${total === 1 ? "" : "s"}, ${String(important)} result${important === 1 ? "" : "s"} worth a look.${parenthetical}`;
29533
+ const copy = headline;
29534
+ return { frame, copy };
29535
+ }
29536
+ var SCAN_CLEAN_HEADLINE = "I looked over Claude's recent work \u2014 nothing needs your attention right now. You're starting clean; here's what I'd recommend:";
29537
+ var NO_HISTORY_HEADLINE = "Nothing to learn from yet \u2014 Claude hasn't left any work on this machine. I'll start each detection category at a careful default:";
29538
+ function zeroCountFrame(posture) {
29539
+ return {
29540
+ counts: { total: 0, important: 0, routine: 0 },
29541
+ routineCategories: [],
29542
+ surfacedCategories: [],
29543
+ findingKinds: [],
29544
+ posture
29545
+ };
29546
+ }
29547
+ function frameEmptyState(cause, posture) {
29548
+ const frame = zeroCountFrame(posture);
29549
+ const copy = cause === "scan-clean" ? `${SCAN_CLEAN_HEADLINE}
29550
+ ${renderRecommendedPosture(posture)}` : `${NO_HISTORY_HEADLINE}
29551
+ ${renderPostureGrid(posture)}`;
29552
+ return { frame, copy };
29553
+ }
29554
+
29555
+ // src/triage/presenter.ts
29556
+ var adapterPresenter = {
29557
+ show,
29558
+ fenced,
29559
+ frameJsonBlock,
29560
+ frameEmptyState,
29561
+ frameCalibration,
29562
+ zeroCountFrame,
29563
+ renderRecommendedPosture,
29564
+ // The applied card's Ready line resolves against the installed command
29565
+ // registry — closed over here so the shared core never reads it.
29566
+ renderApplied: (categoriesTuned, dismissed) => renderApplied(categoriesTuned, dismissed, readRegisteredCommands()),
29567
+ storeUnavailableNote: STORE_UNAVAILABLE_NOTE,
29568
+ // The stale-plan refusal tells the user to restart the wizard by its
29569
+ // Claude Code command name.
29570
+ rerunHint: "/aka:setup"
29571
+ };
29572
+
28504
29573
  // src/apply-suppressions.ts
28505
29574
  function fail(message) {
28506
29575
  process.stderr.write(`AKA apply-suppressions failed: ${message}
@@ -28515,10 +29584,13 @@ function resolveCreatedBy() {
28515
29584
  }
28516
29585
  }
28517
29586
  function loadRubric() {
28518
- const here = dirname5(fileURLToPath3(import.meta.url));
28519
- const shipped = join15(here, "triage-rubric.md");
28520
- if (existsSync7(shipped)) return readFileSync10(shipped, "utf8");
28521
- return readFileSync10(join15(here, "..", "eval", "prompt.md"), "utf8");
29587
+ const here = dirname6(fileURLToPath4(import.meta.url));
29588
+ const shipped = join17(here, "triage-rubric.md");
29589
+ if (existsSync9(shipped)) return readFileSync11(shipped, "utf8");
29590
+ return readFileSync11(
29591
+ join17(here, "..", "..", "..", "packages", "setup-wizard", "assets", "triage-rubric.md"),
29592
+ "utf8"
29593
+ );
28522
29594
  }
28523
29595
  async function main() {
28524
29596
  const argv = process.argv.slice(2);
@@ -28526,7 +29598,7 @@ async function main() {
28526
29598
  argv,
28527
29599
  // fd 0 = stdin; the wizard pipes `backfill.js --triage` into this on preview.
28528
29600
  // Called only on the preview path — the confirm path never reads a stream.
28529
- readStream: (streamPath) => streamPath !== void 0 ? readFileSync10(streamPath, "utf8") : readFileSync10(0, "utf8"),
29601
+ readStream: (streamPath) => streamPath !== void 0 ? readFileSync11(streamPath, "utf8") : readFileSync11(0, "utf8"),
28530
29602
  runJudge: (hits) => runJudge(hits, { spawn: spawnClaude, loadRubric }),
28531
29603
  // The distinct model-judge egress consent, read from settings.json. When it
28532
29604
  // is absent or stale the preview skips the judge instead of sending findings
@@ -28548,6 +29620,7 @@ async function main() {
28548
29620
  now: () => Date.now(),
28549
29621
  createdBy: resolveCreatedBy,
28550
29622
  stdout: (s) => process.stdout.write(s),
29623
+ present: adapterPresenter,
28551
29624
  stderr: (s) => process.stderr.write(s)
28552
29625
  });
28553
29626
  process.exit(code);