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

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,16 +492,15 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // src/hooks/session-start.ts
495
- import { readFileSync as readFileSync10 } from "fs";
495
+ import { readFileSync as readFileSync11 } from "fs";
496
496
 
497
497
  // ../../packages/plugin-sdk/src/config.ts
498
- import { existsSync as existsSync4 } from "fs";
499
- import { join as join7 } from "path";
498
+ import { existsSync as existsSync5 } from "fs";
499
+ import { join as join8 } from "path";
500
500
 
501
501
  // ../../packages/persistence/src/database.ts
502
- import { randomUUID as randomUUID9 } from "crypto";
503
- import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
504
- import { join, sep } from "path";
502
+ import { randomUUID as randomUUID10 } from "crypto";
503
+ import { join as join2, sep } from "path";
505
504
  import { DatabaseSync } from "node:sqlite";
506
505
 
507
506
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
@@ -581,6 +580,14 @@ var SQLITE_MIGRATIONS = [
581
580
  {
582
581
  tag: "0018_serious_tana_nile",
583
582
  sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
583
+ },
584
+ {
585
+ tag: "0019_audit_started_at_index",
586
+ sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
587
+ },
588
+ {
589
+ tag: "0020_secret_vault_pagination_indexes",
590
+ 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');"
584
591
  }
585
592
  ];
586
593
 
@@ -15318,7 +15325,17 @@ var Finding = external_exports.object({
15318
15325
  }).meta({ id: "Finding" });
