@akasecurity/ai-tc-claude-code 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -541,6 +541,10 @@ var SQLITE_MIGRATIONS = [
541
541
  {
542
542
  tag: "0009_findings_path_expression_index",
543
543
  sql: "-- Custom migration: partial expression index for the resolver's per-path reads.\n--\n-- openAtRestKeysForPath / resolvedAtRestKeysForPath (and the scanner's tier-3\n-- open-key probe) filter at-rest events by\n-- e.kind = 'code_change' AND json_extract(e.metadata, '$.filePath') = :path\n-- once per changed/deleted file on every scan \u2014 previously a full events scan\n-- per file. json_extract is deterministic, so SQLite allows it in an index;\n-- the WHERE kind = 'code_change' keeps the index to exactly the rows those\n-- queries can match (in-flight events are never path-addressed).\nCREATE INDEX `idx_events_code_change_path` ON `events` (json_extract(`metadata`, '$.filePath')) WHERE `kind` = 'code_change';\n"
544
+ },
545
+ {
546
+ tag: "0010_events_session_expression_index",
547
+ sql: "-- Custom migration: partial expression index for session-scoped finding reads.\n--\n-- sessionFindingsCount, the session-scoped listGroupedFindings paths, and the\n-- insert-time session dedup all filter live-capture events by\n-- json_extract(e.metadata, '$.sessionId') = :sessionId\n-- \u2014 previously a full findings-join scan with a JSON parse per row. The\n-- IS NOT NULL predicate keeps the index to session-stamped events only (an\n-- equality probe implies non-null, so SQLite still uses it).\nCREATE INDEX `idx_events_session_id` ON `events` (json_extract(`metadata`, '$.sessionId')) WHERE json_extract(`metadata`, '$.sessionId') IS NOT NULL;\n"
544
548
  }
545
549
  ];
546
550
 
@@ -643,6 +647,12 @@ var defaultCostModel = {
643
647
  }
644
648
  };
645
649
 
650
+ // ../../packages/schema/src/token/format.ts
651
+ var COMPACT = new Intl.NumberFormat("en-US", {
652
+ notation: "compact",
653
+ maximumFractionDigits: 1
654
+ });
655
+
646
656
  // ../../packages/schema/src/token/token-report.ts
647
657
  var num = (value) => value ?? 0;
