@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.
@@ -495,7 +495,7 @@ var require_ignore = __commonJS({
495
495
  import { existsSync as existsSync5, readFileSync as readFileSync9 } from "fs";
496
496
  import { userInfo } from "os";
497
497
  import { dirname as dirname4, join as join13 } from "path";
498
- import { fileURLToPath as fileURLToPath2 } from "url";
498
+ import { fileURLToPath as fileURLToPath3 } from "url";
499
499
 
500
500
  // ../../packages/persistence/src/database.ts
501
501
  import { randomUUID as randomUUID8 } from "crypto";
@@ -544,6 +544,10 @@ var SQLITE_MIGRATIONS = [
544
544
  {
545
545
  tag: "0009_findings_path_expression_index",
546
546
  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"
547
+ },
548
+ {
549
+ tag: "0010_events_session_expression_index",
550
+ 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"
547
551
  }
548
552
  ];
549
553
 
@@ -646,6 +650,12 @@ var defaultCostModel = {
646
650
  }
647
651
  };
648
652
 
653
+ // ../../packages/schema/src/token/format.ts
654
+ var COMPACT = new Intl.NumberFormat("en-US", {
655
+ notation: "compact",
656
+ maximumFractionDigits: 1
657
+ });
658
+
649
659
  // ../../packages/schema/src/token/token-report.ts
650
660
  var num = (value) => value ?? 0;
651
661
  function costUsageOf(a) {
@@ -15317,6 +15327,12 @@ var FindingInstance = external_exports.object({
15317
15327
  provider: FindingProvider,
15318
15328
  repo: external_exports.string(),
15319
15329
  file: external_exports.string(),
15330
+ // Host tool that produced the scanned text (event metadata's toolName).
15331
+ // Present whenever the capturing hook recorded one — including
15332
+ // file-attributed captures (views prefer `file`); its display value is
15333
+ // the location fallback ("via Bash") when no filePath exists. Absent for
15334
+ // legacy rows and non-tool captures (prompts, worktree scans).
15335
+ toolName: external_exports.string().optional(),
15320
15336
  // Effective action: override.action ?? actionTaken, translated to FindingAction.
15321
15337
  action: FindingAction,
15322
15338
  detectedAt: external_exports.iso.datetime(),
@@ -15373,6 +15389,9 @@ var ListGroupedFindingsQuery = external_exports.object({
15373
15389
  provider: external_exports.array(FindingProvider).optional(),
15374
15390
  action: external_exports.array(FindingAction).optional(),
15375
15391
  q: external_exports.string().optional(),
15392
+ // Scope to findings whose event carries this session id (the Activity page's
15393
+ // session → findings drilldown). Findings without a session never match.
15394
+ sessionId: external_exports.string().optional(),
15376
15395
  groupBy: external_exports.literal("type").optional(),
15377
15396
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
15378
15397
  cursor: external_exports.string().optional()
@@ -15384,7 +15403,13 @@ var ListGroupedFindingsResponse = external_exports.object({
15384
15403
  }),
15385
15404
  facets: FindingFacets,
15386
15405
  items: external_exports.array(FindingGroup),
15387
- nextCursor: external_exports.string().nullable()
15406
+ nextCursor: external_exports.string().nullable(),
15407
+ // Present only on session-scoped queries (`sessionId` set): per ruleId, how
15408
+ // many times that rule fired in the session's persisted transcript. Findings
15409
+ // here are deduplicated to unique values while the transcript tally counts
15410
+ // every firing, so the two numbers legitimately differ — this map lets a
15411
+ // session-scoped view show both.
15412
+ sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
15388
15413
  }).meta({ id: "ListGroupedFindingsResponse" });
15389
15414
  var ApplyFindingActionRequest = external_exports.object({
15390
15415
  // '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) {
@@ -16331,6 +16373,11 @@ function severityFloorPolicy(category) {
16331
16373
  const peak = CATEGORY_PEAK_SEVERITY[category];
16332
16374
  return peak === "critical" || peak === "high" ? "warn" : "monitor";
16333
16375
  }
16376
+ function severityFloorPosture() {
16377
+ const out = {};
16378
+ for (const c of DetectionCategory.options) out[c] = severityFloorPolicy(c);
16379
+ return out;
16380
+ }
16334
16381
  var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
16335
16382
  var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "block"];
16336
16383
  var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
@@ -16415,6 +16462,10 @@ var ListEventsResponse = external_exports.object({
16415
16462
  items: external_exports.array(Event),
16416
16463
  nextCursor: external_exports.string().nullable()
16417
16464
  }).meta({ id: "ListEventsResponse" });
16465
+ var IngestResponse = external_exports.object({
16466
+ accepted: external_exports.number().int().nonnegative(),
16467
+ duplicates: external_exports.number().int().nonnegative()
16468
+ }).meta({ id: "IngestResponse" });
16418
16469
  var ListFindingsQuery = external_exports.object({
16419
16470
  cursor: external_exports.string().optional(),
16420
16471
  limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
@@ -16804,6 +16855,7 @@ function buildFindingGroups(rows, opts = {}) {
16804
16855
  provider: toApiProvider(r.sourceTool),
16805
16856
  repo: r.repo,
16806
16857
  file: r.file,
16858
+ ...r.toolName === void 0 ? {} : { toolName: r.toolName },
16807
16859
  action: toApiAction(effectiveDbAction),
16808
16860
  detectedAt: r.occurredAt,
16809
16861
  confidence: r.confidence,
@@ -16876,6 +16928,7 @@ function buildHaystack(g, extra) {
16876
16928
  g.id,
16877
16929
  ...g.instances.map((i) => i.repo),
16878
16930
  ...g.instances.map((i) => i.file),
16931
+ ...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
16879
16932
  ...g.instances.map((i) => i.id),
16880
16933
  ...extra === void 0 ? [] : [extra]
16881
16934
  ].join(" ").toLowerCase();
@@ -17142,6 +17195,71 @@ var ProjectFilesScan = external_exports.object({
17142
17195
  scannedAt: external_exports.string()
17143
17196
  });
17144
17197
 
17198
+ // ../../packages/schema/src/zod/ranges.ts
17199
+ var TIME_RANGES = ["7d", "30d", "3m", "6m"];
17200
+ var TimeRange = external_exports.enum(TIME_RANGES).meta({ id: "TimeRange" });
17201
+ var DEFAULT_TIME_RANGE = "7d";
17202
+ var RANGE_DAYS = {
17203
+ "7d": 7,
17204
+ "30d": 30,
17205
+ "3m": 90,
17206
+ "6m": 180
17207
+ };
17208
+ var TIME_RANGE_OR_DEFAULT = TimeRange.catch(DEFAULT_TIME_RANGE);
17209
+
17210
+ // ../../packages/schema/src/zod/remediation.ts
17211
+ var SecretFindingState = external_exports.enum(["still-valid", "unknown", "invalid"]);
17212
+ var MaskedFindingLocation = external_exports.object({
17213
+ filePath: external_exports.string(),
17214
+ span: Span.optional()
17215
+ }).strict();
17216
+ var MaskedSecretFinding = external_exports.object({
17217
+ provider: external_exports.string(),
17218
+ maskedToken: external_exports.string(),
17219
+ where: MaskedFindingLocation,
17220
+ state: SecretFindingState,
17221
+ observedAt: external_exports.iso.datetime().optional()
17222
+ }).strict();
17223
+ var RotationChecklistEntry = external_exports.object({
17224
+ provider: external_exports.string(),
17225
+ maskedToken: external_exports.string(),
17226
+ consolePath: external_exports.string(),
17227
+ occurrenceSpread: external_exports.number().int().positive()
17228
+ }).strict();
17229
+ var RemediationOption = external_exports.enum([
17230
+ "redact-rotation-checklist",
17231
+ "redact-only",
17232
+ "set-secret-redact",
17233
+ "leave"
17234
+ ]);
17235
+ var RemediationEntrySource = external_exports.enum(["first-run", "pre-push", "secret-scan"]);
17236
+ var RemediationEntryContext = external_exports.object({
17237
+ entrySource: RemediationEntrySource
17238
+ }).strict();
17239
+ var RemediationOptionChoice = external_exports.object({
17240
+ id: RemediationOption,
17241
+ label: external_exports.string()
17242
+ });
17243
+ var BatchedRemediationDecision = external_exports.object({
17244
+ kind: external_exports.literal("decision"),
17245
+ entrySource: RemediationEntrySource,
17246
+ secretCount: external_exports.number().int().positive(),
17247
+ prompt: external_exports.string(),
17248
+ options: external_exports.tuple([
17249
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-rotation-checklist") }),
17250
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-only") }),
17251
+ RemediationOptionChoice.extend({ id: external_exports.literal("set-secret-redact") }),
17252
+ RemediationOptionChoice.extend({ id: external_exports.literal("leave") })
17253
+ ])
17254
+ });
17255
+ var NoRemediationDecision = external_exports.object({
17256
+ kind: external_exports.literal("no-decision")
17257
+ });
17258
+ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
17259
+ BatchedRemediationDecision,
17260
+ NoRemediationDecision
17261
+ ]);
17262
+
17145
17263
  // ../../packages/schema/src/zod/rule-test.ts
17146
17264
  var TestRulesRequest = external_exports.object({
17147
17265
  rules: external_exports.array(Rule).min(1).max(100),
@@ -17200,10 +17318,8 @@ var SeveritySummaryResponse = external_exports.object({
17200
17318
  // All four severity levels are always present (count may be 0).
17201
17319
  bySeverity: external_exports.array(SeveritySummaryItem)
17202
17320
  }).meta({ id: "SeveritySummaryResponse" });
17203
- var SECURITY_RANGES = ["7d", "30d", "3m", "6m"];
17204
- var SecurityRange = external_exports.enum(SECURITY_RANGES).meta({ id: "SecurityRange" });
17205
17321
  var SecurityRangeQuery = external_exports.object({
17206
- range: external_exports.enum(SECURITY_RANGES).default("30d")
17322
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE)
17207
17323
  });
17208
17324
  var EnforcementActionKind = external_exports.enum(["blocked", "redacted", "warned"]).meta({ id: "EnforcementActionKind" });
17209
17325
  var EnforcementAction = external_exports.object({
@@ -17213,7 +17329,7 @@ var EnforcementAction = external_exports.object({
17213
17329
  delta: external_exports.number().int()
17214
17330
  }).meta({ id: "EnforcementAction" });
17215
17331
  var EnforcementActionsResponse = external_exports.object({
17216
- range: SecurityRange,
17332
+ range: TimeRange,
17217
17333
  // Sum of actions[].count in the window.
17218
17334
  total: external_exports.number().int().nonnegative(),
17219
17335
  // One entry per kind, always all three present (count may be 0).
@@ -17228,7 +17344,7 @@ var FindingsTimeseriesPoint = external_exports.object({
17228
17344
  medium: external_exports.number().int().nonnegative()
17229
17345
  }).meta({ id: "FindingsTimeseriesPoint" });
17230
17346
  var FindingsTimeseriesResponse = external_exports.object({
17231
- range: SecurityRange,
17347
+ range: TimeRange,
17232
17348
  granularity: TimeseriesGranularity,
17233
17349
  points: external_exports.array(FindingsTimeseriesPoint)
17234
17350
  }).meta({ id: "FindingsTimeseriesResponse" });
@@ -17243,7 +17359,7 @@ var MttrTrendPoint = external_exports.object({
17243
17359
  })
17244
17360
  }).meta({ id: "MttrTrendPoint" });
17245
17361
  var MttrTrendResponse = external_exports.object({
17246
- range: SecurityRange,
17362
+ range: TimeRange,
17247
17363
  granularity: TimeseriesGranularity,
17248
17364
  points: external_exports.array(MttrTrendPoint)
17249
17365
  }).meta({ id: "MttrTrendResponse" });
@@ -17270,11 +17386,11 @@ var TopSource = external_exports.object({
17270
17386
  findingsCount: external_exports.number().int().nonnegative()
17271
17387
  }).meta({ id: "TopSource" });
17272
17388
  var TopSourcesResponse = external_exports.object({
17273
- range: SecurityRange,
17389
+ range: TimeRange,
17274
17390
  items: external_exports.array(TopSource)
17275
17391
  }).meta({ id: "TopSourcesResponse" });
17276
17392
  var TopSourcesQuery = external_exports.object({
17277
- range: external_exports.enum(SECURITY_RANGES).default("30d"),
17393
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE),
17278
17394
  limit: external_exports.coerce.number().int().min(1).max(50).default(5),
17279
17395
  // Omit for both kinds.
17280
17396
  kind: external_exports.enum(SOURCE_KINDS).optional()
@@ -17287,7 +17403,7 @@ var ScanCoverageProvider = external_exports.object({
17287
17403
  supported: external_exports.boolean()
17288
17404
  }).meta({ id: "ScanCoverageProvider" });
17289
17405
  var ScanCoverageResponse = external_exports.object({
17290
- range: SecurityRange,
17406
+ range: TimeRange,
17291
17407
  providers: external_exports.array(ScanCoverageProvider)
17292
17408
  }).meta({ id: "ScanCoverageResponse" });
17293
17409
  var SubjectType = external_exports.enum(["repo", "user", "team", "policy", "share", "rule"]).meta({
@@ -17336,6 +17452,114 @@ var ApplyRecommendedActionResponse = external_exports.object({
17336
17452
  var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
17337
17453
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
17338
17454
 
17455
+ // ../../packages/schema/src/zod/triage.ts
17456
+ var TriageHit = external_exports.object({
17457
+ ruleId: external_exports.string(),
17458
+ category: DetectionCategory,
17459
+ severity: Severity,
17460
+ maskedMatch: external_exports.string(),
17461
+ rawMatch: external_exports.string(),
17462
+ context: external_exports.string(),
17463
+ filePath: external_exports.string().optional(),
17464
+ confidence: external_exports.number().min(0).max(1),
17465
+ id: external_exports.string().optional(),
17466
+ valueFingerprint: external_exports.string().optional(),
17467
+ keyVersion: external_exports.number().int().nonnegative().optional()
17468
+ });
17469
+ var TriagePolicy = BuiltinPolicyId;
17470
+ var TriageCategoryRec = external_exports.object({
17471
+ category: DetectionCategory,
17472
+ action: TriagePolicy,
17473
+ reasoning: external_exports.string(),
17474
+ genuineCount: external_exports.number().int().nonnegative(),
17475
+ fpCount: external_exports.number().int().nonnegative(),
17476
+ // TriageHit ids judged false-positive in this category. fpCount must equal
17477
+ // this array's length — enforced by the consumer, not this schema.
17478
+ fpIds: external_exports.array(external_exports.string())
17479
+ });
17480
+ var TriageRecommendation = external_exports.object({
17481
+ perCategory: external_exports.array(TriageCategoryRec),
17482
+ notes: external_exports.string()
17483
+ });
17484
+
17485
+ // ../../packages/schema/src/zod/setup-frame.ts
17486
+ var CalibrationCounts = external_exports.object({
17487
+ total: external_exports.number().int().nonnegative(),
17488
+ important: external_exports.number().int().nonnegative(),
17489
+ routine: external_exports.number().int().nonnegative()
17490
+ }).refine((c) => c.total === c.important + c.routine, {
17491
+ message: "total must equal important + routine",
17492
+ path: ["total"]
17493
+ });
17494
+ var FalsePositivePatternValue = external_exports.object({
17495
+ ruleId: external_exports.string(),
17496
+ category: DetectionCategory,
17497
+ valueFingerprint: external_exports.string(),
17498
+ keyVersion: external_exports.number().int().nonnegative()
17499
+ });
17500
+ var FalsePositivePatternGroup = external_exports.object({
17501
+ pattern: external_exports.string(),
17502
+ count: external_exports.number().int().nonnegative(),
17503
+ values: external_exports.array(FalsePositivePatternValue).min(1)
17504
+ });
17505
+ var CalibrationFindingKind = external_exports.object({
17506
+ category: DetectionCategory,
17507
+ count: external_exports.number().int().nonnegative(),
17508
+ egress: external_exports.boolean()
17509
+ });
17510
+ var CalibrationFrame = external_exports.object({
17511
+ counts: CalibrationCounts,
17512
+ routineCategories: external_exports.array(DetectionCategory),
17513
+ surfacedCategories: external_exports.array(DetectionCategory),
17514
+ findingKinds: external_exports.array(CalibrationFindingKind),
17515
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId),
17516
+ maskedFindings: external_exports.array(MaskedSecretFinding).optional(),
17517
+ falsePositivePatterns: external_exports.array(FalsePositivePatternGroup).optional()
17518
+ });
17519
+ var CalibrationPreviewCategory = TriageCategoryRec.pick({
17520
+ category: true,
17521
+ genuineCount: true,
17522
+ fpCount: true
17523
+ }).extend({
17524
+ egress: external_exports.boolean()
17525
+ });
17526
+ var CalibrationPreview = external_exports.object({
17527
+ categories: external_exports.array(CalibrationPreviewCategory),
17528
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId)
17529
+ });
17530
+ var CalibrationResult = external_exports.object({
17531
+ frame: CalibrationFrame,
17532
+ copy: external_exports.string()
17533
+ });
17534
+ var FirstRunCalibration = external_exports.enum(["scan", "floor"]);
17535
+ var SetupHandoffOption = external_exports.object({
17536
+ id: external_exports.enum(["enter-remediation", "open-dashboard", "not-now"]),
17537
+ label: external_exports.string()
17538
+ });
17539
+ var DashboardHandoffOptions = external_exports.tuple([
17540
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
17541
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
17542
+ ]);
17543
+ var ComposedRemediationOptions = external_exports.tuple([
17544
+ SetupHandoffOption.extend({ id: external_exports.literal("enter-remediation") }),
17545
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
17546
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
17547
+ ]);
17548
+ var SetupHandoffOffer = external_exports.object({
17549
+ worthALook: external_exports.number().int().nonnegative(),
17550
+ liveKeys: external_exports.number().int().nonnegative().optional(),
17551
+ options: external_exports.union([DashboardHandoffOptions, ComposedRemediationOptions])
17552
+ }).refine(
17553
+ (o) => o.options.some((opt) => opt.id === "enter-remediation") === (o.liveKeys ?? 0) > 0,
17554
+ {
17555
+ message: "the chain-entry option is present exactly when liveKeys > 0",
17556
+ path: ["options"]
17557
+ }
17558
+ ).refine((o) => (o.liveKeys ?? 0) <= o.worthALook, {
17559
+ message: "liveKeys is a subset of worthALook and cannot exceed it",
17560
+ path: ["liveKeys"]
17561
+ });
17562
+
17339
17563
  // ../../packages/schema/src/zod/shares.ts
17340
17564
  var DestinationKind = external_exports.enum(["provider", "internal", "ip"]).meta({ id: "DestinationKind" });
17341
17565
  var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp"]).meta({ id: "Transport" });
@@ -17521,36 +17745,6 @@ function reviewSeverityRank(reasons) {
17521
17745
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
17522
17746
  }
17523
17747
 
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
17748
  // ../../packages/persistence/src/internal/sql-text.ts
17555
17749
  function escapeLikePattern(s) {
17556
17750
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -17635,9 +17829,9 @@ function schemaObjectExists(db, kind, name) {
17635
17829
  function indexExists(db, name) {
17636
17830
  return schemaObjectExists(db, "index", name);
17637
17831
  }
17638
- function columnNames(db, table, opts) {
17832
+ function columnNames(db, table2, opts) {
17639
17833
  const pragma = opts?.includeGenerated ? "table_xinfo" : "table_info";
17640
- const columns = db.prepare(`PRAGMA ${pragma}(${table})`).all();
17834
+ const columns = db.prepare(`PRAGMA ${pragma}(${table2})`).all();
17641
17835
  return columns.map((c) => c.name);
17642
17836
  }
17643
17837
  function evidenceExists(db, object2) {
@@ -17805,12 +17999,12 @@ function reconcileSourceProjectIds(db) {
17805
17999
  const repointCallSite = db.prepare(
17806
18000
  "UPDATE share_call_site SET project_id = ? WHERE project_id = ?"
17807
18001
  );
17808
- const pathTables = ["project_file", "file_access_override"].map((table) => ({
18002
+ const pathTables = ["project_file", "file_access_override"].map((table2) => ({
17809
18003
  dropCollisions: db.prepare(
17810
- `DELETE FROM ${table} WHERE project_id = ?
17811
- AND path IN (SELECT path FROM ${table} WHERE project_id = ?)`
18004
+ `DELETE FROM ${table2} WHERE project_id = ?
18005
+ AND path IN (SELECT path FROM ${table2} WHERE project_id = ?)`
17812
18006
  ),
17813
- repoint: db.prepare(`UPDATE ${table} SET project_id = ? WHERE project_id = ?`)
18007
+ repoint: db.prepare(`UPDATE ${table2} SET project_id = ? WHERE project_id = ?`)
17814
18008
  }));
17815
18009
  const deleteLegacy = db.prepare("DELETE FROM source_project WHERE id = ?");
17816
18010
  withTransaction(
@@ -17844,9 +18038,9 @@ function isForeignSqliteLineage(db) {
17844
18038
  if (schemaObjectExists(db, "table", "tenants")) return true;
17845
18039
  return columnNames(db, "events").includes("tenant_id");
17846
18040
  }
17847
- function ensureSyncedAtColumn(db, table) {
17848
- if (!columnNames(db, table).includes("synced_at")) {
17849
- db.exec(`ALTER TABLE ${table} ADD COLUMN synced_at integer`);
18041
+ function ensureSyncedAtColumn(db, table2) {
18042
+ if (!columnNames(db, table2).includes("synced_at")) {
18043
+ db.exec(`ALTER TABLE ${table2} ADD COLUMN synced_at integer`);
17850
18044
  }
17851
18045
  }
17852
18046
  function ensureScanLedgerTable(db) {
@@ -18113,6 +18307,10 @@ var TIMELINE_COLUMNS = `
18113
18307
  json_extract(attributes, '$.internal') AS internal,
18114
18308
  json_extract(attributes, '$.flagged') AS flagged`;
18115
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'))`;
18116
18314
  var SqliteActivityRepository = class {
18117
18315
  constructor(db, now = () => Date.now()) {
18118
18316
  this.db = db;
@@ -18126,7 +18324,8 @@ var SqliteActivityRepository = class {
18126
18324
  const sessionsToday = countScalar(
18127
18325
  this.db,
18128
18326
  `SELECT count(*) AS n FROM audit_events
18129
- WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?`,
18327
+ WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?
18328
+ AND ${HAS_ACTIVITY}`,
18130
18329
  [startMs, endMs]
18131
18330
  );
18132
18331
  const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
@@ -18198,6 +18397,13 @@ var SqliteActivityRepository = class {
18198
18397
  );
18199
18398
  params.push(pattern, pattern, pattern, pattern, pattern, pattern);
18200
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);
18201
18407
  if (cursor) {
18202
18408
  conditions.push("(started_at < ? OR (started_at = ? AND id < ?))");
18203
18409
  params.push(cursor.startedAtMs, cursor.startedAtMs, cursor.id);
@@ -18234,7 +18440,7 @@ var SqliteActivityRepository = class {
18234
18440
  );
18235
18441
  const last = page[page.length - 1];
18236
18442
  const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
18237
- return Promise.resolve({ items, nextCursor });
18443
+ return Promise.resolve({ items, nextCursor, emptyCount });
18238
18444
  }
18239
18445
  getSession(sessionId) {
18240
18446
  const rootRow = getRow(
@@ -19540,9 +19746,43 @@ var SqliteFindingsRepository = class {
19540
19746
  }))
19541
19747
  );
19542
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
+ }
19543
19783
  /**
19544
- * Grouped findings for the dashboard — joins findings⋈events (repo/file
19545
- * 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,
19546
19786
  * applies the requested filters, and sorts by severity then recency. Filtering
19547
19787
  * and faceting run in JS via the shared @akasecurity/schema helpers. `totals`
19548
19788
  * reflect the full filtered set; `items` is the requested
@@ -19559,11 +19799,16 @@ var SqliteFindingsRepository = class {
19559
19799
  * rule is ever restated in SQL.
19560
19800
  */
19561
19801
  listGroupedFindings(query) {
19562
- 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
+ });
19563
19808
  const rows = allRows(
19564
19809
  this.db.prepare(
19565
19810
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
19566
- 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
19567
19812
  FROM (
19568
19813
  SELECT f.id AS id, f.rule_id AS rule_id, f.category AS category,
19569
19814
  f.severity AS severity, f.masked_match AS masked_match,
@@ -19571,6 +19816,7 @@ var SqliteFindingsRepository = class {
19571
19816
  e.occurred_at AS occurred_at, e.source_tool AS source_tool,
19572
19817
  json_extract(e.metadata, '$.repo') AS repo,
19573
19818
  json_extract(e.metadata, '$.filePath') AS file,
19819
+ json_extract(e.metadata, '$.toolName') AS tool_name,
19574
19820
  e.kind AS kind, f.finding_key AS finding_key,
19575
19821
  latest.status AS latest_status,
19576
19822
  ROW_NUMBER() OVER (
@@ -19581,11 +19827,12 @@ var SqliteFindingsRepository = class {
19581
19827
  JOIN events e ON e.id = f.event_id
19582
19828
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
19583
19829
  ON latest.finding_key = f.finding_key
19830
+ ${sessionPredicate}
19584
19831
  )
19585
19832
  WHERE rn <= :cap
19586
19833
  ORDER BY occurred_at DESC, id DESC`
19587
19834
  ),
19588
- { cap: PREVIEW_INSTANCES_PER_GROUP }
19835
+ { cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
19589
19836
  );
19590
19837
  const groupable = rows.map((r) => ({
19591
19838
  id: r.id,
@@ -19599,6 +19846,7 @@ var SqliteFindingsRepository = class {
19599
19846
  sourceTool: r.source_tool,
19600
19847
  repo: r.repo ?? "",
19601
19848
  file: r.file ?? "",
19849
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
19602
19850
  status: deriveInstanceStatus(r)
19603
19851
  }));
19604
19852
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -19617,7 +19865,13 @@ var SqliteFindingsRepository = class {
19617
19865
  };
19618
19866
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
19619
19867
  const items = sorted.slice(0, limit);
19620
- 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
+ });
19621
19875
  }
19622
19876
  /**
19623
19877
  * One row per rule_id, folding EVERY instance of the group into the values
@@ -19641,9 +19895,10 @@ var SqliteFindingsRepository = class {
19641
19895
  * would silently lose, so it is fetched only when the request actually
19642
19896
  * carries a `q`.
19643
19897
  */
19644
- groupAggregates(withSearchText) {
19898
+ groupAggregates(withSearchText, scope) {
19645
19899
  const searchTextColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.metadata, '$.repo')) AS repos,
19646
- 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`;
19647
19902
  const rows = this.db.prepare(
19648
19903
  `SELECT f.rule_id AS rule_id,
19649
19904
  count(*) AS instance_count,
@@ -19660,8 +19915,9 @@ var SqliteFindingsRepository = class {
19660
19915
  JOIN events e ON e.id = f.event_id
19661
19916
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
19662
19917
  ON latest.finding_key = f.finding_key
19918
+ ${scope.predicate}
19663
19919
  GROUP BY f.rule_id`
19664
- ).all();
19920
+ ).all(scope.params);
19665
19921
  return new Map(
19666
19922
  rows.map((r) => [
19667
19923
  r.rule_id,
@@ -19685,7 +19941,9 @@ var SqliteFindingsRepository = class {
19685
19941
  // Left undefined (not '') when unfetched, so buildFindingGroups can
19686
19942
  // tell "no q this request" from "a group with no repo/file at all"
19687
19943
  // and skip priming a haystack nothing will read.
19688
- ...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
+ } : {}
19689
19947
  }
19690
19948
  ])
19691
19949
  );
@@ -21421,7 +21679,6 @@ var SqliteScanLedgerRepository = class {
21421
21679
  // ../../packages/persistence/src/repositories/security.ts
21422
21680
  var DAY_MS4 = 864e5;
21423
21681
  var SEVERITIES = ["critical", "high", "medium", "low"];
21424
- var RANGE_DAYS = { "7d": 7, "30d": 30, "3m": 90, "6m": 180 };
21425
21682
  var ACTION_TO_KIND = {
21426
21683
  block: "blocked",
21427
21684
  redact: "redacted",
@@ -21436,8 +21693,14 @@ var SCAN_COVERAGE = [
21436
21693
  { provider: "copilot", coverage: 0, supported: false },
21437
21694
  { provider: "api", coverage: 0, supported: false }
21438
21695
  ];
21696
+ var GRANULARITY = {
21697
+ "7d": "day",
21698
+ "30d": "day",
21699
+ "3m": "week",
21700
+ "6m": "week"
21701
+ };
21439
21702
  function granularityFor(range) {
21440
- return range === "7d" || range === "30d" ? "day" : "week";
21703
+ return GRANULARITY[range];
21441
21704
  }
21442
21705
  function startOfUtcDay2(ms) {
21443
21706
  return Math.floor(ms / DAY_MS4) * DAY_MS4;
@@ -22584,21 +22847,28 @@ import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as s
22584
22847
  import { homedir as homedir2 } from "os";
22585
22848
  import { basename as basename2, join as join7 } from "path";
22586
22849
 
22850
+ // ../../packages/detections/src/escape-regexp.ts
22851
+ function escapeRegExp(value) {
22852
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22853
+ }
22854
+
22855
+ // ../../packages/detections/src/matchers/limits.ts
22856
+ var MAX_MATCHES_PER_RULE = 1e4;
22857
+
22587
22858
  // ../../packages/detections/src/matchers/keyword.ts
22588
22859
  var KeywordMatcher2 = class {
22589
22860
  match(text, rule) {
22590
22861
  if (rule.matcher.type !== "keyword") return [];
22591
22862
  const { keywords, caseSensitive } = rule.matcher;
22592
- const haystack = caseSensitive ? text : text.toLowerCase();
22593
22863
  const spans = [];
22594
22864
  for (const kw of keywords) {
22595
- const needle = caseSensitive ? kw : kw.toLowerCase();
22596
- let idx = 0;
22597
- while (idx < haystack.length) {
22598
- const pos = haystack.indexOf(needle, idx);
22599
- if (pos === -1) break;
22600
- spans.push({ start: pos, end: pos + kw.length });
22601
- idx = pos + 1;
22865
+ if (kw.length === 0) continue;
22866
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
22867
+ const re = new RegExp(escapeRegExp(kw), caseSensitive ? "gu" : "giu");
22868
+ let m;
22869
+ while ((m = re.exec(text)) !== null) {
22870
+ spans.push({ start: m.index, end: m.index + m[0].length });
22871
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
22602
22872
  }
22603
22873
  }
22604
22874
  return spans;
@@ -22606,7 +22876,6 @@ var KeywordMatcher2 = class {
22606
22876
  };
22607
22877
 
22608
22878
  // ../../packages/detections/src/matchers/regex.ts
22609
- var MAX_MATCHES_PER_RULE = 1e4;
22610
22879
  var RegexMatcher2 = class {
22611
22880
  match(text, rule) {
22612
22881
  if (rule.matcher.type !== "regex") return [];
@@ -22860,6 +23129,118 @@ async function applySetupTriageSuppressions(entries, writer, opts) {
22860
23129
  import { mkdirSync as mkdirSync4, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
22861
23130
  import { join as join10 } from "path";
22862
23131
 
23132
+ // src/command-registry.ts
23133
+ import { readdirSync as readdirSync3 } from "fs";
23134
+ import { fileURLToPath } from "url";
23135
+ var COMMAND_NAMESPACE = "aka";
23136
+ var COMMANDS_DIR = fileURLToPath(new URL("../commands", import.meta.url));
23137
+ function readRegisteredCommands() {
23138
+ return readdirSync3(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
23139
+ }
23140
+ function selectRegisteredCommands(curated, registry2) {
23141
+ const registered = new Set(registry2);
23142
+ const missing = curated.filter((c) => !registered.has(c));
23143
+ if (missing.length > 0) {
23144
+ throw new Error(
23145
+ `Curated command(s) not registered in the installed plugin: ${missing.join(", ")}`
23146
+ );
23147
+ }
23148
+ return [...curated];
23149
+ }
23150
+
23151
+ // src/setup-frame-json.ts
23152
+ var FRAME_JSON_BEGIN = "<<<AKA_FRAME_JSON";
23153
+ var FRAME_JSON_END = "AKA_FRAME_JSON>>>";
23154
+ function frameJsonBlock(payload) {
23155
+ return `${FRAME_JSON_BEGIN}
23156
+ ${JSON.stringify(payload)}
23157
+ ${FRAME_JSON_END}
23158
+ `;
23159
+ }
23160
+
23161
+ // src/setup-show.ts
23162
+ var SHOW_BEGIN = "<<<AKA_SHOW";
23163
+ var SHOW_END = "AKA_SHOW>>>";
23164
+ function showBlock(body) {
23165
+ return `${SHOW_BEGIN}
23166
+ ${body}
23167
+ ${SHOW_END}
23168
+ `;
23169
+ }
23170
+
23171
+ // src/present.ts
23172
+ var SHADE = {
23173
+ light: "\u2591",
23174
+ medium: "\u2592",
23175
+ dark: "\u2593",
23176
+ full: "\u2588"
23177
+ };
23178
+ var ANSI_RE = /\x1b\[[0-9;]*m/g;
23179
+ function visibleLength(text) {
23180
+ return text.replace(ANSI_RE, "").length;
23181
+ }
23182
+ var fg = (hex3) => (text) => {
23183
+ const r = Number.parseInt(hex3.slice(1, 3), 16);
23184
+ const g = Number.parseInt(hex3.slice(3, 5), 16);
23185
+ const b = Number.parseInt(hex3.slice(5, 7), 16);
23186
+ return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
23187
+ };
23188
+ var paint = {
23189
+ brand: fg("#33e6c6"),
23190
+ // --color-brand · ▸▸ AKA wordmark (accent text)
23191
+ dim: fg("#838995"),
23192
+ // --color-text-3 · separators · "/100" · the "unreviewed" label
23193
+ bold: (text) => `\x1B[1m${text}\x1B[0m`,
23194
+ // the health score number
23195
+ ok: fg("#0db15f"),
23196
+ // --color-ok · healthy ● dot
23197
+ critical: fg("#e63448"),
23198
+ // --color-sev-critical · ■ and the open-findings flag
23199
+ high: fg("#e97a0a"),
23200
+ // --color-sev-high · ■ and the mid-health dot
23201
+ medium: fg("#f7bd00"),
23202
+ // --color-sev-medium · ■
23203
+ low: fg("#0581d4")
23204
+ // --color-sev-low · ■ (azure blue, not purple)
23205
+ };
23206
+ function padEnd(text, width) {
23207
+ const pad = width - visibleLength(text);
23208
+ return pad > 0 ? text + " ".repeat(pad) : text;
23209
+ }
23210
+ function indent(text, spaces = 2) {
23211
+ const pad = " ".repeat(spaces);
23212
+ return text.split("\n").map((line) => pad + line).join("\n");
23213
+ }
23214
+ function table(headers, rows, opts = {}) {
23215
+ const gap = opts.gap ?? 3;
23216
+ const widths = headers.map(
23217
+ (h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
23218
+ );
23219
+ const sep4 = " ".repeat(gap);
23220
+ const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep4);
23221
+ const headerLine = fmt(headers.map((h) => h.toUpperCase()));
23222
+ if (opts.rowSep === true) {
23223
+ const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
23224
+ const rule = "\u2500".repeat(fullWidth);
23225
+ const body = [];
23226
+ rows.forEach((row, i) => {
23227
+ if (i > 0) body.push(rule);
23228
+ body.push(fmt(row));
23229
+ });
23230
+ return [headerLine, rule, ...body].join("\n");
23231
+ }
23232
+ const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep4);
23233
+ return [headerLine, ruleLine, ...rows.map(fmt)].join("\n");
23234
+ }
23235
+ function fenced(body) {
23236
+ const longestRun = Math.max(0, ...[...body.matchAll(/`+/g)].map((m) => m[0].length));
23237
+ const fence = "`".repeat(Math.max(3, longestRun + 1));
23238
+ return [fence, body, fence].join("\n");
23239
+ }
23240
+ function show(body) {
23241
+ return showBlock(body);
23242
+ }
23243
+
22863
23244
  // src/triage/gate-display.ts
22864
23245
  function findContext(entry, join14) {
22865
23246
  const byFingerprint = join14.find(
@@ -22913,11 +23294,14 @@ function renderPosturePlan(posture, current) {
22913
23294
  if (lines.length === 0) {
22914
23295
  return "No per-category detection posture will be written.";
22915
23296
  }
22916
- const footer = downgrades.length > 0 ? `
23297
+ return `Here's the detection level I'd set for each type:
23298
+ ${lines.join("\n")}${downgradeWarning(downgrades)}`;
23299
+ }
23300
+ function downgradeWarning(downgrades) {
23301
+ if (downgrades.length === 0) return "";
23302
+ return `
22917
23303
 
22918
- WARNING: ${String(downgrades.length)} categor${downgrades.length === 1 ? "y" : "ies"} (${downgrades.join(", ")}) would be LOWERED from a stronger existing setting. Confirm you intend to weaken enforcement there before applying.` : "";
22919
- return `Per-category detection posture to be applied:
22920
- ${lines.join("\n")}${footer}`;
23304
+ Heads up \u2014 this would lower ${String(downgrades.length)} detection level${downgrades.length === 1 ? "" : "s"} (${downgrades.join(", ")}) below what you've already set. Confirm you mean to lower ${downgrades.length === 1 ? "it" : "them"} before I apply.`;
22921
23305
  }
22922
23306
  function renderShowcase(showcase) {
22923
23307
  if (showcase.length === 0) {
@@ -22930,7 +23314,7 @@ function renderShowcase(showcase) {
22930
23314
  ` ${s.reasoning}`
22931
23315
  ].join("\n");
22932
23316
  });
22933
- return `What the judgment found, per category:
23317
+ return `Here's what I found, by type:
22934
23318
 
22935
23319
  ${blocks.join("\n\n")}`;
22936
23320
  }
@@ -22938,7 +23322,7 @@ function renderSuppressionGate(entries, join14) {
22938
23322
  if (entries.length === 0) {
22939
23323
  return "No false-positive suppressions to confirm \u2014 nothing will be written.";
22940
23324
  }
22941
- const header = entries.length === 1 ? "The following detection will be suppressed as a false positive:" : `The following ${String(entries.length)} detections will be suppressed as false positives:`;
23325
+ const header = entries.length === 1 ? "This looks like a false positive \u2014 take a look before I suppress it:" : `These ${String(entries.length)} look like false positives \u2014 take a look before I suppress them:`;
22942
23326
  const blocks = entries.map((entry, i) => {
22943
23327
  const context = findContext(entry, join14);
22944
23328
  const lines = [
@@ -22954,6 +23338,218 @@ function renderSuppressionGate(entries, join14) {
22954
23338
  ${blocks.join("\n\n")}`;
22955
23339
  }
22956
23340
 
23341
+ // src/render.ts
23342
+ var STORE_UNAVAILABLE_NOTE = "I couldn't check my records just now \u2014 we can check again soon. Your Claude session keeps going, and I'll fill in as you work.";
23343
+ var SEVERITY_GLYPH = {
23344
+ critical: SHADE.full,
23345
+ high: SHADE.dark,
23346
+ medium: SHADE.medium,
23347
+ low: SHADE.light
23348
+ };
23349
+ var CATEGORY_ORDER2 = DetectionCategory.options;
23350
+ function categoryRank(category) {
23351
+ const i = CATEGORY_ORDER2.indexOf(category);
23352
+ return i === -1 ? CATEGORY_ORDER2.length : i;
23353
+ }
23354
+ function renderRecommendedPosture(posture) {
23355
+ const rows = Object.keys(posture).map((category) => ({
23356
+ category,
23357
+ level: posture[category] ?? ""
23358
+ }));
23359
+ const width = Math.max(0, ...rows.map((r) => r.category.length));
23360
+ return rows.sort((a, b) => categoryRank(a.category) - categoryRank(b.category)).map((r) => ` ${r.category.padEnd(width)} ${r.level}`).join("\n");
23361
+ }
23362
+ var GRID_MARK = "\u25CF";
23363
+ function renderPostureGrid(posture) {
23364
+ const packs = Object.keys(posture).sort(
23365
+ (a, b) => categoryRank(a) - categoryRank(b)
23366
+ );
23367
+ const rows = packs.map((category) => [
23368
+ category,
23369
+ ...BUILTIN_ORDER.map((level) => posture[category] === level ? GRID_MARK : "")
23370
+ ]);
23371
+ return indent(table(["Category", ...BUILTIN_ORDER], rows));
23372
+ }
23373
+ var READY_COMMANDS = ["/aka:health", "/aka:findings", "/aka:recommend"];
23374
+ function renderCategoriesTuned(categoriesTuned) {
23375
+ return `\u2713 Set all ${String(categoriesTuned)} detection categories`;
23376
+ }
23377
+ function renderApplied(categoriesTuned, dismissed, registry2) {
23378
+ const routine = dismissed > 0 ? `set aside ${String(dismissed)} routine result${dismissed === 1 ? "" : "s"}` : "nothing routine to set aside";
23379
+ const ready = `Ready: ${selectRegisteredCommands(READY_COMMANDS, registry2).join(" \xB7 ")}`;
23380
+ return `${renderCategoriesTuned(categoriesTuned)} \xB7 ${routine} \xB7 ${ready}`;
23381
+ }
23382
+
23383
+ // src/calibration.ts
23384
+ var SURFACED_KIND_LABEL = {
23385
+ secret: "live keys",
23386
+ pii: "personal data",
23387
+ financial: "financial records",
23388
+ phi: "health records",
23389
+ code_context: "source context",
23390
+ code_flaw: "code flaws",
23391
+ custom: "custom matches",
23392
+ config: "configuration secrets"
23393
+ };
23394
+ function frameCalibration(preview, maskedFindings = [], falsePositivePatterns = []) {
23395
+ const important = preview.categories.reduce((n, c) => n + c.genuineCount, 0);
23396
+ const routine = preview.categories.reduce((n, c) => n + c.fpCount, 0);
23397
+ const total = important + routine;
23398
+ const surfacedCategories = preview.categories.filter((c) => c.genuineCount > 0).map((c) => c.category);
23399
+ const routineCategories = preview.categories.filter((c) => c.fpCount > 0).map((c) => c.category);
23400
+ const findingKinds = preview.categories.filter((c) => c.genuineCount + c.fpCount > 0).map((c) => ({ category: c.category, count: c.genuineCount + c.fpCount, egress: c.egress }));
23401
+ const frame = {
23402
+ counts: { total, important, routine },
23403
+ routineCategories,
23404
+ surfacedCategories,
23405
+ findingKinds,
23406
+ posture: preview.posture,
23407
+ ...maskedFindings.length > 0 ? { maskedFindings: [...maskedFindings] } : {},
23408
+ ...falsePositivePatterns.length > 0 ? { falsePositivePatterns: [...falsePositivePatterns] } : {}
23409
+ };
23410
+ const kind = surfacedCategories.map((c) => SURFACED_KIND_LABEL[c]).join(", ");
23411
+ const parenthetical = kind ? ` (${kind})` : "";
23412
+ const headline = `I went through Claude's recent work \u2014 ${String(total)} detection${total === 1 ? "" : "s"}, ${String(important)} result${important === 1 ? "" : "s"} worth a look.${parenthetical}`;
23413
+ const copy = headline;
23414
+ return { frame, copy };
23415
+ }
23416
+ var SCAN_CLEAN_HEADLINE = "I looked over Claude's recent work \u2014 nothing needs your attention right now. You're starting clean; here's what I'd recommend:";
23417
+ var NO_HISTORY_HEADLINE = "Nothing to learn from yet \u2014 Claude hasn't left any work on this machine. I'll start each detection category at a careful default:";
23418
+ function frameEmptyState(cause, posture) {
23419
+ const frame = {
23420
+ counts: { total: 0, important: 0, routine: 0 },
23421
+ routineCategories: [],
23422
+ surfacedCategories: [],
23423
+ findingKinds: [],
23424
+ posture
23425
+ };
23426
+ const copy = cause === "scan-clean" ? `${SCAN_CLEAN_HEADLINE}
23427
+ ${renderRecommendedPosture(posture)}` : `${NO_HISTORY_HEADLINE}
23428
+ ${renderPostureGrid(posture)}`;
23429
+ return { frame, copy };
23430
+ }
23431
+
23432
+ // src/triage/dedupe.ts
23433
+ function dedupeKey(hit) {
23434
+ if (hit.valueFingerprint === void 0) return void 0;
23435
+ return `${hit.ruleId}\u2588${hit.valueFingerprint}`;
23436
+ }
23437
+ function dedupeForJudge(hits) {
23438
+ const seen = /* @__PURE__ */ new Set();
23439
+ const out = [];
23440
+ for (const h of hits) {
23441
+ const key = dedupeKey(h);
23442
+ if (key === void 0) {
23443
+ out.push(h);
23444
+ continue;
23445
+ }
23446
+ if (seen.has(key)) continue;
23447
+ seen.add(key);
23448
+ out.push(h);
23449
+ }
23450
+ return out;
23451
+ }
23452
+
23453
+ // src/triage/false-positive-patterns.ts
23454
+ function deriveFalsePositivePatterns(hits, rec, plan) {
23455
+ const hitById = new Map(hits.filter((h) => h.id !== void 0).map((h) => [h.id, h]));
23456
+ const seenCategory = /* @__PURE__ */ new Set();
23457
+ const markedIds = /* @__PURE__ */ new Set();
23458
+ for (const c of rec.perCategory) {
23459
+ if (seenCategory.has(c.category)) continue;
23460
+ seenCategory.add(c.category);
23461
+ if (plan.posture[c.category] === void 0) continue;
23462
+ for (const id of c.fpIds) markedIds.add(id);
23463
+ }
23464
+ const groups = /* @__PURE__ */ new Map();
23465
+ for (const id of markedIds) {
23466
+ const h = hitById.get(id);
23467
+ if (h === void 0) continue;
23468
+ const pattern = safeMaskedMatch(h.rawMatch);
23469
+ const group = groups.get(pattern) ?? { count: 0, values: [] };
23470
+ group.count += 1;
23471
+ if (h.valueFingerprint !== void 0 && h.keyVersion !== void 0) {
23472
+ group.values.push({
23473
+ ruleId: h.ruleId,
23474
+ category: h.category,
23475
+ valueFingerprint: h.valueFingerprint,
23476
+ keyVersion: h.keyVersion
23477
+ });
23478
+ }
23479
+ groups.set(pattern, group);
23480
+ }
23481
+ return [...groups.entries()].filter(([, g]) => g.values.length > 0).map(([pattern, g]) => ({ pattern, count: g.count, values: g.values }));
23482
+ }
23483
+
23484
+ // src/triage/merge.ts
23485
+ var RANK = { monitor: 0, warn: 1, redact: 2, block: 3 };
23486
+ function chunkForJudge(hits, maxBytes = 262144) {
23487
+ const chunks = [];
23488
+ let current = [];
23489
+ let size = 0;
23490
+ for (const h of hits) {
23491
+ const b = Buffer.byteLength(JSON.stringify(h)) + 1;
23492
+ if (size + b > maxBytes && current.length > 0) {
23493
+ chunks.push(current);
23494
+ current = [];
23495
+ size = 0;
23496
+ }
23497
+ current.push(h);
23498
+ size += b;
23499
+ }
23500
+ chunks.push(current);
23501
+ return chunks;
23502
+ }
23503
+ function chunkIds(hits) {
23504
+ return new Set(hits.map((h) => h.id).filter((id) => id !== void 0));
23505
+ }
23506
+ function joinReasonings(reasonings) {
23507
+ const ordered = [...reasonings].sort((a, b) => b.rank - a.rank);
23508
+ return [...new Set(ordered.map((r) => r.text.trim()).filter((t) => t !== ""))].join(" ");
23509
+ }
23510
+ function groundVerdict(rec, ids) {
23511
+ const perCategory = [];
23512
+ const strayNotes = [];
23513
+ for (const c of rec.perCategory) {
23514
+ const grounded = c.fpIds.filter((id) => ids.has(id));
23515
+ const strayCount = c.fpIds.length - grounded.length;
23516
+ if (strayCount > 0) {
23517
+ strayNotes.push(
23518
+ `${c.category}: dropped ${String(strayCount)} false-positive id(s) naming hits outside the batch they were judged in`
23519
+ );
23520
+ }
23521
+ perCategory.push({ ...c, fpIds: grounded });
23522
+ }
23523
+ return { perCategory, notes: [rec.notes, ...strayNotes].filter(Boolean).join("\n") };
23524
+ }
23525
+ function mergeRecommendations(verdicts) {
23526
+ const byCat = /* @__PURE__ */ new Map();
23527
+ const grounded = verdicts.map((v) => groundVerdict(v.rec, v.ids));
23528
+ for (const rec of grounded) {
23529
+ for (const c of rec.perCategory) {
23530
+ const entry = { rank: RANK[c.action], text: c.reasoning };
23531
+ const prev = byCat.get(c.category);
23532
+ if (!prev) {
23533
+ byCat.set(c.category, { ...c, fpIds: [...c.fpIds], reasonings: [entry] });
23534
+ continue;
23535
+ }
23536
+ prev.genuineCount += c.genuineCount;
23537
+ prev.fpCount += c.fpCount;
23538
+ prev.fpIds = [.../* @__PURE__ */ new Set([...prev.fpIds, ...c.fpIds])];
23539
+ prev.reasonings.push(entry);
23540
+ if (RANK[c.action] > RANK[prev.action]) prev.action = c.action;
23541
+ }
23542
+ }
23543
+ const perCategory = [...byCat.values()].map(({ reasonings, ...cat }) => ({
23544
+ ...cat,
23545
+ reasoning: joinReasonings(reasonings)
23546
+ }));
23547
+ return {
23548
+ perCategory,
23549
+ notes: grounded.map((r) => r.notes).filter(Boolean).join("\n")
23550
+ };
23551
+ }
23552
+
22957
23553
  // src/triage/plan-file.ts
22958
23554
  import { mkdtempSync, readFileSync as readFileSync7, rmdirSync, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
22959
23555
  import { tmpdir } from "os";
@@ -23032,6 +23628,38 @@ function deletePlanFile(path) {
23032
23628
  }
23033
23629
  }
23034
23630
 
23631
+ // src/triage/surfaced-secrets.ts
23632
+ var DEFAULT_STATE = "unknown";
23633
+ var UNKNOWN_LOCATION = "(location unavailable)";
23634
+ function deriveProvider(ruleId) {
23635
+ const slug = (ruleId.split("/").pop() ?? ruleId).split(".").pop() ?? ruleId;
23636
+ const provider = slug.split("-")[0] ?? slug;
23637
+ return provider === "" ? "unknown" : provider;
23638
+ }
23639
+ function deriveSurfacedSecretFindings(hits, rec, plan) {
23640
+ if (plan.posture.secret === void 0) return [];
23641
+ const dismissedIds = new Set(
23642
+ rec.perCategory.filter((c) => c.category === "secret").flatMap((c) => c.fpIds)
23643
+ );
23644
+ const dismissedKeys = new Set(
23645
+ hits.filter((h) => h.id !== void 0 && dismissedIds.has(h.id)).map((h) => dedupeKey(h)).filter((k) => k !== void 0)
23646
+ );
23647
+ const isDismissed = (h) => {
23648
+ if (h.id !== void 0 && dismissedIds.has(h.id)) return true;
23649
+ const key = dedupeKey(h);
23650
+ return key !== void 0 && dismissedKeys.has(key);
23651
+ };
23652
+ const rawValues = hits.map((h) => h.rawMatch);
23653
+ return hits.filter((h) => h.category === "secret" && !isDismissed(h)).map((h) => ({
23654
+ provider: deriveProvider(h.ruleId),
23655
+ maskedToken: safeMaskedMatch(h.rawMatch),
23656
+ // A filePath is not a raw secret, but pass it through the egress gate as
23657
+ // defence-in-depth so only a raw-free location can cross into the frame.
23658
+ where: { filePath: assertRawFree(h.filePath ?? UNKNOWN_LOCATION, rawValues) },
23659
+ state: DEFAULT_STATE
23660
+ }));
23661
+ }
23662
+
23035
23663
  // src/triage/join-file.ts
23036
23664
  function buildJoinEntries(hits) {
23037
23665
  const rawValues = hits.map((h) => h.rawMatch);
@@ -23101,7 +23729,7 @@ function resolveSuppressions(rec, join14) {
23101
23729
 
23102
23730
  // src/triage/writeback.ts
23103
23731
  var SCRUBBED_NOTES = "[notes withheld: model text referenced a raw detected value]";
23104
- var TRIAGE_STATUSES = ["complete", "skipped:no-consent"];
23732
+ var TRIAGE_STATUSES = ["complete", "complete:no-history", "skipped:no-consent"];
23105
23733
  function isSentinel(v) {
23106
23734
  return typeof v === "object" && v !== null && v.done === true && typeof v.count === "number" && typeof v.status === "string";
23107
23735
  }
@@ -23135,6 +23763,11 @@ function parseTriageStream(text) {
23135
23763
  `triage stream carried ${String(hitLines.length)} hits under a ${tail.status} sentinel`
23136
23764
  );
23137
23765
  }
23766
+ if (tail.count !== 0) {
23767
+ throw new Error(
23768
+ `triage stream sentinel reported ${String(tail.count)} hits under a ${tail.status} sentinel`
23769
+ );
23770
+ }
23138
23771
  return { hits: [], status: tail.status };
23139
23772
  }
23140
23773
  if (tail.count !== hitLines.length) {
@@ -23210,13 +23843,22 @@ function planTriageWriteback(hits, rec) {
23210
23843
  }
23211
23844
  return { entries, posture, showcase, join: join14, notes, skipped };
23212
23845
  }
23846
+ function recommendedPosture(evidence) {
23847
+ return { ...severityFloorPosture(), ...evidence };
23848
+ }
23213
23849
  async function performTriageWriteback(plan, writers, opts) {
23214
23850
  const applyBoth = async () => {
23215
23851
  applyCategoryPosture(plan.posture, writers.policies, "overwrite");
23216
- return applySetupTriageSuppressions(plan.entries, writers.exceptions, opts);
23852
+ if (opts.floor) applyCategoryPosture(opts.floor, writers.policies, "fill-gaps");
23853
+ return applySetupTriageSuppressions(plan.entries, writers.exceptions, {
23854
+ createdBy: opts.createdBy,
23855
+ now: opts.now
23856
+ });
23217
23857
  };
23218
23858
  const { written, skippedDuplicate } = writers.transaction ? await writers.transaction(applyBoth) : await applyBoth();
23219
- return { written, skippedDuplicate, categoriesWritten: Object.keys(plan.posture).length };
23859
+ const tuned = new Set(Object.keys(plan.posture));
23860
+ if (opts.floor) for (const c of Object.keys(opts.floor)) tuned.add(c);
23861
+ return { written, skippedDuplicate, categoriesWritten: tuned.size };
23220
23862
  }
23221
23863
 
23222
23864
  // src/triage/adapter.ts
@@ -23231,6 +23873,14 @@ function getFlag(argv, name) {
23231
23873
  const next = argv[i + 1];
23232
23874
  return next !== void 0 && !next.startsWith("--") ? next : "";
23233
23875
  }
23876
+ function resolveMaxJudgeBytes(deps) {
23877
+ const flag = getFlag(deps.argv, "max-judge-bytes");
23878
+ if (flag !== void 0 && flag !== "") {
23879
+ const parsed = Number(flag);
23880
+ if (Number.isInteger(parsed) && parsed > 0) return parsed;
23881
+ }
23882
+ return deps.maxJudgeBytes;
23883
+ }
23234
23884
  async function runApply(deps) {
23235
23885
  const planIO = deps.planIO ?? DEFAULT_PLAN_IO;
23236
23886
  const confirmed = deps.argv.includes("--confirmed");
@@ -23241,38 +23891,86 @@ function runPreview(deps, planIO) {
23241
23891
  const streamText = deps.readStream(streamPath === "" ? void 0 : streamPath);
23242
23892
  const { hits, status } = parseTriageStream(streamText);
23243
23893
  if (hits.length === 0) {
23244
- deps.stdout(`No triage hits to review (${status}). Nothing to suppress.
23245
- `);
23894
+ if (status === "complete") {
23895
+ const empty = frameEmptyState("scan-clean", severityFloorPosture());
23896
+ deps.stdout(show(fenced(empty.copy)));
23897
+ deps.stdout(frameJsonBlock(empty.frame));
23898
+ return 0;
23899
+ }
23900
+ if (status === "complete:no-history") {
23901
+ const empty = frameEmptyState("no-history", severityFloorPosture());
23902
+ deps.stdout(show(fenced(empty.copy)));
23903
+ deps.stdout(frameJsonBlock(empty.frame));
23904
+ return 0;
23905
+ }
23906
+ deps.stdout(show("I didn't review anything \u2014 historical access wasn't granted."));
23246
23907
  return 0;
23247
23908
  }
23248
23909
  const rawValues = hits.map((h) => h.rawMatch);
23249
23910
  try {
23250
- const rec = deps.runJudge(hits);
23251
- const plan = planTriageWriteback(hits, rec);
23911
+ const reps = dedupeForJudge(hits);
23912
+ const chunks = chunkForJudge(reps, resolveMaxJudgeBytes(deps));
23913
+ if (chunks.length > 1) {
23914
+ deps.stdout(
23915
+ show(
23916
+ `Reviewing ${String(reps.length)} distinct values in ${String(chunks.length)} batches \u2014 this is the large-history path, so give it a moment.`
23917
+ )
23918
+ );
23919
+ }
23920
+ let rec;
23921
+ if (chunks.length === 1) {
23922
+ const [soleChunk] = chunks;
23923
+ if (soleChunk === void 0) throw new Error("chunkForJudge returned no chunks");
23924
+ rec = groundVerdict(deps.runJudge(soleChunk), chunkIds(soleChunk));
23925
+ } else {
23926
+ rec = mergeRecommendations(chunks.map((c) => ({ rec: deps.runJudge(c), ids: chunkIds(c) })));
23927
+ }
23928
+ const plan = planTriageWriteback(reps, rec);
23252
23929
  const current = {};
23253
- const db = deps.openDb();
23930
+ let storeUnavailable = false;
23254
23931
  try {
23255
- for (const category of Object.keys(plan.posture)) {
23256
- const action = db.policies.getCategoryAction(category);
23257
- if (action !== void 0) current[category] = action;
23932
+ const db = deps.openDb();
23933
+ try {
23934
+ for (const category of Object.keys(plan.posture)) {
23935
+ const action = db.policies.getCategoryAction(category);
23936
+ if (action !== void 0) current[category] = action;
23937
+ }
23938
+ } finally {
23939
+ db.close();
23258
23940
  }
23259
- } finally {
23260
- db.close();
23941
+ } catch {
23942
+ storeUnavailable = true;
23943
+ }
23944
+ const gate = [];
23945
+ if (storeUnavailable) {
23946
+ gate.push(STORE_UNAVAILABLE_NOTE);
23261
23947
  }
23262
- deps.stdout(renderPosturePlan(plan.posture, current) + "\n\n");
23263
- deps.stdout(renderShowcase(plan.showcase) + "\n\n");
23264
- deps.stdout(renderSuppressionGate(plan.entries, plan.join) + "\n");
23948
+ gate.push(renderPosturePlan(plan.posture, storeUnavailable ? {} : current));
23949
+ gate.push(renderShowcase(plan.showcase));
23950
+ gate.push(renderSuppressionGate(plan.entries, plan.join));
23265
23951
  if (plan.skipped.length > 0) {
23266
- deps.stdout(
23267
- `
23268
- Skipped (fail-secure): ${plan.skipped.map((s) => `${s.category} \u2014 ${s.reason}`).join("; ")}
23269
- `
23952
+ gate.push(
23953
+ `Skipped (fail-secure): ${plan.skipped.map((s) => `${s.category} \u2014 ${s.reason}`).join("; ")}`
23270
23954
  );
23271
23955
  }
23272
- deps.stdout(`
23273
- Notes: ${plan.notes}
23274
- `);
23275
- const planPath = planIO.write(plan, current, rawValues);
23956
+ gate.push(`Notes: ${plan.notes}`);
23957
+ const preview = {
23958
+ categories: plan.showcase.map((c) => ({
23959
+ category: c.category,
23960
+ genuineCount: c.genuineCount,
23961
+ fpCount: c.fpCount,
23962
+ egress: false
23963
+ })),
23964
+ posture: recommendedPosture(plan.posture)
23965
+ };
23966
+ const maskedFindings = deriveSurfacedSecretFindings(hits, rec, plan);
23967
+ const falsePositivePatterns = deriveFalsePositivePatterns(reps, rec, plan);
23968
+ const calibration = frameCalibration(preview, maskedFindings, falsePositivePatterns);
23969
+ gate.push(calibration.copy);
23970
+ gate.push(renderRecommendedPosture(preview.posture));
23971
+ deps.stdout(show(fenced(gate.join("\n\n"))));
23972
+ deps.stdout(frameJsonBlock(calibration.frame));
23973
+ const planPath = planIO.write(plan, storeUnavailable ? {} : current, rawValues);
23276
23974
  deps.stdout(`
23277
23975
  Plan saved to: ${planPath}
23278
23976
  `);
@@ -23348,6 +24046,13 @@ async function runConfirm(deps, planIO) {
23348
24046
  // `transaction` makes the posture + suppression writes ALL-OR-NOTHING, so a
23349
24047
  // mid-batch fault rolls the posture overwrite back too — the store is never
23350
24048
  // left half-applied (and the floor fallback below is safe: nothing persisted).
24049
+ // Establish the full 8-pack the preview showed so settings holds
24050
+ // all 8 packs and the confirmation reads 'Set all 8 detection categories': the reviewed,
24051
+ // drift-gated evidence packs (plan.posture) OVERWRITE, and the severity floor
24052
+ // fills the remaining packs with FILL-GAPS. The floor packs are not covered by
24053
+ // the drift gate above (only the reviewed evidence is), so they must never
24054
+ // overwrite — an out-of-band-hardened pack (e.g. code_context=block) is left
24055
+ // as-is rather than silently reset to the weak floor.
23351
24056
  {
23352
24057
  posture: plan.posture,
23353
24058
  entries: plan.entries,
@@ -23362,7 +24067,7 @@ async function runConfirm(deps, planIO) {
23362
24067
  // Only set when the store provides one (exactOptionalPropertyTypes).
23363
24068
  ...db.transaction ? { transaction: db.transaction } : {}
23364
24069
  },
23365
- { createdBy: deps.createdBy(), now: deps.now() }
24070
+ { createdBy: deps.createdBy(), now: deps.now(), floor: severityFloorPosture() }
23366
24071
  );
23367
24072
  closeOnce();
23368
24073
  try {
@@ -23371,8 +24076,7 @@ async function runConfirm(deps, planIO) {
23371
24076
  }
23372
24077
  try {
23373
24078
  deps.stdout(
23374
- `AKA suppressions applied: ${String(res.written)} written` + (res.skippedDuplicate > 0 ? `, ${String(res.skippedDuplicate)} already active` : "") + `; posture calibrated for ${String(res.categoriesWritten)} categories.
23375
- `
24079
+ show(renderApplied(res.categoriesWritten, res.written, readRegisteredCommands()))
23376
24080
  );
23377
24081
  } catch {
23378
24082
  }
@@ -23392,7 +24096,7 @@ import { execFileSync } from "child_process";
23392
24096
  import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
23393
24097
  import { tmpdir as tmpdir2 } from "os";
23394
24098
  import { dirname as dirname3, join as join12 } from "path";
23395
- import { fileURLToPath } from "url";
24099
+ import { fileURLToPath as fileURLToPath2 } from "url";
23396
24100
 
23397
24101
  // src/triage/parse-verdict.ts
23398
24102
  var FENCE_RE = /```json\s*([\s\S]*?)```/g;
@@ -23404,7 +24108,7 @@ function parseRecommendation(text) {
23404
24108
  }
23405
24109
 
23406
24110
  // src/triage/judge.ts
23407
- var TRIAGE_DIR = dirname3(fileURLToPath(import.meta.url));
24111
+ var TRIAGE_DIR = dirname3(fileURLToPath2(import.meta.url));
23408
24112
  var DEFAULT_RUBRIC_PATH = join12(TRIAGE_DIR, "..", "..", "eval", "prompt.md");
23409
24113
  function parseVerdict(stdout) {
23410
24114
  let envelope;
@@ -23436,9 +24140,10 @@ function judgeEnv() {
23436
24140
  }
23437
24141
  return env;
23438
24142
  }
23439
- function spawnClaude(argv, env) {
24143
+ function spawnClaude(argv, env, stdin) {
23440
24144
  return execFileSync("claude", [...argv], {
23441
24145
  env,
24146
+ input: stdin,
23442
24147
  encoding: "utf8",
23443
24148
  timeout: 18e4,
23444
24149
  maxBuffer: 32 * 1024 * 1024
@@ -23463,12 +24168,12 @@ function runJudge(hits, deps) {
23463
24168
  ${hitsJsonl}
23464
24169
  \`\`\`
23465
24170
  `;
23466
- const argv = ["-p", "--no-session-persistence", "--output-format", "json", fullPrompt];
24171
+ const argv = ["-p", "--no-session-persistence", "--output-format", "json"];
23467
24172
  const env = judgeEnv();
23468
24173
  try {
23469
24174
  let stdout;
23470
24175
  try {
23471
- stdout = deps.spawn(argv, env);
24176
+ stdout = deps.spawn(argv, env, fullPrompt);
23472
24177
  } catch (err) {
23473
24178
  throw new Error(`claude -p judge subprocess failed (${spawnFailureMeta(err)})`);
23474
24179
  }
@@ -23494,14 +24199,15 @@ function resolveCreatedBy() {
23494
24199
  }
23495
24200
  }
23496
24201
  function loadRubric() {
23497
- const here = dirname4(fileURLToPath2(import.meta.url));
24202
+ const here = dirname4(fileURLToPath3(import.meta.url));
23498
24203
  const shipped = join13(here, "triage-rubric.md");
23499
24204
  if (existsSync5(shipped)) return readFileSync9(shipped, "utf8");
23500
24205
  return readFileSync9(join13(here, "..", "eval", "prompt.md"), "utf8");
23501
24206
  }
23502
24207
  async function main() {
24208
+ const argv = process.argv.slice(2);
23503
24209
  const code = await runApply({
23504
- argv: process.argv.slice(2),
24210
+ argv,
23505
24211
  // fd 0 = stdin; the wizard pipes `backfill.js --triage` into this on preview.
23506
24212
  // Called only on the preview path — the confirm path never reads a stream.
23507
24213
  readStream: (streamPath) => streamPath !== void 0 ? readFileSync9(streamPath, "utf8") : readFileSync9(0, "utf8"),