@shield-agent/kya 0.33.5 → 0.34.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.
Files changed (118) hide show
  1. package/.cursor-plugin/plugin.json +1 -1
  2. package/.mcp.json +1 -1
  3. package/README.md +22 -5
  4. package/amp/settings.example.json +1 -1
  5. package/assets/screenshot-features.json +17 -0
  6. package/claude/claude_desktop_config.example.json +1 -1
  7. package/cline/cline_mcp_settings.example.json +1 -1
  8. package/copilot/mcp-config.example.json +1 -1
  9. package/dist/alerts/config.d.ts +22 -0
  10. package/dist/alerts/config.d.ts.map +1 -0
  11. package/dist/alerts/config.js +59 -0
  12. package/dist/alerts/config.js.map +1 -0
  13. package/dist/alerts/constants.d.ts +5 -0
  14. package/dist/alerts/constants.d.ts.map +1 -0
  15. package/dist/alerts/constants.js +5 -0
  16. package/dist/alerts/constants.js.map +1 -0
  17. package/dist/alerts/desktop.d.ts +19 -0
  18. package/dist/alerts/desktop.d.ts.map +1 -0
  19. package/dist/alerts/desktop.js +57 -0
  20. package/dist/alerts/desktop.js.map +1 -0
  21. package/dist/alerts/incident-alert.d.ts +42 -0
  22. package/dist/alerts/incident-alert.d.ts.map +1 -0
  23. package/dist/alerts/incident-alert.js +84 -0
  24. package/dist/alerts/incident-alert.js.map +1 -0
  25. package/dist/alerts/scan.d.ts +37 -0
  26. package/dist/alerts/scan.d.ts.map +1 -0
  27. package/dist/alerts/scan.js +188 -0
  28. package/dist/alerts/scan.js.map +1 -0
  29. package/dist/alerts/state.d.ts +55 -0
  30. package/dist/alerts/state.d.ts.map +1 -0
  31. package/dist/alerts/state.js +178 -0
  32. package/dist/alerts/state.js.map +1 -0
  33. package/dist/alerts/triage-input.d.ts +36 -0
  34. package/dist/alerts/triage-input.d.ts.map +1 -0
  35. package/dist/alerts/triage-input.js +159 -0
  36. package/dist/alerts/triage-input.js.map +1 -0
  37. package/dist/alerts/triage-model.d.ts +42 -0
  38. package/dist/alerts/triage-model.d.ts.map +1 -0
  39. package/dist/alerts/triage-model.js +126 -0
  40. package/dist/alerts/triage-model.js.map +1 -0
  41. package/dist/alerts/triage-prompt.d.ts +23 -0
  42. package/dist/alerts/triage-prompt.d.ts.map +1 -0
  43. package/dist/alerts/triage-prompt.js +53 -0
  44. package/dist/alerts/triage-prompt.js.map +1 -0
  45. package/dist/alerts/triage-schema.json +52 -0
  46. package/dist/alerts/triage-setup.d.ts +73 -0
  47. package/dist/alerts/triage-setup.d.ts.map +1 -0
  48. package/dist/alerts/triage-setup.js +280 -0
  49. package/dist/alerts/triage-setup.js.map +1 -0
  50. package/dist/alerts/triage-status.d.ts +26 -0
  51. package/dist/alerts/triage-status.d.ts.map +1 -0
  52. package/dist/alerts/triage-status.js +61 -0
  53. package/dist/alerts/triage-status.js.map +1 -0
  54. package/dist/alerts/triage-tokenizer.d.ts +29 -0
  55. package/dist/alerts/triage-tokenizer.d.ts.map +1 -0
  56. package/dist/alerts/triage-tokenizer.js +82 -0
  57. package/dist/alerts/triage-tokenizer.js.map +1 -0
  58. package/dist/cli.d.ts.map +1 -1
  59. package/dist/cli.js +14 -0
  60. package/dist/cli.js.map +1 -1
  61. package/dist/commands/alerts.d.ts +16 -0
  62. package/dist/commands/alerts.d.ts.map +1 -0
  63. package/dist/commands/alerts.js +112 -0
  64. package/dist/commands/alerts.js.map +1 -0
  65. package/dist/commands/notify-flush.d.ts.map +1 -1
  66. package/dist/commands/notify-flush.js +3 -0
  67. package/dist/commands/notify-flush.js.map +1 -1
  68. package/dist/commands/receipt-serve.d.ts +1 -1
  69. package/dist/commands/receipt-serve.d.ts.map +1 -1
  70. package/dist/commands/receipt-serve.js +3 -2
  71. package/dist/commands/receipt-serve.js.map +1 -1
  72. package/dist/investigate/detectors.d.ts +6 -0
  73. package/dist/investigate/detectors.d.ts.map +1 -1
  74. package/dist/investigate/detectors.js +10 -0
  75. package/dist/investigate/detectors.js.map +1 -1
  76. package/dist/notify/dispatch.d.ts +1 -1
  77. package/dist/notify/dispatch.d.ts.map +1 -1
  78. package/dist/notify/dispatch.js +4 -1
  79. package/dist/notify/dispatch.js.map +1 -1
  80. package/dist/notify/incident-templates.d.ts +23 -0
  81. package/dist/notify/incident-templates.d.ts.map +1 -0
  82. package/dist/notify/incident-templates.js +128 -0
  83. package/dist/notify/incident-templates.js.map +1 -0
  84. package/dist/notify/index.d.ts +12 -0
  85. package/dist/notify/index.d.ts.map +1 -1
  86. package/dist/notify/index.js +50 -3
  87. package/dist/notify/index.js.map +1 -1
  88. package/dist/notify/templates.d.ts +6 -0
  89. package/dist/notify/templates.d.ts.map +1 -1
  90. package/dist/notify/templates.js +12 -2
  91. package/dist/notify/templates.js.map +1 -1
  92. package/dist/receipt/live-server.d.ts +4 -0
  93. package/dist/receipt/live-server.d.ts.map +1 -1
  94. package/dist/receipt/live-server.js +48 -0
  95. package/dist/receipt/live-server.js.map +1 -1
  96. package/dist/receipt/render-receipt.d.ts +3 -0
  97. package/dist/receipt/render-receipt.d.ts.map +1 -1
  98. package/dist/receipt/render-receipt.js +28 -1
  99. package/dist/receipt/render-receipt.js.map +1 -1
  100. package/dist/trail.d.ts +6 -0
  101. package/dist/trail.d.ts.map +1 -1
  102. package/dist/trail.js +11 -0
  103. package/dist/trail.js.map +1 -1
  104. package/droid/README.md +1 -1
  105. package/droid/mcp.example.json +1 -1
  106. package/gemini/settings.example.json +1 -1
  107. package/grok/README.md +2 -2
  108. package/kilo/kilo.example.json +1 -1
  109. package/kimi/mcp.example.json +1 -1
  110. package/kiro/mcp.example.json +1 -1
  111. package/manifest.json +1 -1
  112. package/mastracode/mcp.example.json +1 -1
  113. package/mcp.json +1 -1
  114. package/openai/codex.config.example.toml +1 -1
  115. package/opencode/opencode.example.json +1 -1
  116. package/package.json +1 -1
  117. package/qwen/settings.example.json +1 -1
  118. package/server.json +2 -2
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shield-kya",
3
- "version": "0.33.5",
3
+ "version": "0.34.0",
4
4
  "description": "Audit trail and traceability for AI agents. See what every coding agent did and check each tool call against policy.",