648
658
  function costUsageOf(a) {
@@ -15314,6 +15324,12 @@ var FindingInstance = external_exports.object({
15314
15324
  provider: FindingProvider,
15315
15325
  repo: external_exports.string(),
15316
15326
  file: external_exports.string(),
15327
+ // Host tool that produced the scanned text (event metadata's toolName).
15328
+ // Present whenever the capturing hook recorded one — including
15329
+ // file-attributed captures (views prefer `file`); its display value is
15330
+ // the location fallback ("via Bash") when no filePath exists. Absent for
15331
+ // legacy rows and non-tool captures (prompts, worktree scans).
15332
+ toolName: external_exports.string().optional(),
15317
15333
  // Effective action: override.action ?? actionTaken, translated to FindingAction.
15318
15334
  action: FindingAction,
15319
15335
  detectedAt: external_exports.iso.datetime(),
@@ -15370,6 +15386,9 @@ var ListGroupedFindingsQuery = external_exports.object({
15370
15386
  provider: external_exports.array(FindingProvider).optional(),
15371
15387
  action: external_exports.array(FindingAction).optional(),
15372
15388
  q: external_exports.string().optional(),
15389
+ // Scope to findings whose event carries this session id (the Activity page's
15390
+ // session → findings drilldown). Findings without a session never match.
15391
+ sessionId: external_exports.string().optional(),
15373
15392
  groupBy: external_exports.literal("type").optional(),
15374
15393
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
15375
15394
  cursor: external_exports.string().optional()
@@ -15381,7 +15400,13 @@ var ListGroupedFindingsResponse = external_exports.object({
15381
15400
  }),
15382
15401
  facets: FindingFacets,
15383
15402
  items: external_exports.array(FindingGroup),
15384
- nextCursor: external_exports.string().nullable()
15403
+ nextCursor: external_exports.string().nullable(),
15404
+ // Present only on session-scoped queries (`sessionId` set): per ruleId, how
15405
+ // many times that rule fired in the session's persisted transcript. Findings
15406
+ // here are deduplicated to unique values while the transcript tally counts
15407
+ // every firing, so the two numbers legitimately differ — this map lets a
15408
+ // session-scoped view show both.
15409
+ sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
15385
15410
  }).meta({ id: "ListGroupedFindingsResponse" });
15386
15411
  var ApplyFindingActionRequest = external_exports.object({
15387
15412
  // 'quarantined' is system-assigned (see FindingAction) — clients may not set
@@ -15792,6 +15817,10 @@ var ListActivitySessionsQuery = external_exports.object({
15792
15817
  from: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
15793
15818
  /** Upper bound on startedAt; omitted defaults to now. */
15794
15819
  to: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
15820
+ /** Exclude zero-activity sessions — roots whose only recorded children are
15821
+ * bookkeeping rows (hooks, config scans), typically background `claude`
15822
+ * launches. Omitted = list everything. `z.stringbool()` per the note above. */
15823
+ excludeEmpty: external_exports.stringbool().optional(),
15795
15824
  /** Page size, 1–100; out-of-range values are a 400. `z.coerce` — query params arrive as strings. */
15796
15825
  limit: external_exports.coerce.number().int().min(1).max(100).default(50),
15797
15826
  /** Opaque pagination cursor (most-recent first). */
@@ -15800,7 +15829,11 @@ var ListActivitySessionsQuery = external_exports.object({
15800
15829
  var ListActivitySessionsResponse = external_exports.object({
15801
15830
  items: external_exports.array(ActivitySessionSummary),
15802
15831
  /** `null` once the last page is reached. */
15803
- nextCursor: external_exports.string().nullable()
15832
+ nextCursor: external_exports.string().nullable(),
15833
+ /** Zero-activity sessions matching the query's filters/range (whether or
15834
+ * not `excludeEmpty` dropped them from `items`) — the count a UI toggle
15835
+ * shows when collapsing them. */
15836
+ emptyCount: external_exports.number().int().nonnegative()
15804
15837
  }).meta({ id: "ListActivitySessionsResponse" });
15805
15838
  var ListSessionEventsQuery = external_exports.object({
15806
15839
  /** Default 100, range 1–500. */
@@ -15835,6 +15868,12 @@ var EventMetadata = external_exports.object({
15835
15868
  sessionId: external_exports.string().optional(),
15836
15869
  repo: external_exports.string().optional(),
15837
15870
  filePath: external_exports.string().optional(),
15871
+ // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
15872
+ // set by the tool-scanning hooks. The tool NAME only — never the tool's
15873
+ // arguments or output, which can carry the very value a finding masked
15874
+ // (metadata is stored unredacted). Gives findings on non-file captures a
15875
+ // display location ("via Bash") when no filePath exists.
15876
+ toolName: external_exports.string().optional(),
15838
15877
  // Set (true) by the worktree scanner when the file is excluded by the
15839
15878
  // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
15840
15879
  // generated code can leak real secrets — but the provenance is recorded so
@@ -16166,7 +16205,10 @@ var ExceptionBundleEntry = DetectionException.pick({
16166
16205
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
16167
16206
  var KeywordMatcher = external_exports.object({
16168
16207
  type: external_exports.literal("keyword"),
16169
- keywords: external_exports.array(external_exports.string()).min(1),
16208
+ // An empty keyword matches at every position, yielding one zero-length span
16209
+ // per character. Rejected here because a keyword that matches everything is
16210
+ // never intentional.
16211
+ keywords: external_exports.array(external_exports.string().min(1)).min(1),
16170
16212
  caseSensitive: external_exports.boolean().default(false)
16171
16213
  });
16172
16214
  function isValidRegex(pattern, flags) {
@@ -16415,6 +16457,10 @@ var ListEventsResponse = external_exports.object({
16415
16457
  items: external_exports.array(Event),
16416
16458
  nextCursor: external_exports.string().nullable()
16417
16459
  }).meta({ id: "ListEventsResponse" });
16460
+ var IngestResponse = external_exports.object({
16461
+ accepted: external_exports.number().int().nonnegative(),
16462
+ duplicates: external_exports.number().int().nonnegative()
16463
+ }).meta({ id: "IngestResponse" });
16418
16464
  var ListFindingsQuery = external_exports.object({
16419
16465
  cursor: external_exports.string().optional(),
16420
16466
  limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
@@ -16804,6 +16850,7 @@ function buildFindingGroups(rows, opts = {}) {
16804
16850
  provider: toApiProvider(r.sourceTool),
16805
16851
  repo: r.repo,
16806
16852
  file: r.file,
16853
+ ...r.toolName === void 0 ? {} : { toolName: r.toolName },
16807
16854
  action: toApiAction(effectiveDbAction),
16808
16855
  detectedAt: r.occurredAt,
16809
16856
  confidence: r.confidence,
@@ -16876,6 +16923,7 @@ function buildHaystack(g, extra) {
16876
16923
  g.id,
16877
16924
  ...g.instances.map((i) => i.repo),
16878
16925
  ...g.instances.map((i) => i.file),
16926
+ ...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
16879
16927
  ...g.instances.map((i) => i.id),
16880
16928
  ...extra === void 0 ? [] : [extra]
16881
16929
  ].join(" ").toLowerCase();
@@ -17142,6 +17190,71 @@ var ProjectFilesScan = external_exports.object({
17142
17190
  scannedAt: external_exports.string()
17143
17191
  });
17144
17192
 
17193
+ // ../../packages/schema/src/zod/ranges.ts
17194
+ var TIME_RANGES = ["7d", "30d", "3m", "6m"];
17195
+ var TimeRange = external_exports.enum(TIME_RANGES).meta({ id: "TimeRange" });
17196
+ var DEFAULT_TIME_RANGE = "7d";
17197
+ var RANGE_DAYS = {
17198
+ "7d": 7,
17199
+ "30d": 30,
17200
+ "3m": 90,
17201
+ "6m": 180
17202
+ };
17203
+ var TIME_RANGE_OR_DEFAULT = TimeRange.catch(DEFAULT_TIME_RANGE);
17204
+
17205
+ // ../../packages/schema/src/zod/remediation.ts
17206
+ var SecretFindingState = external_exports.enum(["still-valid", "unknown", "invalid"]);
17207
+ var MaskedFindingLocation = external_exports.object({
17208
+ filePath: external_exports.string(),
17209
+ span: Span.optional()
17210
+ }).strict();
17211
+ var MaskedSecretFinding = external_exports.object({
17212
+ provider: external_exports.string(),
17213
+ maskedToken: external_exports.string(),
17214
+ where: MaskedFindingLocation,
17215
+ state: SecretFindingState,
17216
+ observedAt: external_exports.iso.datetime().optional()
17217
+ }).strict();
17218
+ var RotationChecklistEntry = external_exports.object({
17219
+ provider: external_exports.string(),
17220
+ maskedToken: external_exports.string(),
17221
+ consolePath: external_exports.string(),
17222
+ occurrenceSpread: external_exports.number().int().positive()
17223
+ }).strict();
17224
+ var RemediationOption = external_exports.enum([
17225
+ "redact-rotation-checklist",
17226
+ "redact-only",
17227
+ "set-secret-redact",
17228
+ "leave"
17229
+ ]);
17230
+ var RemediationEntrySource = external_exports.enum(["first-run", "pre-push", "secret-scan"]);
17231
+ var RemediationEntryContext = external_exports.object({
17232
+ entrySource: RemediationEntrySource
17233
+ }).strict();
17234
+ var RemediationOptionChoice = external_exports.object({
17235
+ id: RemediationOption,
17236
+ label: external_exports.string()
17237
+ });
17238
+ var BatchedRemediationDecision = external_exports.object({
17239
+ kind: external_exports.literal("decision"),
17240
+ entrySource: RemediationEntrySource,
17241
+ secretCount: external_exports.number().int().positive(),
17242
+ prompt: external_exports.string(),
17243
+ options: external_exports.tuple([
17244
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-rotation-checklist") }),
17245
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-only") }),
17246
+ RemediationOptionChoice.extend({ id: external_exports.literal("set-secret-redact") }),
17247
+ RemediationOptionChoice.extend({ id: external_exports.literal("leave") })
17248
+ ])
17249
+ });
17250
+ var NoRemediationDecision = external_exports.object({
17251
+ kind: external_exports.literal("no-decision")
17252
+ });
17253
+ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
17254
+ BatchedRemediationDecision,
17255
+ NoRemediationDecision
17256
+ ]);
17257
+
17145
17258
  // ../../packages/schema/src/zod/rule-test.ts
17146
17259
  var TestRulesRequest = external_exports.object({
17147
17260
  rules: external_exports.array(Rule).min(1).max(100),
@@ -17200,10 +17313,8 @@ var SeveritySummaryResponse = external_exports.object({
17200
17313
  // All four severity levels are always present (count may be 0).
17201
17314
  bySeverity: external_exports.array(SeveritySummaryItem)
17202
17315
  }).meta({ id: "SeveritySummaryResponse" });
17203
- var SECURITY_RANGES = ["7d", "30d", "3m", "6m"];
17204
- var SecurityRange = external_exports.enum(SECURITY_RANGES).meta({ id: "SecurityRange" });
17205
17316
  var SecurityRangeQuery = external_exports.object({
17206
- range: external_exports.enum(SECURITY_RANGES).default("30d")
17317
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE)
17207
17318
  });
17208
17319
  var EnforcementActionKind = external_exports.enum(["blocked", "redacted", "warned"]).meta({ id: "EnforcementActionKind" });
17209
17320
  var EnforcementAction = external_exports.object({
@@ -17213,7 +17324,7 @@ var EnforcementAction = external_exports.object({
17213
17324
  delta: external_exports.number().int()
17214
17325
  }).meta({ id: "EnforcementAction" });
17215
17326
  var EnforcementActionsResponse = external_exports.object({
17216
- range: SecurityRange,
17327
+ range: TimeRange,
17217
17328
  // Sum of actions[].count in the window.
17218
17329
  total: external_exports.number().int().nonnegative(),
17219
17330
  // One entry per kind, always all three present (count may be 0).
@@ -17228,7 +17339,7 @@ var FindingsTimeseriesPoint = external_exports.object({
17228
17339
  medium: external_exports.number().int().nonnegative()
17229
17340
  }).meta({ id: "FindingsTimeseriesPoint" });
17230
17341
  var FindingsTimeseriesResponse = external_exports.object({
17231
- range: SecurityRange,
17342
+ range: TimeRange,
17232
17343
  granularity: TimeseriesGranularity,
17233
17344
  points: external_exports.array(FindingsTimeseriesPoint)
17234
17345
  }).meta({ id: "FindingsTimeseriesResponse" });
@@ -17243,7 +17354,7 @@ var MttrTrendPoint = external_exports.object({
17243
17354
  })
17244
17355
  }).meta({ id: "MttrTrendPoint" });
17245
17356
  var MttrTrendResponse = external_exports.object({
17246
- range: SecurityRange,
17357
+ range: TimeRange,
17247
17358
  granularity: TimeseriesGranularity,
17248
17359
  points: external_exports.array(MttrTrendPoint)
17249
17360
  }).meta({ id: "MttrTrendResponse" });
@@ -17270,11 +17381,11 @@ var TopSource = external_exports.object({
17270
17381
  findingsCount: external_exports.number().int().nonnegative()
17271
17382
  }).meta({ id: "TopSource" });
17272
17383
  var TopSourcesResponse = external_exports.object({
17273
- range: SecurityRange,
17384
+ range: TimeRange,
17274
17385
  items: external_exports.array(TopSource)
17275
17386
  }).meta({ id: "TopSourcesResponse" });
17276
17387
  var TopSourcesQuery = external_exports.object({
17277
- range: external_exports.enum(SECURITY_RANGES).default("30d"),
17388
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE),
17278
17389
  limit: external_exports.coerce.number().int().min(1).max(50).default(5),
17279
17390
  // Omit for both kinds.
17280
17391
  kind: external_exports.enum(SOURCE_KINDS).optional()
@@ -17287,7 +17398,7 @@ var ScanCoverageProvider = external_exports.object({
17287
17398
  supported: external_exports.boolean()
17288
17399
  }).meta({ id: "ScanCoverageProvider" });
17289
17400
  var ScanCoverageResponse = external_exports.object({
17290
- range: SecurityRange,
17401
+ range: TimeRange,
17291
17402
  providers: external_exports.array(ScanCoverageProvider)
17292
17403
  }).meta({ id: "ScanCoverageResponse" });
17293
17404
  var SubjectType = external_exports.enum(["repo", "user", "team", "policy", "share", "rule"]).meta({
@@ -17336,6 +17447,114 @@ var ApplyRecommendedActionResponse = external_exports.object({
17336
17447
  var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
17337
17448
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
17338
17449
 
17450
+ // ../../packages/schema/src/zod/triage.ts
17451
+ var TriageHit = external_exports.object({
17452
+ ruleId: external_exports.string(),
17453
+ category: DetectionCategory,
17454
+ severity: Severity,
17455
+ maskedMatch: external_exports.string(),
17456
+ rawMatch: external_exports.string(),
17457
+ context: external_exports.string(),
17458
+ filePath: external_exports.string().optional(),
17459
+ confidence: external_exports.number().min(0).max(1),
17460
+ id: external_exports.string().optional(),
17461
+ valueFingerprint: external_exports.string().optional(),
17462
+ keyVersion: external_exports.number().int().nonnegative().optional()
17463
+ });
17464
+ var TriagePolicy = BuiltinPolicyId;
17465
+ var TriageCategoryRec = external_exports.object({
17466
+ category: DetectionCategory,
17467
+ action: TriagePolicy,
17468
+ reasoning: external_exports.string(),
17469
+ genuineCount: external_exports.number().int().nonnegative(),
17470
+ fpCount: external_exports.number().int().nonnegative(),
17471
+ // TriageHit ids judged false-positive in this category. fpCount must equal
17472
+ // this array's length — enforced by the consumer, not this schema.
17473
+ fpIds: external_exports.array(external_exports.string())
17474
+ });
17475
+ var TriageRecommendation = external_exports.object({
17476
+ perCategory: external_exports.array(TriageCategoryRec),
17477
+ notes: external_exports.string()
17478
+ });
17479
+
17480
+ // ../../packages/schema/src/zod/setup-frame.ts
17481
+ var CalibrationCounts = external_exports.object({
17482
+ total: external_exports.number().int().nonnegative(),
17483
+ important: external_exports.number().int().nonnegative(),
17484
+ routine: external_exports.number().int().nonnegative()
17485
+ }).refine((c) => c.total === c.important + c.routine, {
17486
+ message: "total must equal important + routine",
17487
+ path: ["total"]
17488
+ });
17489
+ var FalsePositivePatternValue = external_exports.object({
17490
+ ruleId: external_exports.string(),
17491
+ category: DetectionCategory,
17492
+ valueFingerprint: external_exports.string(),
17493
+ keyVersion: external_exports.number().int().nonnegative()
17494
+ });
17495
+ var FalsePositivePatternGroup = external_exports.object({
17496
+ pattern: external_exports.string(),
17497
+ count: external_exports.number().int().nonnegative(),
17498
+ values: external_exports.array(FalsePositivePatternValue).min(1)
17499
+ });
17500
+ var CalibrationFindingKind = external_exports.object({
17501
+ category: DetectionCategory,
17502
+ count: external_exports.number().int().nonnegative(),
17503
+ egress: external_exports.boolean()
17504
+ });
17505
+ var CalibrationFrame = external_exports.object({
17506
+ counts: CalibrationCounts,
17507
+ routineCategories: external_exports.array(DetectionCategory),
17508
+ surfacedCategories: external_exports.array(DetectionCategory),
17509
+ findingKinds: external_exports.array(CalibrationFindingKind),
17510
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId),
17511
+ maskedFindings: external_exports.array(MaskedSecretFinding).optional(),
17512
+ falsePositivePatterns: external_exports.array(FalsePositivePatternGroup).optional()
17513
+ });
17514
+ var CalibrationPreviewCategory = TriageCategoryRec.pick({
17515
+ category: true,
17516
+ genuineCount: true,
17517
+ fpCount: true
17518
+ }).extend({
17519
+ egress: external_exports.boolean()
17520
+ });
17521
+ var CalibrationPreview = external_exports.object({
17522
+ categories: external_exports.array(CalibrationPreviewCategory),
17523
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId)
17524
+ });
17525
+ var CalibrationResult = external_exports.object({
17526
+ frame: CalibrationFrame,
17527
+ copy: external_exports.string()
17528
+ });
17529
+ var FirstRunCalibration = external_exports.enum(["scan", "floor"]);
17530
+ var SetupHandoffOption = external_exports.object({
17531
+ id: external_exports.enum(["enter-remediation", "open-dashboard", "not-now"]),
17532
+ label: external_exports.string()
17533
+ });
17534
+ var DashboardHandoffOptions = external_exports.tuple([
17535
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
17536
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
17537
+ ]);
17538
+ var ComposedRemediationOptions = external_exports.tuple([
17539
+ SetupHandoffOption.extend({ id: external_exports.literal("enter-remediation") }),
17540
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
17541
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
17542
+ ]);
17543
+ var SetupHandoffOffer = external_exports.object({
17544
+ worthALook: external_exports.number().int().nonnegative(),
17545
+ liveKeys: external_exports.number().int().nonnegative().optional(),
17546
+ options: external_exports.union([DashboardHandoffOptions, ComposedRemediationOptions])
17547
+ }).refine(
17548
+ (o) => o.options.some((opt) => opt.id === "enter-remediation") === (o.liveKeys ?? 0) > 0,
17549
+ {
17550
+ message: "the chain-entry option is present exactly when liveKeys > 0",
17551
+ path: ["options"]
17552
+ }
17553
+ ).refine((o) => (o.liveKeys ?? 0) <= o.worthALook, {
17554
+ message: "liveKeys is a subset of worthALook and cannot exceed it",
17555
+ path: ["liveKeys"]
17556
+ });
17557
+
17339
17558
  // ../../packages/schema/src/zod/shares.ts
17340
17559
  var DestinationKind = external_exports.enum(["provider", "internal", "ip"]).meta({ id: "DestinationKind" });
17341
17560
  var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp"]).meta({ id: "Transport" });
@@ -17521,36 +17740,6 @@ function reviewSeverityRank(reasons) {
17521
17740
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
17522
17741
  }
17523
17742
 
17524
- // ../../packages/schema/src/zod/triage.ts
17525
- var TriageHit = external_exports.object({
17526
- ruleId: external_exports.string(),
17527
- category: DetectionCategory,
17528
- severity: Severity,
17529
- maskedMatch: external_exports.string(),
17530
- rawMatch: external_exports.string(),
17531
- context: external_exports.string(),
17532
- filePath: external_exports.string().optional(),
17533
- confidence: external_exports.number().min(0).max(1),
17534
- id: external_exports.string().optional(),
17535
- valueFingerprint: external_exports.string().optional(),
17536
- keyVersion: external_exports.number().int().nonnegative().optional()
17537
- });
17538
- var TriagePolicy = BuiltinPolicyId;
17539
- var TriageCategoryRec = external_exports.object({
17540
- category: DetectionCategory,
17541
- action: TriagePolicy,
17542
- reasoning: external_exports.string(),
17543
- genuineCount: external_exports.number().int().nonnegative(),
17544
- fpCount: external_exports.number().int().nonnegative(),
17545
- // TriageHit ids judged false-positive in this category. fpCount must equal
17546
- // this array's length — enforced by the consumer, not this schema.
17547
- fpIds: external_exports.array(external_exports.string())
17548
- });
17549
- var TriageRecommendation = external_exports.object({
17550
- perCategory: external_exports.array(TriageCategoryRec),
17551
- notes: external_exports.string()
17552
- });
17553
-
17554
17743
  // ../../packages/persistence/src/internal/sql-text.ts
17555
17744
  function escapeLikePattern(s) {
17556
17745
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18124,6 +18313,10 @@ var TIMELINE_COLUMNS = `
18124
18313
  json_extract(attributes, '$.internal') AS internal,
18125
18314
  json_extract(attributes, '$.flagged') AS flagged`;
18126
18315
  var SESSION_ROOT = `event_type = 'session'`;
18316
+ var HAS_ACTIVITY = `EXISTS (
18317
+ SELECT 1 FROM audit_events c
18318
+ WHERE c.root_session_id = audit_events.id
18319
+ AND c.event_type NOT IN ('hook', 'config_scan'))`;
18127
18320
  var SqliteActivityRepository = class {
18128
18321
  constructor(db, now = () => Date.now()) {
18129
18322
  this.db = db;
@@ -18137,7 +18330,8 @@ var SqliteActivityRepository = class {
18137
18330
  const sessionsToday = countScalar(
18138
18331
  this.db,
18139
18332
  `SELECT count(*) AS n FROM audit_events
18140
- WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?`,
18333
+ WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?
18334
+ AND ${HAS_ACTIVITY}`,
18141
18335
  [startMs, endMs]
18142
18336
  );
18143
18337
  const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
@@ -18209,6 +18403,13 @@ var SqliteActivityRepository = class {
18209
18403
  );
18210
18404
  params.push(pattern, pattern, pattern, pattern, pattern, pattern);
18211
18405
  }
18406
+ const emptyCount = countScalar(
18407
+ this.db,
18408
+ `SELECT count(*) AS n FROM audit_events
18409
+ WHERE ${[...conditions, `NOT ${HAS_ACTIVITY}`].join(" AND ")}`,
18410
+ params
18411
+ );
18412
+ if (query.excludeEmpty) conditions.push(HAS_ACTIVITY);
18212
18413
  if (cursor) {
18213
18414
  conditions.push("(started_at < ? OR (started_at = ? AND id < ?))");
18214
18415
  params.push(cursor.startedAtMs, cursor.startedAtMs, cursor.id);
@@ -18245,7 +18446,7 @@ var SqliteActivityRepository = class {
18245
18446
  );
18246
18447
  const last = page[page.length - 1];
18247
18448
  const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
18248
- return Promise.resolve({ items, nextCursor });
18449
+ return Promise.resolve({ items, nextCursor, emptyCount });
18249
18450
  }
18250
18451
  getSession(sessionId) {
18251
18452
  const rootRow = getRow(
@@ -19551,9 +19752,43 @@ var SqliteFindingsRepository = class {
19551
19752
  }))
19552
19753
  );
19553
19754
  }
19755
+ /** Live-enforced findings recorded for one session — a bare COUNT over the
19756
+ * session-stamped events (served by idx_events_session_id), so the Activity
19757
+ * page can label its findings link without the grouped pipeline. */
19758
+ sessionFindingsCount(sessionId) {
19759
+ if (!sessionId) return Promise.resolve(0);
19760
+ return Promise.resolve(
19761
+ countScalar(
19762
+ this.db,
19763
+ `SELECT count(*) AS n FROM findings f
19764
+ JOIN events e ON e.id = f.event_id
19765
+ WHERE json_extract(e.metadata, '$.sessionId') = :sessionId`,
19766
+ { sessionId }
19767
+ )
19768
+ );
19769
+ }
19770
+ /** Per-rule transcript firing tally for one session — reads the OTHER finding
19771
+ * store (inspection_findings, keyed to audit_events): every detection the
19772
+ * transcript pass recorded, counted per firing rather than per unique value.
19773
+ * Rides on session-scoped grouped responses so the findings view can
19774
+ * reconcile the Activity page's tally with the deduped groups it lists. */
19775
+ sessionFirings(sessionId) {
19776
+ return Object.fromEntries(
19777
+ countBy(
19778
+ this.db,
19779
+ `SELECT d.rule_id AS k, count(*) AS n
19780
+ FROM inspection_findings f
19781
+ JOIN audit_events e ON e.id = f.audit_event_id
19782
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
19783
+ WHERE e.root_session_id = :sessionId
19784
+ GROUP BY d.rule_id`,
19785
+ { sessionId }
19786
+ )
19787
+ );
19788
+ }
19554
19789
  /**
19555
- * Grouped findings for the dashboard — joins findings⋈events (repo/file
19556
- * from event metadata), groups by ruleId, computes per-filter-excluded facets,
19790
+ * Grouped findings for the dashboard — joins findings⋈events (repo/file/
19791
+ * toolName from event metadata), groups by ruleId, computes per-filter-excluded facets,
19557
19792
  * applies the requested filters, and sorts by severity then recency. Filtering
19558
19793
  * and faceting run in JS via the shared @akasecurity/schema helpers. `totals`
19559
19794
  * reflect the full filtered set; `items` is the requested
@@ -19570,11 +19805,16 @@ var SqliteFindingsRepository = class {
19570
19805
  * rule is ever restated in SQL.
19571
19806
  */
19572
19807
  listGroupedFindings(query) {
19573
- const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "");
19808
+ const sessionPredicate = query.sessionId ? `WHERE json_extract(e.metadata, '$.sessionId') = :sessionId` : "";
19809
+ const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
19810
+ const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
19811
+ predicate: sessionPredicate,
19812
+ params: sessionParams
19813
+ });
19574
19814
  const rows = allRows(
19575
19815
  this.db.prepare(
19576
19816
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
19577
- occurred_at, source_tool, repo, file, kind, finding_key, latest_status
19817
+ occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
19578
19818
  FROM (
19579
19819
  SELECT f.id AS id, f.rule_id AS rule_id, f.category AS category,
19580
19820
  f.severity AS severity, f.masked_match AS masked_match,
@@ -19582,6 +19822,7 @@ var SqliteFindingsRepository = class {
19582
19822
  e.occurred_at AS occurred_at, e.source_tool AS source_tool,
19583
19823
  json_extract(e.metadata, '$.repo') AS repo,
19584
19824
  json_extract(e.metadata, '$.filePath') AS file,
19825
+ json_extract(e.metadata, '$.toolName') AS tool_name,
19585
19826
  e.kind AS kind, f.finding_key AS finding_key,
19586
19827
  latest.status AS latest_status,
19587
19828
  ROW_NUMBER() OVER (
@@ -19592,11 +19833,12 @@ var SqliteFindingsRepository = class {
19592
19833
  JOIN events e ON e.id = f.event_id
19593
19834
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
19594
19835
  ON latest.finding_key = f.finding_key
19836
+ ${sessionPredicate}
19595
19837
  )
19596
19838
  WHERE rn <= :cap
19597
19839
  ORDER BY occurred_at DESC, id DESC`
19598
19840
  ),
19599
- { cap: PREVIEW_INSTANCES_PER_GROUP }
19841
+ { cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
19600
19842
  );
19601
19843
  const groupable = rows.map((r) => ({
19602
19844
  id: r.id,
@@ -19610,6 +19852,7 @@ var SqliteFindingsRepository = class {
19610
19852
  sourceTool: r.source_tool,
19611
19853
  repo: r.repo ?? "",
19612
19854
  file: r.file ?? "",
19855
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
19613
19856
  status: deriveInstanceStatus(r)
19614
19857
  }));
19615
19858
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -19628,7 +19871,13 @@ var SqliteFindingsRepository = class {
19628
19871
  };
19629
19872
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
19630
19873
  const items = sorted.slice(0, limit);
19631
- return Promise.resolve({ totals, facets, items, nextCursor: null });
19874
+ return Promise.resolve({
19875
+ totals,
19876
+ facets,
19877
+ items,
19878
+ nextCursor: null,
19879
+ ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
19880
+ });
19632
19881
  }
19633
19882
  /**
19634
19883
  * One row per rule_id, folding EVERY instance of the group into the values
@@ -19652,9 +19901,10 @@ var SqliteFindingsRepository = class {
19652
19901
  * would silently lose, so it is fetched only when the request actually
19653
19902
  * carries a `q`.
19654
19903
  */
19655
- groupAggregates(withSearchText) {
19904
+ groupAggregates(withSearchText, scope) {
19656
19905
  const searchTextColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.metadata, '$.repo')) AS repos,
19657
- group_concat(DISTINCT json_extract(e.metadata, '$.filePath')) AS files` : `, NULL AS repos, NULL AS files`;
19906
+ group_concat(DISTINCT json_extract(e.metadata, '$.filePath')) AS files,
19907
+ group_concat(DISTINCT 'via ' || json_extract(e.metadata, '$.toolName')) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
19658
19908
  const rows = this.db.prepare(
19659
19909
  `SELECT f.rule_id AS rule_id,
19660
19910
  count(*) AS instance_count,
@@ -19671,8 +19921,9 @@ var SqliteFindingsRepository = class {
19671
19921
  JOIN events e ON e.id = f.event_id
19672
19922
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
19673
19923
  ON latest.finding_key = f.finding_key
19924
+ ${scope.predicate}
19674
19925
  GROUP BY f.rule_id`
19675
- ).all();
19926
+ ).all(scope.params);
19676
19927
  return new Map(
19677
19928
  rows.map((r) => [
19678
19929
  r.rule_id,
@@ -19696,7 +19947,9 @@ var SqliteFindingsRepository = class {
19696
19947
  // Left undefined (not '') when unfetched, so buildFindingGroups can
19697
19948
  // tell "no q this request" from "a group with no repo/file at all"
19698
19949
  // and skip priming a haystack nothing will read.
19699
- ...withSearchText ? { searchText: [r.repos ?? "", r.files ?? ""].filter((s) => s !== "").join(" ") } : {}
19950
+ ...withSearchText ? {
19951
+ searchText: [r.repos ?? "", r.files ?? "", r.tool_names ?? ""].filter((s) => s !== "").join(" ")
19952
+ } : {}
19700
19953
  }
19701
19954
  ])
19702
19955
  );
@@ -21432,7 +21685,6 @@ var SqliteScanLedgerRepository = class {
21432
21685
  // ../../packages/persistence/src/repositories/security.ts
21433
21686
  var DAY_MS4 = 864e5;
21434
21687
  var SEVERITIES = ["critical", "high", "medium", "low"];
21435
- var RANGE_DAYS = { "7d": 7, "30d": 30, "3m": 90, "6m": 180 };
21436
21688
  var ACTION_TO_KIND = {
21437
21689
  block: "blocked",
21438
21690
  redact: "redacted",
@@ -21447,8 +21699,14 @@ var SCAN_COVERAGE = [
21447
21699
  { provider: "copilot", coverage: 0, supported: false },
21448
21700
  { provider: "api", coverage: 0, supported: false }
21449
21701
  ];
21702
+ var GRANULARITY = {
21703
+ "7d": "day",
21704
+ "30d": "day",
21705
+ "3m": "week",
21706
+ "6m": "week"
21707
+ };
21450
21708
  function granularityFor(range) {
21451
- return range === "7d" || range === "30d" ? "day" : "week";
21709
+ return GRANULARITY[range];
21452
21710
  }
21453
21711
  function startOfUtcDay2(ms) {
21454
21712
  return Math.floor(ms / DAY_MS4) * DAY_MS4;
@@ -22676,21 +22934,28 @@ import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as s
22676
22934
  import { homedir as homedir2 } from "os";
22677
22935
  import { basename as basename2, join as join7 } from "path";
22678
22936
 
22937
+ // ../../packages/detections/src/escape-regexp.ts
22938
+ function escapeRegExp(value) {
22939
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22940
+ }
22941
+
22942
+ // ../../packages/detections/src/matchers/limits.ts
22943
+ var MAX_MATCHES_PER_RULE = 1e4;
22944
+
22679
22945
  // ../../packages/detections/src/matchers/keyword.ts
22680
22946
  var KeywordMatcher2 = class {
22681
22947
  match(text, rule) {
22682
22948
  if (rule.matcher.type !== "keyword") return [];
22683
22949
  const { keywords, caseSensitive } = rule.matcher;
22684
- const haystack = caseSensitive ? text : text.toLowerCase();
22685
22950
  const spans = [];
22686
22951
  for (const kw of keywords) {
22687
- const needle = caseSensitive ? kw : kw.toLowerCase();
22688
- let idx = 0;
22689
- while (idx < haystack.length) {
22690
- const pos = haystack.indexOf(needle, idx);
22691
- if (pos === -1) break;
22692
- spans.push({ start: pos, end: pos + kw.length });
22693
- idx = pos + 1;
22952
+ if (kw.length === 0) continue;
22953
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
22954
+ const re = new RegExp(escapeRegExp(kw), caseSensitive ? "gu" : "giu");
22955
+ let m;
22956
+ while ((m = re.exec(text)) !== null) {
22957
+ spans.push({ start: m.index, end: m.index + m[0].length });
22958
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
22694
22959
  }
22695
22960
  }
22696
22961
  return spans;
@@ -22698,7 +22963,6 @@ var KeywordMatcher2 = class {
22698
22963
  };
22699
22964
 
22700
22965
  // ../../packages/detections/src/matchers/regex.ts
22701
- var MAX_MATCHES_PER_RULE = 1e4;
22702
22966
  var RegexMatcher2 = class {
22703
22967
  match(text, rule) {
22704
22968
  if (rule.matcher.type !== "regex") return [];
@@ -22784,9 +23048,6 @@ function registerPack(pack) {
22784
23048
  function getLoadedRules() {
22785
23049
  return [...packs.values()].flatMap((p) => p.rules);
22786
23050
  }
22787
- function escapeRegExp(value) {
22788
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22789
- }
22790
23051
  function isCorroborated(candidate, candidates, text) {
22791
23052
  const req = candidate.rule.requiresNearby;
22792
23053
  if (!req) return true;
@@ -23568,7 +23829,6 @@ var db_table_name_default = {
23568
23829
  "SELECT * FROM ",
23569
23830
  "SELECT COUNT(*) FROM ",
23570
23831
  "INSERT INTO ",
23571
- "UPDATE ",
23572
23832
  "DELETE FROM ",
23573
23833
  "CREATE TABLE ",
23574
23834
  "ALTER TABLE ",
@@ -25352,17 +25612,22 @@ function createPluginRuntime(gateway, settings, opts) {
25352
25612
  const fallback = DEFAULT_ACTIONS[category];
25353
25613
  return fallback ?? "log";
25354
25614
  }
25615
+ function actionForFinding(finding, excepted) {
25616
+ if (excepted?.has(finding)) return "allow";
25617
+ const action = resolveAction(finding.ruleId, finding.category);
25618
+ if (ENFORCEMENT_CEILING_ENABLED && policyMode === "warn" && (action === "block" || action === "redact")) {
25619
+ return "warn";
25620
+ }
25621
+ return action;
25622
+ }
25355
25623
  function decide(findings, text, excepted) {
25356
25624
  if (findings.length === 0) return { action: "log", text, findings: [] };
25357
- const actionFor = (finding) => excepted?.has(finding) ? "allow" : resolveAction(finding.ruleId, finding.category);
25625
+ const actionFor = (finding) => actionForFinding(finding, excepted);
25358
25626
  let worst = "log";
25359
25627
  for (const finding of findings) {
25360
25628
  const action = actionFor(finding);
25361
25629
  if (ACTION_PRIORITY.indexOf(action) < ACTION_PRIORITY.indexOf(worst)) worst = action;
25362
25630
  }
25363
- if (ENFORCEMENT_CEILING_ENABLED && policyMode === "warn" && (worst === "block" || worst === "redact")) {
25364
- return { action: "warn", text, findings };
25365
- }
25366
25631
  if (worst === "block") return { action: "block", text: null, findings };
25367
25632
  if (worst === "redact") {
25368
25633
  const redactFindings = findings.filter((f) => actionFor(f) === "redact");
@@ -25435,8 +25700,8 @@ function createPluginRuntime(gateway, settings, opts) {
25435
25700
  if (!key) return references;
25436
25701
  const seen = /* @__PURE__ */ new Set();
25437
25702
  for (const finding of decision.findings) {
25438
- const action = resolveAction(finding.ruleId, finding.category);
25439
- if (action !== "block" && action !== "redact" || excepted.has(finding)) continue;
25703
+ const action = actionForFinding(finding, excepted);
25704
+ if (action !== "block" && action !== "redact") continue;
25440
25705
  const fp = fingerprintOf(key, finding, fpCache);
25441
25706
  const pair = `${finding.ruleId}:${fp}`;
25442
25707
  if (seen.has(pair)) continue;
@@ -25525,7 +25790,7 @@ function createPluginRuntime(gateway, settings, opts) {
25525
25790
  severity: match.severity,
25526
25791
  span: match.span,
25527
25792
  maskedMatch,
25528
- actionTaken: excepted.has(match) ? "allow" : decision.action,
25793
+ actionTaken: actionForFinding(match, excepted),
25529
25794
  confidence: match.confidence,
25530
25795
  ...findingKey ? { findingKey } : {}
25531
25796
  };
@@ -25859,8 +26124,8 @@ var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
25859
26124
  import { readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
25860
26125
  import { homedir as homedir3 } from "os";
25861
26126
  import { join as join11 } from "path";
25862
- function transcriptsDir() {
25863
- return join11(homedir3(), ".claude", "projects");
26127
+ function transcriptsDir(home) {
26128
+ return join11(home ?? homedir3(), ".claude", "projects");
25864
26129
  }
25865
26130
  function isRecord(value) {
25866
26131
  return typeof value === "object" && value !== null;
@@ -25876,7 +26141,7 @@ function extractText(content) {
25876
26141
  }
25877
26142
  return parts.join("\n");
25878
26143
  }
25879
- function parseTranscript(jsonl, sinceMs = 0, beforeMs = Infinity) {
26144
+ function parseTranscript(jsonl, sinceMs = 0, beforeMs = Infinity, filePath = "") {
25880
26145
  const out = [];
25881
26146
  for (const line of jsonl.split("\n")) {
25882
26147
  const trimmed = line.trim();
@@ -25899,7 +26164,7 @@ function parseTranscript(jsonl, sinceMs = 0, beforeMs = Infinity) {
25899
26164
  if (!isRecord(message)) continue;
25900
26165
  const text = extractText(message.content);
25901
26166
  if (text.trim() === "") continue;
25902
- out.push({ kind: rec.type === "user" ? "prompt" : "response", text, occurredAt });
26167
+ out.push({ kind: rec.type === "user" ? "prompt" : "response", text, occurredAt, filePath });
25903
26168
  }
25904
26169
  return out;
25905
26170
  }
@@ -26118,13 +26383,14 @@ function* iterateFileContents(dir, excludeSessionId) {
26118
26383
  for (const file2 of files) {
26119
26384
  if (excludeSessionId !== void 0 && file2.slice(0, -".jsonl".length) === excludeSessionId)
26120
26385
  continue;
26386
+ const filePath = join11(projectDir, file2);
26121
26387
  let content;
26122
26388
  try {
26123
- content = readFileSync7(join11(projectDir, file2), "utf8");
26389
+ content = readFileSync7(filePath, "utf8");
26124
26390
  } catch {
26125
26391
  continue;
26126
26392
  }
26127
- yield content;
26393
+ yield { content, filePath };
26128
26394
  }
26129
26395
  }
26130
26396
  }
@@ -26135,12 +26401,15 @@ function windowStartMs(opts) {
26135
26401
  function* iterateHistory(opts = {}) {
26136
26402
  const sinceMs = windowStartMs(opts);
26137
26403
  const beforeMs = opts.beforeMs ?? Infinity;
26138
- for (const content of iterateFileContents(opts.dir ?? transcriptsDir(), opts.excludeSessionId))
26139
- yield* parseTranscript(content, sinceMs, beforeMs);
26404
+ for (const { content, filePath } of iterateFileContents(
26405
+ opts.dir ?? transcriptsDir(),
26406
+ opts.excludeSessionId
26407
+ ))
26408
+ yield* parseTranscript(content, sinceMs, beforeMs, filePath);
26140
26409
  }
26141
26410
  function* iterateUsageAndToolCalls(opts = {}) {
26142
26411
  const sinceMs = windowStartMs(opts);
26143
- for (const content of iterateFileContents(opts.dir ?? transcriptsDir())) {
26412
+ for (const { content } of iterateFileContents(opts.dir ?? transcriptsDir())) {
26144
26413
  yield {
26145
26414
  usage: parseTranscriptUsage(content, sinceMs),
26146
26415
  toolCalls: parseTranscriptToolCalls(content, sinceMs)
@@ -26162,7 +26431,7 @@ function redactOverlapping(rawContext, contextStart, others) {
26162
26431
  return safe;
26163
26432
  }
26164
26433
  }
26165
- function buildTriageHit(text, f, otherFindings = []) {
26434
+ function buildTriageHit(text, f, otherFindings = [], filePath = "") {
26166
26435
  const start = Math.max(0, f.span.start - CONTEXT_RADIUS);
26167
26436
  const end = Math.min(text.length, f.span.end + CONTEXT_RADIUS);
26168
26437
  const rawContext = text.slice(start, end);
@@ -26174,7 +26443,8 @@ function buildTriageHit(text, f, otherFindings = []) {
26174
26443
  maskedMatch: safeMaskedMatch(f.rawMatch),
26175
26444
  rawMatch: f.rawMatch,
26176
26445
  context: redactOverlapping(rawContext, start, overlapping),
26177
- confidence: f.confidence
26446
+ confidence: f.confidence,
26447
+ ...filePath !== "" ? { filePath } : {}
26178
26448
  };
26179
26449
  }
26180
26450
  async function scanHistory(config2, opts = {}, onHit) {
@@ -26215,7 +26485,7 @@ async function scanHistory(config2, opts = {}, onHit) {
26215
26485
  bySeverity[finding.severity] = (bySeverity[finding.severity] ?? 0) + 1;
26216
26486
  if (onHit) {
26217
26487
  const otherFindings = result.findings.filter((other) => other !== finding);
26218
- const hit = buildTriageHit(message.text, finding, otherFindings);
26488
+ const hit = buildTriageHit(message.text, finding, otherFindings, message.filePath);
26219
26489
  try {
26220
26490
  onHit(hit);
26221
26491
  } catch {
@@ -26483,6 +26753,7 @@ function fenced(body) {
26483
26753
  }
26484
26754
 
26485
26755
  // src/backfill.ts
26756
+ var TRIAGE_STATUSES = ["complete", "complete:no-history", "skipped:no-consent"];
26486
26757
  function triageSentinel(count, status) {
26487
26758
  return JSON.stringify({ done: true, count, status }) + "\n";
26488
26759
  }
@@ -26539,7 +26810,8 @@ async function runBackfill(deps) {
26539
26810
  } catch {
26540
26811
  }
26541
26812
  if (triage) {
26542
- io.stdout(triageSentinel(count, "complete"));
26813
+ const status = summary.scanned === 0 && summary.skipped === 0 ? "complete:no-history" : "complete";
26814
+ io.stdout(triageSentinel(count, status));
26543
26815
  } else {
26544
26816
  const heading = "\u2713 Historical scan complete";
26545
26817
  const scope = `Scanned ${String(summary.scanned)} messages from the last ${String(summary.windowDays)} days of Claude Code history.`;
@@ -26572,9 +26844,9 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
26572
26844
  process.exitCode = 1;
26573
26845
  }
26574
26846
  },
26575
- loadConfig,
26847
+ loadConfig: () => loadConfig(),
26576
26848
  scanHistory,
26577
- reconcileHistory,
26849
+ reconcileHistory: (cfg) => reconcileHistory(cfg),
26578
26850
  guard: {
26579
26851
  beforeMs: startedAt,
26580
26852
  ...sessionId ? { excludeSessionId: sessionId } : {}
@@ -26590,6 +26862,7 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
26590
26862
  process.exit(process.exitCode ?? 0);
26591
26863
  }
26592
26864
  export {
26865
+ TRIAGE_STATUSES,
26593
26866
  runBackfill,
26594
26867
  triageSentinel
26595
26868
  };