15319
15326
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15320
15327
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15321
- var FindingProvider = external_exports.enum(["claudecode", "claudedesktop", "cursor", "copilot", "chatgpt", "api"]).meta({ id: "FindingProvider" });
15328
+ var FindingProvider = external_exports.enum([
15329
+ "claudecode",
15330
+ "claudedesktop",
15331
+ "cursor",
15332
+ "copilot",
15333
+ "chatgpt",
15334
+ "claudeai",
15335
+ "codex",
15336
+ "antigravity",
15337
+ "api"
15338
+ ]).meta({ id: "FindingProvider" });
15322
15339
  var FindingCategory = external_exports.enum([
15323
15340
  "secret",
15324
15341
  "pii",
@@ -15372,7 +15389,16 @@ var FindingInstance = external_exports.object({
15372
15389
  confidence: external_exports.number().min(0).max(1),
15373
15390
  // Lifecycle status (see FindingStatus). Optional so legacy callers/rows
15374
15391
  // that predate the resolution feature stay valid.
15375
- status: FindingStatus.optional()
15392
+ status: FindingStatus.optional(),
15393
+ // The audit event this finding was captured from. Optional so callers that
15394
+ // do not project it stay valid. An at-rest finding is content-addressed by
15395
+ // finding_key and its row is upserted on re-detection, so this names the
15396
+ // MOST RECENT detection event, not the first.
15397
+ eventId: external_exports.string().optional(),
15398
+ // The session that event belongs to, when it has one — the seam a
15399
+ // per-instance "view session" link needs. Absent for events captured
15400
+ // outside a session.
15401
+ sessionId: external_exports.string().optional()
15376
15402
  }).meta({ id: "FindingInstance" });
15377
15403
  var FindingGroup = external_exports.object({
15378
15404
  id: external_exports.string(),
@@ -15416,7 +15442,11 @@ var FindingFacets = external_exports.object({
15416
15442
  // for every instance, so every group lands in a bucket; a status-less
15417
15443
  // group (possible only for callers whose rows carry no statuses) is
15418
15444
  // counted under no value.
15419
- status: external_exports.array(FindingFacetItem)
15445
+ status: external_exports.array(FindingFacetItem),
15446
+ // Host tool (attributes.tool_name). Present only on the instance-level
15447
+ // reads, which can filter by it; the grouped read omits the dimension
15448
+ // because a group spans tools.
15449
+ tool: external_exports.array(FindingFacetItem).optional()
15420
15450
  }).meta({ id: "FindingFacets" });
15421
15451
  var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
15422
15452
  var ListGroupedFindingsQuery = external_exports.object({
@@ -15434,6 +15464,16 @@ var ListGroupedFindingsQuery = external_exports.object({
15434
15464
  // Scope to findings whose event carries this session id (the Activity page's
15435
15465
  // session → findings drilldown). Findings without a session never match.
15436
15466
  sessionId: external_exports.string().optional(),
15467
+ // Inclusive lower bound on the parent event's timestamp, so a caller arriving
15468
+ // from a time-scoped page (Activity's range) can carry that scope. Absent
15469
+ // means all time — this list has no default window.
15470
+ from: external_exports.iso.datetime().optional(),
15471
+ // A group or instance id that must appear in the page even when the cursor
15472
+ // has already advanced past its sort position. This is what keeps the
15473
+ // Findings page's one-shot ?finding= deep link resolving once the list
15474
+ // paginates: the target group is appended out of sort order rather than
15475
+ // scanning forward for it. Never affects totals, facets or the cursor.
15476
+ includeId: external_exports.string().optional(),
15437
15477
  groupBy: external_exports.literal("type").optional(),
15438
15478
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
15439
15479
  cursor: external_exports.string().optional()
@@ -15478,15 +15518,110 @@ var FindingInstanceDetail = FindingInstance.extend({
15478
15518
  detection: FindingDetectionRef,
15479
15519
  policy: FindingPolicyRef
15480
15520
  }).meta({ id: "FindingInstanceDetail" });
15521
+ var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15522
+ var ListFindingInstancesQuery = external_exports.object({
15523
+ severity: external_exports.array(Severity).optional(),
15524
+ // Rule ids, the same vocabulary the grouped list's `subtype` carries.
15525
+ subtype: external_exports.array(external_exports.string()).optional(),
15526
+ provider: external_exports.array(FindingProvider).optional(),
15527
+ action: external_exports.array(FindingAction).optional(),
15528
+ // Matches each instance's OWN derived status (deriveFindingStatus), unlike
15529
+ // the grouped query's group-level fold.
15530
+ status: external_exports.array(FindingStatus).optional(),
15531
+ // Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
15532
+ // where the free-text `q` can only match the rendered "via Bash" label.
15533
+ tool: external_exports.array(external_exports.string()).optional(),
15534
+ // Exact repository / file-path matches, for the drill-down out of the
15535
+ // locations view. A row whose event carries no repo/file matches neither.
15536
+ repo: external_exports.string().optional(),
15537
+ file: external_exports.string().optional(),
15538
+ q: external_exports.string().optional(),
15539
+ sessionId: external_exports.string().optional(),
15540
+ from: external_exports.iso.datetime().optional(),
15541
+ limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15542
+ cursor: external_exports.string().optional()
15543
+ });
15544
+ var ListFindingInstancesResponse = external_exports.object({
15545
+ // Instances matching the filters across the whole scope, not just this
15546
+ // page — cursor-independent, like the grouped list's totals.
15547
+ totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
15548
+ // Counts in INSTANCES here, where the grouped response counts groups. Each
15549
+ // dimension still excludes its own filter.
15550
+ facets: FindingFacets,
15551
+ items: external_exports.array(FindingInstanceDetail),
15552
+ nextCursor: external_exports.string().nullable()
15553
+ }).meta({ id: "ListFindingInstancesResponse" });
15554
+ var FindingLocationFile = external_exports.object({
15555
+ // Empty when the instances carried no file path (a prompt or a tool call
15556
+ // with no file attribution).
15557
+ file: external_exports.string(),
15558
+ instanceCount: external_exports.number().int().nonnegative(),
15559
+ maxSeverity: Severity,
15560
+ latestDetectedAt: external_exports.iso.datetime(),
15561
+ // Folded from the instances' derived statuses with the same
15562
+ // open-dominates precedence a group uses.
15563
+ status: FindingStatus.optional(),
15564
+ // Distinct rules seen at this location, capped — the row shows them as
15565
+ // chips, and the count is what conveys scale.
15566
+ ruleIds: external_exports.array(external_exports.string())
15567
+ }).meta({ id: "FindingLocationFile" });
15568
+ var FindingLocationRepo = external_exports.object({
15569
+ /** Empty when the instances carried no repo attribute. */
15570
+ repo: external_exports.string(),
15571
+ instanceCount: external_exports.number().int().nonnegative(),
15572
+ maxSeverity: Severity,
15573
+ latestDetectedAt: external_exports.iso.datetime(),
15574
+ status: FindingStatus.optional(),
15575
+ files: external_exports.array(FindingLocationFile)
15576
+ }).meta({ id: "FindingLocationRepo" });
15577
+ var ListFindingLocationsQuery = external_exports.object({
15578
+ severity: external_exports.array(Severity).optional(),
15579
+ subtype: external_exports.array(external_exports.string()).optional(),
15580
+ provider: external_exports.array(FindingProvider).optional(),
15581
+ action: external_exports.array(FindingAction).optional(),
15582
+ // Per-instance, as in ListFindingInstancesQuery: a location keeps the
15583
+ // instances that match, and folds its status from those.
15584
+ status: external_exports.array(FindingStatus).optional(),
15585
+ tool: external_exports.array(external_exports.string()).optional(),
15586
+ q: external_exports.string().optional(),
15587
+ sessionId: external_exports.string().optional(),
15588
+ from: external_exports.iso.datetime().optional(),
15589
+ limit: external_exports.coerce.number().int().min(1).max(500).optional()
15590
+ });
15591
+ var ListFindingLocationsResponse = external_exports.object({
15592
+ totals: external_exports.object({
15593
+ findings: external_exports.number().int().nonnegative(),
15594
+ repos: external_exports.number().int().nonnegative(),
15595
+ files: external_exports.number().int().nonnegative()
15596
+ }),
15597
+ /** Sorted by max severity, then most recent. */
15598
+ items: external_exports.array(FindingLocationRepo),
15599
+ /** Whether `limit` truncated the repo list. */
15600
+ hasMore: external_exports.boolean()
15601
+ }).meta({ id: "ListFindingLocationsResponse" });
15481
15602
 
15482
15603
  // ../../packages/schema/src/zod/harness-map.ts
15483
- var Harness = external_exports.enum(["claudecode", "cursor", "copilot", "codex", "windsurf", "claudedesktop", "chatgpt", "api"]).meta({ id: "Harness" });
15604
+ var Harness = external_exports.enum([
15605
+ "claudecode",
15606
+ "cursor",
15607
+ "copilot",
15608
+ "codex",
15609
+ "antigravity",
15610
+ "windsurf",
15611
+ "claudedesktop",
15612
+ "chatgpt",
15613
+ "claudeai",
15614
+ "api"
15615
+ ]).meta({ id: "Harness" });
15484
15616
  var TOOL_TO_HARNESS = {
15485
15617
  "claude-code": "claudecode",
15486
15618
  "claude-desktop": "claudedesktop",
15487
15619
  "github-copilot": "copilot",
15488
15620
  cursor: "cursor",
15489
- chatgpt: "chatgpt"
15621
+ chatgpt: "chatgpt",
15622
+ codex: "codex",
15623
+ antigravity: "antigravity",
15624
+ "claude-ai": "claudeai"
15490
15625
  };
15491
15626
  function harnessFromTool(tool) {
15492
15627
  return TOOL_TO_HARNESS[tool] ?? tool;
@@ -15947,7 +16082,18 @@ var ActivityOverviewResponse = external_exports.object({
15947
16082
  // ../../packages/schema/src/zod/event.ts
15948
16083
  var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
15949
16084
  var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
15950
- var SourceTool = external_exports.enum(["claude-code", "claude-desktop", "cursor", "chatgpt", "github-copilot", "cli", "unknown"]).meta({ id: "SourceTool" });
16085
+ var SourceTool = external_exports.enum([
16086
+ "claude-code",
16087
+ "claude-desktop",
16088
+ "cursor",
16089
+ "chatgpt",
16090
+ "claude-ai",
16091
+ "github-copilot",
16092
+ "codex",
16093
+ "antigravity",
16094
+ "cli",
16095
+ "unknown"
16096
+ ]).meta({ id: "SourceTool" });
15951
16097
  var EventMetadata = external_exports.object({
15952
16098
  sessionId: external_exports.string().optional(),
15953
16099
  repo: external_exports.string().optional(),
@@ -16018,7 +16164,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
16018
16164
  var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16019
16165
  var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16020
16166
  var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16021
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
16167
+ var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16022
16168
  var AccessCounts = external_exports.object({
16023
16169
  open: external_exports.number().int().nonnegative(),
16024
16170
  approved: external_exports.number().int().nonnegative(),
@@ -16287,6 +16433,7 @@ var ExceptionBundleEntry = DetectionException.pick({
16287
16433
  useCount: true,
16288
16434
  conditions: true
16289
16435
  });
16436
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16290
16437
 
16291
16438
  // ../../packages/schema/src/zod/rule.ts
16292
16439
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
@@ -17211,6 +17358,35 @@ var EgressWriteSummary = external_exports.object({
17211
17358
  droppedFiles: external_exports.array(external_exports.string()).default([])
17212
17359
  }).meta({ id: "EgressWriteSummary" });
17213
17360
 
17361
+ // ../../packages/schema/src/zod/exception-action.ts
17362
+ var confirmation = external_exports.string().optional();
17363
+ var ApproveBlockedInput = external_exports.object({
17364
+ reference: external_exports.string(),
17365
+ scope: external_exports.string(),
17366
+ reason: external_exports.string(),
17367
+ confirmation
17368
+ });
17369
+ var AddExceptionInput = external_exports.object({
17370
+ ruleId: external_exports.string(),
17371
+ value: external_exports.string(),
17372
+ scope: external_exports.string(),
17373
+ reason: external_exports.string(),
17374
+ confirmation
17375
+ });
17376
+ var GrantRevealInput = external_exports.object({
17377
+ pointer: external_exports.string(),
17378
+ scope: external_exports.string(),
17379
+ justification: external_exports.string(),
17380
+ confirmation
17381
+ });
17382
+ var RevokeExceptionInput = external_exports.object({
17383
+ id: external_exports.string(),
17384
+ reason: external_exports.string()
17385
+ });
17386
+ var RotateKeyInput = external_exports.object({
17387
+ confirmation: external_exports.string()
17388
+ });
17389
+
17214
17390
  // ../../packages/schema/src/zod/findings-group-build.ts
17215
17391
  function toApiAction(dbVal) {
17216
17392
  const map2 = {
@@ -17266,6 +17442,8 @@ function buildFindingGroups(rows, opts = {}) {
17266
17442
  repo: r.repo,
17267
17443
  file: r.file,
17268
17444
  ...r.toolName === void 0 ? {} : { toolName: r.toolName },
17445
+ ...r.eventId === void 0 ? {} : { eventId: r.eventId },
17446
+ ...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
17269
17447
  action: toApiAction(effectiveDbAction),
17270
17448
  detectedAt: r.occurredAt,
17271
17449
  confidence: r.confidence,
@@ -17397,14 +17575,17 @@ function applyFindingFilters(groups, opts) {
17397
17575
  }
17398
17576
  var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
17399
17577
  var SEVERITY_RANK = SEVERITY_ORDER;
17578
+ function compareFindingGroupOrder(a, b) {
17579
+ const rankA = SEVERITY_RANK[a.severity] ?? -1;
17580
+ const rankB = SEVERITY_RANK[b.severity] ?? -1;
17581
+ const severityDiff = rankA - rankB;
17582
+ if (severityDiff !== 0) return severityDiff;
17583
+ const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17584
+ if (recencyDiff !== 0) return recencyDiff;
17585
+ return a.id.localeCompare(b.id);
17586
+ }
17400
17587
  function sortFindingGroups(groups) {
17401
- return [...groups].sort((a, b) => {
17402
- const rankA = SEVERITY_RANK[a.severity] ?? -1;
17403
- const rankB = SEVERITY_RANK[b.severity] ?? -1;
17404
- const severityDiff = rankA - rankB;
17405
- if (severityDiff !== 0) return severityDiff;
17406
- return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17407
- });
17588
+ return [...groups].sort(compareFindingGroupOrder);
17408
17589
  }
17409
17590
  function computeFindingFacets(allGroups, opts) {
17410
17591
  const forSeverity = applyFindingFilters(allGroups, {
@@ -17460,15 +17641,158 @@ function computeFindingFacets(allGroups, opts) {
17460
17641
  for (const g of forStatus) {
17461
17642
  if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
17462
17643
  }
17463
- const toItems = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17644
+ const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17645
+ return {
17646
+ severity: toItems2(severityMap),
17647
+ provider: toItems2(providerMap),
17648
+ action: toItems2(actionMap),
17649
+ subtype: toItems2(subtypeMap),
17650
+ status: toItems2(statusMap)
17651
+ };
17652
+ }
17653
+
17654
+ // ../../packages/schema/src/zod/findings-flat-build.ts
17655
+ function rowHaystack(row) {
17656
+ return [
17657
+ row.ruleId,
17658
+ row.category,
17659
+ row.maskedMatch,
17660
+ row.repo,
17661
+ row.file,
17662
+ row.toolName ? `via ${row.toolName}` : "",
17663
+ row.id
17664
+ ].join(" ").toLowerCase();
17665
+ }
17666
+ function matchesDimension(row, opts, dimension) {
17667
+ switch (dimension) {
17668
+ case "severity":
17669
+ return !opts.severity?.length || opts.severity.includes(row.severity);
17670
+ case "subtype":
17671
+ return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
17672
+ case "providers":
17673
+ return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
17674
+ case "actions":
17675
+ return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
17676
+ case "statuses":
17677
+ return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
17678
+ case "tools":
17679
+ return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
17680
+ case "repo":
17681
+ return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
17682
+ case "file":
17683
+ return opts.file === void 0 || opts.file === "" || row.file === opts.file;
17684
+ case "q":
17685
+ return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
17686
+ }
17687
+ }
17688
+ var DIMENSIONS = [
17689
+ "severity",
17690
+ "subtype",
17691
+ "providers",
17692
+ "actions",
17693
+ "statuses",
17694
+ "tools",
17695
+ "repo",
17696
+ "file",
17697
+ "q"
17698
+ ];
17699
+ function matchesInstanceFilters(row, opts, except) {
17700
+ for (const dimension of DIMENSIONS) {
17701
+ if (dimension === except) continue;
17702
+ if (!matchesDimension(row, opts, dimension)) return false;
17703
+ }
17704
+ return true;
17705
+ }
17706
+ function toItems(counts) {
17707
+ return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
17708
+ }
17709
+ function bump(counts, value) {
17710
+ counts.set(value, (counts.get(value) ?? 0) + 1);
17711
+ }
17712
+ function createInstanceFacetAccumulator(opts) {
17713
+ const severity = /* @__PURE__ */ new Map();
17714
+ const subtype = /* @__PURE__ */ new Map();
17715
+ const provider = /* @__PURE__ */ new Map();
17716
+ const action = /* @__PURE__ */ new Map();
17717
+ const status = /* @__PURE__ */ new Map();
17718
+ const tool = /* @__PURE__ */ new Map();
17719
+ return {
17720
+ add(row) {
17721
+ if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
17722
+ if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
17723
+ if (matchesInstanceFilters(row, opts, "providers")) {
17724
+ bump(provider, toApiProvider(row.sourceTool));
17725
+ }
17726
+ if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
17727
+ if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
17728
+ bump(status, row.status);
17729
+ }
17730
+ if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
17731
+ bump(tool, row.toolName);
17732
+ }
17733
+ },
17734
+ facets: () => ({
17735
+ severity: toItems(severity),
17736
+ subtype: toItems(subtype),
17737
+ provider: toItems(provider),
17738
+ action: toItems(action),
17739
+ status: toItems(status),
17740
+ tool: toItems(tool)
17741
+ })
17742
+ };
17743
+ }
17744
+ function toInstanceDetail(row) {
17745
+ const category = toApiCategory(row.category);
17746
+ return {
17747
+ id: row.id,
17748
+ provider: toApiProvider(row.sourceTool),
17749
+ repo: row.repo,
17750
+ file: row.file,
17751
+ ...row.toolName === void 0 ? {} : { toolName: row.toolName },
17752
+ eventId: row.eventId,
17753
+ ...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
17754
+ action: toApiAction(row.actionTaken),
17755
+ detectedAt: row.occurredAt,
17756
+ confidence: row.confidence,
17757
+ ...row.status === void 0 ? {} : { status: row.status },
17758
+ groupId: row.ruleId,
17759
+ category,
17760
+ subtype: row.ruleId,
17761
+ severity: row.severity,
17762
+ match: { maskedValue: row.maskedMatch, contextPrefix: "" },
17763
+ detection: { id: row.ruleId, name: null },
17764
+ policy: { id: `category:${category}`, name: category }
17765
+ };
17766
+ }
17767
+ var SEVERITY_ORDER2 = {
17768
+ critical: 0,
17769
+ high: 1,
17770
+ medium: 2,
17771
+ low: 3
17772
+ };
17773
+ function newLocationAccumulator() {
17464
17774
  return {
17465
- severity: toItems(severityMap),
17466
- provider: toItems(providerMap),
17467
- action: toItems(actionMap),
17468
- subtype: toItems(subtypeMap),
17469
- status: toItems(statusMap)
17775
+ instanceCount: 0,
17776
+ // Sorts after every known severity, so the first row always wins the
17777
+ // comparison below rather than an unknown value pinning the location.
17778
+ maxSeverityRank: Number.MAX_SAFE_INTEGER,
17779
+ maxSeverity: "low",
17780
+ latestDetectedAt: "",
17781
+ statuses: [],
17782
+ ruleIds: /* @__PURE__ */ new Set()
17470
17783
  };
17471
17784
  }
17785
+ function addToLocation(acc, row) {
17786
+ acc.instanceCount += 1;
17787
+ const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
17788
+ if (rank < acc.maxSeverityRank) {
17789
+ acc.maxSeverityRank = rank;
17790
+ acc.maxSeverity = row.severity;
17791
+ }
17792
+ if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
17793
+ acc.statuses.push(row.status);
17794
+ acc.ruleIds.add(row.ruleId);
17795
+ }
17472
17796
 
17473
17797
  // ../../packages/schema/src/zod/installed-pack.ts
17474
17798
  var InstalledPack = external_exports.object({
@@ -17605,6 +17929,50 @@ var VaultInventoryEntry = external_exports.object({
17605
17929
  revealGrantId: external_exports.string().nullable(),
17606
17930
  sightings: external_exports.array(VaultSighting)
17607
17931
  });
17932
+ var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
17933
+ var DEFAULT_VAULT_DEREFS_LIMIT = 50;
17934
+ var MAX_VAULT_PAGE_LIMIT = 200;
17935
+ var ListVaultInventoryQuery = external_exports.object({
17936
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17937
+ // Opaque; names the last row of the page just served.
17938
+ cursor: external_exports.string().optional()
17939
+ });
17940
+ var ListVaultInventoryResponse = external_exports.object({
17941
+ // Vaulted values across the whole store, not just this page — cursor-
17942
+ // independent, so paging never changes what the count claims.
17943
+ totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
17944
+ items: external_exports.array(VaultInventoryEntry),
17945
+ // `null` once the last page is reached.
17946
+ nextCursor: external_exports.string().nullable()
17947
+ });
17948
+ var ListVaultReuseQuery = external_exports.object({
17949
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17950
+ cursor: external_exports.string().optional()
17951
+ });
17952
+ var ListVaultReuseResponse = external_exports.object({
17953
+ // Reused values across the whole store — the number the section's claim
17954
+ // ("values detected in more than one place") is about.
17955
+ totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
17956
+ items: external_exports.array(VaultInventoryEntry),
17957
+ nextCursor: external_exports.string().nullable()
17958
+ });
17959
+ var ListVaultDerefsQuery = external_exports.object({
17960
+ // Include the batched, high-volume reasons (display, view-render). Omitted
17961
+ // hides them and counts them into `hiddenBatched` instead, so the model
17962
+ // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
17963
+ // over a Server Action, which preserves the type, never as a URL param.
17964
+ includeBatched: external_exports.boolean().optional(),
17965
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17966
+ cursor: external_exports.string().optional()
17967
+ });
17968
+ var ListVaultDerefsResponse = external_exports.object({
17969
+ items: external_exports.array(VaultDeref),
17970
+ nextCursor: external_exports.string().nullable(),
17971
+ // Display/view-render rows the query hid, over the WHOLE trail rather than
17972
+ // this page — it is the count the "N hidden" line and its toggle speak for.
17973
+ // Always 0 when `includeBatched` was set, since nothing was hidden.
17974
+ hiddenBatched: external_exports.number().int().nonnegative()
17975
+ });
17608
17976
  var VaultKeyCustody = external_exports.string();
17609
17977
  var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17610
17978
  var VAULT_CONSENT_VERSION = 1;
@@ -17981,7 +18349,7 @@ var TopSourcesQuery = external_exports.object({
17981
18349
  // Omit for both kinds.
17982
18350
  kind: external_exports.enum(SOURCE_KINDS).optional()
17983
18351
  });
17984
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18352
+ var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
17985
18353
  var ScanCoverageProvider = external_exports.object({
17986
18354
  provider: Provider,
17987
18355
  // Percent of that provider's traffic scanned in the window. 0 when unsupported.
@@ -18234,6 +18602,138 @@ function captureId(sessionId, contentHash, filePath = null) {
18234
18602
  );
18235
18603
  }
18236
18604
 
18605
+ // ../../packages/persistence/src/internal/snapshot.ts
18606
+ import { randomUUID } from "crypto";
18607
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18608
+ import { basename, dirname, join } from "path";
18609
+
18610
+ // ../../packages/persistence/src/paths.ts
18611
+ import {
18612
+ chmodSync,
18613
+ linkSync,
18614
+ lstatSync,
18615
+ mkdirSync,
18616
+ renameSync,
18617
+ rmSync,
18618
+ writeFileSync
18619
+ } from "fs";
18620
+ import { threadId } from "worker_threads";
18621
+ var DATA_DIR_MODE = 448;
18622
+ var DATA_FILE_MODE = 384;
18623
+ var DB_FILENAME = "aka.db";
18624
+ function isSymlink(path) {
18625
+ try {
18626
+ return lstatSync(path).isSymbolicLink();
18627
+ } catch {
18628
+ return false;
18629
+ }
18630
+ }
18631
+ function chmodBestEffort(path, mode) {
18632
+ if (isSymlink(path)) return;
18633
+ try {
18634
+ chmodSync(path, mode);
18635
+ } catch {
18636
+ }
18637
+ }
18638
+ function tightenDir(dir) {
18639
+ chmodBestEffort(dir, DATA_DIR_MODE);
18640
+ }
18641
+ function ensureDataDirSync(dir) {
18642
+ mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18643
+ tightenDir(dir);
18644
+ }
18645
+ function dbSidecars(file2) {
18646
+ return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18647
+ }
18648
+ function tightenFile(file2) {
18649
+ chmodBestEffort(file2, DATA_FILE_MODE);
18650
+ }
18651
+ function tightenPerms(file2) {
18652
+ for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18653
+ }
18654
+
18655
+ // ../../packages/persistence/src/internal/snapshot.ts
18656
+ function backupPath(file2, tag) {
18657
+ return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18658
+ }
18659
+ var STALE_PARTIAL_MS = 5 * 6e4;
18660
+ function reapStalePartials(file2) {
18661
+ const dir = dirname(file2);
18662
+ const prefix = `${basename(file2)}.`;
18663
+ let entries;
18664
+ try {
18665
+ entries = readdirSync(dir);
18666
+ } catch {
18667
+ return;
18668
+ }
18669
+ for (const name of entries) {
18670
+ if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18671
+ const partial2 = join(dir, name);
18672
+ try {
18673
+ if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18674
+ rmSync2(partial2, { force: true });
18675
+ }
18676
+ } catch {
18677
+ }
18678
+ }
18679
+ }
18680
+ function snapshotStore(db, backup) {
18681
+ const partial2 = `${backup}.partial`;
18682
+ try {
18683
+ rmSync2(partial2, { force: true });
18684
+ db.prepare("VACUUM INTO ?").run(partial2);
18685
+ tightenFile(partial2);
18686
+ renameSync2(partial2, backup);
18687
+ } catch (error51) {
18688
+ try {
18689
+ rmSync2(partial2, { force: true });
18690
+ } catch {
18691
+ }
18692
+ throw error51;
18693
+ }
18694
+ }
18695
+ function moveStoreAside(file2, backup) {
18696
+ const undo = [];
18697
+ renameSync2(file2, backup);
18698
+ undo.push([backup, file2]);
18699
+ try {
18700
+ for (const sidecar of dbSidecars(file2)) {
18701
+ const moved = `${backup}${sidecar.slice(file2.length)}`;
18702
+ try {
18703
+ renameSync2(sidecar, moved);
18704
+ undo.push([moved, sidecar]);
18705
+ } catch {
18706
+ rmSync2(sidecar, { force: true });
18707
+ }
18708
+ }
18709
+ } catch (error51) {
18710
+ for (const [from, to] of undo.reverse()) {
18711
+ try {
18712
+ renameSync2(from, to);
18713
+ } catch {
18714
+ }
18715
+ }
18716
+ throw error51;
18717
+ }
18718
+ tightenPerms(backup);
18719
+ }
18720
+ function discardStore(file2, backup) {
18721
+ try {
18722
+ rmSync2(file2, { force: true });
18723
+ for (const sidecar of dbSidecars(file2)) {
18724
+ rmSync2(sidecar, { force: true });
18725
+ }
18726
+ } catch (error51) {
18727
+ if (existsSync(file2)) {
18728
+ try {
18729
+ rmSync2(backup, { force: true });
18730
+ } catch {
18731
+ }
18732
+ }
18733
+ throw error51;
18734
+ }
18735
+ }
18736
+
18237
18737
  // ../../packages/persistence/src/internal/sql-text.ts
18238
18738
  function escapeLikePattern(s) {
18239
18739
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18375,38 +18875,6 @@ function mapRowsTolerant(rows, map2) {
18375
18875
  return out;
18376
18876
  }
18377
18877
 
18378
- // ../../packages/persistence/src/paths.ts
18379
- import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
18380
- var DATA_DIR_MODE = 448;
18381
- var DATA_FILE_MODE = 384;
18382
- var DB_FILENAME = "aka.db";
18383
- function chmodBestEffort(path, mode) {
18384
- try {
18385
- chmodSync(path, mode);
18386
- } catch {
18387
- }
18388
- }
18389
- function tightenDir(dir) {
18390
- chmodBestEffort(dir, DATA_DIR_MODE);
18391
- }
18392
- function ensureDataDirSync(dir) {
18393
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18394
- tightenDir(dir);
18395
- }
18396
- function dbSidecars(file2) {
18397
- return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18398
- }
18399
- function tightenFile(file2) {
18400
- try {
18401
- if (lstatSync(file2).isSymbolicLink()) return;
18402
- } catch {
18403
- }
18404
- chmodBestEffort(file2, DATA_FILE_MODE);
18405
- }
18406
- function tightenPerms(file2) {
18407
- for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18408
- }
18409
-
18410
18878
  // ../../packages/persistence/src/migrations.ts
18411
18879
  function describeObject(object2) {
18412
18880
  return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
@@ -18522,9 +18990,9 @@ function applyLegacyDropMigration(db, file2) {
18522
18990
  }
18523
18991
  }
18524
18992
  function backupBeforeLegacyDrop(db, file2) {
18525
- const backup = `${file2}.pre-drop.${String(Date.now())}.bak`;
18526
- db.prepare("VACUUM INTO ?").run(backup);
18527
- tightenFile(backup);
18993
+ reapStalePartials(file2);
18994
+ const backup = backupPath(file2, "pre-drop");
18995
+ snapshotStore(db, backup);
18528
18996
  return backup;
18529
18997
  }
18530
18998
  var TOKEN_USAGE_COLUMNS = [
@@ -18868,6 +19336,25 @@ function parseJsonObject(s) {
18868
19336
  return void 0;
18869
19337
  }
18870
19338
 
19339
+ // ../../packages/persistence/src/internal/keyset-cursor.ts
19340
+ function encodeKeysetCursor(payload) {
19341
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
19342
+ }
19343
+ function decodeKeysetCursor(cursor) {
19344
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19345
+ if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19346
+ // resumes from is epoch millis, and a payload carrying ±Infinity or a
19347
+ // fraction binds cleanly rather than failing — returning an EMPTY page with
19348
+ // a null cursor, which a caller reads as "end of list". That is the one
19349
+ // outcome a cursor that does not decode must never produce, since the
19350
+ // documented behaviour above is to restart from the top. (`1e999` is valid
19351
+ // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19352
+ Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19353
+ return parsed;
19354
+ }
19355
+ return null;
19356
+ }
19357
+
18871
19358
  // ../../packages/persistence/src/repositories/activity.ts
18872
19359
  var DAY_MS = 864e5;
18873
19360
  var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
@@ -18913,16 +19400,6 @@ function utcWindow(nowMs) {
18913
19400
  const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
18914
19401
  return { startMs, endMs: startMs + DAY_MS };
18915
19402
  }
18916
- function encodeCursor(payload) {
18917
- return Buffer.from(JSON.stringify(payload)).toString("base64url");
18918
- }
18919
- function decodeCursor(cursor) {
18920
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
18921
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
18922
- return parsed;
18923
- }
18924
- return null;
18925
- }
18926
19403
  var DB_EVENT_TYPE_TO_KIND = {
18927
19404
  session: "session",
18928
19405
  prompt: "prompt",
@@ -19067,7 +19544,7 @@ var SqliteActivityRepository = class {
19067
19544
  return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
19068
19545
  }
19069
19546
  listSessions(query) {
19070
- const cursor = query.cursor ? decodeCursor(query.cursor) : null;
19547
+ const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
19071
19548
  const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
19072
19549
  const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
19073
19550
  const conditions = [SESSION_ROOT];
@@ -19141,7 +19618,7 @@ var SqliteActivityRepository = class {
19141
19618
  )
19142
19619
  );
19143
19620
  const last = page[page.length - 1];
19144
- const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19621
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19145
19622
  return Promise.resolve({ items, nextCursor, emptyCount });
19146
19623
  }
19147
19624
  getSession(sessionId) {
@@ -20014,7 +20491,7 @@ var SqliteEventsRepository = class {
20014
20491
  };
20015
20492
 
20016
20493
  // ../../packages/persistence/src/repositories/exceptions.ts
20017
- import { randomUUID } from "crypto";
20494
+ import { randomUUID as randomUUID2 } from "crypto";
20018
20495
 
20019
20496
  // ../../packages/persistence/src/internal/sqlite-errors.ts
20020
20497
  var SQLITE_CONSTRAINT_UNIQUE = 2067;
@@ -20050,8 +20527,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
20050
20527
  AND conditions IS NULL
20051
20528
  AND ${ACTIVE_PREDICATE}`;
20052
20529
  var SqliteExceptionsRepository = class {
20053
- constructor(db) {
20530
+ constructor(db, now = () => Date.now()) {
20054
20531
  this.db = db;
20532
+ this.now = now;
20055
20533
  this.consumeStmt = db.prepare(
20056
20534
  `UPDATE exceptions
20057
20535
  SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
@@ -20069,6 +20547,7 @@ var SqliteExceptionsRepository = class {
20069
20547
  );
20070
20548
  }
20071
20549
  db;
20550
+ now;
20072
20551
  consumeStmt;
20073
20552
  insertBlockedStmt;
20074
20553
  sweepBlockedStmt;
@@ -20095,8 +20574,8 @@ var SqliteExceptionsRepository = class {
20095
20574
  "provider conditions are not supported yet \u2014 a grant with one would never apply"
20096
20575
  );
20097
20576
  }
20098
- const id = randomUUID();
20099
- const now = Date.now();
20577
+ const id = randomUUID2();
20578
+ const now = this.now();
20100
20579
  try {
20101
20580
  this.insertExceptionRow(id, input, now);
20102
20581
  } catch (err) {
@@ -20174,7 +20653,7 @@ var SqliteExceptionsRepository = class {
20174
20653
  const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
20175
20654
  const rows = allRows(
20176
20655
  this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
20177
- opts?.includeTerminal ? {} : { now: Date.now() }
20656
+ opts?.includeTerminal ? {} : { now: this.now() }
20178
20657
  );
20179
20658
  const exceptions = mapRowsTolerant(rows, parseExceptionRow);
20180
20659
  return Promise.resolve(exceptions);
@@ -20209,7 +20688,7 @@ var SqliteExceptionsRepository = class {
20209
20688
  * already revoked.
20210
20689
  */
20211
20690
  revoke(id, revokedBy, reason) {
20212
- const now = Date.now();
20691
+ const now = this.now();
20213
20692
  const result = this.db.prepare(
20214
20693
  `UPDATE exceptions
20215
20694
  SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
@@ -20223,7 +20702,7 @@ var SqliteExceptionsRepository = class {
20223
20702
  * callers must treat identically — means it does not and the detection is
20224
20703
  * enforced as usual. Deliberately NOT wrapped in try/catch.
20225
20704
  */
20226
- consume(id, now = Date.now()) {
20705
+ consume(id, now = this.now()) {
20227
20706
  const result = this.consumeStmt.run({ id, now });
20228
20707
  return Promise.resolve(Number(result.changes) === 1);
20229
20708
  }
@@ -20232,7 +20711,7 @@ var SqliteExceptionsRepository = class {
20232
20711
  * version — what rides the policy bundle to the hook. Grants written under
20233
20712
  * a different (rotated-away) key never match, so they are excluded at read.
20234
20713
  */
20235
- activeBundleEntries(keyVersion, now = Date.now()) {
20714
+ activeBundleEntries(keyVersion, now = this.now()) {
20236
20715
  const rows = allRows(
20237
20716
  this.db.prepare(
20238
20717
  `SELECT * FROM exceptions
@@ -20264,7 +20743,7 @@ var SqliteExceptionsRepository = class {
20264
20743
  * than the retention window on every write, so the ledger self-limits.
20265
20744
  */
20266
20745
  recordBlocked(entry) {
20267
- const now = Date.now();
20746
+ const now = this.now();
20268
20747
  this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
20269
20748
  this.insertBlockedStmt.run({
20270
20749
  reference: entry.reference,
@@ -20287,7 +20766,7 @@ var SqliteExceptionsRepository = class {
20287
20766
  WHERE blocked_at > :cutoff
20288
20767
  ORDER BY blocked_at DESC, rowid DESC`
20289
20768
  ),
20290
- { cutoff: Date.now() - windowMs }
20769
+ { cutoff: this.now() - windowMs }
20291
20770
  );
20292
20771
  return Promise.resolve(
20293
20772
  rows.map((row) => ({
@@ -20315,8 +20794,9 @@ var SqliteExceptionsRepository = class {
20315
20794
  * evaluate conditions, and a narrowing clause that is ignored would WIDEN the
20316
20795
  * grant instead. Fail closed until reveal-side condition evaluation exists.
20317
20796
  */
20318
- activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
20797
+ activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
20319
20798
  try {
20799
+ const at = now ?? this.now();
20320
20800
  const row = getRow(
20321
20801
  this.db.prepare(
20322
20802
  `SELECT id FROM exceptions
@@ -20325,7 +20805,7 @@ var SqliteExceptionsRepository = class {
20325
20805
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
20326
20806
  LIMIT 1`
20327
20807
  ),
20328
- { ruleId, valueFingerprint, keyVersion, now }
20808
+ { ruleId, valueFingerprint, keyVersion, now: at }
20329
20809
  );
20330
20810
  return Promise.resolve(row ?? null);
20331
20811
  } catch (err) {
@@ -20339,7 +20819,7 @@ var SqliteExceptionsRepository = class {
20339
20819
  * predicate, so correctness never depends on this sweep; it only bounds how
20340
20820
  * long the audit evidence is kept locally. Returns the deleted count.
20341
20821
  */
20342
- sweepTerminal(retentionMs, now = Date.now()) {
20822
+ sweepTerminal(retentionMs, now = this.now()) {
20343
20823
  const result = this.db.prepare(
20344
20824
  `DELETE FROM exceptions
20345
20825
  WHERE updated_at < :cutoff
@@ -20402,6 +20882,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
20402
20882
 
20403
20883
  // ../../packages/persistence/src/repositories/findings.ts
20404
20884
  var PREVIEW_INSTANCES_PER_GROUP = 200;
20885
+ var SCAN_BATCH_ROWS = 1e3;
20886
+ var DEFAULT_LOCATIONS_LIMIT = 100;
20887
+ var LOCATION_RULE_IDS_CAP = 20;
20888
+ function compareLocationOrder(a, b) {
20889
+ return compareFindingGroupOrder(
20890
+ {
20891
+ severity: a.maxSeverity,
20892
+ latestDetectedAt: a.latestDetectedAt,
20893
+ id: ""
20894
+ },
20895
+ {
20896
+ severity: b.maxSeverity,
20897
+ latestDetectedAt: b.latestDetectedAt,
20898
+ id: ""
20899
+ }
20900
+ );
20901
+ }
20405
20902
  var CONCAT_SEP = ",";
20406
20903
  var TUPLE_SEP = "|";
20407
20904
  function splitConcat(value) {
@@ -20414,6 +20911,33 @@ function deriveInstanceStatus(row) {
20414
20911
  latestResolutionStatus: row.latest_status
20415
20912
  });
20416
20913
  }
20914
+ function encodeGroupCursor(group) {
20915
+ const payload = {
20916
+ sev: group.severity,
20917
+ t: group.latestDetectedAt,
20918
+ id: group.id
20919
+ };
20920
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
20921
+ }
20922
+ function decodeGroupCursor(cursor) {
20923
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
20924
+ if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
20925
+ return {
20926
+ severity: parsed.sev,
20927
+ latestDetectedAt: parsed.t,
20928
+ id: parsed.id
20929
+ };
20930
+ }
20931
+ return null;
20932
+ }
20933
+ function firstAfter(sorted, cursor) {
20934
+ const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
20935
+ return index === -1 ? sorted.length : index;
20936
+ }
20937
+ function findDeepLinked(sorted, page, id) {
20938
+ if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
20939
+ return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
20940
+ }
20417
20941
  var DAY_MS3 = 864e5;
20418
20942
  var SqliteFindingsRepository = class {
20419
20943
  constructor(db) {
@@ -20523,8 +21047,13 @@ var SqliteFindingsRepository = class {
20523
21047
  */
20524
21048
  listGroupedFindings(query) {
20525
21049
  const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
20526
- const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}`;
20527
- const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
21050
+ const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
21051
+ const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
21052
+ const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
21053
+ const sessionParams = {
21054
+ ...query.sessionId ? { sessionId: query.sessionId } : {},
21055
+ ...fromMs === void 0 ? {} : { fromMs }
21056
+ };
20528
21057
  const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
20529
21058
  predicate,
20530
21059
  params: sessionParams
@@ -20532,7 +21061,8 @@ var SqliteFindingsRepository = class {
20532
21061
  const rows = allRows(
20533
21062
  this.db.prepare(
20534
21063
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
20535
- occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
21064
+ occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
21065
+ kind, finding_key, latest_status
20536
21066
  FROM (
20537
21067
  SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
20538
21068
  d.severity AS severity, f.masked_match AS masked_match,
@@ -20542,6 +21072,7 @@ var SqliteFindingsRepository = class {
20542
21072
  json_extract(e.attributes, '$.repo') AS repo,
20543
21073
  json_extract(e.attributes, '$.file_path') AS file,
20544
21074
  json_extract(e.attributes, '$.tool_name') AS tool_name,
21075
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
20545
21076
  e.event_type AS kind, f.finding_key AS finding_key,
20546
21077
  latest.status AS latest_status,
20547
21078
  ROW_NUMBER() OVER (
@@ -20573,6 +21104,8 @@ var SqliteFindingsRepository = class {
20573
21104
  repo: r.repo ?? "",
20574
21105
  file: r.file ?? "",
20575
21106
  ...r.tool_name === null ? {} : { toolName: r.tool_name },
21107
+ eventId: r.event_id,
21108
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
20576
21109
  status: deriveInstanceStatus(r)
20577
21110
  }));
20578
21111
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -20596,18 +21129,23 @@ var SqliteFindingsRepository = class {
20596
21129
  groups: sorted.length
20597
21130
  };
20598
21131
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
21132
+ const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
21133
+ const start = cursor === null ? 0 : firstAfter(sorted, cursor);
21134
+ const page = sorted.slice(start, start + limit);
21135
+ const lastOnPage = page.at(-1);
21136
+ const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
21137
+ const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
20599
21138
  const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
20600
- const items = sorted.slice(0, limit).map(
20601
- (g) => statusSet ? {
20602
- ...g,
20603
- instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
20604
- } : g
20605
- );
21139
+ const narrow = (g) => statusSet ? {
21140
+ ...g,
21141
+ instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
21142
+ } : g;
21143
+ const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
20606
21144
  return Promise.resolve({
20607
21145
  totals,
20608
21146
  facets,
20609
21147
  items,
20610
- nextCursor: null,
21148
+ nextCursor,
20611
21149
  ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
20612
21150
  });
20613
21151
  }
@@ -20639,6 +21177,266 @@ var SqliteFindingsRepository = class {
20639
21177
  * request actually carries a `q`. (Substring matching is unaffected by a
20640
21178
  * path repeating across tuples.)
20641
21179
  */
21180
+ /**
21181
+ * The instance-level (flat) findings list: one row per finding, newest first,
21182
+ * paged by keyset.
21183
+ *
21184
+ * SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
21185
+ * bound are SQL predicates: nothing counts them, so narrowing the scan by
21186
+ * them changes no reported number. Severity, subtype, provider, action,
21187
+ * status, tool, repo, file and `q` all stay in JS — each has a facet, and a
21188
+ * facet excludes its own filter, so a row the filter rejects still has to be
21189
+ * counted. Pushing any of them into SQL would silently empty its own facet.
21190
+ * Several could not be expressed there anyway: status comes from the one
21191
+ * shared classifier (deriveFindingStatus), and provider 'api' means "a tool
21192
+ * none of the mappers names", which no IN-list can say.
21193
+ *
21194
+ * The scan runs from the top of the scope on every request, not from the
21195
+ * cursor: `totals` and `facets` describe the whole filtered scope and must not
21196
+ * move as the caller pages. Rows are pulled in batches so memory stays flat
21197
+ * while the counting runs, and only the page itself is retained.
21198
+ */
21199
+ listFindingInstances(query) {
21200
+ const opts = {
21201
+ severity: query.severity,
21202
+ subtype: query.subtype,
21203
+ providers: query.provider,
21204
+ actions: query.action,
21205
+ statuses: query.status,
21206
+ tools: query.tool,
21207
+ repo: query.repo,
21208
+ file: query.file,
21209
+ q: query.q
21210
+ };
21211
+ const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
21212
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
21213
+ const accumulator = createInstanceFacetAccumulator(opts);
21214
+ const items = [];
21215
+ let total = 0;
21216
+ let last;
21217
+ let hasMore = false;
21218
+ for (const row of this.scanFindingRows({
21219
+ sessionId: query.sessionId,
21220
+ from: query.from
21221
+ })) {
21222
+ accumulator.add(row);
21223
+ if (!matchesInstanceFilters(row, opts)) continue;
21224
+ total += 1;
21225
+ if (items.length < limit) {
21226
+ items.push(toInstanceDetail(row));
21227
+ last = row;
21228
+ } else {
21229
+ hasMore = true;
21230
+ }
21231
+ }
21232
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
21233
+ if (cursor !== null) {
21234
+ const resumed = this.pageAfter(cursor, opts, limit, query);
21235
+ return Promise.resolve({
21236
+ totals: { findings: total },
21237
+ facets: accumulator.facets(),
21238
+ items: resumed.items,
21239
+ nextCursor: resumed.nextCursor
21240
+ });
21241
+ }
21242
+ return Promise.resolve({
21243
+ totals: { findings: total },
21244
+ facets: accumulator.facets(),
21245
+ items,
21246
+ nextCursor
21247
+ });
21248
+ }
21249
+ /**
21250
+ * The page of matching rows strictly after `cursor`. Separate from the
21251
+ * counting pass because that one starts at the top of the scope by design;
21252
+ * this one narrows the scan with the same keyset predicate the activity list
21253
+ * uses, so a later page costs less than the first rather than more.
21254
+ */
21255
+ pageAfter(cursor, opts, limit, query) {
21256
+ const items = [];
21257
+ let last;
21258
+ let hasMore = false;
21259
+ for (const row of this.scanFindingRows({
21260
+ sessionId: query.sessionId,
21261
+ from: query.from,
21262
+ after: cursor
21263
+ })) {
21264
+ if (!matchesInstanceFilters(row, opts)) continue;
21265
+ if (items.length < limit) {
21266
+ items.push(toInstanceDetail(row));
21267
+ last = row;
21268
+ } else {
21269
+ hasMore = true;
21270
+ break;
21271
+ }
21272
+ }
21273
+ return {
21274
+ items,
21275
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
21276
+ };
21277
+ }
21278
+ /**
21279
+ * The same findings folded by location: repository, then file within it.
21280
+ *
21281
+ * The grouping keys come from the capturing event's attributes, which is what
21282
+ * the local store relates a finding to — there is no finding↔asset row to
21283
+ * group by instead. A repo or file the event did not record folds into the
21284
+ * empty-string bucket, which the view renders but does not link, since no
21285
+ * filter can name it.
21286
+ */
21287
+ listFindingLocations(query) {
21288
+ const opts = {
21289
+ severity: query.severity,
21290
+ subtype: query.subtype,
21291
+ providers: query.provider,
21292
+ actions: query.action,
21293
+ statuses: query.status,
21294
+ tools: query.tool,
21295
+ q: query.q
21296
+ };
21297
+ const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
21298
+ const byRepo = /* @__PURE__ */ new Map();
21299
+ let total = 0;
21300
+ for (const row of this.scanFindingRows({
21301
+ sessionId: query.sessionId,
21302
+ from: query.from
21303
+ })) {
21304
+ if (!matchesInstanceFilters(row, opts)) continue;
21305
+ total += 1;
21306
+ let files = byRepo.get(row.repo);
21307
+ if (files === void 0) {
21308
+ files = /* @__PURE__ */ new Map();
21309
+ byRepo.set(row.repo, files);
21310
+ }
21311
+ let acc = files.get(row.file);
21312
+ if (acc === void 0) {
21313
+ acc = newLocationAccumulator();
21314
+ files.set(row.file, acc);
21315
+ }
21316
+ addToLocation(acc, row);
21317
+ }
21318
+ let fileCount = 0;
21319
+ const repos = [...byRepo.entries()].map(([repo, files]) => {
21320
+ fileCount += files.size;
21321
+ const fileRows = [...files.entries()].map(([file2, acc]) => ({
21322
+ file: file2,
21323
+ instanceCount: acc.instanceCount,
21324
+ maxSeverity: acc.maxSeverity,
21325
+ latestDetectedAt: acc.latestDetectedAt,
21326
+ ...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
21327
+ ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
21328
+ })).sort(compareLocationOrder);
21329
+ const rollup = fileRows.reduce(
21330
+ (a, f) => ({
21331
+ instanceCount: a.instanceCount + f.instanceCount,
21332
+ maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
21333
+ latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
21334
+ }),
21335
+ {
21336
+ instanceCount: 0,
21337
+ maxSeverity: fileRows[0]?.maxSeverity ?? "low",
21338
+ latestDetectedAt: ""
21339
+ }
21340
+ );
21341
+ const statuses = fileRows.map((f) => f.status);
21342
+ const folded = foldGroupStatus(statuses);
21343
+ return {
21344
+ repo,
21345
+ instanceCount: rollup.instanceCount,
21346
+ maxSeverity: rollup.maxSeverity,
21347
+ latestDetectedAt: rollup.latestDetectedAt,
21348
+ ...folded === void 0 ? {} : { status: folded },
21349
+ files: fileRows
21350
+ };
21351
+ });
21352
+ repos.sort(compareLocationOrder);
21353
+ return Promise.resolve({
21354
+ totals: { findings: total, repos: repos.length, files: fileCount },
21355
+ items: repos.slice(0, limit),
21356
+ hasMore: repos.length > limit
21357
+ });
21358
+ }
21359
+ /**
21360
+ * Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
21361
+ *
21362
+ * A generator so a caller streams the scope without it ever being an array:
21363
+ * the flat list counts and facets the whole filtered scope, which on a large
21364
+ * store is far more rows than any page. Each batch advances the same keyset
21365
+ * predicate the page read uses, so the scan is a sequence of bounded reads
21366
+ * rather than one unbounded result set.
21367
+ *
21368
+ * The latest-resolution lookup is the CORRELATED form, not the derived table
21369
+ * the grouped path joins: only `status` is needed, idx_finding_resolution_key
21370
+ * makes it a point lookup per row, and the derived table would re-materialize
21371
+ * a window over the whole resolution table once per batch.
21372
+ *
21373
+ * `scope` carries ONLY what no facet counts. A filter dimension narrowed here
21374
+ * would be missing from its own facet, which is computed by excluding that
21375
+ * dimension — see listFindingInstances.
21376
+ */
21377
+ *scanFindingRows(scope) {
21378
+ const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
21379
+ const params = [];
21380
+ if (scope.sessionId !== void 0 && scope.sessionId !== "") {
21381
+ conditions.push("e.root_session_id = ?");
21382
+ params.push(scope.sessionId);
21383
+ }
21384
+ if (scope.from !== void 0) {
21385
+ conditions.push("e.started_at >= ?");
21386
+ params.push(isoToEpochMillis(scope.from));
21387
+ }
21388
+ const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
21389
+ d.severity AS severity, f.masked_match AS masked_match,
21390
+ f.action_taken AS action_taken, f.confidence AS confidence,
21391
+ e.started_at AS occurred_at,
21392
+ json_extract(e.attributes, '$.source_tool') AS source_tool,
21393
+ json_extract(e.attributes, '$.repo') AS repo,
21394
+ json_extract(e.attributes, '$.file_path') AS file,
21395
+ json_extract(e.attributes, '$.tool_name') AS tool_name,
21396
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
21397
+ e.event_type AS kind, f.finding_key AS finding_key,
21398
+ ${latestResolutionStatusSql("f")} AS latest_status
21399
+ FROM inspection_findings f
21400
+ JOIN audit_events e ON e.id = f.audit_event_id
21401
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21402
+ WHERE ${conditions.join(" AND ")}
21403
+ AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
21404
+ ORDER BY e.started_at DESC, f.id DESC
21405
+ LIMIT ?`;
21406
+ let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
21407
+ for (; ; ) {
21408
+ const rows = allRows(this.db.prepare(sql), [
21409
+ ...params,
21410
+ after.startedAtMs,
21411
+ after.startedAtMs,
21412
+ after.id,
21413
+ SCAN_BATCH_ROWS
21414
+ ]);
21415
+ for (const r of rows) {
21416
+ yield {
21417
+ id: r.id,
21418
+ ruleId: r.rule_id,
21419
+ category: r.category,
21420
+ severity: r.severity,
21421
+ maskedMatch: r.masked_match,
21422
+ actionTaken: r.action_taken,
21423
+ confidence: r.confidence,
21424
+ occurredAt: epochMillisToIso(r.occurred_at),
21425
+ sourceTool: r.source_tool,
21426
+ repo: r.repo ?? "",
21427
+ file: r.file ?? "",
21428
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
21429
+ eventId: r.event_id,
21430
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
21431
+ status: deriveInstanceStatus(r)
21432
+ };
21433
+ }
21434
+ if (rows.length < SCAN_BATCH_ROWS) return;
21435
+ const lastRow = rows[rows.length - 1];
21436
+ if (lastRow === void 0) return;
21437
+ after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
21438
+ }
21439
+ }
20642
21440
  groupAggregates(withSearchText, scope) {
20643
21441
  const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
20644
21442
  group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
@@ -20899,7 +21697,7 @@ var SqliteInspectionFindingsRepository = class {
20899
21697
  };
20900
21698
 
20901
21699
  // ../../packages/persistence/src/repositories/installed-packs.ts
20902
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
21700
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
20903
21701
 
20904
21702
  // ../../packages/persistence/src/semver.ts
20905
21703
  function parse3(version2) {
@@ -21050,7 +21848,7 @@ var SqliteInstalledPacksRepository = class {
21050
21848
  let behind = false;
21051
21849
  for (const row of rows) {
21052
21850
  const params = {
21053
- id: randomUUID2(),
21851
+ id: randomUUID3(),
21054
21852
  namespace: row.namespace,
21055
21853
  packId: row.packId,
21056
21854
  version: row.version,
@@ -21062,7 +21860,7 @@ var SqliteInstalledPacksRepository = class {
21062
21860
  if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
21063
21861
  this.upsertAvailableStmt.run({
21064
21862
  ...params,
21065
- id: randomUUID2(),
21863
+ id: randomUUID3(),
21066
21864
  recordedBy: meta3?.recordedBy ?? null
21067
21865
  });
21068
21866
  } else {
@@ -21385,14 +22183,15 @@ var SqliteInventoryRepository = class {
21385
22183
  };
21386
22184
 
21387
22185
  // ../../packages/persistence/src/repositories/inventory-assets.ts
21388
- import { randomUUID as randomUUID3 } from "crypto";
22186
+ import { randomUUID as randomUUID4 } from "crypto";
21389
22187
  var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
21390
22188
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
21391
22189
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
21392
22190
  var HARNESS_LABELS = {
21393
22191
  claudecode: "Claude Code",
21394
22192
  cursor: "Cursor",
21395
- codex: "Codex"
22193
+ codex: "Codex",
22194
+ antigravity: "Antigravity"
21396
22195
  };
21397
22196
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
21398
22197
  var EMPTY_PROJECT_AGG = {
@@ -21407,6 +22206,7 @@ function resolveHarnessId(attrs, row) {
21407
22206
  if (t.includes("claudecode") || t === "claude") return "claudecode";
21408
22207
  if (t.includes("cursor")) return "cursor";
21409
22208
  if (t.includes("codex")) return "codex";
22209
+ if (t.includes("antigravity")) return "antigravity";
21410
22210
  return null;
21411
22211
  }
21412
22212
  function isLiveRealClaudeCode(rows) {
@@ -21865,7 +22665,7 @@ var SqliteInventoryAssetsRepository = class {
21865
22665
  `INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
21866
22666
  VALUES (:id, :projectId, :path, :access, :now, :now)
21867
22667
  ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
21868
- ).run({ id: randomUUID3(), projectId, path, access, now: Date.now() });
22668
+ ).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
21869
22669
  }
21870
22670
  return true;
21871
22671
  }
@@ -21886,7 +22686,7 @@ var SqliteInventoryAssetsRepository = class {
21886
22686
  `INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
21887
22687
  VALUES (:id, :assetId, :trust, :now, :now)
21888
22688
  ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
21889
- ).run({ id: randomUUID3(), assetId, trust, now: Date.now() });
22689
+ ).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
21890
22690
  }
21891
22691
  this.configRowsCache = void 0;
21892
22692
  return "ok";
@@ -22183,7 +22983,7 @@ var SqliteInventoryAssetsRepository = class {
22183
22983
  };
22184
22984
 
22185
22985
  // ../../packages/persistence/src/repositories/policies.ts
22186
- import { randomUUID as randomUUID4 } from "crypto";
22986
+ import { randomUUID as randomUUID5 } from "crypto";
22187
22987
  var SqlitePoliciesRepository = class {
22188
22988
  constructor(db) {
22189
22989
  this.db = db;
@@ -22218,7 +23018,7 @@ var SqlitePoliciesRepository = class {
22218
23018
  failOpenTransaction(this.db, () => {
22219
23019
  for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
22220
23020
  stmt.run({
22221
- id: randomUUID4(),
23021
+ id: randomUUID5(),
22222
23022
  target: JSON.stringify({ category }),
22223
23023
  action,
22224
23024
  now: Date.now()
@@ -22238,7 +23038,7 @@ var SqlitePoliciesRepository = class {
22238
23038
  `INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
22239
23039
  VALUES (:id, 'global', :target, :action, 1, :now, :now)
22240
23040
  ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
22241
- ).run({ id: randomUUID4(), target: JSON.stringify({ category }), action, now });
23041
+ ).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
22242
23042
  }
22243
23043
  // Caps every global per-category policy currently set to block/redact down
22244
23044
  // to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
@@ -22306,7 +23106,7 @@ var SqlitePolicyCatalogRepository = class {
22306
23106
  };
22307
23107
 
22308
23108
  // ../../packages/persistence/src/repositories/project-files.ts
22309
- import { randomUUID as randomUUID5 } from "crypto";
23109
+ import { randomUUID as randomUUID6 } from "crypto";
22310
23110
  var SqliteProjectFilesRepository = class {
22311
23111
  constructor(db) {
22312
23112
  this.db = db;
@@ -22338,7 +23138,7 @@ var SqliteProjectFilesRepository = class {
22338
23138
  const stamp = Math.max(now, maxStamp + 1);
22339
23139
  for (const file2 of scan2.files) {
22340
23140
  this.upsertStmt.run({
22341
- id: randomUUID5(),
23141
+ id: randomUUID6(),
22342
23142
  projectId,
22343
23143
  path: file2.path,
22344
23144
  name: file2.name,
@@ -22352,7 +23152,7 @@ var SqliteProjectFilesRepository = class {
22352
23152
  };
22353
23153
 
22354
23154
  // ../../packages/persistence/src/repositories/resolutions.ts
22355
- import { randomUUID as randomUUID6 } from "crypto";
23155
+ import { randomUUID as randomUUID7 } from "crypto";
22356
23156
  var SqliteResolutionsRepository = class {
22357
23157
  constructor(db, now = () => Date.now()) {
22358
23158
  this.db = db;
@@ -22406,7 +23206,7 @@ var SqliteResolutionsRepository = class {
22406
23206
  */
22407
23207
  insertResolution(r) {
22408
23208
  this.insertStmt.run({
22409
- id: randomUUID6(),
23209
+ id: randomUUID7(),
22410
23210
  findingKey: r.findingKey,
22411
23211
  status: FindingStatus.parse(r.status),
22412
23212
  method: ResolutionMethod.parse(r.method),
@@ -22465,13 +23265,51 @@ var SqliteRuleProbeCacheRepository = class {
22465
23265
  this.readStmt = db.prepare(
22466
23266
  `SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
22467
23267
  );
23268
+ this.countQuarantinedStmt = db.prepare(
23269
+ `SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
23270
+ );
23271
+ this.clearQuarantinedStmt = db.prepare(
23272
+ `DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
23273
+ );
22468
23274
  }
22469
23275
  db;
22470
23276
  upsertStmt;
22471
23277
  readStmt;
23278
+ countQuarantinedStmt;
23279
+ clearQuarantinedStmt;
22472
23280
  getVerdict(ruleKey) {
22473
23281
  return getRow(this.readStmt, { ruleKey });
22474
23282
  }
23283
+ /** How many rules are currently excluded by a cached quarantine verdict. */
23284
+ countQuarantined() {
23285
+ return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
23286
+ }
23287
+ /**
23288
+ * Forgets every quarantine verdict, so the rules behind them are measured
23289
+ * again on the next load. This is the undo for a verdict the machine reached
23290
+ * on its own: a rule terminated mid-scan is cached forever and dropped from
23291
+ * every later scan, and a timing verdict is a wall-clock judgement that a
23292
+ * loaded or slow machine can reach about a rule that is in fact fine.
23293
+ *
23294
+ * Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
23295
+ * keeping, and dropping it would make every rule pay the battery again.
23296
+ *
23297
+ * Reports `refused` from the write's own result rather than inferring it from
23298
+ * the row count. The two are NOT the same answer: `failOpenTransaction`
23299
+ * swallows a contended DELETE (another writer holding the lock past
23300
+ * `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
23301
+ * leaves the count unchanged, which is indistinguishable from "there was
23302
+ * nothing to clear". An undo that reports success while the quarantines are
23303
+ * still in place is worse than one that fails, because the rules it claimed
23304
+ * to restore are silently still disabled.
23305
+ */
23306
+ clearQuarantined() {
23307
+ const before = this.countQuarantined();
23308
+ const committed = failOpenTransaction(this.db, () => {
23309
+ this.clearQuarantinedStmt.run();
23310
+ });
23311
+ return { refused: !committed, cleared: before - this.countQuarantined() };
23312
+ }
22475
23313
  setVerdict(ruleKey, verdict, worstProbeMs) {
22476
23314
  failOpenTransaction(this.db, () => {
22477
23315
  this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
@@ -22526,7 +23364,39 @@ var SqliteScanLedgerRepository = class {
22526
23364
  };
22527
23365
 
22528
23366
  // ../../packages/persistence/src/repositories/secret-vault.ts
22529
- import { randomUUID as randomUUID7 } from "crypto";
23367
+ import { randomUUID as randomUUID8 } from "crypto";
23368
+ function pageLimit(requested, fallback) {
23369
+ if (requested === void 0) return fallback;
23370
+ return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
23371
+ }
23372
+ function encodeReuseCursor(payload) {
23373
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
23374
+ }
23375
+ function decodeReuseCursor(cursor) {
23376
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23377
+ if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23378
+ // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23379
+ // null cursor, which the caller reads as "end of list" — the one outcome a
23380
+ // malformed cursor must never produce, since restarting from the top is the
23381
+ // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23382
+ // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23383
+ Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23384
+ return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23385
+ }
23386
+ return null;
23387
+ }
23388
+ var REUSED_PREDICATE = `(v.occurrence_count > 1
23389
+ OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
23390
+ var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
23391
+ v.occurrence_count, v.first_seen, v.last_seen`;
23392
+ function toSighting(row) {
23393
+ return {
23394
+ location: row.location,
23395
+ kind: row.kind,
23396
+ firstSeen: new Date(row.first_seen).toISOString(),
23397
+ lastSeen: new Date(row.last_seen).toISOString()
23398
+ };
23399
+ }
22530
23400
  var SELECT_COLUMNS = `
22531
23401
  pointer_id AS pointerId,
22532
23402
  value_fingerprint AS valueFingerprint,
@@ -22710,39 +23580,67 @@ var SqliteSecretVaultRepository = class {
22710
23580
  VALUES (:id, :pointerId, :location, :kind, :now, :now)
22711
23581
  ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
22712
23582
  ).run({
22713
- id: randomUUID7(),
23583
+ id: randomUUID8(),
22714
23584
  pointerId: entry.pointerId,
22715
23585
  location: entry.location,
22716
23586
  kind: entry.kind,
22717
23587
  now
22718
23588
  });
22719
23589
  }
22720
- listSightings(pointerId) {
23590
+ /**
23591
+ * Sightings for a whole page of pointers, in ONE query grouped in JS rather
23592
+ * than one query per row. A pointer with no sightings still gets an entry, so
23593
+ * the caller never has to distinguish "none" from "missing".
23594
+ *
23595
+ * The `IN` list is sized to the page, so this statement cannot be cached on
23596
+ * the instance the way the fixed-shape ones in the constructor are.
23597
+ */
23598
+ sightingsFor(pointerIds) {
23599
+ const byPointer = new Map(pointerIds.map((id) => [id, []]));
23600
+ if (pointerIds.length === 0) return byPointer;
22721
23601
  const rows = allRows(
22722
23602
  this.db.prepare(
22723
- `SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
22724
- WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
23603
+ `SELECT pointer_id, location, kind, first_seen, last_seen
23604
+ FROM secret_vault_sighting
23605
+ WHERE pointer_id IN (${placeholders(pointerIds.length)})
23606
+ ORDER BY last_seen DESC`
22725
23607
  ),
22726
- { pointerId }
23608
+ pointerIds
22727
23609
  );
23610
+ for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
23611
+ return byPointer;
23612
+ }
23613
+ /** Hydrate a page of raw inventory rows with their sightings, batched. */
23614
+ toInventoryEntries(rows) {
23615
+ const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
22728
23616
  return rows.map((r) => ({
22729
- location: r.location,
22730
- kind: r.kind,
23617
+ pointerId: r.pointer_id,
23618
+ category: r.category,
23619
+ ...r.provider === null ? {} : { provider: r.provider },
23620
+ maskedMatch: r.masked_match,
23621
+ occurrences: r.occurrence_count,
22731
23622
  firstSeen: new Date(r.first_seen).toISOString(),
22732
- lastSeen: new Date(r.last_seen).toISOString()
23623
+ lastSeen: new Date(r.last_seen).toISOString(),
23624
+ revealGrantId: r.grant_id,
23625
+ sightings: sightings.get(r.pointer_id) ?? []
22733
23626
  }));
22734
23627
  }
22735
23628
  /**
22736
- * The dashboard inventory: every vaulted value's descriptor data joined with
22737
- * its sightings and the active reveal-to-model grant when one exists.
22738
- * Raw-free by construction — neither the fingerprint nor the ciphertext
22739
- * columns are selected.
23629
+ * The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
23630
+ * value's descriptor data joined with its sightings and the active
23631
+ * reveal-to-model grant when one exists. Raw-free by construction — neither
23632
+ * the fingerprint nor the ciphertext columns are selected.
23633
+ *
23634
+ * `totals.values` counts the whole store, not the page, so the count a reader
23635
+ * sees never depends on how far they have paged.
22740
23636
  */
22741
- listInventory(now = Date.now()) {
23637
+ listInventory(query = {}, now = Date.now()) {
23638
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23639
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23640
+ const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
22742
23641
  const rows = allRows(
22743
23642
  this.db.prepare(
22744
- `SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
22745
- v.occurrence_count, v.first_seen, v.last_seen,
23643
+ `SELECT ${INVENTORY_COLUMNS},
22746
23644
  (SELECT e.id FROM exceptions e
22747
23645
  WHERE e.rule_id = v.rule_id
22748
23646
  AND e.value_fingerprint = v.value_fingerprint
@@ -22750,45 +23648,109 @@ var SqliteSecretVaultRepository = class {
22750
23648
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
22751
23649
  LIMIT 1) AS grant_id
22752
23650
  FROM secret_vault v
22753
- ORDER BY v.last_seen DESC`
23651
+ ${where}
23652
+ ORDER BY v.last_seen DESC, v.pointer_id DESC
23653
+ LIMIT :limit`
22754
23654
  ),
22755
- { now }
23655
+ bindParams({
23656
+ now,
23657
+ limit: limit + 1,
23658
+ ...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
23659
+ })
22756
23660
  );
22757
- return rows.map((r) => ({
22758
- pointerId: r.pointer_id,
22759
- category: r.category,
22760
- ...r.provider === null ? {} : { provider: r.provider },
22761
- maskedMatch: r.masked_match,
22762
- occurrences: r.occurrence_count,
22763
- firstSeen: new Date(r.first_seen).toISOString(),
22764
- lastSeen: new Date(r.last_seen).toISOString(),
22765
- revealGrantId: r.grant_id,
22766
- sightings: this.listSightings(r.pointer_id)
22767
- }));
23661
+ const hasMore = rows.length > limit;
23662
+ const page = hasMore ? rows.slice(0, limit) : rows;
23663
+ const last = page[page.length - 1];
23664
+ return {
23665
+ totals: { values: this.countEntries() },
23666
+ items: this.toInventoryEntries(page),
23667
+ // Minted from the last row of the PAGE, never the extra probe row.
23668
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
23669
+ };
23670
+ }
23671
+ /**
23672
+ * Values reused on this machine — detected more than once, or written to more
23673
+ * than one location — most-reused first, one page at a time.
23674
+ *
23675
+ * Its own read rather than a filter over an inventory page: reuse is a
23676
+ * property of the whole store, and deriving it from 50 newest rows would
23677
+ * under-report exactly the values a reader most needs to see.
23678
+ */
23679
+ listReuse(query = {}, now = Date.now()) {
23680
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23681
+ const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
23682
+ const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
23683
+ OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
23684
+ const rows = allRows(
23685
+ this.db.prepare(
23686
+ `SELECT ${INVENTORY_COLUMNS},
23687
+ (SELECT e.id FROM exceptions e
23688
+ WHERE e.rule_id = v.rule_id
23689
+ AND e.value_fingerprint = v.value_fingerprint
23690
+ AND e.key_version = v.fingerprint_key_version
23691
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
23692
+ LIMIT 1) AS grant_id
23693
+ FROM secret_vault v
23694
+ WHERE ${REUSED_PREDICATE} ${after}
23695
+ ORDER BY v.occurrence_count DESC, v.pointer_id DESC
23696
+ LIMIT :limit`
23697
+ ),
23698
+ bindParams({
23699
+ now,
23700
+ limit: limit + 1,
23701
+ ...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
23702
+ })
23703
+ );
23704
+ const hasMore = rows.length > limit;
23705
+ const page = hasMore ? rows.slice(0, limit) : rows;
23706
+ const last = page[page.length - 1];
23707
+ return {
23708
+ totals: { reused: this.countReused() },
23709
+ items: this.toInventoryEntries(page),
23710
+ nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
23711
+ };
22768
23712
  }
22769
23713
  /**
22770
- * The de-reference trail, newest first. By default the batched, high-volume
22771
- * reasons (display, view-render) are hidden and counted instead — the rows
22772
- * that matter as a signal are the model crossings, and burying them under
22773
- * render noise would defeat the audit's purpose.
23714
+ * The de-reference trail, newest first, one page at a time. By default the
23715
+ * batched, high-volume reasons (display, view-render) are hidden and counted
23716
+ * instead — the rows that matter as a signal are the model crossings, and
23717
+ * burying them under render noise would defeat the audit's purpose.
23718
+ *
23719
+ * `hiddenBatched` counts the whole trail rather than the page: it is what the
23720
+ * view's "N hidden" line and its toggle speak for, so it must not shrink as
23721
+ * the reader pages.
22774
23722
  */
22775
- listDerefs(opts) {
22776
- const limit = opts?.limit ?? 200;
22777
- const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
23723
+ listDerefs(query = {}) {
23724
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
23725
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23726
+ const conditions = [];
23727
+ if (query.includeBatched !== true) {
23728
+ conditions.push(`reason NOT IN ('display', 'view-render')`);
23729
+ }
23730
+ if (cursor !== null) {
23731
+ conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
23732
+ }
23733
+ const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
22778
23734
  const rows = allRows(
22779
23735
  this.db.prepare(
22780
23736
  `SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
22781
23737
  FROM secret_vault_deref ${where}
22782
- ORDER BY at DESC, rowid DESC LIMIT :limit`
23738
+ ORDER BY at DESC, id DESC LIMIT :limit`
22783
23739
  ),
22784
- { limit }
23740
+ bindParams({
23741
+ limit: limit + 1,
23742
+ ...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
23743
+ })
22785
23744
  );
22786
- const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
23745
+ const hasMore = rows.length > limit;
23746
+ const page = hasMore ? rows.slice(0, limit) : rows;
23747
+ const last = page[page.length - 1];
23748
+ const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
22787
23749
  this.db,
22788
23750
  `SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
22789
23751
  );
22790
23752
  return {
22791
- rows: rows.map((r) => ({
23753
+ items: page.map((r) => ({
22792
23754
  id: r.id,
22793
23755
  pointerId: r.pointer_id,
22794
23756
  at: new Date(r.at).toISOString(),
@@ -22798,12 +23760,20 @@ var SqliteSecretVaultRepository = class {
22798
23760
  ...r.grant_id === null ? {} : { grantId: r.grant_id },
22799
23761
  pointerCount: r.pointer_count
22800
23762
  })),
23763
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
22801
23764
  hiddenBatched
22802
23765
  };
22803
23766
  }
22804
23767
  countEntries() {
22805
23768
  return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
22806
23769
  }
23770
+ /** Values reused on this machine — the reuse list's page-independent total. */
23771
+ countReused() {
23772
+ return countScalar(
23773
+ this.db,
23774
+ `SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
23775
+ );
23776
+ }
22807
23777
  };
22808
23778
 
22809
23779
  // ../../packages/persistence/src/repositories/security.ts
@@ -22818,7 +23788,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
22818
23788
  var SCAN_COVERAGE = [
22819
23789
  { provider: "claudecode", coverage: 100, supported: true },
22820
23790
  { provider: "cursor", coverage: 0, supported: false },
22821
- { provider: "codex", coverage: 0, supported: false },
23791
+ { provider: "codex", coverage: 80, supported: true },
23792
+ { provider: "antigravity", coverage: 60, supported: true },
23793
+ { provider: "claudeai", coverage: 0, supported: false },
22822
23794
  { provider: "chatgpt", coverage: 0, supported: false },
22823
23795
  { provider: "copilot", coverage: 0, supported: false },
22824
23796
  { provider: "api", coverage: 0, supported: false }
@@ -23151,7 +24123,7 @@ var SqliteSecurityRepository = class {
23151
24123
  };
23152
24124
 
23153
24125
  // ../../packages/persistence/src/repositories/shares.ts
23154
- import { randomUUID as randomUUID8 } from "crypto";
24126
+ import { randomUUID as randomUUID9 } from "crypto";
23155
24127
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
23156
24128
  var IN_CHUNK = 500;
23157
24129
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -23407,7 +24379,7 @@ var SqliteSharesRepository = class {
23407
24379
  (id, destination_id, host, decision, created_at, updated_at)
23408
24380
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
23409
24381
  ).run({
23410
- id: randomUUID8(),
24382
+ id: randomUUID9(),
23411
24383
  destinationId,
23412
24384
  host: dest.host,
23413
24385
  decision,
@@ -23556,7 +24528,7 @@ var SqliteSharesRepository = class {
23556
24528
  let destinationId = destIds.get(hit.host);
23557
24529
  if (destinationId === void 0) {
23558
24530
  destStmt.run({
23559
- id: randomUUID8(),
24531
+ id: randomUUID9(),
23560
24532
  kind: hit.kind,
23561
24533
  name: hit.name,
23562
24534
  host: hit.host,
@@ -23572,7 +24544,7 @@ var SqliteSharesRepository = class {
23572
24544
  let endpointId = endpointIds.get(endpointKey);
23573
24545
  if (endpointId === void 0) {
23574
24546
  endpointStmt.run({
23575
- id: randomUUID8(),
24547
+ id: randomUUID9(),
23576
24548
  destinationId,
23577
24549
  method: hit.method,
23578
24550
  transport: hit.transport,
@@ -23585,7 +24557,7 @@ var SqliteSharesRepository = class {
23585
24557
  endpointIds.set(endpointKey, endpointId);
23586
24558
  }
23587
24559
  siteStmt.run({
23588
- id: randomUUID8(),
24560
+ id: randomUUID9(),
23589
24561
  endpointId,
23590
24562
  project: input.project,
23591
24563
  projectKey: input.projectKey,
@@ -23950,6 +24922,9 @@ function purgeSampleData(db) {
23950
24922
  }
23951
24923
 
23952
24924
  // ../../packages/persistence/src/database.ts
24925
+ var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
24926
+ "aka.persistence.unsafeTestOnlyRawHandle"
24927
+ );
23953
24928
  function linkHost(input, hostId) {
23954
24929
  return hostId ? { ...input, hostId } : input;
23955
24930
  }
@@ -23971,21 +24946,34 @@ function openWithPragmas(file2) {
23971
24946
  }
23972
24947
  return db;
23973
24948
  }
23974
- function backupLegacyStore(file2) {
23975
- const backup = `${file2}.legacy.${String(Date.now())}.bak`;
23976
- renameSync2(file2, backup);
23977
- tightenFile(backup);
23978
- for (const sidecar of dbSidecars(file2)) {
23979
- if (existsSync(sidecar)) rmSync2(sidecar);
24949
+ function backupLegacyStore(db, file2) {
24950
+ reapStalePartials(file2);
24951
+ const backup = backupPath(file2, "legacy");
24952
+ let snapshotted = false;
24953
+ let snapshotError;
24954
+ try {
24955
+ snapshotStore(db, backup);
24956
+ snapshotted = true;
24957
+ } catch (error51) {
24958
+ snapshotError = error51;
24959
+ } finally {
24960
+ db.close();
24961
+ }
24962
+ if (!snapshotted) {
24963
+ akaWarn(
24964
+ `Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
24965
+ );
24966
+ moveStoreAside(file2, backup);
24967
+ return backup;
23980
24968
  }
24969
+ discardStore(file2, backup);
23981
24970
  return backup;
23982
24971
  }
23983
24972
  function openAndInitialize(file2) {
23984
24973
  let db = openWithPragmas(file2);
23985
24974
  try {
23986
24975
  if (isForeignSqliteLineage(db)) {
23987
- db.close();
23988
- const backup = backupLegacyStore(file2);
24976
+ const backup = backupLegacyStore(db, file2);
23989
24977
  db = openWithPragmas(file2);
23990
24978
  akaWarn(
23991
24979
  `Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
@@ -24029,7 +25017,7 @@ function openAndInitialize(file2) {
24029
25017
  }
24030
25018
  function openLocalDatabase(dir) {
24031
25019
  ensureDataDirSync(dir);
24032
- const file2 = join(dir, DB_FILENAME);
25020
+ const file2 = join2(dir, DB_FILENAME);
24033
25021
  const {
24034
25022
  db,
24035
25023
  events,
@@ -24146,7 +25134,7 @@ function openLocalDatabase(dir) {
24146
25134
  const definitionId = definitionIds.get(`${finding2.ruleId}@${finding2.version}`);
24147
25135
  if (!definitionId) continue;
24148
25136
  inspectionFindings.insertFinding({
24149
- id: randomUUID9(),
25137
+ id: randomUUID10(),
24150
25138
  auditEventId: record2.scanEvent.id,
24151
25139
  inspectionDefinitionId: definitionId,
24152
25140
  span: finding2.span,
@@ -24252,22 +25240,38 @@ function openLocalDatabase(dir) {
24252
25240
  transaction,
24253
25241
  close: () => {
24254
25242
  db.close();
24255
- }
25243
+ },
25244
+ // Last, and a plain value rather than a getter, so `{ ...db }` carries it.
25245
+ [UNSAFE_TEST_ONLY_RAW_HANDLE]: db
24256
25246
  };
24257
25247
  }
24258
25248
 
25249
+ // ../../packages/persistence/src/file-lock.ts
25250
+ import { randomUUID as randomUUID11 } from "crypto";
25251
+ import {
25252
+ closeSync,
25253
+ existsSync as existsSync2,
25254
+ openSync,
25255
+ readFileSync,
25256
+ rmSync as rmSync3,
25257
+ statSync as statSync2,
25258
+ writeFileSync as writeFileSync2
25259
+ } from "fs";
25260
+ import { hostname as hostname3 } from "os";
25261
+ var PARK = new Int32Array(new SharedArrayBuffer(4));
25262
+
24259
25263
  // ../../packages/persistence/src/finding-key.ts
24260
25264
  import { createHash as createHash3 } from "crypto";
24261
25265
 
24262
25266
  // ../../packages/persistence/src/fingerprint.ts
24263
25267
  import { createHmac, randomBytes } from "crypto";
24264
- import { existsSync as existsSync2, readFileSync } from "fs";
24265
- import { join as join2 } from "path";
25268
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25269
+ import { join as join3 } from "path";
24266
25270
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
24267
- var KEY_FILENAME = "exception.key";
25271
+ var EXCEPTION_KEY_FILENAME = "exception.key";
24268
25272
  var KEY_MATERIAL_BYTES = 32;
24269
25273
  function keyFilePath(dataDir2) {
24270
- return join2(dataDir2, KEY_FILENAME);
25274
+ return join3(dataDir2, EXCEPTION_KEY_FILENAME);
24271
25275
  }
24272
25276
  function parseKeyFile(raw) {
24273
25277
  const parsed = JSON.parse(raw);
@@ -24290,7 +25294,7 @@ function parseKeyFile(raw) {
24290
25294
  function readFingerprintKey(dataDir2) {
24291
25295
  let raw;
24292
25296
  try {
24293
- raw = readFileSync(keyFilePath(dataDir2), "utf8");
25297
+ raw = readFileSync2(keyFilePath(dataDir2), "utf8");
24294
25298
  } catch (err) {
24295
25299
  if (err.code === "ENOENT") return null;
24296
25300
  throw err instanceof Error ? err : new Error(String(err));
@@ -24302,18 +25306,18 @@ function readFingerprintKey(dataDir2) {
24302
25306
  import { renameSync as renameSync3 } from "fs";
24303
25307
  import { mkdir } from "fs/promises";
24304
25308
  import { homedir } from "os";
24305
- import { join as join3 } from "path";
25309
+ import { join as join4 } from "path";
24306
25310
  function defaultDataDir() {
24307
- return join3(homedir(), ".aka");
25311
+ return join4(homedir(), ".aka");
24308
25312
  }
24309
25313
  function settingsDir(base = defaultDataDir()) {
24310
- return join3(base, "settings");
25314
+ return join4(base, "settings");
24311
25315
  }
24312
25316
  function dataDir(base = defaultDataDir()) {
24313
- return join3(base, "data");
25317
+ return join4(base, "data");
24314
25318
  }
24315
25319
  function dbPath(base = defaultDataDir()) {
24316
- return join3(dataDir(base), "aka.db");
25320
+ return join4(dataDir(base), "aka.db");
24317
25321
  }
24318
25322
  function ensureLayoutDirSync(dir = defaultDataDir()) {
24319
25323
  ensureDataDirSync(dir);
@@ -24326,8 +25330,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24326
25330
  for (const { name, dest } of moves) {
24327
25331
  try {
24328
25332
  ensureDataDirSync(dest);
24329
- const moved = join3(dest, name);
24330
- renameSync3(join3(base, name), moved);
25333
+ const moved = join4(dest, name);
25334
+ renameSync3(join4(base, name), moved);
24331
25335
  tightenFile(moved);
24332
25336
  } catch {
24333
25337
  }
@@ -24335,10 +25339,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24335
25339
  }
24336
25340
 
24337
25341
  // ../../packages/persistence/src/settings.ts
24338
- import { readFileSync as readFileSync2 } from "fs";
24339
- import { join as join4 } from "path";
25342
+ import { readFileSync as readFileSync3 } from "fs";
25343
+ import { join as join5 } from "path";
25344
+ var SETTINGS_FILENAME = "settings.json";
24340
25345
  function readWorkspaceSettings(base = defaultDataDir()) {
24341
- const record2 = readJson(join4(settingsDir(base), "settings.json"));
25346
+ const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
24342
25347
  if (!record2) return defaultWorkspaceSettings();
24343
25348
  try {
24344
25349
  return WorkspaceSettings.parse(record2);
@@ -24349,7 +25354,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
24349
25354
  function readJson(file2) {
24350
25355
  let text;
24351
25356
  try {
24352
- text = readFileSync2(file2, "utf8");
25357
+ text = readFileSync3(file2, "utf8");
24353
25358
  } catch {
24354
25359
  return null;
24355
25360
  }
@@ -24371,27 +25376,27 @@ import { randomBytes as randomBytes2 } from "crypto";
24371
25376
  import {
24372
25377
  chmodSync as chmodSync2,
24373
25378
  mkdirSync as mkdirSync2,
24374
- readFileSync as readFileSync3,
25379
+ readFileSync as readFileSync4,
24375
25380
  renameSync as renameSync4,
24376
- rmSync as rmSync3,
24377
- statSync,
24378
- writeFileSync as writeFileSync2
25381
+ rmSync as rmSync4,
25382
+ statSync as statSync3,
25383
+ writeFileSync as writeFileSync3
24379
25384
  } from "fs";
24380
- import { join as join5 } from "path";
25385
+ import { join as join6 } from "path";
24381
25386
 
24382
25387
  // ../../packages/persistence/src/vault/vault.ts
24383
- import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
25388
+ import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
24384
25389
 
24385
25390
  // ../../packages/persistence/src/warn-era-cap.ts
24386
- import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
24387
- import { join as join6 } from "path";
25391
+ import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
25392
+ import { join as join7 } from "path";
24388
25393
  var MARKER = "warn-era-capped";
24389
25394
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
24390
25395
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
24391
- const marker = join6(dataDir2, MARKER);
24392
- if (existsSync3(marker)) return { capped: 0, skipped: "already-run" };
25396
+ const marker = join7(dataDir2, MARKER);
25397
+ if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
24393
25398
  const capped = db.policies.capCategoryActions();
24394
- writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
25399
+ writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
24395
25400
  `, { mode: DATA_FILE_MODE });
24396
25401
  return { capped };
24397
25402
  }
@@ -24445,11 +25450,11 @@ function resolveProvider() {
24445
25450
  }
24446
25451
 
24447
25452
  // ../../packages/plugin-sdk/src/config.ts
24448
- function loadConfig(base = defaultDataDir()) {
25453
+ function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
24449
25454
  try {
24450
25455
  ensureLayoutDirSync(base);
24451
- const settingsFile = join7(settingsDir(base), "settings.json");
24452
- if (existsSync4(settingsFile)) tightenFile(settingsFile);
25456
+ const settingsFile = join8(settingsDir(base), "settings.json");
25457
+ if (existsSync5(settingsFile)) tightenFile(settingsFile);
24453
25458
  } catch {
24454
25459
  }
24455
25460
  migrateLegacyLayout(base);
@@ -24460,21 +25465,21 @@ function loadConfig(base = defaultDataDir()) {
24460
25465
  dbPath: dbPath(base),
24461
25466
  settingsDir: settingsDir(base),
24462
25467
  onboarded: settings.onboardedAt != null,
24463
- provider: resolveProviderSafe()
25468
+ provider: resolveProviderSafe(resolveProviderFn)
24464
25469
  };
24465
25470
  }
24466
- function resolveProviderSafe() {
25471
+ function resolveProviderSafe(resolveProviderFn) {
24467
25472
  try {
24468
- return resolveProvider();
25473
+ return resolveProviderFn();
24469
25474
  } catch {
24470
25475
  return { provider: "anthropic" };
24471
25476
  }
24472
25477
  }
24473
25478
 
24474
25479
  // ../../packages/plugin-sdk/src/config-inventory.ts
24475
- import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
25480
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
24476
25481
  import { homedir as homedir2 } from "os";
24477
- import { basename as basename3, join as join9 } from "path";
25482
+ import { basename as basename4, join as join10 } from "path";
24478
25483
 
24479
25484
  // ../../packages/detections/src/egress/registry.ts
24480
25485
  var EXTRACTOR_VERSION = "1";
@@ -25048,6 +26053,40 @@ function escapeRegExp2(value) {
25048
26053
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25049
26054
  }
25050
26055
 
26056
+ // ../../packages/detections/src/regex-cache.ts
26057
+ var singles = /* @__PURE__ */ new WeakMap();
26058
+ var keywordLists = /* @__PURE__ */ new WeakMap();
26059
+ var labelLists = /* @__PURE__ */ new WeakMap();
26060
+ function listCache(kind) {
26061
+ return kind === "keyword" ? keywordLists : labelLists;
26062
+ }
26063
+ function memoizedRegExp(owner, build) {
26064
+ const cached2 = singles.get(owner);
26065
+ if (cached2 !== void 0) {
26066
+ cached2.lastIndex = 0;
26067
+ return cached2;
26068
+ }
26069
+ const compiled = build();
26070
+ singles.set(owner, compiled);
26071
+ return compiled;
26072
+ }
26073
+ function memoizedRegExpList(kind, owner, build) {
26074
+ const cache = listCache(kind);
26075
+ const cached2 = cache.get(owner);
26076
+ if (cached2 !== void 0) {
26077
+ if (cached2.stateful) {
26078
+ for (const re of cached2.entries) if (re !== void 0) re.lastIndex = 0;
26079
+ }
26080
+ return cached2.entries;
26081
+ }
26082
+ const entries = build();
26083
+ cache.set(owner, {
26084
+ entries,
26085
+ stateful: entries.some((re) => re !== void 0 && (re.global || re.sticky))
26086
+ });
26087
+ return entries;
26088
+ }
26089
+
25051
26090
  // ../../packages/detections/src/matchers/limits.ts
25052
26091
  var MAX_MATCHES_PER_RULE = 1e4;
25053
26092
  var MAX_REGEX_INPUT_LENGTH = 2e5;
@@ -25058,10 +26097,17 @@ var KeywordMatcher2 = class {
25058
26097
  if (rule.matcher.type !== "keyword") return [];
25059
26098
  const { keywords, caseSensitive } = rule.matcher;
25060
26099
  const spans = [];
25061
- for (const kw of keywords) {
25062
- if (kw.length === 0) continue;
26100
+ const compiled = memoizedRegExpList(
26101
+ "keyword",
26102
+ rule.matcher,
26103
+ () => keywords.map((kw) => {
26104
+ if (kw.length === 0) return void 0;
26105
+ return new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
26106
+ })
26107
+ );
26108
+ for (const re of compiled) {
26109
+ if (re === void 0) continue;
25063
26110
  if (spans.length >= MAX_MATCHES_PER_RULE) break;
25064
- const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
25065
26111
  let m;
25066
26112
  while ((m = re.exec(text)) !== null) {
25067
26113
  spans.push({ start: m.index, end: m.index + m[0].length });
@@ -25077,7 +26123,10 @@ var RegexMatcher2 = class {
25077
26123
  match(text, rule) {
25078
26124
  if (rule.matcher.type !== "regex") return [];
25079
26125
  const { pattern, flags, captureGroup } = rule.matcher;
25080
- const re = new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`);
26126
+ const re = memoizedRegExp(
26127
+ rule.matcher,
26128
+ () => new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`)
26129
+ );
25081
26130
  const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
25082
26131
  const spans = [];
25083
26132
  let m;
@@ -25185,11 +26234,15 @@ function isCorroborated(candidate, candidates, text) {
25185
26234
  const labels = req.labels;
25186
26235
  if (labels && labels.length > 0) {
25187
26236
  const haystack = text.slice(Math.max(0, winStart), winEnd);
25188
- for (const label of labels) {
25189
- const trimmed = label.trim();
25190
- if (trimmed.length === 0) continue;
25191
- const re = new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
25192
- if (re.test(haystack)) return true;
26237
+ for (const re of memoizedRegExpList(
26238
+ "label",
26239
+ req,
26240
+ () => labels.map((label) => {
26241
+ const trimmed = label.trim();
26242
+ return trimmed.length === 0 ? void 0 : new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
26243
+ })
26244
+ )) {
26245
+ if (re?.test(haystack)) return true;
25193
26246
  }
25194
26247
  }
25195
26248
  return false;
@@ -25403,7 +26456,7 @@ function isUnknown(hook) {
25403
26456
  if (hook.scope === "plugin") return false;
25404
26457
  const token = firstExecutableToken(hook.command);
25405
26458
  if (token === void 0) return true;
25406
- return !KNOWN_TOOLS.has(basename(token));
26459
+ return !KNOWN_TOOLS.has(basename2(token));
25407
26460
  }
25408
26461
  function firstExecutableToken(command) {
25409
26462
  for (const token of command.trim().split(/\s+/)) {
@@ -25412,7 +26465,7 @@ function firstExecutableToken(command) {
25412
26465
  }
25413
26466
  return void 0;
25414
26467
  }
25415
- function basename(token) {
26468
+ function basename2(token) {
25416
26469
  const slash = token.lastIndexOf("/");
25417
26470
  return slash === -1 ? token : token.slice(slash + 1);
25418
26471
  }
@@ -27132,7 +28185,7 @@ var gcp_service_account_default = {
27132
28185
  severity: "critical",
27133
28186
  matcher: {
27134
28187
  type: "regex",
27135
- pattern: "\\b[a-z0-9][a-z0-9-]{2,60}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
28188
+ pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
27136
28189
  flags: "g"
27137
28190
  },
27138
28191
  examples: [
@@ -27556,8 +28609,8 @@ function maskText(text) {
27556
28609
  }
27557
28610
 
27558
28611
  // ../../packages/plugin-sdk/src/repo.ts
27559
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
27560
- import { basename as basename2, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
28612
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
28613
+ import { basename as basename3, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
27561
28614
  function resolveRepoIdentity(cwd) {
27562
28615
  try {
27563
28616
  const root = findGitRoot(cwd);
@@ -27570,7 +28623,7 @@ function resolveRepoIdentity(cwd) {
27570
28623
  // win32) so the persistence layer's `/`-separated checkout-path patterns
27571
28624
  // (the ghost sweep + the read-side worktree filter) match it as written.
27572
28625
  url: url2 ?? headRoot.split(sep2).join("/"),
27573
- name: (url2 ? slugFromUrl(url2) : void 0) ?? basename2(headRoot)
28626
+ name: (url2 ? slugFromUrl(url2) : void 0) ?? basename3(headRoot)
27574
28627
  };
27575
28628
  } catch {
27576
28629
  return void 0;
@@ -27607,15 +28660,15 @@ function resolveGitBranch(cwd) {
27607
28660
  try {
27608
28661
  const root = findGitRoot(cwd);
27609
28662
  if (!root) return void 0;
27610
- const dotGit = join8(root, ".git");
28663
+ const dotGit = join9(root, ".git");
27611
28664
  let gitdir;
27612
28665
  try {
27613
- gitdir = statSync2(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
28666
+ gitdir = statSync4(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
27614
28667
  } catch {
27615
28668
  return void 0;
27616
28669
  }
27617
28670
  if (gitdir === void 0) return void 0;
27618
- const head = safeRead(join8(gitdir, "HEAD"));
28671
+ const head = safeRead(join9(gitdir, "HEAD"));
27619
28672
  if (!head) return void 0;
27620
28673
  return /^ref:\s*refs\/heads\/(.+?)\s*$/m.exec(head)?.[1];
27621
28674
  } catch {
@@ -27625,41 +28678,41 @@ function resolveGitBranch(cwd) {
27625
28678
  function resolveWorktreeGitdir(root, dotGitFile) {
27626
28679
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGitFile) ?? "")?.[1];
27627
28680
  if (!target) return void 0;
27628
- return isAbsolute(target) ? target : join8(root, target);
28681
+ return isAbsolute(target) ? target : join9(root, target);
27629
28682
  }
27630
28683
  function findGitRoot(start) {
27631
28684
  let dir = start;
27632
28685
  for (; ; ) {
27633
- if (existsSync5(join8(dir, ".git"))) return dir;
27634
- const parent = dirname(dir);
28686
+ if (existsSync6(join9(dir, ".git"))) return dir;
28687
+ const parent = dirname2(dir);
27635
28688
  if (parent === dir) return void 0;
27636
28689
  dir = parent;
27637
28690
  }
27638
28691
  }
27639
28692
  function resolveGitContext(root) {
27640
- const dotGit = join8(root, ".git");
28693
+ const dotGit = join9(root, ".git");
27641
28694
  try {
27642
- if (statSync2(dotGit).isDirectory()) {
27643
- return { configPath: join8(dotGit, "config"), headRoot: root };
28695
+ if (statSync4(dotGit).isDirectory()) {
28696
+ return { configPath: join9(dotGit, "config"), headRoot: root };
27644
28697
  }
27645
28698
  } catch {
27646
28699
  return void 0;
27647
28700
  }
27648
28701
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
27649
28702
  if (!target) return void 0;
27650
- const gitdir = isAbsolute(target) ? target : join8(root, target);
27651
- if (existsSync5(join8(gitdir, "config"))) {
27652
- return { configPath: join8(gitdir, "config"), headRoot: root };
28703
+ const gitdir = isAbsolute(target) ? target : join9(root, target);
28704
+ if (existsSync6(join9(gitdir, "config"))) {
28705
+ return { configPath: join9(gitdir, "config"), headRoot: root };
27653
28706
  }
27654
- const commonRaw = safeRead(join8(gitdir, "commondir"))?.trim();
28707
+ const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
27655
28708
  if (!commonRaw) return void 0;
27656
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join8(gitdir, commonRaw);
27657
- const headRoot = basename2(commonGitDir) === ".git" ? dirname(commonGitDir) : root;
27658
- return { configPath: join8(commonGitDir, "config"), headRoot };
28709
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
28710
+ const headRoot = basename3(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
28711
+ return { configPath: join9(commonGitDir, "config"), headRoot };
27659
28712
  }
27660
28713
  function safeRead(path) {
27661
28714
  try {
27662
- return readFileSync4(path, "utf8");
28715
+ return readFileSync5(path, "utf8");
27663
28716
  } catch {
27664
28717
  return void 0;
27665
28718
  }
@@ -27721,31 +28774,31 @@ function resolveConfigInventory(input) {
27721
28774
  };
27722
28775
  try {
27723
28776
  const home = input.homeDir ?? homedir2();
27724
- const claudeDir = join9(home, ".claude");
28777
+ const claudeDir = join10(home, ".claude");
27725
28778
  const repo = resolveRepoIdentity(input.cwd);
27726
28779
  const repoIdentity = repo?.url ?? input.cwd;
27727
28780
  const projectSource = `project:${repoIdentity}`;
27728
- collectSettingsHooks(scan2, join9(claudeDir, "settings.json"), "user");
27729
- collectSettingsHooks(scan2, join9(input.cwd, ".claude", "settings.json"), "project");
27730
- collectSettingsHooks(scan2, join9(input.cwd, ".claude", "settings.local.json"), "local");
28781
+ collectSettingsHooks(scan2, join10(claudeDir, "settings.json"), "user");
28782
+ collectSettingsHooks(scan2, join10(input.cwd, ".claude", "settings.json"), "project");
28783
+ collectSettingsHooks(scan2, join10(input.cwd, ".claude", "settings.local.json"), "local");
27731
28784
  const projectOrigin = { scope: "project", project: repoIdentity };
27732
- collectMcpFile(scan2, join9(input.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
27733
- collectUserClaudeJson(scan2, join9(home, ".claude.json"), input.cwd, repoIdentity);
27734
- collectMcpFile(scan2, join9(claudeDir, "settings.json"), { scope: "user" });
27735
- collectMcpFile(scan2, join9(input.cwd, ".claude", "settings.json"), projectOrigin);
27736
- collectMcpFile(scan2, join9(input.cwd, ".claude", "settings.local.json"), {
28785
+ collectMcpFile(scan2, join10(input.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
28786
+ collectUserClaudeJson(scan2, join10(home, ".claude.json"), input.cwd, repoIdentity);
28787
+ collectMcpFile(scan2, join10(claudeDir, "settings.json"), { scope: "user" });
28788
+ collectMcpFile(scan2, join10(input.cwd, ".claude", "settings.json"), projectOrigin);
28789
+ collectMcpFile(scan2, join10(input.cwd, ".claude", "settings.local.json"), {
27737
28790
  scope: "local",
27738
28791
  project: repoIdentity
27739
28792
  });
27740
28793
  collectConfigFiles(scan2, claudeDir, input.cwd);
27741
- collectSkillsDir(scan2, join9(claudeDir, "skills"), { source: "local", scope: "user" });
27742
- collectSkillsDir(scan2, join9(input.cwd, ".claude", "skills"), {
28794
+ collectSkillsDir(scan2, join10(claudeDir, "skills"), { source: "local", scope: "user" });
28795
+ collectSkillsDir(scan2, join10(input.cwd, ".claude", "skills"), {
27743
28796
  source: projectSource,
27744
28797
  scope: "project"
27745
28798
  });
27746
28799
  collectInstalledPlugins(scan2, claudeDir);
27747
28800
  collectMarketplaceSkills(scan2, claudeDir);
27748
- collectSkillsDir(scan2, join9(input.cwd, "skills"), { source: projectSource, scope: "project" });
28801
+ collectSkillsDir(scan2, join10(input.cwd, "skills"), { source: projectSource, scope: "project" });
27749
28802
  scan2.skills = dedupeSkills(scan2.skills);
27750
28803
  scan2.mcpServers = dedupeMcpServers(scan2.mcpServers);
27751
28804
  } catch (err) {
@@ -27874,7 +28927,7 @@ function projectEntryFor(projects, cwd) {
27874
28927
  return void 0;
27875
28928
  }
27876
28929
  function collectPluginManifestMcp(scan2, installPath, origin) {
27877
- const manifestPath = join9(installPath, ".claude-plugin", "plugin.json");
28930
+ const manifestPath = join10(installPath, ".claude-plugin", "plugin.json");
27878
28931
  const raw = readOptional(manifestPath);
27879
28932
  if (raw === void 0) return;
27880
28933
  try {
@@ -27882,7 +28935,7 @@ function collectPluginManifestMcp(scan2, installPath, origin) {
27882
28935
  if (typeof parsed !== "object" || parsed === null) return;
27883
28936
  const declared = parsed.mcpServers;
27884
28937
  if (typeof declared === "string" && declared.length > 0) {
27885
- collectMcpFile(scan2, join9(installPath, declared), origin, { recordErrors: true });
28938
+ collectMcpFile(scan2, join10(installPath, declared), origin, { recordErrors: true });
27886
28939
  } else {
27887
28940
  collectMcpObject(scan2, declared, manifestPath, origin);
27888
28941
  }
@@ -27899,19 +28952,19 @@ var SETTINGS_KEY_LABELS = [
27899
28952
  ["statusLine", "status line"]
27900
28953
  ];
27901
28954
  function collectConfigFiles(scan2, claudeDir, cwd) {
27902
- settingsConfigFile(scan2, join9(claudeDir, "settings.json"), "user", "User settings");
27903
- settingsConfigFile(scan2, join9(cwd, ".claude", "settings.json"), "project", "Project settings");
27904
- settingsConfigFile(scan2, join9(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
27905
- memoryConfigFile(scan2, join9(claudeDir, "CLAUDE.md"), "user", "User memory");
27906
- memoryConfigFile(scan2, join9(cwd, "CLAUDE.md"), "project", "Project memory");
27907
- mcpJsonConfigFile(scan2, join9(cwd, ".mcp.json"));
27908
- dirConfigFile(scan2, join9(cwd, ".claude", "commands"), "Slash commands", "command");
27909
- dirConfigFile(scan2, join9(cwd, ".claude", "agents"), "Subagents", "subagent");
28955
+ settingsConfigFile(scan2, join10(claudeDir, "settings.json"), "user", "User settings");
28956
+ settingsConfigFile(scan2, join10(cwd, ".claude", "settings.json"), "project", "Project settings");
28957
+ settingsConfigFile(scan2, join10(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
28958
+ memoryConfigFile(scan2, join10(claudeDir, "CLAUDE.md"), "user", "User memory");
28959
+ memoryConfigFile(scan2, join10(cwd, "CLAUDE.md"), "project", "Project memory");
28960
+ mcpJsonConfigFile(scan2, join10(cwd, ".mcp.json"));
28961
+ dirConfigFile(scan2, join10(cwd, ".claude", "commands"), "Slash commands", "command");
28962
+ dirConfigFile(scan2, join10(cwd, ".claude", "agents"), "Subagents", "subagent");
27910
28963
  }
27911
28964
  function configFileEntry(path, scope, kind) {
27912
28965
  try {
27913
- const stat = statSync3(path);
27914
- return { name: basename3(path), path, scope, kind, updatedAt: stat.mtime.toISOString() };
28966
+ const stat = statSync5(path);
28967
+ return { name: basename4(path), path, scope, kind, updatedAt: stat.mtime.toISOString() };
27915
28968
  } catch {
27916
28969
  return void 0;
27917
28970
  }
@@ -27979,9 +29032,9 @@ function dirConfigFile(scan2, path, kind, noun) {
27979
29032
  function countMarkdownFiles(dir, depth) {
27980
29033
  if (depth > 4) return 0;
27981
29034
  let count = 0;
27982
- for (const dirent of readdirSync(dir, { withFileTypes: true })) {
29035
+ for (const dirent of readdirSync2(dir, { withFileTypes: true })) {
27983
29036
  if (dirent.name.startsWith(".")) continue;
27984
- if (dirent.isDirectory()) count += countMarkdownFiles(join9(dir, dirent.name), depth + 1);
29037
+ if (dirent.isDirectory()) count += countMarkdownFiles(join10(dir, dirent.name), depth + 1);
27985
29038
  else if (dirent.name.endsWith(".md")) count += 1;
27986
29039
  }
27987
29040
  return count;
@@ -27989,12 +29042,12 @@ function countMarkdownFiles(dir, depth) {
27989
29042
  function collectSkillsDir(scan2, dir, origin) {
27990
29043
  let names;
27991
29044
  try {
27992
- names = readdirSync(dir);
29045
+ names = readdirSync2(dir);
27993
29046
  } catch {
27994
29047
  return;
27995
29048
  }
27996
29049
  for (const name of names) {
27997
- const skillFile = join9(dir, name, "SKILL.md");
29050
+ const skillFile = join10(dir, name, "SKILL.md");
27998
29051
  try {
27999
29052
  const raw = readOptional(skillFile);
28000
29053
  if (raw === void 0) continue;
@@ -28003,8 +29056,8 @@ function collectSkillsDir(scan2, dir, origin) {
28003
29056
  name: front.name ?? name,
28004
29057
  source: origin.source,
28005
29058
  scope: origin.scope,
28006
- location: join9(dir, name),
28007
- updatedAt: statSync3(skillFile).mtime.toISOString()
29059
+ location: join10(dir, name),
29060
+ updatedAt: statSync5(skillFile).mtime.toISOString()
28008
29061
  };
28009
29062
  const version2 = front.version ?? origin.defaultVersion;
28010
29063
  if (version2 !== void 0) entry.version = version2;
@@ -28022,10 +29075,10 @@ function parseFrontmatter(raw) {
28022
29075
  if (lines[0]?.trim() !== "---") return out;
28023
29076
  for (const line of lines.slice(1)) {
28024
29077
  if (line.trim() === "---") break;
28025
- const sep5 = line.indexOf(":");
28026
- if (sep5 === -1) continue;
28027
- const key = line.slice(0, sep5).trim();
28028
- const value = line.slice(sep5 + 1).trim().replace(/^['"]|['"]$/g, "");
29078
+ const sep4 = line.indexOf(":");
29079
+ if (sep4 === -1) continue;
29080
+ const key = line.slice(0, sep4).trim();
29081
+ const value = line.slice(sep4 + 1).trim().replace(/^['"]|['"]$/g, "");
28029
29082
  if (value === "") continue;
28030
29083
  if (key === "name") out.name = value;
28031
29084
  else if (key === "description") out.description = value;
@@ -28034,7 +29087,7 @@ function parseFrontmatter(raw) {
28034
29087
  return out;
28035
29088
  }
28036
29089
  function collectInstalledPlugins(scan2, claudeDir) {
28037
- const manifestPath = join9(claudeDir, "plugins", "installed_plugins.json");
29090
+ const manifestPath = join10(claudeDir, "plugins", "installed_plugins.json");
28038
29091
  const raw = readOptional(manifestPath);
28039
29092
  if (raw === void 0) return;
28040
29093
  let plugins;
@@ -28059,7 +29112,7 @@ function collectInstalledPlugins(scan2, claudeDir) {
28059
29112
  if (typeof installPath !== "string" || seen.has(installPath)) continue;
28060
29113
  seen.add(installPath);
28061
29114
  const version2 = install.version;
28062
- const hooksPath = join9(installPath, "hooks", "hooks.json");
29115
+ const hooksPath = join10(installPath, "hooks", "hooks.json");
28063
29116
  const hooksRaw = readOptional(hooksPath);
28064
29117
  if (hooksRaw !== void 0) {
28065
29118
  try {
@@ -28079,33 +29132,33 @@ function collectInstalledPlugins(scan2, claudeDir) {
28079
29132
  }
28080
29133
  const origin = { source: marketplace, scope: "plugin", pluginName };
28081
29134
  if (typeof version2 === "string") origin.defaultVersion = version2;
28082
- collectSkillsDir(scan2, join9(installPath, "skills"), origin);
29135
+ collectSkillsDir(scan2, join10(installPath, "skills"), origin);
28083
29136
  const mcpOrigin = { scope: "plugin", pluginName, marketplace };
28084
- collectMcpFile(scan2, join9(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
29137
+ collectMcpFile(scan2, join10(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
28085
29138
  collectPluginManifestMcp(scan2, installPath, mcpOrigin);
28086
29139
  }
28087
29140
  }
28088
29141
  }
28089
29142
  function collectMarketplaceSkills(scan2, claudeDir) {
28090
- for (const mp of readMarketplaces(join9(claudeDir, "plugins", "known_marketplaces.json"))) {
29143
+ for (const mp of readMarketplaces(join10(claudeDir, "plugins", "known_marketplaces.json"))) {
28091
29144
  if (isClaudeOfficialMarketplace(mp.name, mp.repo)) continue;
28092
- collectSkillsDir(scan2, join9(mp.installLocation, "skills"), {
29145
+ collectSkillsDir(scan2, join10(mp.installLocation, "skills"), {
28093
29146
  source: mp.name,
28094
29147
  scope: "plugin"
28095
29148
  });
28096
- collectPluginSkillDirs(scan2, join9(mp.installLocation, "plugins"), mp.name);
28097
- collectPluginSkillDirs(scan2, join9(mp.installLocation, "external_plugins"), mp.name);
29149
+ collectPluginSkillDirs(scan2, join10(mp.installLocation, "plugins"), mp.name);
29150
+ collectPluginSkillDirs(scan2, join10(mp.installLocation, "external_plugins"), mp.name);
28098
29151
  }
28099
29152
  }
28100
29153
  function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
28101
29154
  let plugins;
28102
29155
  try {
28103
- plugins = readdirSync(pluginsDir);
29156
+ plugins = readdirSync2(pluginsDir);
28104
29157
  } catch {
28105
29158
  return;
28106
29159
  }
28107
29160
  for (const plugin of plugins) {
28108
- collectSkillsDir(scan2, join9(pluginsDir, plugin, "skills"), {
29161
+ collectSkillsDir(scan2, join10(pluginsDir, plugin, "skills"), {
28109
29162
  source: marketplace,
28110
29163
  scope: "plugin",
28111
29164
  pluginName: plugin
@@ -28159,7 +29212,7 @@ function dedupeMcpServers(servers) {
28159
29212
  }
28160
29213
  function readOptional(path) {
28161
29214
  try {
28162
- return readFileSync5(path, "utf8");
29215
+ return readFileSync6(path, "utf8");
28163
29216
  } catch {
28164
29217
  return void 0;
28165
29218
  }
@@ -28179,18 +29232,23 @@ function str2(value) {
28179
29232
  }
28180
29233
 
28181
29234
  // ../../packages/plugin-sdk/src/events.ts
28182
- import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
29235
+ import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
29236
+
29237
+ // ../../packages/plugin-sdk/src/isolated-scan.ts
29238
+ import { existsSync as existsSync7 } from "fs";
29239
+ import { fileURLToPath } from "url";
29240
+ import { Worker } from "worker_threads";
28183
29241
 
28184
29242
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
28185
- import { arch, hostname as hostname3, platform, release } from "os";
29243
+ import { arch, hostname as hostname4, platform, release } from "os";
28186
29244
  function resolveInventoryContext(input) {
28187
29245
  const host = {
28188
29246
  objectType: "host",
28189
29247
  // Stable-ish machine id; os/arch live in the descriptive bag (a
28190
29248
  // harder machine id can replace this without a schema change).
28191
- identityKey: hostname3(),
28192
- title: hostname3(),
28193
- attributes: { host_name: hostname3(), os: platform(), os_version: release(), arch: arch() }
29249
+ identityKey: hostname4(),
29250
+ title: hostname4(),
29251
+ attributes: { host_name: hostname4(), os: platform(), os_version: release(), arch: arch() }
28194
29252
  };
28195
29253
  const harnessAttributes = {};
28196
29254
  if (input.harnessVersion != null) harnessAttributes.harness_version = input.harnessVersion;
@@ -28211,35 +29269,35 @@ function resolveInventoryContext(input) {
28211
29269
  }
28212
29270
 
28213
29271
  // ../../packages/plugin-sdk/src/nudge.ts
28214
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
28215
- import { join as join10 } from "path";
29272
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
29273
+ import { join as join11 } from "path";
28216
29274
  var SESSION_START_MARKER = "session-start-last";
28217
29275
  function claimSessionStart(dataDir2, sessionId) {
28218
29276
  return claimOncePerSession(dataDir2, SESSION_START_MARKER, sessionId);
28219
29277
  }
28220
29278
  function claimOncePerSession(dataDir2, marker, sessionId) {
28221
29279
  if (!sessionId) return true;
28222
- const path = join10(dataDir2, marker);
29280
+ const path = join11(dataDir2, marker);
28223
29281
  try {
28224
- if (readFileSync6(path, "utf8") === sessionId) return false;
29282
+ if (readFileSync7(path, "utf8") === sessionId) return false;
28225
29283
  } catch {
28226
29284
  }
28227
29285
  try {
28228
29286
  mkdirSync3(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
28229
- writeFileSync4(path, sessionId, { mode: DATA_FILE_MODE });
29287
+ writeFileSync5(path, sessionId, { mode: DATA_FILE_MODE });
28230
29288
  } catch {
28231
29289
  }
28232
29290
  return true;
28233
29291
  }
28234
29292
 
28235
29293
  // ../../packages/plugin-sdk/src/paths.ts
28236
- import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
28237
- import { basename as basename4, dirname as dirname2, sep as sep3 } from "path";
29294
+ import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
29295
+ import { basename as basename5, dirname as dirname3, sep as sep3 } from "path";
28238
29296
 
28239
29297
  // ../../packages/plugin-sdk/src/project-files.ts
28240
29298
  var import_ignore = __toESM(require_ignore(), 1);
28241
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
28242
- import { basename as basename5, join as join11, relative, sep as sep4 } from "path";
29299
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
29300
+ import { basename as basename6, join as join12 } from "path";
28243
29301
  var SKIP_DIRS = /* @__PURE__ */ new Set([
28244
29302
  ".git",
28245
29303
  "node_modules",
@@ -28257,23 +29315,28 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
28257
29315
  ".cache"
28258
29316
  ]);
28259
29317
  var MAX_FILES = 2e4;
28260
- function readIgnoreLayer(dir) {
29318
+ function readIgnoreLayer(dir, anchorLen) {
28261
29319
  try {
28262
- const content = readFileSync7(join11(dir, ".gitignore"), "utf8");
28263
- return { base: dir, matcher: (0, import_ignore.default)().add(content) };
29320
+ const content = readFileSync8(join12(dir, ".gitignore"), "utf8");
29321
+ return { matcher: (0, import_ignore.default)().add(content), anchorLen };
28264
29322
  } catch {
28265
29323
  return void 0;
28266
29324
  }
28267
29325
  }
28268
- function isIgnored(layers, absPath, isDir) {
28269
- let ignored = false;
28270
- for (const layer of layers) {
28271
- const rel = relative(layer.base, absPath).split(sep4).join("/") + (isDir ? "/" : "");
28272
- const verdict = layer.matcher.test(rel);
28273
- if (verdict.ignored) ignored = true;
28274
- else if (verdict.unignored) ignored = false;
29326
+ function relToAnchor(dirRel, anchorLen, name) {
29327
+ if (anchorLen === dirRel.length) return name;
29328
+ return `${dirRel.slice(anchorLen === 0 ? 0 : anchorLen + 1)}/${name}`;
29329
+ }
29330
+ function isIgnored(layers, dirRel, name, isDir) {
29331
+ const suffix = isDir ? "/" : "";
29332
+ for (let i = layers.length - 1; i >= 0; i--) {
29333
+ const layer = layers[i];
29334
+ if (layer === void 0) continue;
29335
+ const verdict = layer.matcher.test(relToAnchor(dirRel, layer.anchorLen, name) + suffix);
29336
+ if (verdict.ignored) return true;
29337
+ if (verdict.unignored) return false;
28275
29338
  }
28276
- return ignored;
29339
+ return false;
28277
29340
  }
28278
29341
  var GENERATED_FILES = /* @__PURE__ */ new Set([
28279
29342
  "pnpm-lock.yaml",
@@ -28326,32 +29389,33 @@ function classifyOrigin(relPath, name) {
28326
29389
  }
28327
29390
  function resolveProjectFiles(cwd) {
28328
29391
  try {
28329
- let visit2 = function(dir, layers) {
29392
+ let visit2 = function(dir, dirRel, layers) {
28330
29393
  let dirents;
28331
29394
  try {
28332
- dirents = readdirSync3(dir, { withFileTypes: true, encoding: "utf8" });
29395
+ dirents = readdirSync4(dir, { withFileTypes: true, encoding: "utf8" });
28333
29396
  } catch {
28334
29397
  walk.lostSubtree = true;
28335
29398
  return false;
28336
29399
  }
28337
- const layer = readIgnoreLayer(dir);
29400
+ const layer = readIgnoreLayer(dir, dirRel.length);
28338
29401
  const dirLayers = layer ? [...layers, layer] : layers;
28339
29402
  for (const entry of dirents) {
28340
- const fullPath = join11(dir, entry.name);
28341
29403
  if (entry.isDirectory()) {
28342
- if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, fullPath, true)) continue;
28343
- if (existsSync6(join11(fullPath, ".git"))) continue;
28344
- if (visit2(fullPath, dirLayers)) return true;
29404
+ if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, dirRel, entry.name, true)) continue;
29405
+ const fullPath = join12(dir, entry.name);
29406
+ if (existsSync8(join12(fullPath, ".git"))) continue;
29407
+ const childRel = dirRel === "" ? entry.name : `${dirRel}/${entry.name}`;
29408
+ if (visit2(fullPath, childRel, dirLayers)) return true;
28345
29409
  continue;
28346
29410
  }
28347
29411
  if (!entry.isFile()) continue;
28348
29412
  if (entry.name === ".git") continue;
28349
- if (isIgnored(dirLayers, fullPath, false)) continue;
29413
+ if (isIgnored(dirLayers, dirRel, entry.name, false)) continue;
28350
29414
  if (files.length >= MAX_FILES) return true;
28351
- const relPath = relative(root, fullPath).split(sep4).join("/");
29415
+ const relPath = dirRel === "" ? entry.name : `${dirRel}/${entry.name}`;
28352
29416
  files.push({
28353
29417
  path: relPath,
28354
- name: basename5(entry.name),
29418
+ name: basename6(entry.name),
28355
29419
  origin: classifyOrigin(relPath, entry.name),
28356
29420
  defaultAccess: "approved"
28357
29421
  });
@@ -28365,7 +29429,7 @@ function resolveProjectFiles(cwd) {
28365
29429
  const isLinkedCheckout = resolveHeadRoot(cwd) !== worktree;
28366
29430
  const files = [];
28367
29431
  const walk = { lostSubtree: false };
28368
- const truncated = visit2(root, []) || walk.lostSubtree || isLinkedCheckout;
29432
+ const truncated = visit2(root, "", []) || walk.lostSubtree || isLinkedCheckout;
28369
29433
  if (files.length === 0) return void 0;
28370
29434
  files.sort((a, b) => a.path.localeCompare(b.path));
28371
29435
  return { files, truncated, scannedAt: (/* @__PURE__ */ new Date()).toISOString() };
@@ -28374,31 +29438,57 @@ function resolveProjectFiles(cwd) {
28374
29438
  }
28375
29439
  }
28376
29440
 
29441
+ // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
29442
+ var optionalBaseUrl2 = external_exports.preprocess((v) => {
29443
+ if (typeof v === "string" && v.trim() === "") return void 0;
29444
+ return v;
29445
+ }, external_exports.string().optional()).catch(void 0);
29446
+ var optionalFlag = external_exports.preprocess((v) => {
29447
+ if (typeof v !== "string") return false;
29448
+ const normalized = v.trim().toLowerCase();
29449
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
29450
+ }, external_exports.boolean()).catch(false);
29451
+ var antigravityProviderEnvShape = {
29452
+ GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
29453
+ GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
29454
+ };
29455
+ var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
29456
+
29457
+ // ../../packages/plugin-sdk/src/provider-env-codex.ts
29458
+ var optionalBaseUrl3 = external_exports.preprocess((v) => {
29459
+ if (typeof v === "string" && v.trim() === "") return void 0;
29460
+ return v;
29461
+ }, external_exports.string().optional()).catch(void 0);
29462
+ var codexProviderEnvShape = {
29463
+ OPENAI_BASE_URL: optionalBaseUrl3
29464
+ };
29465
+ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
29466
+
28377
29467
  // ../../packages/plugin-sdk/src/runtime.ts
28378
- import { randomUUID as randomUUID12 } from "crypto";
29468
+ import { randomUUID as randomUUID14 } from "crypto";
28379
29469
 
28380
29470
  // ../../packages/plugin-sdk/src/suppressions.ts
28381
29471
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
28382
29472
 
28383
29473
  // ../../packages/plugin-sdk/src/throttle.ts
28384
- import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
28385
- import { join as join12 } from "path";
29474
+ import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
29475
+ import { join as join13 } from "path";
28386
29476
  function throttled(dataDir2, markerName, windowMs) {
28387
- const marker = join12(dataDir2, markerName);
29477
+ const marker = join13(dataDir2, markerName);
28388
29478
  try {
28389
- if (Date.now() - statSync4(marker).mtimeMs < windowMs) return true;
29479
+ if (Date.now() - statSync6(marker).mtimeMs < windowMs) return true;
28390
29480
  } catch {
28391
29481
  }
28392
29482
  try {
28393
29483
  mkdirSync4(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
28394
- writeFileSync5(marker, String(Date.now()), { mode: DATA_FILE_MODE });
29484
+ writeFileSync6(marker, String(Date.now()), { mode: DATA_FILE_MODE });
28395
29485
  } catch {
28396
29486
  }
28397
29487
  return false;
28398
29488
  }
28399
29489
 
28400
29490
  // ../../packages/plugin-runtime/src/standalone-gateway.ts
28401
- import { randomUUID as randomUUID13 } from "crypto";
29491
+ import { randomUUID as randomUUID15 } from "crypto";
28402
29492
 
28403
29493
  // ../../packages/plugin-runtime/src/recorder.ts
28404
29494
  var PLUGIN_RECORDER_BINARY = "plugin";
@@ -28563,7 +29653,7 @@ var StandaloneDataGateway = class {
28563
29653
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
28564
29654
  const installed = this.installedScanRules();
28565
29655
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
28566
- id: randomUUID13(),
29656
+ id: randomUUID15(),
28567
29657
  scope: "global",
28568
29658
  target: { ruleId },
28569
29659
  action,
@@ -28716,7 +29806,7 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
28716
29806
  }
28717
29807
 
28718
29808
  // ../../packages/plugin-runtime/src/handle-session-start.ts
28719
- import { randomUUID as randomUUID14 } from "crypto";
29809
+ import { randomUUID as randomUUID16 } from "crypto";
28720
29810
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
28721
29811
  async function handleSessionStart(input, config2 = loadConfig()) {
28722
29812
  const silent = { staleBinaryNotice: null };
@@ -28799,7 +29889,7 @@ async function recordConfigInventory(gateway, sessionId, cwd, homeDir) {
28799
29889
  }
28800
29890
  function buildConfigScanEvent(sessionId, scan2) {
28801
29891
  return {
28802
- id: randomUUID14(),
29892
+ id: randomUUID16(),
28803
29893
  eventType: "config_scan",
28804
29894
  startedAt: scan2.scannedAt,
28805
29895
  parentId: sessionId,
@@ -28820,8 +29910,10 @@ function buildSessionRoot(sessionId, input, ctx, resolved, provider, branch) {
28820
29910
  const attributes = {};
28821
29911
  const osVersion = ctx.host?.attributes.os_version;
28822
29912
  const harnessVersion2 = ctx.harness?.attributes.harness_version;
29913
+ const harnessInterface = ctx.harness?.attributes.interface;
28823
29914
  if (typeof osVersion === "string") attributes.os_version = osVersion;
28824
29915
  if (typeof harnessVersion2 === "string") attributes.harness_version = harnessVersion2;
29916
+ if (typeof harnessInterface === "string") attributes.harness_interface = harnessInterface;
28825
29917
  attributes.provider = provider.provider;
28826
29918
  if (provider.gatewayHost !== void 0) attributes.gateway_host = provider.gatewayHost;
28827
29919
  attributes.harness = harnessFromTool(input.tool);
@@ -28847,21 +29939,21 @@ function buildSessionRoot(sessionId, input, ctx, resolved, provider, branch) {
28847
29939
 
28848
29940
  // src/history/reconcile-trigger.ts
28849
29941
  import { spawn } from "child_process";
28850
- import { dirname as dirname3, join as join14 } from "path";
28851
- import { fileURLToPath } from "url";
29942
+ import { dirname as dirname4, join as join15 } from "path";
29943
+ import { fileURLToPath as fileURLToPath2 } from "url";
28852
29944
 
28853
29945
  // src/history/tail.ts
28854
29946
  import { createHash as createHash5 } from "crypto";
28855
29947
  import {
28856
- closeSync,
29948
+ closeSync as closeSync2,
28857
29949
  fstatSync,
28858
29950
  mkdirSync as mkdirSync5,
28859
- openSync,
28860
- readFileSync as readFileSync8,
29951
+ openSync as openSync2,
29952
+ readFileSync as readFileSync9,
28861
29953
  readSync,
28862
- writeFileSync as writeFileSync6
29954
+ writeFileSync as writeFileSync7
28863
29955
  } from "fs";
28864
- import { join as join13 } from "path";
29956
+ import { join as join14 } from "path";
28865
29957
  var SAFE_SESSION_ID = /^[A-Za-z0-9._-]+$/;
28866
29958
  function safeSessionId(sessionId) {
28867
29959
  if (SAFE_SESSION_ID.test(sessionId) && sessionId !== "." && sessionId !== "..") {
@@ -28877,8 +29969,8 @@ function triggerReconcile(dataDir2, sessionId, transcriptPath) {
28877
29969
  try {
28878
29970
  const marker = `${RECONCILE_MARKER_PREFIX}-${safeSessionId(sessionId)}`;
28879
29971
  if (throttled(dataDir2, marker, RECONCILE_THROTTLE_MS)) return;
28880
- const here = dirname3(fileURLToPath(import.meta.url));
28881
- const child = spawn(process.execPath, [join14(here, "reconcile.js"), sessionId, transcriptPath], {
29972
+ const here = dirname4(fileURLToPath2(import.meta.url));
29973
+ const child = spawn(process.execPath, [join15(here, "reconcile.js"), sessionId, transcriptPath], {
28882
29974
  detached: true,
28883
29975
  stdio: "ignore"
28884
29976
  });
@@ -28889,17 +29981,17 @@ function triggerReconcile(dataDir2, sessionId, transcriptPath) {
28889
29981
 
28890
29982
  // src/protocol/marker.ts
28891
29983
  import { randomBytes as randomBytes4 } from "crypto";
28892
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
28893
- import { join as join15 } from "path";
29984
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync10, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "fs";
29985
+ import { join as join16 } from "path";
28894
29986
  var MARKER_FILE = "protocol-marker";
28895
29987
  function mintMarker() {
28896
29988
  return randomBytes4(8).toString("hex");
28897
29989
  }
28898
29990
  function sessionProtocolMarker(dataDir2, sessionId) {
28899
29991
  if (!sessionId) return mintMarker();
28900
- const path = join15(dataDir2, MARKER_FILE);
29992
+ const path = join16(dataDir2, MARKER_FILE);
28901
29993
  try {
28902
- const stored = JSON.parse(readFileSync9(path, "utf8"));
29994
+ const stored = JSON.parse(readFileSync10(path, "utf8"));
28903
29995
  if (stored.sessionId === sessionId && typeof stored.marker === "string" && /^[0-9a-f]{16}$/.test(stored.marker)) {
28904
29996
  return stored.marker;
28905
29997
  }
@@ -28908,8 +30000,8 @@ function sessionProtocolMarker(dataDir2, sessionId) {
28908
30000
  const marker = mintMarker();
28909
30001
  try {
28910
30002
  mkdirSync6(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
28911
- const tmp = join15(dataDir2, `${MARKER_FILE}.tmp`);
28912
- writeFileSync7(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
30003
+ const tmp = join16(dataDir2, `${MARKER_FILE}.tmp`);
30004
+ writeFileSync8(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
28913
30005
  renameSync5(tmp, path);
28914
30006
  } catch {
28915
30007
  }
@@ -28975,7 +30067,7 @@ function harnessVersion() {
28975
30067
  const manifestPath = process.argv[2];
28976
30068
  if (!manifestPath) return void 0;
28977
30069
  try {
28978
- const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
30070
+ const manifest = JSON.parse(readFileSync11(manifestPath, "utf8"));
28979
30071
  return typeof manifest.version === "string" ? manifest.version : void 0;
28980
30072
  } catch {
28981
30073
  return void 0;