5
5
  "author": {
6
6
  "name": "Shield Agent",
package/.mcp.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "shield-kya": {
4
4
  "command": "npx",
5
- "args": ["--no-install", "@shield-agent/kya@0.33.5", "serve-mcp", "--stdio"],
5
+ "args": ["--no-install", "@shield-agent/kya@0.34.0", "serve-mcp", "--stdio"],
6
6
  "env": {
7
7
  "KYA_BASE_URL": "https://shield-agent.com",
8
8
  "KYA_API_KEY": "${KYA_API_KEY}",
package/README.md CHANGED
@@ -228,7 +228,7 @@ kya gate doctor # binary, config, listener health, loopback-only posture
228
228
 
229
229
  If another process already holds a default port (`port` 3930 or `otlpPort` 3931), `kya gate run` stops with `EADDRINUSE`. Set free ports in `.kya/gateways.json` (`port` and `otlpPort`), then run it again.
230
230
 
231
- `kya gate stop` stops the supervisor and the binary with it. Honest notes: `kya gate setup` (and `kya start`'s auto-bootstrap, only when it found servers to govern) downloads the pinned gateway binary from our releases. Nothing else downloads anything. The only other network use is the opt-in anonymous usage stats in [docs/telemetry.md](docs/telemetry.md), off unless you say yes. `run` never downloads. Observe mode means allowed calls are recorded, not blocked. The deny set above is the only hard stop, and plane enforcement stays with `kya.policy_evaluate`. `kya connect <host> --gate` covers the same hosts as plain `connect`. Grok gets a `[mcp_servers.shield-kya-gate]` url table in its config.toml (Grok supports remote MCP servers over HTTP). Per-server tiers and recipes: [docs/mcp-servers.md](docs/mcp-servers.md).
231
+ `kya gate stop` stops the supervisor and the binary with it. Honest notes: `kya gate setup` (and `kya start`'s auto-bootstrap, only when it found servers to govern) downloads the pinned gateway binary from our releases. Nothing else downloads anything by default. The opt-in offers download only after you say yes: the security alerts offer (the local triage model and its runtime, see [docs/alerts.md](docs/alerts.md)) and the plain-language search offer. The only other network use is the opt-in anonymous usage stats in [docs/telemetry.md](docs/telemetry.md), off unless you say yes. `run` never downloads. Observe mode means allowed calls are recorded, not blocked. The deny set above is the only hard stop, and plane enforcement stays with `kya.policy_evaluate`. `kya connect <host> --gate` covers the same hosts as plain `connect`. Grok gets a `[mcp_servers.shield-kya-gate]` url table in its config.toml (Grok supports remote MCP servers over HTTP). Per-server tiers and recipes: [docs/mcp-servers.md](docs/mcp-servers.md).
232
232
 
233
233
  ## Longer path (optional)
234
234
 
@@ -383,7 +383,7 @@ MCP Registry entry: `server.json` plus package `mcpName` `io.github.The-Pixel-Bo
383
383
  "mcpServers": {
384
384
  "shield-kya": {
385
385
  "command": "npx",
386
- "args": ["--no-install", "@shield-agent/kya@0.33.5", "serve-mcp", "--stdio"],
386
+ "args": ["--no-install", "@shield-agent/kya@0.34.0", "serve-mcp", "--stdio"],
387
387
  "env": {
388
388
  "KYA_BASE_URL": "http://127.0.0.1:8090",
389
389
  "KYA_API_KEY": "${KYA_API_KEY}",
@@ -427,7 +427,7 @@ Framework recipes (each one tested with a scripted model and the published CLI):
427
427
 
428
428
  ```bash
429
429
  # Prefer a preinstalled package (no registry auto-install):
430
- npx --no-install @shield-agent/kya@0.33.5 serve-mcp --stdio
430
+ npx --no-install @shield-agent/kya@0.34.0 serve-mcp --stdio
431
431
  # Or after npm i -g / local install:
432
432
  kya serve-mcp --stdio
433
433
  ```
@@ -438,7 +438,7 @@ Copy `claude/claude_desktop_config.example.json` into Claude Desktop MCP setting
438
438
 
439
439
  ## OpenAI (Codex / Responses)
440
440
 
441
- **Codex CLI plugin (hooks):** see [Codex plugin](#codex-plugin). **Codex CLI / IDE (MCP):** copy `openai/codex.config.example.toml` into `~/.codex/config.toml`. Local stdio uses `npx --no-install @shield-agent/kya@0.33.5 serve-mcp --stdio`. Hosted Codex uses `url = "https://shield-agent.com/mcp"` with `bearer_token_env_var = "KYA_API_KEY"`.
441
+ **Codex CLI plugin (hooks):** see [Codex plugin](#codex-plugin). **Codex CLI / IDE (MCP):** copy `openai/codex.config.example.toml` into `~/.codex/config.toml`. Local stdio uses `npx --no-install @shield-agent/kya@0.34.0 serve-mcp --stdio`. Hosted Codex uses `url = "https://shield-agent.com/mcp"` with `bearer_token_env_var = "KYA_API_KEY"`.
442
442
 
443
443
  **Responses API:** see `openai/responses-mcp.example.json` (`server_url` + `Authorization: Bearer <KYA_API_KEY>`).
444
444
 
@@ -597,6 +597,21 @@ The `teams` template sends one Adaptive Card to a Teams Workflows webhook (in Te
597
597
 
598
598
  `KYA_NOTIFY_WEBHOOK=<url>` is the zero-config variant (generic payload). Delivery retries with exponential backoff (250ms/1s/4s + jitter), per-attempt timeouts, a per-URL circuit breaker (stops sending to a target that keeps failing), and a send rate limiter. Payloads are built only from redacted record fields, with an automatic minimized fallback if the secret scan finds something. From short-lived hook/wrap spawns, a detached helper (`kya notify-flush`) sends the webhooks, so the gate path never waits on the network. Every delivery attempt is appended to `~/.kya/notify-log.jsonl` (target host only, never the full URL; capped at the last 500 entries), and the report's System tab shows an **Alerts** panel with per-target delivered/failed counts and the last attempt.
599
599
 
600
+ ## Security alerts
601
+
602
+ `kya` can alert you when its detectors find an incident, for example a never-list tool that an agent tries again and again. The fixed detectors decide that it is an incident. An optional local model (about 150 MB plus a local runtime of about 100 MB, fully offline after one download, advisory) adds a category, an urgency and a next step. Alerts are **off by default** and **advisory**: they never block a tool call, and the model never removes an alert.
603
+
604
+ `kya start` asks once, in a terminal: `Turn on security alerts? [Y/n]`. Yes installs the local runtime and model, turns alerts on and sends one test alert (desktop notification and webhooks). No is remembered and never asked again; turn it on later with `KYA_ALERTS=on kya start`. `KYA_ALERTS=off` never asks. Without a terminal `kya start` prints one hint line. If the install fails, `kya start` still works and alerts use the detector severity only. To turn alerts off: `kya alerts off` (or `KYA_ALERTS=off`). Each new incident also shows a desktop notification (fixed text, no agent text); turn it off with `"alerts": {"desktop": false}`.
605
+
606
+ ```bash
607
+ kya alerts on # write alerts.enabled to ~/.kya/config.json (no model download)
608
+ kya alerts status # setting, severity floor, desktop, triage model, receivers, last 10 alerts (--json for scripts)
609
+ kya alerts test # send one synthetic incident, marked as a test, to the desktop and to each receiver
610
+ kya alerts off
611
+ ```
612
+
613
+ The alert goes to every webhook in `notify.webhooks` of `~/.kya/config.json` whose `events` list holds `"incident.opened"` (or that has no `events` list). It uses the same templates, retries, circuit breaker and rate limit as the decision alerts. The live report also lists the incidents that alerted, with the triage. The `triage` object holds `model`, `category`, `urgency` and `action` (each `{id, p}`) and `benign` (the probability that the incident is harmless); the model cannot change the severity or remove an alert, and a missing model means no `triage` object. Settings (user level only, a repo config cannot change them): `{"alerts": {"enabled": true, "minSeverity": "high", "windowMinutes": 120, "desktop": true}}`. `KYA_ALERTS=on|off` overrides `enabled`. See [docs/alerts.md](docs/alerts.md).
614
+
600
615
  ## Optional sandbox wrap (Firecracker, OpenShell)
601
616
 
602
617
  Beside the gate, not inside MCP. Opt-in only:
@@ -677,7 +692,8 @@ Generated from `sdks/kya/features.json` by `pnpm sync:features`. Do not edit by
677
692
  | Detections and fix briefs | `kya investigate`, `--json` | beta |
678
693
  | Endpoint discovery | `kya discover`, `--json`, `--diff`, `--push`, `--file` | beta |
679
694
  | Opt-in Firecracker wrap | `kya sandbox`, `--sandbox`, `--sandbox-id`, `--cmd`, `--path`, `--offline`, `KYA_SANDBOX` | beta |
680
- | Alert webhooks | none | beta |
695
+ | Alerts | `kya alerts`, `kya alerts status`, `kya alerts test`, `kya alerts on`, `kya alerts off`, `--json`, `KYA_ALERTS=on`, `alerts.desktop`, `KYA_ALERTS_DESKTOP=off` | beta |
696
+ | Local triage model for security alerts | `KYA_ALERTS=on`, `KYA_ALERTS=off`, `KYA_TRIAGE_MODEL_PATH` | beta |
681
697
  | OTLP export | `otlpPort` | beta |
682
698
  | OTLP map attributes | `otlpExport.mapAttrs` | beta |
683
699
  | In-process SDK shims | none | stable |
@@ -711,6 +727,7 @@ Generated from `sdks/kya/features.json` by `pnpm sync:features`. Do not edit by
711
727
  - [Import the AGT audit log (CLI)](docs/import-agt.md) · [hosted](docs/import-agt-hosted.md)
712
728
  - [kya certify - Agent Trust Baseline gap reports](docs/certify.md)
713
729
  - [EU AI Act crosswalk for the Agent Trust Baseline](docs/certify-eu-ai-act.md)
730
+ - [Security alerts for detected incidents (optional local triage model, advisory)](docs/alerts.md)
714
731
  - [Sandbox backends (mock / firecracker / openshell)](docs/sandbox.md)
715
732
  - [Works with your observability stack](docs/marketing/observability-stack.md)
716
733
  - [SDK & framework integrations (in-process shims)](docs/sdk-integrations.md)
@@ -2,7 +2,7 @@
2
2
  "amp.mcpServers": {
3
3
  "shield-kya": {
4
4
  "command": "npx",
5
- "args": ["--no-install", "@shield-agent/kya@0.33.5", "serve-mcp", "--stdio"],
5
+ "args": ["--no-install", "@shield-agent/kya@0.34.0", "serve-mcp", "--stdio"],
6
6
  "env": {
7
7
  "KYA_BASE_URL": "https://shield-agent.com",
8
8
  "KYA_API_KEY": "${KYA_API_KEY}",
@@ -0,0 +1,17 @@
1
+ {
2
+ "$comment": "Links every screenshot, video or demo listed in channel-copy.json to the features.json ids it shows. Rule R9 of scripts/check-feature-coverage.mjs: a PR that changes one of these feature rows must retake the picture, or add stillValid.<id> = { rowSha, note } here (rowSha is printed by the failing check). Docs: docs/operations/kya-published-refs.md in the monorepo.",
3
+ "screenshots": {
4
+ "assets/logo.svg": { "features": [] },
5
+ "assets/activity-receipt.png": { "features": ["kya-receipt"] },
6
+ "assets/report-activity.png": { "features": ["kya-receipt"] },
7
+ "assets/report-certify.png": { "features": ["kya-certify", "kya-receipt"] },
8
+ "assets/report-changes.png": { "features": ["kya-diff-preview", "kya-receipt"] },
9
+ "assets/report-gateway-backends.png": { "features": ["kya-gate"] },
10
+ "assets/report-gateway-listeners.png": { "features": ["kya-gate"] },
11
+ "assets/report-gateway-playground.png": { "features": ["kya-gate"] },
12
+ "assets/report-gateway-policies.png": { "features": ["kya-gate", "kya-gate-deny"] },
13
+ "assets/report-gateway-routes.png": { "features": ["kya-gate"] },
14
+ "assets/report-gateway.png": { "features": ["kya-gate"] },
15
+ "assets/report-overview.png": { "features": ["kya-receipt", "kya-certify"] }
16
+ }
17
+ }
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "shield-kya": {
4
4
  "command": "npx",
5
- "args": ["--no-install", "@shield-agent/kya@0.33.5", "serve-mcp", "--stdio"],
5
+ "args": ["--no-install", "@shield-agent/kya@0.34.0", "serve-mcp", "--stdio"],
6
6
  "env": {
7
7
  "KYA_BASE_URL": "https://shield-agent.com",
8
8
  "KYA_API_KEY": "${KYA_API_KEY}",
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "shield-kya": {
4
4
  "command": "npx",
5
- "args": ["--no-install", "@shield-agent/kya@0.33.5", "serve-mcp", "--stdio"],
5
+ "args": ["--no-install", "@shield-agent/kya@0.34.0", "serve-mcp", "--stdio"],
6
6
  "env": {
7
7
  "KYA_BASE_URL": "https://shield-agent.com",
8
8
  "KYA_API_KEY": "${KYA_API_KEY}",
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "shield-kya": {
4
4
  "command": "npx",
5
- "args": ["--no-install", "@shield-agent/kya@0.33.5", "serve-mcp", "--stdio"],
5
+ "args": ["--no-install", "@shield-agent/kya@0.34.0", "serve-mcp", "--stdio"],
6
6
  "env": {
7
7
  "KYA_BASE_URL": "https://shield-agent.com",
8
8
  "KYA_API_KEY": "${KYA_API_KEY}",
@@ -0,0 +1,22 @@
1
+ import type { Severity } from "../investigate/detectors.js";
2
+ export declare const SEVERITIES: readonly Severity[];
3
+ export declare const DEFAULT_MIN_SEVERITY: Severity;
4
+ export declare const DEFAULT_WINDOW_MINUTES = 120;
5
+ /** Upper bound so a typo cannot make every flush read a huge window. */
6
+ export declare const MAX_WINDOW_MINUTES: number;
7
+ export interface AlertsConfig {
8
+ readonly enabled: boolean;
9
+ readonly minSeverity: Severity;
10
+ readonly windowMinutes: number;
11
+ /** Desktop notification for each new incident alert. `alerts.desktop: false` or KYA_ALERTS_DESKTOP=off turns it off. */
12
+ readonly desktop: boolean;
13
+ /** Where `enabled` came from. */
14
+ readonly source: "env" | "config" | "default";
15
+ }
16
+ export declare function severityRank(s: Severity): number;
17
+ export declare function isSeverity(v: unknown): v is Severity;
18
+ /** The user-level `alerts` object, defensively parsed. Never throws. */
19
+ export declare function resolveAlertsConfig(env?: NodeJS.ProcessEnv): AlertsConfig;
20
+ /** `kya alerts on|off`: write `alerts.enabled` to the user-level config, keep the other keys. */
21
+ export declare function writeAlertsEnabled(env: NodeJS.ProcessEnv, enabled: boolean): void;
22
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/alerts/config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAA0C,CAAC;AACrF,eAAO,MAAM,oBAAoB,EAAE,QAAiB,CAAC;AACrD,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,QAAc,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,wHAAwH;IACxH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC/C;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,CAEhD;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,QAAQ,CAEpD;AAUD,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CAqBtF;AAED,iGAAiG;AACjG,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAKjF"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Incident alert settings. User level only: they come from <KYA_HOME|home>/.kya/config.json
3
+ * and the KYA_ALERTS env switch. A project `.kya/config.json` is never read here, so a repo
4
+ * cannot turn alerts off, lower the floor, or add a receiver. Advisory: local KYA never blocks on this.
5
+ */
6
+ import { kyaHome, readFileConfig, writeFileConfig } from "../config.js";
7
+ export const SEVERITIES = ["low", "medium", "high", "critical"];
8
+ export const DEFAULT_MIN_SEVERITY = "high";
9
+ export const DEFAULT_WINDOW_MINUTES = 120;
10
+ /** Upper bound so a typo cannot make every flush read a huge window. */
11
+ export const MAX_WINDOW_MINUTES = 7 * 24 * 60;
12
+ export function severityRank(s) {
13
+ return SEVERITIES.indexOf(s);
14
+ }
15
+ export function isSeverity(v) {
16
+ return typeof v === "string" && SEVERITIES.includes(v);
17
+ }
18
+ /** KYA_ALERTS=on|off (also 1/true, 0/false). Anything else: no override. */
19
+ function envSwitch(env) {
20
+ const v = env.KYA_ALERTS?.trim().toLowerCase();
21
+ if (v === "on" || v === "1" || v === "true")
22
+ return true;
23
+ if (v === "off" || v === "0" || v === "false")
24
+ return false;
25
+ return undefined;
26
+ }
27
+ /** The user-level `alerts` object, defensively parsed. Never throws. */
28
+ export function resolveAlertsConfig(env = process.env) {
29
+ let raw = {};
30
+ try {
31
+ const a = readFileConfig(kyaHome(env)).alerts;
32
+ if (a && typeof a === "object" && !Array.isArray(a))
33
+ raw = a;
34
+ }
35
+ catch {
36
+ /* unreadable config: defaults */
37
+ }
38
+ const minSeverity = isSeverity(raw["minSeverity"]) ? raw["minSeverity"] : DEFAULT_MIN_SEVERITY;
39
+ const w = raw["windowMinutes"];
40
+ const windowMinutes = typeof w === "number" && Number.isFinite(w) && w > 0
41
+ ? Math.min(Math.floor(w), MAX_WINDOW_MINUTES)
42
+ : DEFAULT_WINDOW_MINUTES;
43
+ const desktop = raw["desktop"] !== false && !/^(off|0|false)$/i.test(env.KYA_ALERTS_DESKTOP?.trim() ?? "");
44
+ const override = envSwitch(env);
45
+ if (override !== undefined)
46
+ return { enabled: override, minSeverity, windowMinutes, desktop, source: "env" };
47
+ if (typeof raw["enabled"] === "boolean") {
48
+ return { enabled: raw["enabled"], minSeverity, windowMinutes, desktop, source: "config" };
49
+ }
50
+ return { enabled: false, minSeverity, windowMinutes, desktop, source: "default" };
51
+ }
52
+ /** `kya alerts on|off`: write `alerts.enabled` to the user-level config, keep the other keys. */
53
+ export function writeAlertsEnabled(env, enabled) {
54
+ const home = kyaHome(env);
55
+ const prev = readFileConfig(home).alerts;
56
+ const base = prev && typeof prev === "object" && !Array.isArray(prev) ? prev : {};
57
+ writeFileConfig(home, { alerts: { ...base, enabled } });
58
+ }
59
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/alerts/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** Light constants shared by the hook path (notify) and the alert modules. No imports on purpose. */
2
+ export declare const INCIDENT_EVENT = "incident.opened";
3
+ export declare const INCIDENT_KIND = "kya.incident.opened";
4
+ export declare const ADVISORY_LINE = "Local KYA is advisory.";
5
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/alerts/constants.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAChD,eAAO,MAAM,aAAa,wBAAwB,CAAC;AACnD,eAAO,MAAM,aAAa,2BAA2B,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Light constants shared by the hook path (notify) and the alert modules. No imports on purpose. */
2
+ export const INCIDENT_EVENT = "incident.opened";
3
+ export const INCIDENT_KIND = "kya.incident.opened";
4
+ export const ADVISORY_LINE = "Local KYA is advisory.";
5
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/alerts/constants.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,MAAM,CAAC,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAChD,MAAM,CAAC,MAAM,aAAa,GAAG,qBAAqB,CAAC;AACnD,MAAM,CAAC,MAAM,aAAa,GAAG,wBAAwB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { IncidentAlert } from "./incident-alert.js";
2
+ export declare const DESKTOP_TITLE = "KYA security alert";
3
+ export interface DesktopCommand {
4
+ readonly file: string;
5
+ readonly args: readonly string[];
6
+ readonly env?: Readonly<Record<string, string>>;
7
+ }
8
+ export declare function desktopCommand(platform: NodeJS.Platform, title: string, body: string): DesktopCommand | undefined;
9
+ /** `severity: <level>. Detectors: <ids>.` Only fixed words and safe ids. */
10
+ export declare function desktopBody(alert: Pick<IncidentAlert, "severity" | "detectorIds" | "test">): string;
11
+ export type DesktopRunner = (cmd: DesktopCommand, env: NodeJS.ProcessEnv) => Promise<boolean>;
12
+ export interface DesktopDeps {
13
+ /** Test seam: the platform. */
14
+ readonly platform?: NodeJS.Platform;
15
+ /** Test seam: runs the command. */
16
+ readonly run?: DesktopRunner;
17
+ }
18
+ export declare function desktopNotify(alert: IncidentAlert, env: NodeJS.ProcessEnv, deps?: DesktopDeps): Promise<boolean>;
19
+ //# sourceMappingURL=desktop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop.d.ts","sourceRoot":"","sources":["../../src/alerts/desktop.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,eAAO,MAAM,aAAa,uBAAuB,CAAC;AAalD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAejH;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,aAAa,GAAG,MAAM,CAAC,GAAG,MAAM,CAInG;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAY9F,MAAM,WAAW,WAAW;IAC1B,+BAA+B;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IACpC,mCAAmC;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ1H"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Desktop notification for a new incident alert. macOS: osascript. Linux: notify-send when it exists.
3
+ * Windows: a PowerShell toast. The text is static: a fixed title, the severity and the detector ids
4
+ * (ids pass the safe-id check in triage-input.ts). No summary, tool name or path from the trail is shown.
5
+ * Arguments go to the program as argv (execFile, no shell). Windows reads the text from two environment
6
+ * variables, because PowerShell has no argv-safe way to pass text to a script block.
7
+ * Advisory and fail open: a missing notifier or a failure returns false and changes nothing else.
8
+ */
9
+ import { execFile } from "node:child_process";
10
+ export const DESKTOP_TITLE = "KYA security alert";
11
+ const TIMEOUT_MS = 5_000;
12
+ const OSA_SCRIPT = ["on run argv", "display notification (item 1 of argv) with title (item 2 of argv)", "end run"];
13
+ const WIN_SCRIPT = [
14
+ "[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
15
+ "$x = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)",
16
+ "$t = $x.GetElementsByTagName('text')",
17
+ "$t.Item(0).AppendChild($x.CreateTextNode($env:KYA_TOAST_TITLE)) | Out-Null",
18
+ "$t.Item(1).AppendChild($x.CreateTextNode($env:KYA_TOAST_BODY)) | Out-Null",
19
+ "[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('KYA').Show([Windows.UI.Notifications.ToastNotification]::new($x))",
20
+ ].join("; ");
21
+ export function desktopCommand(platform, title, body) {
22
+ switch (platform) {
23
+ case "darwin":
24
+ return { file: "osascript", args: [...OSA_SCRIPT.flatMap((l) => ["-e", l]), body, title] };
25
+ case "linux":
26
+ return { file: "notify-send", args: ["--app-name", "KYA", "--", title, body] };
27
+ case "win32":
28
+ return {
29
+ file: "powershell.exe",
30
+ args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", WIN_SCRIPT],
31
+ env: { KYA_TOAST_TITLE: title, KYA_TOAST_BODY: body },
32
+ };
33
+ default:
34
+ return undefined;
35
+ }
36
+ }
37
+ /** `severity: <level>. Detectors: <ids>.` Only fixed words and safe ids. */
38
+ export function desktopBody(alert) {
39
+ const safe = alert.detectorIds.filter((d) => /^[a-z0-9-]{1,40}$/.test(d));
40
+ const ids = safe.length > 0 ? safe.join(", ") : "none";
41
+ return `${alert.test ? "Test. " : ""}Severity: ${alert.severity}. Detectors: ${ids}. Open kya report.`;
42
+ }
43
+ const defaultRun = (cmd, env) => new Promise((resolve) => {
44
+ execFile(cmd.file, [...cmd.args], { env: { ...env, ...cmd.env }, timeout: TIMEOUT_MS, windowsHide: true, shell: false }, (err) => resolve(!err));
45
+ });
46
+ export async function desktopNotify(alert, env, deps = {}) {
47
+ try {
48
+ const cmd = desktopCommand(deps.platform ?? process.platform, DESKTOP_TITLE, desktopBody(alert));
49
+ if (!cmd)
50
+ return false;
51
+ return await (deps.run ?? defaultRun)(cmd, env);
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ //# sourceMappingURL=desktop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop.js","sourceRoot":"","sources":["../../src/alerts/desktop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The alert body for one detected incident. Built from the privacy-safe triage input (redacted,
3
+ * clipped summaries, counts, detector ids) and static detector text. The incident id, title and
4
+ * fix brief header can hold a session id or a raw tool key, so none of them is copied here.
5
+ * No raw args, argument hashes, target paths, diff previews, session ids or home folders.
6
+ */
7
+ import type { Severity } from "../investigate/detectors.js";
8
+ import { type Incident } from "../investigate/index.js";
9
+ import type { TrailEvent } from "../trail.js";
10
+ import type { IncidentTriage } from "./triage-model.js";
11
+ export { ADVISORY_LINE, INCIDENT_EVENT, INCIDENT_KIND } from "./constants.js";
12
+ export declare const MAX_ALERT_SUMMARIES = 3;
13
+ export interface IncidentAlert {
14
+ readonly ref: string;
15
+ readonly title: string;
16
+ readonly severity: Severity;
17
+ readonly detectorIds: readonly string[];
18
+ readonly findingCount: number;
19
+ /** Verdict counts; absent when the triage input was not available. */
20
+ readonly counts?: {
21
+ readonly allow: number;
22
+ readonly hold: number;
23
+ readonly deny: number;
24
+ };
25
+ readonly firstTs: string;
26
+ readonly lastTs: string;
27
+ readonly tools: readonly string[];
28
+ readonly summaries: readonly string[];
29
+ /** First step of the suggested change, static text per detector. Empty when none. */
30
+ readonly fix: string;
31
+ /** Local model triage (optional, advisory). Absent when the model is not installed or failed. Never changes severity. */
32
+ readonly triage?: IncidentTriage;
33
+ readonly advisory: string;
34
+ readonly test: boolean;
35
+ }
36
+ /** First non-empty line under "## Suggested change" in a fix brief (static detector text). */
37
+ export declare function fixBriefFirstLine(brief: string | undefined): string;
38
+ export declare function incidentTitle(detectorIds: readonly string[]): string;
39
+ export declare function buildIncidentAlert(incident: Incident, events: readonly TrailEvent[], brief?: string): IncidentAlert;
40
+ /** The synthetic incident `kya alerts test` sends. Marked as a test in the title and the `test` flag. */
41
+ export declare function buildTestIncidentAlert(now?: Date): IncidentAlert;
42
+ //# sourceMappingURL=incident-alert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incident-alert.d.ts","sourceRoot":"","sources":["../../src/alerts/incident-alert.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9E,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,qFAAqF;IACrF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yHAAyH;IACzH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAWnE;AAED,wBAAgB,aAAa,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWpE;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,SAAS,UAAU,EAAE,EAC7B,KAAK,CAAC,EAAE,MAAM,GACb,aAAa,CA2Bf;AAED,yGAAyG;AACzG,wBAAgB,sBAAsB,CAAC,GAAG,GAAE,IAAiB,GAAG,aAAa,CAiB5E"}
@@ -0,0 +1,84 @@
1
+ import { detectorTitle } from "../investigate/index.js";
2
+ import { buildTriageInput } from "./triage-input.js";
3
+ import { incidentRef } from "./state.js";
4
+ import { ADVISORY_LINE } from "./constants.js";
5
+ export { ADVISORY_LINE, INCIDENT_EVENT, INCIDENT_KIND } from "./constants.js";
6
+ export const MAX_ALERT_SUMMARIES = 3;
7
+ /** First non-empty line under "## Suggested change" in a fix brief (static detector text). */
8
+ export function fixBriefFirstLine(brief) {
9
+ if (!brief)
10
+ return "";
11
+ const lines = brief.split("\n");
12
+ const at = lines.findIndex((l) => l.trim() === "## Suggested change");
13
+ if (at === -1)
14
+ return "";
15
+ for (const line of lines.slice(at + 1)) {
16
+ const t = line.trim();
17
+ if (t.startsWith("## "))
18
+ return "";
19
+ if (t)
20
+ return t.slice(0, 240);
21
+ }
22
+ return "";
23
+ }
24
+ export function incidentTitle(detectorIds) {
25
+ const first = detectorIds[0];
26
+ let name = "Security incident";
27
+ if (first !== undefined) {
28
+ try {
29
+ name = detectorTitle(first) ?? name;
30
+ }
31
+ catch {
32
+ /* unknown id: generic name */
33
+ }
34
+ }
35
+ return `KYA incident: ${name}`;
36
+ }
37
+ export function buildIncidentAlert(incident, events, brief) {
38
+ // The builder may return nothing or a reduced input when its secret check trips: then send the minimum.
39
+ let triage;
40
+ try {
41
+ triage = buildTriageInput(incident, events);
42
+ }
43
+ catch {
44
+ triage = null;
45
+ }
46
+ const detectorIds = triage && triage.detectorIds.length > 0
47
+ ? [...triage.detectorIds]
48
+ : [...new Set(incident.findings.map((f) => f.detectorId))].sort();
49
+ return {
50
+ ref: incidentRef(incident.id),
51
+ title: incidentTitle(detectorIds),
52
+ severity: incident.severity,
53
+ detectorIds,
54
+ findingCount: incident.findingCount,
55
+ ...(triage ? { counts: { ...triage.counts } } : {}),
56
+ firstTs: incident.firstTs,
57
+ lastTs: incident.lastTs,
58
+ tools: triage ? triage.toolIds.slice(0, MAX_ALERT_SUMMARIES) : [],
59
+ summaries: triage ? triage.summaries.slice(0, MAX_ALERT_SUMMARIES) : [],
60
+ fix: fixBriefFirstLine(brief),
61
+ advisory: ADVISORY_LINE,
62
+ test: false,
63
+ };
64
+ }
65
+ /** The synthetic incident `kya alerts test` sends. Marked as a test in the title and the `test` flag. */
66
+ export function buildTestIncidentAlert(now = new Date()) {
67
+ const ts = now.toISOString();
68
+ return {
69
+ ref: "test",
70
+ title: "[TEST] KYA incident: Never-list tool attempted repeatedly",
71
+ severity: "high",
72
+ detectorIds: ["never-repeat"],
73
+ findingCount: 1,
74
+ counts: { allow: 0, hold: 0, deny: 3 },
75
+ firstTs: ts,
76
+ lastTs: ts,
77
+ tools: ["kya.test"],
78
+ summaries: ["This is a test alert from kya alerts test. No action needed."],
79
+ fix: "This is a test. Nothing happened on your machine.",
80
+ advisory: ADVISORY_LINE,
81
+ test: true,
82
+ };
83
+ }
84
+ //# sourceMappingURL=incident-alert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incident-alert.js","sourceRoot":"","sources":["../../src/alerts/incident-alert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { type Incident, type Severity } from "../investigate/index.js";
2
+ import type { NotifyDeps } from "../notify/sender.js";
3
+ import { type TrailEvent } from "../trail.js";
4
+ import { type DesktopDeps } from "./desktop.js";
5
+ import { type IncidentAlert } from "./incident-alert.js";
6
+ import { type TriageInput } from "./triage-input.js";
7
+ import type { IncidentTriage } from "./triage-model.js";
8
+ export interface AlertScanDeps extends NotifyDeps {
9
+ /** Test seam: the clock. */
10
+ readonly now?: () => Date;
11
+ /** Test seam: triage of one incident input. Default: the local model when it is installed. */
12
+ readonly triage?: (input: TriageInput) => Promise<IncidentTriage | undefined>;
13
+ /** Test seam: the desktop notifier. */
14
+ readonly desktop?: DesktopDeps;
15
+ /** Test seam: shorter triage timeout. */
16
+ readonly triageTimeoutMs?: number;
17
+ }
18
+ export interface AlertScanResult {
19
+ /** Incidents that passed the floor and the dedupe check and were sent. */
20
+ readonly alerted: readonly IncidentAlert[];
21
+ readonly delivered: number;
22
+ }
23
+ /** Model load plus one inference. Longer than this and the alert goes out without triage. */
24
+ export declare const TRIAGE_TIMEOUT_MS = 8000;
25
+ /** One scan opens at most this many desktop notifications (an incident storm must not flood the screen). */
26
+ export declare const MAX_DESKTOP_PER_SCAN = 3;
27
+ /** Events may be at most this far in the future (clock skew). Later ones are forged or broken. */
28
+ export declare const MAX_FUTURE_MS: number;
29
+ /** Events inside the window. A ts that does not parse, or lies more than 5 minutes ahead, is dropped. */
30
+ export declare function windowEvents(events: readonly TrailEvent[], now: Date, windowMinutes: number): TrailEvent[];
31
+ /** Pure core: events in, incidents at or above the floor out. */
32
+ export declare function incidentsAboveFloor(events: readonly TrailEvent[], minSeverity: Severity): {
33
+ incidents: Incident[];
34
+ briefs: ReadonlyMap<string, string>;
35
+ };
36
+ export declare function runAlertScan(_cwd: string, env: NodeJS.ProcessEnv, deps?: AlertScanDeps): Promise<AlertScanResult>;
37
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/alerts/scan.ts"],"names":[],"mappings":"AAaA,OAAO,EAAoB,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAE/D,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAS7E,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,4BAA4B;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAC9E,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,yCAAyC;IACzC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAID,6FAA6F;AAC7F,eAAO,MAAM,iBAAiB,OAAQ,CAAC;AACvC,4GAA4G;AAC5G,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAmDtC,kGAAkG;AAClG,eAAO,MAAM,aAAa,QAAa,CAAC;AAExC,yGAAyG;AACzG,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,GAAG,UAAU,EAAE,CAO1G;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,UAAU,EAAE,EAC7B,WAAW,EAAE,QAAQ,GACpB;IAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAMhE;AAeD,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,eAAe,CAAC,CAgF1B"}