@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.
@@ -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
 
@@ -640,6 +644,12 @@ var defaultCostModel = {
640
644
  }
641
645
  };
642
646
 
647
+ // ../../packages/schema/src/token/format.ts
648
+ var COMPACT = new Intl.NumberFormat("en-US", {
649
+ notation: "compact",
650
+ maximumFractionDigits: 1
651
+ });
652
+
643
653
  // ../../packages/schema/src/token/token-report.ts
644
654
  var num = (value) => value ?? 0;
645
655
  function costUsageOf(a) {
@@ -15311,6 +15321,12 @@ var FindingInstance = external_exports.object({
15311
15321
  provider: FindingProvider,
15312
15322
  repo: external_exports.string(),
15313
15323
  file: external_exports.string(),
15324
+ // Host tool that produced the scanned text (event metadata's toolName).
15325
+ // Present whenever the capturing hook recorded one — including
15326
+ // file-attributed captures (views prefer `file`); its display value is
15327
+ // the location fallback ("via Bash") when no filePath exists. Absent for
15328
+ // legacy rows and non-tool captures (prompts, worktree scans).
15329
+ toolName: external_exports.string().optional(),
15314
15330
  // Effective action: override.action ?? actionTaken, translated to FindingAction.
15315
15331
  action: FindingAction,
15316
15332
  detectedAt: external_exports.iso.datetime(),
@@ -15367,6 +15383,9 @@ var ListGroupedFindingsQuery = external_exports.object({
15367
15383
  provider: external_exports.array(FindingProvider).optional(),
15368
15384
  action: external_exports.array(FindingAction).optional(),
15369
15385
  q: external_exports.string().optional(),
15386
+ // Scope to findings whose event carries this session id (the Activity page's
15387
+ // session → findings drilldown). Findings without a session never match.
15388
+ sessionId: external_exports.string().optional(),
15370
15389
  groupBy: external_exports.literal("type").optional(),
15371
15390
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
15372
15391
  cursor: external_exports.string().optional()
@@ -15378,7 +15397,13 @@ var ListGroupedFindingsResponse = external_exports.object({
15378
15397
  }),
15379
15398
  facets: FindingFacets,
15380
15399
  items: external_exports.array(FindingGroup),
15381
- nextCursor: external_exports.string().nullable()
15400
+ nextCursor: external_exports.string().nullable(),
15401
+ // Present only on session-scoped queries (`sessionId` set): per ruleId, how
15402
+ // many times that rule fired in the session's persisted transcript. Findings
15403
+ // here are deduplicated to unique values while the transcript tally counts
15404
+ // every firing, so the two numbers legitimately differ — this map lets a
15405
+ // session-scoped view show both.
15406
+ sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
15382
15407
  }).meta({ id: "ListGroupedFindingsResponse" });
15383
15408
  var ApplyFindingActionRequest = external_exports.object({
15384
15409
  // 'quarantined' is system-assigned (see FindingAction) — clients may not set
@@ -15786,6 +15811,10 @@ var ListActivitySessionsQuery = external_exports.object({
15786
15811
  from: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
15787
15812
  /** Upper bound on startedAt; omitted defaults to now. */
15788
15813
  to: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
15814
+ /** Exclude zero-activity sessions — roots whose only recorded children are
15815
+ * bookkeeping rows (hooks, config scans), typically background `claude`
15816
+ * launches. Omitted = list everything. `z.stringbool()` per the note above. */
15817
+ excludeEmpty: external_exports.stringbool().optional(),
15789
15818
  /** Page size, 1–100; out-of-range values are a 400. `z.coerce` — query params arrive as strings. */
15790
15819
  limit: external_exports.coerce.number().int().min(1).max(100).default(50),
15791
15820
  /** Opaque pagination cursor (most-recent first). */
@@ -15794,7 +15823,11 @@ var ListActivitySessionsQuery = external_exports.object({
15794
15823
  var ListActivitySessionsResponse = external_exports.object({
15795
15824
  items: external_exports.array(ActivitySessionSummary),
15796
15825
  /** `null` once the last page is reached. */
15797
- nextCursor: external_exports.string().nullable()
15826
+ nextCursor: external_exports.string().nullable(),
15827
+ /** Zero-activity sessions matching the query's filters/range (whether or
15828
+ * not `excludeEmpty` dropped them from `items`) — the count a UI toggle
15829
+ * shows when collapsing them. */
15830
+ emptyCount: external_exports.number().int().nonnegative()
15798
15831
  }).meta({ id: "ListActivitySessionsResponse" });
15799
15832
  var ListSessionEventsQuery = external_exports.object({
15800
15833
  /** Default 100, range 1–500. */
@@ -15829,6 +15862,12 @@ var EventMetadata = external_exports.object({
15829
15862
  sessionId: external_exports.string().optional(),
15830
15863
  repo: external_exports.string().optional(),
15831
15864
  filePath: external_exports.string().optional(),
15865
+ // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
15866
+ // set by the tool-scanning hooks. The tool NAME only — never the tool's
15867
+ // arguments or output, which can carry the very value a finding masked
15868
+ // (metadata is stored unredacted). Gives findings on non-file captures a
15869
+ // display location ("via Bash") when no filePath exists.
15870
+ toolName: external_exports.string().optional(),
15832
15871
  // Set (true) by the worktree scanner when the file is excluded by the
15833
15872
  // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
15834
15873
  // generated code can leak real secrets — but the provenance is recorded so
@@ -16160,7 +16199,10 @@ var ExceptionBundleEntry = DetectionException.pick({
16160
16199
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
16161
16200
  var KeywordMatcher = external_exports.object({
16162
16201
  type: external_exports.literal("keyword"),
16163
- keywords: external_exports.array(external_exports.string()).min(1),
16202
+ // An empty keyword matches at every position, yielding one zero-length span
16203
+ // per character. Rejected here because a keyword that matches everything is
16204
+ // never intentional.
16205
+ keywords: external_exports.array(external_exports.string().min(1)).min(1),
16164
16206
  caseSensitive: external_exports.boolean().default(false)
16165
16207
  });
16166
16208
  function isValidRegex(pattern, flags) {
@@ -16409,6 +16451,10 @@ var ListEventsResponse = external_exports.object({
16409
16451
  items: external_exports.array(Event),
16410
16452
  nextCursor: external_exports.string().nullable()
16411
16453
  }).meta({ id: "ListEventsResponse" });
16454
+ var IngestResponse = external_exports.object({
16455
+ accepted: external_exports.number().int().nonnegative(),
16456
+ duplicates: external_exports.number().int().nonnegative()
16457
+ }).meta({ id: "IngestResponse" });
16412
16458
  var ListFindingsQuery = external_exports.object({
16413
16459
  cursor: external_exports.string().optional(),
16414
16460
  limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
@@ -16798,6 +16844,7 @@ function buildFindingGroups(rows, opts = {}) {
16798
16844
  provider: toApiProvider(r.sourceTool),
16799
16845
  repo: r.repo,
16800
16846
  file: r.file,
16847
+ ...r.toolName === void 0 ? {} : { toolName: r.toolName },
16801
16848
  action: toApiAction(effectiveDbAction),
16802
16849
  detectedAt: r.occurredAt,
16803
16850
  confidence: r.confidence,
@@ -16870,6 +16917,7 @@ function buildHaystack(g, extra) {
16870
16917
  g.id,
16871
16918
  ...g.instances.map((i) => i.repo),
16872
16919
  ...g.instances.map((i) => i.file),
16920
+ ...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
16873
16921
  ...g.instances.map((i) => i.id),
16874
16922
  ...extra === void 0 ? [] : [extra]
16875
16923
  ].join(" ").toLowerCase();
@@ -17136,6 +17184,71 @@ var ProjectFilesScan = external_exports.object({
17136
17184
  scannedAt: external_exports.string()
17137
17185
  });
17138
17186
 
17187
+ // ../../packages/schema/src/zod/ranges.ts
17188
+ var TIME_RANGES = ["7d", "30d", "3m", "6m"];
17189
+ var TimeRange = external_exports.enum(TIME_RANGES).meta({ id: "TimeRange" });
17190
+ var DEFAULT_TIME_RANGE = "7d";
17191
+ var RANGE_DAYS = {
17192
+ "7d": 7,
17193
+ "30d": 30,
17194
+ "3m": 90,
17195
+ "6m": 180
17196
+ };
17197
+ var TIME_RANGE_OR_DEFAULT = TimeRange.catch(DEFAULT_TIME_RANGE);
17198
+
17199
+ // ../../packages/schema/src/zod/remediation.ts
17200
+ var SecretFindingState = external_exports.enum(["still-valid", "unknown", "invalid"]);
17201
+ var MaskedFindingLocation = external_exports.object({
17202
+ filePath: external_exports.string(),
17203
+ span: Span.optional()
17204
+ }).strict();
17205
+ var MaskedSecretFinding = external_exports.object({
17206
+ provider: external_exports.string(),
17207
+ maskedToken: external_exports.string(),
17208
+ where: MaskedFindingLocation,
17209
+ state: SecretFindingState,
17210
+ observedAt: external_exports.iso.datetime().optional()
17211
+ }).strict();
17212
+ var RotationChecklistEntry = external_exports.object({
17213
+ provider: external_exports.string(),
17214
+ maskedToken: external_exports.string(),
17215
+ consolePath: external_exports.string(),
17216
+ occurrenceSpread: external_exports.number().int().positive()
17217
+ }).strict();
17218
+ var RemediationOption = external_exports.enum([
17219
+ "redact-rotation-checklist",
17220
+ "redact-only",
17221
+ "set-secret-redact",
17222
+ "leave"
17223
+ ]);
17224
+ var RemediationEntrySource = external_exports.enum(["first-run", "pre-push", "secret-scan"]);
17225
+ var RemediationEntryContext = external_exports.object({
17226
+ entrySource: RemediationEntrySource
17227
+ }).strict();
17228
+ var RemediationOptionChoice = external_exports.object({
17229
+ id: RemediationOption,
17230
+ label: external_exports.string()
17231
+ });
17232
+ var BatchedRemediationDecision = external_exports.object({
17233
+ kind: external_exports.literal("decision"),
17234
+ entrySource: RemediationEntrySource,
17235
+ secretCount: external_exports.number().int().positive(),
17236
+ prompt: external_exports.string(),
17237
+ options: external_exports.tuple([
17238
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-rotation-checklist") }),
17239
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-only") }),
17240
+ RemediationOptionChoice.extend({ id: external_exports.literal("set-secret-redact") }),
17241
+ RemediationOptionChoice.extend({ id: external_exports.literal("leave") })
17242
+ ])
17243
+ });
17244
+ var NoRemediationDecision = external_exports.object({
17245
+ kind: external_exports.literal("no-decision")
17246
+ });
17247
+ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
17248
+ BatchedRemediationDecision,
17249
+ NoRemediationDecision
17250
+ ]);
17251
+
17139
17252
  // ../../packages/schema/src/zod/rule-test.ts
17140
17253
  var TestRulesRequest = external_exports.object({
17141
17254
  rules: external_exports.array(Rule).min(1).max(100),
@@ -17194,10 +17307,8 @@ var SeveritySummaryResponse = external_exports.object({
17194
17307
  // All four severity levels are always present (count may be 0).
17195
17308
  bySeverity: external_exports.array(SeveritySummaryItem)
17196
17309
  }).meta({ id: "SeveritySummaryResponse" });
17197
- var SECURITY_RANGES = ["7d", "30d", "3m", "6m"];
17198
- var SecurityRange = external_exports.enum(SECURITY_RANGES).meta({ id: "SecurityRange" });
17199
17310
  var SecurityRangeQuery = external_exports.object({
17200
- range: external_exports.enum(SECURITY_RANGES).default("30d")
17311
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE)
17201
17312
  });
17202
17313
  var EnforcementActionKind = external_exports.enum(["blocked", "redacted", "warned"]).meta({ id: "EnforcementActionKind" });
17203
17314
  var EnforcementAction = external_exports.object({
@@ -17207,7 +17318,7 @@ var EnforcementAction = external_exports.object({
17207
17318
  delta: external_exports.number().int()
17208
17319
  }).meta({ id: "EnforcementAction" });
17209
17320
  var EnforcementActionsResponse = external_exports.object({
17210
- range: SecurityRange,
17321
+ range: TimeRange,
17211
17322
  // Sum of actions[].count in the window.
17212
17323
  total: external_exports.number().int().nonnegative(),
17213
17324
  // One entry per kind, always all three present (count may be 0).
@@ -17222,7 +17333,7 @@ var FindingsTimeseriesPoint = external_exports.object({
17222
17333
  medium: external_exports.number().int().nonnegative()
17223
17334
  }).meta({ id: "FindingsTimeseriesPoint" });
17224
17335
  var FindingsTimeseriesResponse = external_exports.object({
17225
- range: SecurityRange,
17336
+ range: TimeRange,
17226
17337
  granularity: TimeseriesGranularity,
17227
17338
  points: external_exports.array(FindingsTimeseriesPoint)
17228
17339
  }).meta({ id: "FindingsTimeseriesResponse" });
@@ -17237,7 +17348,7 @@ var MttrTrendPoint = external_exports.object({
17237
17348
  })
17238
17349
  }).meta({ id: "MttrTrendPoint" });
17239
17350
  var MttrTrendResponse = external_exports.object({
17240
- range: SecurityRange,
17351
+ range: TimeRange,
17241
17352
  granularity: TimeseriesGranularity,
17242
17353
  points: external_exports.array(MttrTrendPoint)
17243
17354
  }).meta({ id: "MttrTrendResponse" });
@@ -17264,11 +17375,11 @@ var TopSource = external_exports.object({
17264
17375
  findingsCount: external_exports.number().int().nonnegative()
17265
17376
  }).meta({ id: "TopSource" });
17266
17377
  var TopSourcesResponse = external_exports.object({
17267
- range: SecurityRange,
17378
+ range: TimeRange,
17268
17379
  items: external_exports.array(TopSource)
17269
17380
  }).meta({ id: "TopSourcesResponse" });
17270
17381
  var TopSourcesQuery = external_exports.object({
17271
- range: external_exports.enum(SECURITY_RANGES).default("30d"),
17382
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE),
17272
17383
  limit: external_exports.coerce.number().int().min(1).max(50).default(5),
17273
17384
  // Omit for both kinds.
17274
17385
  kind: external_exports.enum(SOURCE_KINDS).optional()
@@ -17281,7 +17392,7 @@ var ScanCoverageProvider = external_exports.object({
17281
17392
  supported: external_exports.boolean()
17282
17393
  }).meta({ id: "ScanCoverageProvider" });
17283
17394
  var ScanCoverageResponse = external_exports.object({
17284
- range: SecurityRange,
17395
+ range: TimeRange,
17285
17396
  providers: external_exports.array(ScanCoverageProvider)
17286
17397
  }).meta({ id: "ScanCoverageResponse" });
17287
17398
  var SubjectType = external_exports.enum(["repo", "user", "team", "policy", "share", "rule"]).meta({
@@ -17330,6 +17441,114 @@ var ApplyRecommendedActionResponse = external_exports.object({
17330
17441
  var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
17331
17442
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
17332
17443
 
17444
+ // ../../packages/schema/src/zod/triage.ts
17445
+ var TriageHit = external_exports.object({
17446
+ ruleId: external_exports.string(),
17447
+ category: DetectionCategory,
17448
+ severity: Severity,
17449
+ maskedMatch: external_exports.string(),
17450
+ rawMatch: external_exports.string(),
17451
+ context: external_exports.string(),
17452
+ filePath: external_exports.string().optional(),
17453
+ confidence: external_exports.number().min(0).max(1),
17454
+ id: external_exports.string().optional(),
17455
+ valueFingerprint: external_exports.string().optional(),
17456
+ keyVersion: external_exports.number().int().nonnegative().optional()
17457
+ });
17458
+ var TriagePolicy = BuiltinPolicyId;
17459
+ var TriageCategoryRec = external_exports.object({
17460
+ category: DetectionCategory,
17461
+ action: TriagePolicy,
17462
+ reasoning: external_exports.string(),
17463
+ genuineCount: external_exports.number().int().nonnegative(),
17464
+ fpCount: external_exports.number().int().nonnegative(),
17465
+ // TriageHit ids judged false-positive in this category. fpCount must equal
17466
+ // this array's length — enforced by the consumer, not this schema.
17467
+ fpIds: external_exports.array(external_exports.string())
17468
+ });
17469
+ var TriageRecommendation = external_exports.object({
17470
+ perCategory: external_exports.array(TriageCategoryRec),
17471
+ notes: external_exports.string()
17472
+ });
17473
+
17474
+ // ../../packages/schema/src/zod/setup-frame.ts
17475
+ var CalibrationCounts = external_exports.object({
17476
+ total: external_exports.number().int().nonnegative(),
17477
+ important: external_exports.number().int().nonnegative(),
17478
+ routine: external_exports.number().int().nonnegative()
17479
+ }).refine((c) => c.total === c.important + c.routine, {
17480
+ message: "total must equal important + routine",
17481
+ path: ["total"]
17482
+ });
17483
+ var FalsePositivePatternValue = external_exports.object({
17484
+ ruleId: external_exports.string(),
17485
+ category: DetectionCategory,
17486
+ valueFingerprint: external_exports.string(),
17487
+ keyVersion: external_exports.number().int().nonnegative()
17488
+ });
17489
+ var FalsePositivePatternGroup = external_exports.object({
17490
+ pattern: external_exports.string(),
17491
+ count: external_exports.number().int().nonnegative(),
17492
+ values: external_exports.array(FalsePositivePatternValue).min(1)
17493
+ });
17494
+ var CalibrationFindingKind = external_exports.object({
17495
+ category: DetectionCategory,
17496
+ count: external_exports.number().int().nonnegative(),
17497
+ egress: external_exports.boolean()
17498
+ });
17499
+ var CalibrationFrame = external_exports.object({
17500
+ counts: CalibrationCounts,
17501
+ routineCategories: external_exports.array(DetectionCategory),
17502
+ surfacedCategories: external_exports.array(DetectionCategory),
17503
+ findingKinds: external_exports.array(CalibrationFindingKind),
17504
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId),
17505
+ maskedFindings: external_exports.array(MaskedSecretFinding).optional(),
17506
+ falsePositivePatterns: external_exports.array(FalsePositivePatternGroup).optional()
17507
+ });
17508
+ var CalibrationPreviewCategory = TriageCategoryRec.pick({
17509
+ category: true,
17510
+ genuineCount: true,
17511
+ fpCount: true
17512
+ }).extend({
17513
+ egress: external_exports.boolean()
17514
+ });
17515
+ var CalibrationPreview = external_exports.object({
17516
+ categories: external_exports.array(CalibrationPreviewCategory),
17517
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId)
17518
+ });
17519
+ var CalibrationResult = external_exports.object({
17520
+ frame: CalibrationFrame,
17521
+ copy: external_exports.string()
17522
+ });
17523
+ var FirstRunCalibration = external_exports.enum(["scan", "floor"]);
17524
+ var SetupHandoffOption = external_exports.object({
17525
+ id: external_exports.enum(["enter-remediation", "open-dashboard", "not-now"]),
17526
+ label: external_exports.string()
17527
+ });
17528
+ var DashboardHandoffOptions = external_exports.tuple([
17529
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
17530
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
17531
+ ]);
17532
+ var ComposedRemediationOptions = external_exports.tuple([
17533
+ SetupHandoffOption.extend({ id: external_exports.literal("enter-remediation") }),
17534
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
17535
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
17536
+ ]);
17537
+ var SetupHandoffOffer = external_exports.object({
17538
+ worthALook: external_exports.number().int().nonnegative(),
17539
+ liveKeys: external_exports.number().int().nonnegative().optional(),
17540
+ options: external_exports.union([DashboardHandoffOptions, ComposedRemediationOptions])
17541
+ }).refine(
17542
+ (o) => o.options.some((opt) => opt.id === "enter-remediation") === (o.liveKeys ?? 0) > 0,
17543
+ {
17544
+ message: "the chain-entry option is present exactly when liveKeys > 0",
17545
+ path: ["options"]
17546
+ }
17547
+ ).refine((o) => (o.liveKeys ?? 0) <= o.worthALook, {
17548
+ message: "liveKeys is a subset of worthALook and cannot exceed it",
17549
+ path: ["liveKeys"]
17550
+ });
17551
+
17333
17552
  // ../../packages/schema/src/zod/shares.ts
17334
17553
  var DestinationKind = external_exports.enum(["provider", "internal", "ip"]).meta({ id: "DestinationKind" });
17335
17554
  var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp"]).meta({ id: "Transport" });
@@ -17515,36 +17734,6 @@ function reviewSeverityRank(reasons) {
17515
17734
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
17516
17735
  }
17517
17736
 
17518
- // ../../packages/schema/src/zod/triage.ts
17519
- var TriageHit = external_exports.object({
17520
- ruleId: external_exports.string(),
17521
- category: DetectionCategory,
17522
- severity: Severity,
17523
- maskedMatch: external_exports.string(),
17524
- rawMatch: external_exports.string(),
17525
- context: external_exports.string(),
17526
- filePath: external_exports.string().optional(),
17527
- confidence: external_exports.number().min(0).max(1),
17528
- id: external_exports.string().optional(),
17529
- valueFingerprint: external_exports.string().optional(),
17530
- keyVersion: external_exports.number().int().nonnegative().optional()
17531
- });
17532
- var TriagePolicy = BuiltinPolicyId;
17533
- var TriageCategoryRec = external_exports.object({
17534
- category: DetectionCategory,
17535
- action: TriagePolicy,
17536
- reasoning: external_exports.string(),
17537
- genuineCount: external_exports.number().int().nonnegative(),
17538
- fpCount: external_exports.number().int().nonnegative(),
17539
- // TriageHit ids judged false-positive in this category. fpCount must equal
17540
- // this array's length — enforced by the consumer, not this schema.
17541
- fpIds: external_exports.array(external_exports.string())
17542
- });
17543
- var TriageRecommendation = external_exports.object({
17544
- perCategory: external_exports.array(TriageCategoryRec),
17545
- notes: external_exports.string()
17546
- });
17547
-
17548
17737
  // ../../packages/persistence/src/internal/sql-text.ts
17549
17738
  function escapeLikePattern(s) {
17550
17739
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18118,6 +18307,10 @@ var TIMELINE_COLUMNS = `
18118
18307
  json_extract(attributes, '$.internal') AS internal,
18119
18308
  json_extract(attributes, '$.flagged') AS flagged`;
18120
18309
  var SESSION_ROOT = `event_type = 'session'`;
18310
+ var HAS_ACTIVITY = `EXISTS (
18311
+ SELECT 1 FROM audit_events c
18312
+ WHERE c.root_session_id = audit_events.id
18313
+ AND c.event_type NOT IN ('hook', 'config_scan'))`;
18121
18314
  var SqliteActivityRepository = class {
18122
18315
  constructor(db, now = () => Date.now()) {
18123
18316
  this.db = db;
@@ -18131,7 +18324,8 @@ var SqliteActivityRepository = class {
18131
18324
  const sessionsToday = countScalar(
18132
18325
  this.db,
18133
18326
  `SELECT count(*) AS n FROM audit_events
18134
- WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?`,
18327
+ WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?
18328
+ AND ${HAS_ACTIVITY}`,
18135
18329
  [startMs, endMs]
18136
18330
  );
18137
18331
  const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
@@ -18203,6 +18397,13 @@ var SqliteActivityRepository = class {
18203
18397
  );
18204
18398
  params.push(pattern, pattern, pattern, pattern, pattern, pattern);
18205
18399
  }
18400
+ const emptyCount = countScalar(
18401
+ this.db,
18402
+ `SELECT count(*) AS n FROM audit_events
18403
+ WHERE ${[...conditions, `NOT ${HAS_ACTIVITY}`].join(" AND ")}`,
18404
+ params
18405
+ );
18406
+ if (query.excludeEmpty) conditions.push(HAS_ACTIVITY);
18206
18407
  if (cursor) {
18207
18408
  conditions.push("(started_at < ? OR (started_at = ? AND id < ?))");
18208
18409
  params.push(cursor.startedAtMs, cursor.startedAtMs, cursor.id);
@@ -18239,7 +18440,7 @@ var SqliteActivityRepository = class {
18239
18440
  );
18240
18441
  const last = page[page.length - 1];
18241
18442
  const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
18242
- return Promise.resolve({ items, nextCursor });
18443
+ return Promise.resolve({ items, nextCursor, emptyCount });
18243
18444
  }
18244
18445
  getSession(sessionId) {
18245
18446
  const rootRow = getRow(
@@ -19545,9 +19746,43 @@ var SqliteFindingsRepository = class {
19545
19746
  }))
19546
19747
  );
19547
19748
  }
19749
+ /** Live-enforced findings recorded for one session — a bare COUNT over the
19750
+ * session-stamped events (served by idx_events_session_id), so the Activity
19751
+ * page can label its findings link without the grouped pipeline. */
19752
+ sessionFindingsCount(sessionId) {
19753
+ if (!sessionId) return Promise.resolve(0);
19754
+ return Promise.resolve(
19755
+ countScalar(
19756
+ this.db,
19757
+ `SELECT count(*) AS n FROM findings f
19758
+ JOIN events e ON e.id = f.event_id
19759
+ WHERE json_extract(e.metadata, '$.sessionId') = :sessionId`,
19760
+ { sessionId }
19761
+ )
19762
+ );
19763
+ }
19764
+ /** Per-rule transcript firing tally for one session — reads the OTHER finding
19765
+ * store (inspection_findings, keyed to audit_events): every detection the
19766
+ * transcript pass recorded, counted per firing rather than per unique value.
19767
+ * Rides on session-scoped grouped responses so the findings view can
19768
+ * reconcile the Activity page's tally with the deduped groups it lists. */
19769
+ sessionFirings(sessionId) {
19770
+ return Object.fromEntries(
19771
+ countBy(
19772
+ this.db,
19773
+ `SELECT d.rule_id AS k, count(*) AS n
19774
+ FROM inspection_findings f
19775
+ JOIN audit_events e ON e.id = f.audit_event_id
19776
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
19777
+ WHERE e.root_session_id = :sessionId
19778
+ GROUP BY d.rule_id`,
19779
+ { sessionId }
19780
+ )
19781
+ );
19782
+ }
19548
19783
  /**
19549
- * Grouped findings for the dashboard — joins findings⋈events (repo/file
19550
- * from event metadata), groups by ruleId, computes per-filter-excluded facets,
19784
+ * Grouped findings for the dashboard — joins findings⋈events (repo/file/
19785
+ * toolName from event metadata), groups by ruleId, computes per-filter-excluded facets,
19551
19786
  * applies the requested filters, and sorts by severity then recency. Filtering
19552
19787
  * and faceting run in JS via the shared @akasecurity/schema helpers. `totals`
19553
19788
  * reflect the full filtered set; `items` is the requested
@@ -19564,11 +19799,16 @@ var SqliteFindingsRepository = class {
19564
19799
  * rule is ever restated in SQL.
19565
19800
  */
19566
19801
  listGroupedFindings(query) {
19567
- const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "");
19802
+ const sessionPredicate = query.sessionId ? `WHERE json_extract(e.metadata, '$.sessionId') = :sessionId` : "";
19803
+ const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
19804
+ const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
19805
+ predicate: sessionPredicate,
19806
+ params: sessionParams
19807
+ });
19568
19808
  const rows = allRows(
19569
19809
  this.db.prepare(
19570
19810
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
19571
- occurred_at, source_tool, repo, file, kind, finding_key, latest_status
19811
+ occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
19572
19812
  FROM (
19573
19813
  SELECT f.id AS id, f.rule_id AS rule_id, f.category AS category,
19574
19814
  f.severity AS severity, f.masked_match AS masked_match,
@@ -19576,6 +19816,7 @@ var SqliteFindingsRepository = class {
19576
19816
  e.occurred_at AS occurred_at, e.source_tool AS source_tool,
19577
19817
  json_extract(e.metadata, '$.repo') AS repo,
19578
19818
  json_extract(e.metadata, '$.filePath') AS file,
19819
+ json_extract(e.metadata, '$.toolName') AS tool_name,
19579
19820
  e.kind AS kind, f.finding_key AS finding_key,
19580
19821
  latest.status AS latest_status,
19581
19822
  ROW_NUMBER() OVER (
@@ -19586,11 +19827,12 @@ var SqliteFindingsRepository = class {
19586
19827
  JOIN events e ON e.id = f.event_id
19587
19828
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
19588
19829
  ON latest.finding_key = f.finding_key
19830
+ ${sessionPredicate}
19589
19831
  )
19590
19832
  WHERE rn <= :cap
19591
19833
  ORDER BY occurred_at DESC, id DESC`
19592
19834
  ),
19593
- { cap: PREVIEW_INSTANCES_PER_GROUP }
19835
+ { cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
19594
19836
  );
19595
19837
  const groupable = rows.map((r) => ({
19596
19838
  id: r.id,
@@ -19604,6 +19846,7 @@ var SqliteFindingsRepository = class {
19604
19846
  sourceTool: r.source_tool,
19605
19847
  repo: r.repo ?? "",
19606
19848
  file: r.file ?? "",
19849
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
19607
19850
  status: deriveInstanceStatus(r)
19608
19851
  }));
19609
19852
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -19622,7 +19865,13 @@ var SqliteFindingsRepository = class {
19622
19865
  };
19623
19866
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
19624
19867
  const items = sorted.slice(0, limit);
19625
- return Promise.resolve({ totals, facets, items, nextCursor: null });
19868
+ return Promise.resolve({
19869
+ totals,
19870
+ facets,
19871
+ items,
19872
+ nextCursor: null,
19873
+ ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
19874
+ });
19626
19875
  }
19627
19876
  /**
19628
19877
  * One row per rule_id, folding EVERY instance of the group into the values
@@ -19646,9 +19895,10 @@ var SqliteFindingsRepository = class {
19646
19895
  * would silently lose, so it is fetched only when the request actually
19647
19896
  * carries a `q`.
19648
19897
  */
19649
- groupAggregates(withSearchText) {
19898
+ groupAggregates(withSearchText, scope) {
19650
19899
  const searchTextColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.metadata, '$.repo')) AS repos,
19651
- group_concat(DISTINCT json_extract(e.metadata, '$.filePath')) AS files` : `, NULL AS repos, NULL AS files`;
19900
+ group_concat(DISTINCT json_extract(e.metadata, '$.filePath')) AS files,
19901
+ group_concat(DISTINCT 'via ' || json_extract(e.metadata, '$.toolName')) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
19652
19902
  const rows = this.db.prepare(
19653
19903
  `SELECT f.rule_id AS rule_id,
19654
19904
  count(*) AS instance_count,
@@ -19665,8 +19915,9 @@ var SqliteFindingsRepository = class {
19665
19915
  JOIN events e ON e.id = f.event_id
19666
19916
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
19667
19917
  ON latest.finding_key = f.finding_key
19918
+ ${scope.predicate}
19668
19919
  GROUP BY f.rule_id`
19669
- ).all();
19920
+ ).all(scope.params);
19670
19921
  return new Map(
19671
19922
  rows.map((r) => [
19672
19923
  r.rule_id,
@@ -19690,7 +19941,9 @@ var SqliteFindingsRepository = class {
19690
19941
  // Left undefined (not '') when unfetched, so buildFindingGroups can
19691
19942
  // tell "no q this request" from "a group with no repo/file at all"
19692
19943
  // and skip priming a haystack nothing will read.
19693
- ...withSearchText ? { searchText: [r.repos ?? "", r.files ?? ""].filter((s) => s !== "").join(" ") } : {}
19944
+ ...withSearchText ? {
19945
+ searchText: [r.repos ?? "", r.files ?? "", r.tool_names ?? ""].filter((s) => s !== "").join(" ")
19946
+ } : {}
19694
19947
  }
19695
19948
  ])
19696
19949
  );
@@ -21426,7 +21679,6 @@ var SqliteScanLedgerRepository = class {
21426
21679
  // ../../packages/persistence/src/repositories/security.ts
21427
21680
  var DAY_MS4 = 864e5;
21428
21681
  var SEVERITIES = ["critical", "high", "medium", "low"];
21429
- var RANGE_DAYS = { "7d": 7, "30d": 30, "3m": 90, "6m": 180 };
21430
21682
  var ACTION_TO_KIND = {
21431
21683
  block: "blocked",
21432
21684
  redact: "redacted",
@@ -21441,8 +21693,14 @@ var SCAN_COVERAGE = [
21441
21693
  { provider: "copilot", coverage: 0, supported: false },
21442
21694
  { provider: "api", coverage: 0, supported: false }
21443
21695
  ];
21696
+ var GRANULARITY = {
21697
+ "7d": "day",
21698
+ "30d": "day",
21699
+ "3m": "week",
21700
+ "6m": "week"
21701
+ };
21444
21702
  function granularityFor(range) {
21445
- return range === "7d" || range === "30d" ? "day" : "week";
21703
+ return GRANULARITY[range];
21446
21704
  }
21447
21705
  function startOfUtcDay2(ms) {
21448
21706
  return Math.floor(ms / DAY_MS4) * DAY_MS4;
@@ -22660,21 +22918,28 @@ import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as s
22660
22918
  import { homedir as homedir2 } from "os";
22661
22919
  import { basename as basename2, join as join7 } from "path";
22662
22920
 
22921
+ // ../../packages/detections/src/escape-regexp.ts
22922
+ function escapeRegExp(value) {
22923
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22924
+ }
22925
+
22926
+ // ../../packages/detections/src/matchers/limits.ts
22927
+ var MAX_MATCHES_PER_RULE = 1e4;
22928
+
22663
22929
  // ../../packages/detections/src/matchers/keyword.ts
22664
22930
  var KeywordMatcher2 = class {
22665
22931
  match(text, rule) {
22666
22932
  if (rule.matcher.type !== "keyword") return [];
22667
22933
  const { keywords, caseSensitive } = rule.matcher;
22668
- const haystack = caseSensitive ? text : text.toLowerCase();
22669
22934
  const spans = [];
22670
22935
  for (const kw of keywords) {
22671
- const needle = caseSensitive ? kw : kw.toLowerCase();
22672
- let idx = 0;
22673
- while (idx < haystack.length) {
22674
- const pos = haystack.indexOf(needle, idx);
22675
- if (pos === -1) break;
22676
- spans.push({ start: pos, end: pos + kw.length });
22677
- idx = pos + 1;
22936
+ if (kw.length === 0) continue;
22937
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
22938
+ const re = new RegExp(escapeRegExp(kw), caseSensitive ? "gu" : "giu");
22939
+ let m;
22940
+ while ((m = re.exec(text)) !== null) {
22941
+ spans.push({ start: m.index, end: m.index + m[0].length });
22942
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
22678
22943
  }
22679
22944
  }
22680
22945
  return spans;
@@ -22682,7 +22947,6 @@ var KeywordMatcher2 = class {
22682
22947
  };
22683
22948
 
22684
22949
  // ../../packages/detections/src/matchers/regex.ts
22685
- var MAX_MATCHES_PER_RULE = 1e4;
22686
22950
  var RegexMatcher2 = class {
22687
22951
  match(text, rule) {
22688
22952
  if (rule.matcher.type !== "regex") return [];
@@ -22768,9 +23032,6 @@ function registerPack(pack) {
22768
23032
  function getLoadedRules() {
22769
23033
  return [...packs.values()].flatMap((p) => p.rules);
22770
23034
  }
22771
- function escapeRegExp(value) {
22772
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22773
- }
22774
23035
  function isCorroborated(candidate, candidates, text) {
22775
23036
  const req = candidate.rule.requiresNearby;
22776
23037
  if (!req) return true;
@@ -23552,7 +23813,6 @@ var db_table_name_default = {
23552
23813
  "SELECT * FROM ",
23553
23814
  "SELECT COUNT(*) FROM ",
23554
23815
  "INSERT INTO ",
23555
- "UPDATE ",
23556
23816
  "DELETE FROM ",
23557
23817
  "CREATE TABLE ",
23558
23818
  "ALTER TABLE ",
@@ -25219,17 +25479,22 @@ function createPluginRuntime(gateway, settings, opts) {
25219
25479
  const fallback = DEFAULT_ACTIONS[category];
25220
25480
  return fallback ?? "log";
25221
25481
  }
25482
+ function actionForFinding(finding, excepted) {
25483
+ if (excepted?.has(finding)) return "allow";
25484
+ const action = resolveAction(finding.ruleId, finding.category);
25485
+ if (ENFORCEMENT_CEILING_ENABLED && policyMode === "warn" && (action === "block" || action === "redact")) {
25486
+ return "warn";
25487
+ }
25488
+ return action;
25489
+ }
25222
25490
  function decide(findings, text, excepted) {
25223
25491
  if (findings.length === 0) return { action: "log", text, findings: [] };
25224
- const actionFor = (finding) => excepted?.has(finding) ? "allow" : resolveAction(finding.ruleId, finding.category);
25492
+ const actionFor = (finding) => actionForFinding(finding, excepted);
25225
25493
  let worst = "log";
25226
25494
  for (const finding of findings) {
25227
25495
  const action = actionFor(finding);
25228
25496
  if (ACTION_PRIORITY.indexOf(action) < ACTION_PRIORITY.indexOf(worst)) worst = action;
25229
25497
  }
25230
- if (ENFORCEMENT_CEILING_ENABLED && policyMode === "warn" && (worst === "block" || worst === "redact")) {
25231
- return { action: "warn", text, findings };
25232
- }
25233
25498
  if (worst === "block") return { action: "block", text: null, findings };
25234
25499
  if (worst === "redact") {
25235
25500
  const redactFindings = findings.filter((f) => actionFor(f) === "redact");
@@ -25302,8 +25567,8 @@ function createPluginRuntime(gateway, settings, opts) {
25302
25567
  if (!key) return references;
25303
25568
  const seen = /* @__PURE__ */ new Set();
25304
25569
  for (const finding of decision.findings) {
25305
- const action = resolveAction(finding.ruleId, finding.category);
25306
- if (action !== "block" && action !== "redact" || excepted.has(finding)) continue;
25570
+ const action = actionForFinding(finding, excepted);
25571
+ if (action !== "block" && action !== "redact") continue;
25307
25572
  const fp = fingerprintOf(key, finding, fpCache);
25308
25573
  const pair = `${finding.ruleId}:${fp}`;
25309
25574
  if (seen.has(pair)) continue;
@@ -25392,7 +25657,7 @@ function createPluginRuntime(gateway, settings, opts) {
25392
25657
  severity: match.severity,
25393
25658
  span: match.span,
25394
25659
  maskedMatch,
25395
- actionTaken: excepted.has(match) ? "allow" : decision.action,
25660
+ actionTaken: actionForFinding(match, excepted),
25396
25661
  confidence: match.confidence,
25397
25662
  ...findingKey ? { findingKey } : {}
25398
25663
  };
@@ -25474,6 +25739,9 @@ function exceptionPointer(references) {
25474
25739
  return ` To allow this exact value intentionally, run: aka exception approve ${ref.reference}.`;
25475
25740
  }
25476
25741
 
25742
+ // src/hooks/onboarding-nudge.ts
25743
+ var ONBOARDING_NUDGE = "AKA Security is installed but not calibrated \u2014 run /aka:setup to tune notifications to this machine (about a minute).";
25744
+
25477
25745
  // src/hooks/shared.ts
25478
25746
  async function readStdin() {
25479
25747
  return new Promise((resolve) => {
@@ -25886,9 +26154,7 @@ async function main() {
25886
26154
  return;
25887
26155
  }
25888
26156
  if (!config2.onboarded && claimOnboardingNudge(config2.dataDir, sessionId)) {
25889
- await emit({
25890
- systemMessage: "AKA is active and monitoring your prompts (log-only by default \u2014 nothing is blocked or redacted yet). Run /aka:setup to choose your installation type and set enforcement (warn/redact/block) per detection."
25891
- });
26157
+ await emit({ systemMessage: ONBOARDING_NUDGE });
25892
26158
  }
25893
26159
  }
25894
26160
  try {