@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.
package/scripts/query.js CHANGED
@@ -538,6 +538,10 @@ var SQLITE_MIGRATIONS = [
538
538
  {
539
539
  tag: "0009_findings_path_expression_index",
540
540
  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"
541
+ },
542
+ {
543
+ tag: "0010_events_session_expression_index",
544
+ 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"
541
545
  }
542
546
  ];
543
547
 
@@ -641,6 +645,10 @@ var defaultCostModel = {
641
645
  };
642
646
 
643
647
  // ../../packages/schema/src/token/format.ts
648
+ var COMPACT = new Intl.NumberFormat("en-US", {
649
+ notation: "compact",
650
+ maximumFractionDigits: 1
651
+ });
644
652
  function formatUsd(value) {
645
653
  return value > 0 && value < 0.1 ? `$${value.toFixed(4)}` : `$${value.toFixed(2)}`;
646
654
  }
@@ -15373,6 +15381,12 @@ var FindingInstance = external_exports.object({
15373
15381
  provider: FindingProvider,
15374
15382
  repo: external_exports.string(),
15375
15383
  file: external_exports.string(),
15384
+ // Host tool that produced the scanned text (event metadata's toolName).
15385
+ // Present whenever the capturing hook recorded one — including
15386
+ // file-attributed captures (views prefer `file`); its display value is
15387
+ // the location fallback ("via Bash") when no filePath exists. Absent for
15388
+ // legacy rows and non-tool captures (prompts, worktree scans).
15389
+ toolName: external_exports.string().optional(),
15376
15390
  // Effective action: override.action ?? actionTaken, translated to FindingAction.
15377
15391
  action: FindingAction,
15378
15392
  detectedAt: external_exports.iso.datetime(),
@@ -15429,6 +15443,9 @@ var ListGroupedFindingsQuery = external_exports.object({
15429
15443
  provider: external_exports.array(FindingProvider).optional(),
15430
15444
  action: external_exports.array(FindingAction).optional(),
15431
15445
  q: external_exports.string().optional(),
15446
+ // Scope to findings whose event carries this session id (the Activity page's
15447
+ // session → findings drilldown). Findings without a session never match.
15448
+ sessionId: external_exports.string().optional(),
15432
15449
  groupBy: external_exports.literal("type").optional(),
15433
15450
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
15434
15451
  cursor: external_exports.string().optional()
@@ -15440,7 +15457,13 @@ var ListGroupedFindingsResponse = external_exports.object({
15440
15457
  }),
15441
15458
  facets: FindingFacets,
15442
15459
  items: external_exports.array(FindingGroup),
15443
- nextCursor: external_exports.string().nullable()
15460
+ nextCursor: external_exports.string().nullable(),
15461
+ // Present only on session-scoped queries (`sessionId` set): per ruleId, how
15462
+ // many times that rule fired in the session's persisted transcript. Findings
15463
+ // here are deduplicated to unique values while the transcript tally counts
15464
+ // every firing, so the two numbers legitimately differ — this map lets a
15465
+ // session-scoped view show both.
15466
+ sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
15444
15467
  }).meta({ id: "ListGroupedFindingsResponse" });
15445
15468
  var ApplyFindingActionRequest = external_exports.object({
15446
15469
  // 'quarantined' is system-assigned (see FindingAction) — clients may not set
@@ -15848,6 +15871,10 @@ var ListActivitySessionsQuery = external_exports.object({
15848
15871
  from: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
15849
15872
  /** Upper bound on startedAt; omitted defaults to now. */
15850
15873
  to: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
15874
+ /** Exclude zero-activity sessions — roots whose only recorded children are
15875
+ * bookkeeping rows (hooks, config scans), typically background `claude`
15876
+ * launches. Omitted = list everything. `z.stringbool()` per the note above. */
15877
+ excludeEmpty: external_exports.stringbool().optional(),
15851
15878
  /** Page size, 1–100; out-of-range values are a 400. `z.coerce` — query params arrive as strings. */
15852
15879
  limit: external_exports.coerce.number().int().min(1).max(100).default(50),
15853
15880
  /** Opaque pagination cursor (most-recent first). */
@@ -15856,7 +15883,11 @@ var ListActivitySessionsQuery = external_exports.object({
15856
15883
  var ListActivitySessionsResponse = external_exports.object({
15857
15884
  items: external_exports.array(ActivitySessionSummary),
15858
15885
  /** `null` once the last page is reached. */
15859
- nextCursor: external_exports.string().nullable()
15886
+ nextCursor: external_exports.string().nullable(),
15887
+ /** Zero-activity sessions matching the query's filters/range (whether or
15888
+ * not `excludeEmpty` dropped them from `items`) — the count a UI toggle
15889
+ * shows when collapsing them. */
15890
+ emptyCount: external_exports.number().int().nonnegative()
15860
15891
  }).meta({ id: "ListActivitySessionsResponse" });
15861
15892
  var ListSessionEventsQuery = external_exports.object({
15862
15893
  /** Default 100, range 1–500. */
@@ -15891,6 +15922,12 @@ var EventMetadata = external_exports.object({
15891
15922
  sessionId: external_exports.string().optional(),
15892
15923
  repo: external_exports.string().optional(),
15893
15924
  filePath: external_exports.string().optional(),
15925
+ // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
15926
+ // set by the tool-scanning hooks. The tool NAME only — never the tool's
15927
+ // arguments or output, which can carry the very value a finding masked
15928
+ // (metadata is stored unredacted). Gives findings on non-file captures a
15929
+ // display location ("via Bash") when no filePath exists.
15930
+ toolName: external_exports.string().optional(),
15894
15931
  // Set (true) by the worktree scanner when the file is excluded by the
15895
15932
  // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
15896
15933
  // generated code can leak real secrets — but the provenance is recorded so
@@ -16222,7 +16259,10 @@ var ExceptionBundleEntry = DetectionException.pick({
16222
16259
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
16223
16260
  var KeywordMatcher = external_exports.object({
16224
16261
  type: external_exports.literal("keyword"),
16225
- keywords: external_exports.array(external_exports.string()).min(1),
16262
+ // An empty keyword matches at every position, yielding one zero-length span
16263
+ // per character. Rejected here because a keyword that matches everything is
16264
+ // never intentional.
16265
+ keywords: external_exports.array(external_exports.string().min(1)).min(1),
16226
16266
  caseSensitive: external_exports.boolean().default(false)
16227
16267
  });
16228
16268
  function isValidRegex(pattern, flags) {
@@ -16471,6 +16511,10 @@ var ListEventsResponse = external_exports.object({
16471
16511
  items: external_exports.array(Event),
16472
16512
  nextCursor: external_exports.string().nullable()
16473
16513
  }).meta({ id: "ListEventsResponse" });
16514
+ var IngestResponse = external_exports.object({
16515
+ accepted: external_exports.number().int().nonnegative(),
16516
+ duplicates: external_exports.number().int().nonnegative()
16517
+ }).meta({ id: "IngestResponse" });
16474
16518
  var ListFindingsQuery = external_exports.object({
16475
16519
  cursor: external_exports.string().optional(),
16476
16520
  limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
@@ -16860,6 +16904,7 @@ function buildFindingGroups(rows, opts = {}) {
16860
16904
  provider: toApiProvider(r.sourceTool),
16861
16905
  repo: r.repo,
16862
16906
  file: r.file,
16907
+ ...r.toolName === void 0 ? {} : { toolName: r.toolName },
16863
16908
  action: toApiAction(effectiveDbAction),
16864
16909
  detectedAt: r.occurredAt,
16865
16910
  confidence: r.confidence,
@@ -16932,6 +16977,7 @@ function buildHaystack(g, extra) {
16932
16977
  g.id,
16933
16978
  ...g.instances.map((i) => i.repo),
16934
16979
  ...g.instances.map((i) => i.file),
16980
+ ...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
16935
16981
  ...g.instances.map((i) => i.id),
16936
16982
  ...extra === void 0 ? [] : [extra]
16937
16983
  ].join(" ").toLowerCase();
@@ -17198,6 +17244,71 @@ var ProjectFilesScan = external_exports.object({
17198
17244
  scannedAt: external_exports.string()
17199
17245
  });
17200
17246
 
17247
+ // ../../packages/schema/src/zod/ranges.ts
17248
+ var TIME_RANGES = ["7d", "30d", "3m", "6m"];
17249
+ var TimeRange = external_exports.enum(TIME_RANGES).meta({ id: "TimeRange" });
17250
+ var DEFAULT_TIME_RANGE = "7d";
17251
+ var RANGE_DAYS = {
17252
+ "7d": 7,
17253
+ "30d": 30,
17254
+ "3m": 90,
17255
+ "6m": 180
17256
+ };
17257
+ var TIME_RANGE_OR_DEFAULT = TimeRange.catch(DEFAULT_TIME_RANGE);
17258
+
17259
+ // ../../packages/schema/src/zod/remediation.ts
17260
+ var SecretFindingState = external_exports.enum(["still-valid", "unknown", "invalid"]);
17261
+ var MaskedFindingLocation = external_exports.object({
17262
+ filePath: external_exports.string(),
17263
+ span: Span.optional()
17264
+ }).strict();
17265
+ var MaskedSecretFinding = external_exports.object({
17266
+ provider: external_exports.string(),
17267
+ maskedToken: external_exports.string(),
17268
+ where: MaskedFindingLocation,
17269
+ state: SecretFindingState,
17270
+ observedAt: external_exports.iso.datetime().optional()
17271
+ }).strict();
17272
+ var RotationChecklistEntry = external_exports.object({
17273
+ provider: external_exports.string(),
17274
+ maskedToken: external_exports.string(),
17275
+ consolePath: external_exports.string(),
17276
+ occurrenceSpread: external_exports.number().int().positive()
17277
+ }).strict();
17278
+ var RemediationOption = external_exports.enum([
17279
+ "redact-rotation-checklist",
17280
+ "redact-only",
17281
+ "set-secret-redact",
17282
+ "leave"
17283
+ ]);
17284
+ var RemediationEntrySource = external_exports.enum(["first-run", "pre-push", "secret-scan"]);
17285
+ var RemediationEntryContext = external_exports.object({
17286
+ entrySource: RemediationEntrySource
17287
+ }).strict();
17288
+ var RemediationOptionChoice = external_exports.object({
17289
+ id: RemediationOption,
17290
+ label: external_exports.string()
17291
+ });
17292
+ var BatchedRemediationDecision = external_exports.object({
17293
+ kind: external_exports.literal("decision"),
17294
+ entrySource: RemediationEntrySource,
17295
+ secretCount: external_exports.number().int().positive(),
17296
+ prompt: external_exports.string(),
17297
+ options: external_exports.tuple([
17298
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-rotation-checklist") }),
17299
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-only") }),
17300
+ RemediationOptionChoice.extend({ id: external_exports.literal("set-secret-redact") }),
17301
+ RemediationOptionChoice.extend({ id: external_exports.literal("leave") })
17302
+ ])
17303
+ });
17304
+ var NoRemediationDecision = external_exports.object({
17305
+ kind: external_exports.literal("no-decision")
17306
+ });
17307
+ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
17308
+ BatchedRemediationDecision,
17309
+ NoRemediationDecision
17310
+ ]);
17311
+
17201
17312
  // ../../packages/schema/src/zod/rule-test.ts
17202
17313
  var TestRulesRequest = external_exports.object({
17203
17314
  rules: external_exports.array(Rule).min(1).max(100),
@@ -17256,10 +17367,8 @@ var SeveritySummaryResponse = external_exports.object({
17256
17367
  // All four severity levels are always present (count may be 0).
17257
17368
  bySeverity: external_exports.array(SeveritySummaryItem)
17258
17369
  }).meta({ id: "SeveritySummaryResponse" });
17259
- var SECURITY_RANGES = ["7d", "30d", "3m", "6m"];
17260
- var SecurityRange = external_exports.enum(SECURITY_RANGES).meta({ id: "SecurityRange" });
17261
17370
  var SecurityRangeQuery = external_exports.object({
17262
- range: external_exports.enum(SECURITY_RANGES).default("30d")
17371
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE)
17263
17372
  });
17264
17373
  var EnforcementActionKind = external_exports.enum(["blocked", "redacted", "warned"]).meta({ id: "EnforcementActionKind" });
17265
17374
  var EnforcementAction = external_exports.object({
@@ -17269,7 +17378,7 @@ var EnforcementAction = external_exports.object({
17269
17378
  delta: external_exports.number().int()
17270
17379
  }).meta({ id: "EnforcementAction" });
17271
17380
  var EnforcementActionsResponse = external_exports.object({
17272
- range: SecurityRange,
17381
+ range: TimeRange,
17273
17382
  // Sum of actions[].count in the window.
17274
17383
  total: external_exports.number().int().nonnegative(),
17275
17384
  // One entry per kind, always all three present (count may be 0).
@@ -17284,7 +17393,7 @@ var FindingsTimeseriesPoint = external_exports.object({
17284
17393
  medium: external_exports.number().int().nonnegative()
17285
17394
  }).meta({ id: "FindingsTimeseriesPoint" });
17286
17395
  var FindingsTimeseriesResponse = external_exports.object({
17287
- range: SecurityRange,
17396
+ range: TimeRange,
17288
17397
  granularity: TimeseriesGranularity,
17289
17398
  points: external_exports.array(FindingsTimeseriesPoint)
17290
17399
  }).meta({ id: "FindingsTimeseriesResponse" });
@@ -17299,7 +17408,7 @@ var MttrTrendPoint = external_exports.object({
17299
17408
  })
17300
17409
  }).meta({ id: "MttrTrendPoint" });
17301
17410
  var MttrTrendResponse = external_exports.object({
17302
- range: SecurityRange,
17411
+ range: TimeRange,
17303
17412
  granularity: TimeseriesGranularity,
17304
17413
  points: external_exports.array(MttrTrendPoint)
17305
17414
  }).meta({ id: "MttrTrendResponse" });
@@ -17326,11 +17435,11 @@ var TopSource = external_exports.object({
17326
17435
  findingsCount: external_exports.number().int().nonnegative()
17327
17436
  }).meta({ id: "TopSource" });
17328
17437
  var TopSourcesResponse = external_exports.object({
17329
- range: SecurityRange,
17438
+ range: TimeRange,
17330
17439
  items: external_exports.array(TopSource)
17331
17440
  }).meta({ id: "TopSourcesResponse" });
17332
17441
  var TopSourcesQuery = external_exports.object({
17333
- range: external_exports.enum(SECURITY_RANGES).default("30d"),
17442
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE),
17334
17443
  limit: external_exports.coerce.number().int().min(1).max(50).default(5),
17335
17444
  // Omit for both kinds.
17336
17445
  kind: external_exports.enum(SOURCE_KINDS).optional()
@@ -17343,7 +17452,7 @@ var ScanCoverageProvider = external_exports.object({
17343
17452
  supported: external_exports.boolean()
17344
17453
  }).meta({ id: "ScanCoverageProvider" });
17345
17454
  var ScanCoverageResponse = external_exports.object({
17346
- range: SecurityRange,
17455
+ range: TimeRange,
17347
17456
  providers: external_exports.array(ScanCoverageProvider)
17348
17457
  }).meta({ id: "ScanCoverageResponse" });
17349
17458
  var SubjectType = external_exports.enum(["repo", "user", "team", "policy", "share", "rule"]).meta({
@@ -17392,6 +17501,114 @@ var ApplyRecommendedActionResponse = external_exports.object({
17392
17501
  var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
17393
17502
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
17394
17503
 
17504
+ // ../../packages/schema/src/zod/triage.ts
17505
+ var TriageHit = external_exports.object({
17506
+ ruleId: external_exports.string(),
17507
+ category: DetectionCategory,
17508
+ severity: Severity,
17509
+ maskedMatch: external_exports.string(),
17510
+ rawMatch: external_exports.string(),
17511
+ context: external_exports.string(),
17512
+ filePath: external_exports.string().optional(),
17513
+ confidence: external_exports.number().min(0).max(1),
17514
+ id: external_exports.string().optional(),
17515
+ valueFingerprint: external_exports.string().optional(),
17516
+ keyVersion: external_exports.number().int().nonnegative().optional()
17517
+ });
17518
+ var TriagePolicy = BuiltinPolicyId;
17519
+ var TriageCategoryRec = external_exports.object({
17520
+ category: DetectionCategory,
17521
+ action: TriagePolicy,
17522
+ reasoning: external_exports.string(),
17523
+ genuineCount: external_exports.number().int().nonnegative(),
17524
+ fpCount: external_exports.number().int().nonnegative(),
17525
+ // TriageHit ids judged false-positive in this category. fpCount must equal
17526
+ // this array's length — enforced by the consumer, not this schema.
17527
+ fpIds: external_exports.array(external_exports.string())
17528
+ });
17529
+ var TriageRecommendation = external_exports.object({
17530
+ perCategory: external_exports.array(TriageCategoryRec),
17531
+ notes: external_exports.string()
17532
+ });
17533
+
17534
+ // ../../packages/schema/src/zod/setup-frame.ts
17535
+ var CalibrationCounts = external_exports.object({
17536
+ total: external_exports.number().int().nonnegative(),
17537
+ important: external_exports.number().int().nonnegative(),
17538
+ routine: external_exports.number().int().nonnegative()
17539
+ }).refine((c) => c.total === c.important + c.routine, {
17540
+ message: "total must equal important + routine",
17541
+ path: ["total"]
17542
+ });
17543
+ var FalsePositivePatternValue = external_exports.object({
17544
+ ruleId: external_exports.string(),
17545
+ category: DetectionCategory,
17546
+ valueFingerprint: external_exports.string(),
17547
+ keyVersion: external_exports.number().int().nonnegative()
17548
+ });
17549
+ var FalsePositivePatternGroup = external_exports.object({
17550
+ pattern: external_exports.string(),
17551
+ count: external_exports.number().int().nonnegative(),
17552
+ values: external_exports.array(FalsePositivePatternValue).min(1)
17553
+ });
17554
+ var CalibrationFindingKind = external_exports.object({
17555
+ category: DetectionCategory,
17556
+ count: external_exports.number().int().nonnegative(),
17557
+ egress: external_exports.boolean()
17558
+ });
17559
+ var CalibrationFrame = external_exports.object({
17560
+ counts: CalibrationCounts,
17561
+ routineCategories: external_exports.array(DetectionCategory),
17562
+ surfacedCategories: external_exports.array(DetectionCategory),
17563
+ findingKinds: external_exports.array(CalibrationFindingKind),
17564
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId),
17565
+ maskedFindings: external_exports.array(MaskedSecretFinding).optional(),
17566
+ falsePositivePatterns: external_exports.array(FalsePositivePatternGroup).optional()
17567
+ });
17568
+ var CalibrationPreviewCategory = TriageCategoryRec.pick({
17569
+ category: true,
17570
+ genuineCount: true,
17571
+ fpCount: true
17572
+ }).extend({
17573
+ egress: external_exports.boolean()
17574
+ });
17575
+ var CalibrationPreview = external_exports.object({
17576
+ categories: external_exports.array(CalibrationPreviewCategory),
17577
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId)
17578
+ });
17579
+ var CalibrationResult = external_exports.object({
17580
+ frame: CalibrationFrame,
17581
+ copy: external_exports.string()
17582
+ });
17583
+ var FirstRunCalibration = external_exports.enum(["scan", "floor"]);
17584
+ var SetupHandoffOption = external_exports.object({
17585
+ id: external_exports.enum(["enter-remediation", "open-dashboard", "not-now"]),
17586
+ label: external_exports.string()
17587
+ });
17588
+ var DashboardHandoffOptions = external_exports.tuple([
17589
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
17590
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
17591
+ ]);
17592
+ var ComposedRemediationOptions = external_exports.tuple([
17593
+ SetupHandoffOption.extend({ id: external_exports.literal("enter-remediation") }),
17594
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
17595
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
17596
+ ]);
17597
+ var SetupHandoffOffer = external_exports.object({
17598
+ worthALook: external_exports.number().int().nonnegative(),
17599
+ liveKeys: external_exports.number().int().nonnegative().optional(),
17600
+ options: external_exports.union([DashboardHandoffOptions, ComposedRemediationOptions])
17601
+ }).refine(
17602
+ (o) => o.options.some((opt) => opt.id === "enter-remediation") === (o.liveKeys ?? 0) > 0,
17603
+ {
17604
+ message: "the chain-entry option is present exactly when liveKeys > 0",
17605
+ path: ["options"]
17606
+ }
17607
+ ).refine((o) => (o.liveKeys ?? 0) <= o.worthALook, {
17608
+ message: "liveKeys is a subset of worthALook and cannot exceed it",
17609
+ path: ["liveKeys"]
17610
+ });
17611
+
17395
17612
  // ../../packages/schema/src/zod/shares.ts
17396
17613
  var DestinationKind = external_exports.enum(["provider", "internal", "ip"]).meta({ id: "DestinationKind" });
17397
17614
  var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp"]).meta({ id: "Transport" });
@@ -17577,36 +17794,6 @@ function reviewSeverityRank(reasons) {
17577
17794
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
17578
17795
  }
17579
17796
 
17580
- // ../../packages/schema/src/zod/triage.ts
17581
- var TriageHit = external_exports.object({
17582
- ruleId: external_exports.string(),
17583
- category: DetectionCategory,
17584
- severity: Severity,
17585
- maskedMatch: external_exports.string(),
17586
- rawMatch: external_exports.string(),
17587
- context: external_exports.string(),
17588
- filePath: external_exports.string().optional(),
17589
- confidence: external_exports.number().min(0).max(1),
17590
- id: external_exports.string().optional(),
17591
- valueFingerprint: external_exports.string().optional(),
17592
- keyVersion: external_exports.number().int().nonnegative().optional()
17593
- });
17594
- var TriagePolicy = BuiltinPolicyId;
17595
- var TriageCategoryRec = external_exports.object({
17596
- category: DetectionCategory,
17597
- action: TriagePolicy,
17598
- reasoning: external_exports.string(),
17599
- genuineCount: external_exports.number().int().nonnegative(),
17600
- fpCount: external_exports.number().int().nonnegative(),
17601
- // TriageHit ids judged false-positive in this category. fpCount must equal
17602
- // this array's length — enforced by the consumer, not this schema.
17603
- fpIds: external_exports.array(external_exports.string())
17604
- });
17605
- var TriageRecommendation = external_exports.object({
17606
- perCategory: external_exports.array(TriageCategoryRec),
17607
- notes: external_exports.string()
17608
- });
17609
-
17610
17797
  // ../../packages/persistence/src/internal/sql-text.ts
17611
17798
  function escapeLikePattern(s) {
17612
17799
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18180,6 +18367,10 @@ var TIMELINE_COLUMNS = `
18180
18367
  json_extract(attributes, '$.internal') AS internal,
18181
18368
  json_extract(attributes, '$.flagged') AS flagged`;
18182
18369
  var SESSION_ROOT = `event_type = 'session'`;
18370
+ var HAS_ACTIVITY = `EXISTS (
18371
+ SELECT 1 FROM audit_events c
18372
+ WHERE c.root_session_id = audit_events.id
18373
+ AND c.event_type NOT IN ('hook', 'config_scan'))`;
18183
18374
  var SqliteActivityRepository = class {
18184
18375
  constructor(db, now = () => Date.now()) {
18185
18376
  this.db = db;
@@ -18193,7 +18384,8 @@ var SqliteActivityRepository = class {
18193
18384
  const sessionsToday = countScalar(
18194
18385
  this.db,
18195
18386
  `SELECT count(*) AS n FROM audit_events
18196
- WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?`,
18387
+ WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?
18388
+ AND ${HAS_ACTIVITY}`,
18197
18389
  [startMs, endMs]
18198
18390
  );
18199
18391
  const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
@@ -18265,6 +18457,13 @@ var SqliteActivityRepository = class {
18265
18457
  );
18266
18458
  params.push(pattern, pattern, pattern, pattern, pattern, pattern);
18267
18459
  }
18460
+ const emptyCount = countScalar(
18461
+ this.db,
18462
+ `SELECT count(*) AS n FROM audit_events
18463
+ WHERE ${[...conditions, `NOT ${HAS_ACTIVITY}`].join(" AND ")}`,
18464
+ params
18465
+ );
18466
+ if (query.excludeEmpty) conditions.push(HAS_ACTIVITY);
18268
18467
  if (cursor) {
18269
18468
  conditions.push("(started_at < ? OR (started_at = ? AND id < ?))");
18270
18469
  params.push(cursor.startedAtMs, cursor.startedAtMs, cursor.id);
@@ -18301,7 +18500,7 @@ var SqliteActivityRepository = class {
18301
18500
  );
18302
18501
  const last = page[page.length - 1];
18303
18502
  const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
18304
- return Promise.resolve({ items, nextCursor });
18503
+ return Promise.resolve({ items, nextCursor, emptyCount });
18305
18504
  }
18306
18505
  getSession(sessionId) {
18307
18506
  const rootRow = getRow(
@@ -19607,9 +19806,43 @@ var SqliteFindingsRepository = class {
19607
19806
  }))
19608
19807
  );
19609
19808
  }
19809
+ /** Live-enforced findings recorded for one session — a bare COUNT over the
19810
+ * session-stamped events (served by idx_events_session_id), so the Activity
19811
+ * page can label its findings link without the grouped pipeline. */
19812
+ sessionFindingsCount(sessionId) {
19813
+ if (!sessionId) return Promise.resolve(0);
19814
+ return Promise.resolve(
19815
+ countScalar(
19816
+ this.db,
19817
+ `SELECT count(*) AS n FROM findings f
19818
+ JOIN events e ON e.id = f.event_id
19819
+ WHERE json_extract(e.metadata, '$.sessionId') = :sessionId`,
19820
+ { sessionId }
19821
+ )
19822
+ );
19823
+ }
19824
+ /** Per-rule transcript firing tally for one session — reads the OTHER finding
19825
+ * store (inspection_findings, keyed to audit_events): every detection the
19826
+ * transcript pass recorded, counted per firing rather than per unique value.
19827
+ * Rides on session-scoped grouped responses so the findings view can
19828
+ * reconcile the Activity page's tally with the deduped groups it lists. */
19829
+ sessionFirings(sessionId) {
19830
+ return Object.fromEntries(
19831
+ countBy(
19832
+ this.db,
19833
+ `SELECT d.rule_id AS k, count(*) AS n
19834
+ FROM inspection_findings f
19835
+ JOIN audit_events e ON e.id = f.audit_event_id
19836
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
19837
+ WHERE e.root_session_id = :sessionId
19838
+ GROUP BY d.rule_id`,
19839
+ { sessionId }
19840
+ )
19841
+ );
19842
+ }
19610
19843
  /**
19611
- * Grouped findings for the dashboard — joins findings⋈events (repo/file
19612
- * from event metadata), groups by ruleId, computes per-filter-excluded facets,
19844
+ * Grouped findings for the dashboard — joins findings⋈events (repo/file/
19845
+ * toolName from event metadata), groups by ruleId, computes per-filter-excluded facets,
19613
19846
  * applies the requested filters, and sorts by severity then recency. Filtering
19614
19847
  * and faceting run in JS via the shared @akasecurity/schema helpers. `totals`
19615
19848
  * reflect the full filtered set; `items` is the requested
@@ -19626,11 +19859,16 @@ var SqliteFindingsRepository = class {
19626
19859
  * rule is ever restated in SQL.
19627
19860
  */
19628
19861
  listGroupedFindings(query) {
19629
- const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "");
19862
+ const sessionPredicate = query.sessionId ? `WHERE json_extract(e.metadata, '$.sessionId') = :sessionId` : "";
19863
+ const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
19864
+ const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
19865
+ predicate: sessionPredicate,
19866
+ params: sessionParams
19867
+ });
19630
19868
  const rows = allRows(
19631
19869
  this.db.prepare(
19632
19870
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
19633
- occurred_at, source_tool, repo, file, kind, finding_key, latest_status
19871
+ occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
19634
19872
  FROM (
19635
19873
  SELECT f.id AS id, f.rule_id AS rule_id, f.category AS category,
19636
19874
  f.severity AS severity, f.masked_match AS masked_match,
@@ -19638,6 +19876,7 @@ var SqliteFindingsRepository = class {
19638
19876
  e.occurred_at AS occurred_at, e.source_tool AS source_tool,
19639
19877
  json_extract(e.metadata, '$.repo') AS repo,
19640
19878
  json_extract(e.metadata, '$.filePath') AS file,
19879
+ json_extract(e.metadata, '$.toolName') AS tool_name,
19641
19880
  e.kind AS kind, f.finding_key AS finding_key,
19642
19881
  latest.status AS latest_status,
19643
19882
  ROW_NUMBER() OVER (
@@ -19648,11 +19887,12 @@ var SqliteFindingsRepository = class {
19648
19887
  JOIN events e ON e.id = f.event_id
19649
19888
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
19650
19889
  ON latest.finding_key = f.finding_key
19890
+ ${sessionPredicate}
19651
19891
  )
19652
19892
  WHERE rn <= :cap
19653
19893
  ORDER BY occurred_at DESC, id DESC`
19654
19894
  ),
19655
- { cap: PREVIEW_INSTANCES_PER_GROUP }
19895
+ { cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
19656
19896
  );
19657
19897
  const groupable = rows.map((r) => ({
19658
19898
  id: r.id,
@@ -19666,6 +19906,7 @@ var SqliteFindingsRepository = class {
19666
19906
  sourceTool: r.source_tool,
19667
19907
  repo: r.repo ?? "",
19668
19908
  file: r.file ?? "",
19909
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
19669
19910
  status: deriveInstanceStatus(r)
19670
19911
  }));
19671
19912
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -19684,7 +19925,13 @@ var SqliteFindingsRepository = class {
19684
19925
  };
19685
19926
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
19686
19927
  const items = sorted.slice(0, limit);
19687
- return Promise.resolve({ totals, facets, items, nextCursor: null });
19928
+ return Promise.resolve({
19929
+ totals,
19930
+ facets,
19931
+ items,
19932
+ nextCursor: null,
19933
+ ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
19934
+ });
19688
19935
  }
19689
19936
  /**
19690
19937
  * One row per rule_id, folding EVERY instance of the group into the values
@@ -19708,9 +19955,10 @@ var SqliteFindingsRepository = class {
19708
19955
  * would silently lose, so it is fetched only when the request actually
19709
19956
  * carries a `q`.
19710
19957
  */
19711
- groupAggregates(withSearchText) {
19958
+ groupAggregates(withSearchText, scope) {
19712
19959
  const searchTextColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.metadata, '$.repo')) AS repos,
19713
- group_concat(DISTINCT json_extract(e.metadata, '$.filePath')) AS files` : `, NULL AS repos, NULL AS files`;
19960
+ group_concat(DISTINCT json_extract(e.metadata, '$.filePath')) AS files,
19961
+ group_concat(DISTINCT 'via ' || json_extract(e.metadata, '$.toolName')) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
19714
19962
  const rows = this.db.prepare(
19715
19963
  `SELECT f.rule_id AS rule_id,
19716
19964
  count(*) AS instance_count,
@@ -19727,8 +19975,9 @@ var SqliteFindingsRepository = class {
19727
19975
  JOIN events e ON e.id = f.event_id
19728
19976
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
19729
19977
  ON latest.finding_key = f.finding_key
19978
+ ${scope.predicate}
19730
19979
  GROUP BY f.rule_id`
19731
- ).all();
19980
+ ).all(scope.params);
19732
19981
  return new Map(
19733
19982
  rows.map((r) => [
19734
19983
  r.rule_id,
@@ -19752,7 +20001,9 @@ var SqliteFindingsRepository = class {
19752
20001
  // Left undefined (not '') when unfetched, so buildFindingGroups can
19753
20002
  // tell "no q this request" from "a group with no repo/file at all"
19754
20003
  // and skip priming a haystack nothing will read.
19755
- ...withSearchText ? { searchText: [r.repos ?? "", r.files ?? ""].filter((s) => s !== "").join(" ") } : {}
20004
+ ...withSearchText ? {
20005
+ searchText: [r.repos ?? "", r.files ?? "", r.tool_names ?? ""].filter((s) => s !== "").join(" ")
20006
+ } : {}
19756
20007
  }
19757
20008
  ])
19758
20009
  );
@@ -21488,7 +21739,6 @@ var SqliteScanLedgerRepository = class {
21488
21739
  // ../../packages/persistence/src/repositories/security.ts
21489
21740
  var DAY_MS4 = 864e5;
21490
21741
  var SEVERITIES = ["critical", "high", "medium", "low"];
21491
- var RANGE_DAYS = { "7d": 7, "30d": 30, "3m": 90, "6m": 180 };
21492
21742
  var ACTION_TO_KIND = {
21493
21743
  block: "blocked",
21494
21744
  redact: "redacted",
@@ -21503,8 +21753,14 @@ var SCAN_COVERAGE = [
21503
21753
  { provider: "copilot", coverage: 0, supported: false },
21504
21754
  { provider: "api", coverage: 0, supported: false }
21505
21755
  ];
21756
+ var GRANULARITY = {
21757
+ "7d": "day",
21758
+ "30d": "day",
21759
+ "3m": "week",
21760
+ "6m": "week"
21761
+ };
21506
21762
  function granularityFor(range) {
21507
- return range === "7d" || range === "30d" ? "day" : "week";
21763
+ return GRANULARITY[range];
21508
21764
  }
21509
21765
  function startOfUtcDay2(ms) {
21510
21766
  return Math.floor(ms / DAY_MS4) * DAY_MS4;
@@ -22694,21 +22950,28 @@ import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as s
22694
22950
  import { homedir as homedir2 } from "os";
22695
22951
  import { basename as basename2, join as join7 } from "path";
22696
22952
 
22953
+ // ../../packages/detections/src/escape-regexp.ts
22954
+ function escapeRegExp(value) {
22955
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22956
+ }
22957
+
22958
+ // ../../packages/detections/src/matchers/limits.ts
22959
+ var MAX_MATCHES_PER_RULE = 1e4;
22960
+
22697
22961
  // ../../packages/detections/src/matchers/keyword.ts
22698
22962
  var KeywordMatcher2 = class {
22699
22963
  match(text, rule) {
22700
22964
  if (rule.matcher.type !== "keyword") return [];
22701
22965
  const { keywords, caseSensitive } = rule.matcher;
22702
- const haystack = caseSensitive ? text : text.toLowerCase();
22703
22966
  const spans = [];
22704
22967
  for (const kw of keywords) {
22705
- const needle = caseSensitive ? kw : kw.toLowerCase();
22706
- let idx = 0;
22707
- while (idx < haystack.length) {
22708
- const pos = haystack.indexOf(needle, idx);
22709
- if (pos === -1) break;
22710
- spans.push({ start: pos, end: pos + kw.length });
22711
- idx = pos + 1;
22968
+ if (kw.length === 0) continue;
22969
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
22970
+ const re = new RegExp(escapeRegExp(kw), caseSensitive ? "gu" : "giu");
22971
+ let m;
22972
+ while ((m = re.exec(text)) !== null) {
22973
+ spans.push({ start: m.index, end: m.index + m[0].length });
22974
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
22712
22975
  }
22713
22976
  }
22714
22977
  return spans;
@@ -22716,7 +22979,6 @@ var KeywordMatcher2 = class {
22716
22979
  };
22717
22980
 
22718
22981
  // ../../packages/detections/src/matchers/regex.ts
22719
- var MAX_MATCHES_PER_RULE = 1e4;
22720
22982
  var RegexMatcher2 = class {
22721
22983
  match(text, rule) {
22722
22984
  if (rule.matcher.type !== "regex") return [];
@@ -23389,7 +23651,6 @@ var db_table_name_default = {
23389
23651
  "SELECT * FROM ",
23390
23652
  "SELECT COUNT(*) FROM ",
23391
23653
  "INSERT INTO ",
23392
- "UPDATE ",
23393
23654
  "DELETE FROM ",
23394
23655
  "CREATE TABLE ",
23395
23656
  "ALTER TABLE ",
@@ -25268,6 +25529,11 @@ function fenced(body) {
25268
25529
  return [fence, body, fence].join("\n");
25269
25530
  }
25270
25531
 
25532
+ // src/command-registry.ts
25533
+ import { readdirSync as readdirSync3 } from "fs";
25534
+ import { fileURLToPath } from "url";
25535
+ var COMMANDS_DIR = fileURLToPath(new URL("../commands", import.meta.url));
25536
+
25271
25537
  // src/render.ts
25272
25538
  var SEVERITY_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
25273
25539
  var SEVERITY_GLYPH = {