project-athena 0.4.0 → 0.5.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 (51) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +10 -0
  3. package/dist/{activity-BKW7G4I5.js → activity-XCHKBZTG.js} +4 -4
  4. package/dist/{agents-DFSNEOP5.js → agents-HUJ4JXIB.js} +4 -4
  5. package/dist/{ai-ASL4FFQ7.js → ai-LGTLQKL4.js} +3 -3
  6. package/dist/{analyze-ZY3MXBOI.js → analyze-AP4XJT7D.js} +7 -7
  7. package/dist/{chunk-ZB7LC6FP.js → chunk-3KFOI2X4.js} +2 -2
  8. package/dist/{chunk-MPG3IEGB.js → chunk-44EGCEB2.js} +23 -14
  9. package/dist/{chunk-GGXPG6OE.js → chunk-4FETKOY3.js} +8 -7
  10. package/dist/chunk-4IIBVTBS.js +76 -0
  11. package/dist/{chunk-LEMPCNAY.js → chunk-56EXAGTM.js} +3 -3
  12. package/dist/{chunk-DZDI2HRT.js → chunk-6OSQMXUP.js} +5 -5
  13. package/dist/{chunk-OLPSEQS5.js → chunk-AKOI4VTD.js} +10 -1
  14. package/dist/{chunk-FVCDMQUQ.js → chunk-C25FDXOO.js} +133 -23
  15. package/dist/{chunk-7DZK6WSQ.js → chunk-DH5AGULY.js} +2 -2
  16. package/dist/{chunk-TDNZ2RYA.js → chunk-F65QINMR.js} +2 -2
  17. package/dist/{chunk-WVQC2EGO.js → chunk-GU3ZX2J7.js} +2 -2
  18. package/dist/{chunk-UAZLPNVC.js → chunk-GYQG4Q2N.js} +4 -4
  19. package/dist/chunk-IDODPGTX.js +595 -0
  20. package/dist/chunk-KJYSNZXS.js +934 -0
  21. package/dist/{chunk-3XSDI5WF.js → chunk-L2EHJCH4.js} +5 -5
  22. package/dist/{chunk-65TSRDQ3.js → chunk-P4TRQMES.js} +2 -2
  23. package/dist/{chunk-KHMBNT66.js → chunk-PZPMWCXS.js} +5 -5
  24. package/dist/{chunk-XDCKZ5TV.js → chunk-R5AX7VST.js} +2 -2
  25. package/dist/{chunk-QLW657NT.js → chunk-RQKGT2EK.js} +2 -2
  26. package/dist/{chunk-VSYBC3VY.js → chunk-UMWX4UTK.js} +2 -2
  27. package/dist/{chunk-RXNRMIZP.js → chunk-WUIC65QO.js} +2 -2
  28. package/dist/{chunk-JCGDR4UV.js → chunk-WWQ5NKEC.js} +3 -1
  29. package/dist/{chunk-RVTNSNGK.js → chunk-X7EYOAEX.js} +2 -2
  30. package/dist/chunk-Y6SDMZET.js +278 -0
  31. package/dist/{clean-MDBYH62P.js → clean-LQOG56AM.js} +2 -2
  32. package/dist/cli.js +2 -2
  33. package/dist/{context-PLPCAPCO.js → context-G5YHY3L2.js} +8 -8
  34. package/dist/{doctor-BX677KRE.js → doctor-L34QJ7WR.js} +8 -8
  35. package/dist/{event-TNT6CQZR.js → event-6YW6TGFI.js} +3 -3
  36. package/dist/{git-hook-YZNZGAAP.js → git-hook-Y7Y262F7.js} +2 -2
  37. package/dist/{init-WQCAOJ2R.js → init-427QR5AS.js} +7 -7
  38. package/dist/{mcp-U7EPH43L.js → mcp-DWNWRGJW.js} +51 -14
  39. package/dist/{memory-VSVNLEJC.js → memory-XBZCYQLW.js} +3 -3
  40. package/dist/{open-UJZRYF4E.js → open-LJMOB6U2.js} +33 -31
  41. package/dist/{program-CWJNGCTZ.js → program-DCP526LU.js} +46 -32
  42. package/dist/{review-OOKXSYMP.js → review-VEJBPACX.js} +10 -9
  43. package/dist/{rules-4FXJUCXB.js → rules-M4DLLXBL.js} +4 -4
  44. package/dist/scan-JVKCVHPD.js +939 -0
  45. package/dist/{security-LME3XFL3.js → security-JTJN3E7K.js} +13 -11
  46. package/dist/{staged-check-X24C3YRD.js → staged-check-PQSLGQBW.js} +11 -11
  47. package/dist/{status-RR57ZNGI.js → status-PO27BSOZ.js} +4 -4
  48. package/dist/{sync-KFJANRXF.js → sync-GVO2VHPI.js} +10 -10
  49. package/dist/{watch-CY7QHIYN.js → watch-TEHFO47V.js} +10 -10
  50. package/package.json +3 -1
  51. package/dist/chunk-SJ4DQFIJ.js +0 -379
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 — 2026-10-03
4
+
5
+ Unified findings: every security check (secrets, dependency audits, review checks) now reports into one findings model with value-free fingerprints, an honest coverage record, and a quality gate driven by an optional committed policy, baseline and triage. New `athena scan` writes text, JSON, Markdown, GitHub annotations or SARIF 2.1.0 for code scanning, and `--policy-from <base>` keeps a pull request from quietly weakening the gate. Static analysis rules arrive in 0.6.0; until then reports say plainly that SAST and IaC are not covered.
6
+
7
+ ### `athena scan` and friends
8
+
9
+ - New `athena scan`: runs the secret scanner and dependency audits over the whole project, or with `--base <ref>` / `--staged` / `--changed` over a change (adding the review checks), then applies the policy, baseline, triage and inline suppressions and evaluates the quality gate. Exit 1 when the gate fails (`--no-fail` to report only), 2 when Git can't compute the requested change. `--only secrets,deps,review`, `--format text|json|markdown|github|sarif`, `-o <file>`, gate overrides `--fail-on`, `--min-confidence`, `--unrated`, plus `--no-baseline`, `--policy-from <ref>` and `--offline` (audits are skipped and reported as skipped). Full-project scans are saved to `.athena/findings.json`.
10
+ - New `athena baseline show|create|update|prune`, `athena findings list|show|triage` (fingerprint prefixes of 6+ characters; a reason is required and committed) and `athena explain <ruleId>` / `athena scan --list-rules`.
11
+ - New MCP tool `findings`: a read-only summary of the last scan (gate, ratings, coverage gaps, active findings with fingerprints), fenced as untrusted data and never containing secret values. It never starts a scan.
12
+ - `athena security` keeps its flags, output and `--json` shape; it now also writes `.athena/findings.json`.
13
+
14
+ ### Unified findings: secrets, dependencies and review checks
15
+
16
+ - New `.athena/findings.json` (gitignored): every security check now reports into one findings model with stable, value-free fingerprints, severity (`critical`…`info`, plus `unrated`), confidence, a FACT/DETECTED/INFERRED label, CWE, location or package, the engine that reported it, and a coverage record per engine (files scanned and skipped, tools unavailable/failed/timed out/skipped offline).
17
+ - Secrets: one `secret/<pattern>` finding per match with file, line and column. Private keys and AWS/GCP/Azure credentials are `critical`, other known token formats `high`, keyword heuristics `medium` and marked *potential* (low confidence). Fingerprints hash the line with the matched value masked, so they survive rotating the value and reveal nothing about it; no secret value is ever written.
18
+ - Dependency audits: one `dependency/vulnerable-package` finding per package and advisory (FACT, attributed to `npm-audit`, `pnpm-audit`, `pip-audit`, `govulncheck`, `cargo-audit` or `composer-audit`), with version, lockfile/target, advisory ids, vulnerable range, fixed version and CWE where the tool reports them. `moderate` is now `medium`; a missing severity stays `unrated`. npm packages that are only vulnerable through another reported package are not counted twice.
19
+ - `athena review --json` gains a `scan` object (`scan.findings`, `scan.coverage`) with the checks as `review/<check>` findings (blocker → high, warning → medium, info → info) and added secrets as `secret/*` findings at the exact line. The existing `findings` list, human output and exit codes are unchanged.
20
+ - `athena security` runs on the new scanners and writes both `.athena/findings.json` and the legacy `.athena/security-scan.json` (still used by the web UI and `security.md`). Flags, output, `--fail-on` and `--unrated` behave as before; `--json` keeps the v1 shape — the unified shape will be `athena scan --format json`.
21
+ - A project that only has a `security-scan.json` from an older version is read as findings (its secret count becomes a coverage note, since v1 recorded no lines).
22
+
23
+ ### Policy, baseline, triage and quality gate
24
+
25
+ - `.athena/policy.json` (optional, committed; `schemaVersion: 1`): `gate` (`failOn` severity, default `high`; `minConfidence`, default `medium`; `unrated: fail|warn`, default `fail`; `scope: new|all`, default `new`; gated `categories`, default secret, dependency, sast, iac, review), per-rule `rules` (`"off"` or a `severity`/`confidence` override; exact ids or `family/*` prefixes), `paths.exclude`/`paths.tests` globs, `licenses.deny`/`warn`, and `external` engines (`semgrep`, `gitleaks`, `trivy`, `osv-scanner`: `auto|off`; `network: allow|deny`). A missing file means defaults. An invalid file, including an unknown key, is an error that names the offending path. It is never silently ignored.
26
+ - `.athena/baseline.json` (committed): records findings that already exist so that only new ones fail the gate (scope `new`). Baselined findings stay visible and still count in ratings. Entries hold only value-free fingerprints, rule ids and files. Pruning needs a full scan and keeps entries of any category whose engine failed or did not run, because a broken engine reports nothing and that must not read as "fixed".
27
+ - `.athena/triage.json` (committed): a reviewed decision per finding (`safe`, `false-positive`, `accepted-risk`, `fixed`, `to-review`), similar to SonarQube's hotspot review. Every decision needs a reason (at most 500 characters). A reason that looks like it contains a secret is refused, and the value is never echoed. `to-review` keeps the finding open.
28
+ - Inline suppressions: `athena-ignore <ruleId> -- <reason>` in a `//`, `#`, `/* */`, `<!-- -->` or `--` comment on the finding's line or the line above. Each comment names one exact rule. A suppression without a reason is ignored and reported as an `info` finding (`review/suppression-without-reason`). A comment with no valid rule id is reported as `review/invalid-suppression`.
29
+ - Every finding gets a status on each run (never stored): `open`, `baselined`, `suppressed`, `triaged:<safe|false-positive|accepted-risk|fixed>` or `excluded`.
30
+ - Quality gate: fails on active findings in gated categories at or above `failOn` with at least `minConfidence` confidence. Unrated findings follow `gate.unrated`. With scope `new`, baselined findings are skipped. The gate also fails when an engine for a gated category failed or timed out, and the result says so explicitly, because an engine that broke checked nothing. Unavailable or skipped engines, and gated categories that nothing scanned, are listed as warnings.
31
+ - Ratings A–E per category, based on the worst active finding: A none, B low/info, C medium, D high, E critical. Unrated counts as D, or C under `unrated: warn`. Each rating comes with a basis ("N rules across <languages>; X files not covered") and no numeric score. A category is not rated when no engine ran for it or one of its engines failed.
32
+ - Anti-tamper for CI (`policyFrom: <ref>`): the gate uses the policy, baseline and triage committed at the base ref (missing there means defaults). Every change to them since `merge-base(ref, HEAD)`, and every added `athena-ignore` comment (tracked or untracked), is reported as `review/policy-weakened`. Severity is `high` when the change relaxes the gate (a higher `failOn` or `minConfidence`, rules turned off or downgraded, new excludes, fewer gated categories, engines turned off, added baseline entries, new closing triage decisions, added suppressions); other changes are `info`. A base-branch policy can downgrade this rule if a team prefers review notes over a failing check.
33
+
3
34
  ## 0.4.0 — 2026-10-03
4
35
 
5
36
  Agent memory: coding agents can now record what they learn — decisions and why, gotchas, bug root causes, conventions — and later sessions recall it. Memory lives in `.athena/memory/` as plain Markdown you commit and review like code. Anything an agent writes is `unreviewed` (INFERRED) until you confirm it (`athena memory review`, or the Memory page in `athena open`); entries turn `stale` when the files they describe change. Secrets are refused, and text that looks like it is trying to instruct the agent is flagged and kept out of recall.
package/README.md CHANGED
@@ -62,6 +62,10 @@ Generated content sits between `athena:generated` markers. Anything outside the
62
62
  | `athena rules` | `list`, `add "<rule>" --section <name>`, `edit`, `enable`, `disable`, `remove` |
63
63
  | `athena agents` | `list`, `add <claude-code\|cursor\|codex\|copilot\|gemini-cli\|antigravity\|windsurf\|cline\|agents-md\|all>`, `remove` |
64
64
  | `athena activity` | Show AI agent activity observed through hooks (`-n`, `--agent`) |
65
+ | `athena scan` | Unified scan: secrets, dependency audits and (on a change) review checks, with policy, baseline, triage and a quality gate (`--base`, `--staged`, `--changed`, `--only`, `-f text\|json\|markdown\|github\|sarif`, `-o`, `--fail-on`, `--policy-from`, `--offline`, `--list-rules`) |
66
+ | `athena baseline` | `show`, `create`, `update`, `prune` — record existing findings so only new ones fail the gate |
67
+ | `athena findings` | `list`, `show <fingerprint>`, `triage <fingerprint> <safe\|false-positive\|accepted-risk\|fixed\|to-review\|clear> --reason …` |
68
+ | `athena explain <ruleId>` | What a rule detects and what to do about it |
65
69
  | `athena security` | Audit dependencies with the tools installed for this project, and report secret findings (`--fail-on`, `--last`, `--no-audit`) |
66
70
  | `athena review` | Check the current diff for facts worth reviewing, and list your rules and checklist (`--base`, `--no-fail`) |
67
71
  | `athena git-hook` | `install` (`--review`), `uninstall`, `status` — a Git pre-commit hook that blocks commits when knowledge is out of date |
@@ -170,6 +174,10 @@ $ athena activity
170
174
  ## Security and review
171
175
 
172
176
  ```bash
177
+ athena scan # secrets + dependency audits, policy and quality gate
178
+ athena scan --base origin/main -f sarif -o athena.sarif --policy-from origin/main # in CI
179
+ athena baseline create # accept today's findings; only new ones fail the gate
180
+ athena findings triage 5f65caf9 false-positive --reason "test fixture"
173
181
  athena security # audit dependencies + report secret findings
174
182
  athena security --fail-on high # exit 1 in CI
175
183
  athena review # check the current diff before you commit
@@ -177,6 +185,8 @@ athena review # check the current diff before you commit
177
185
 
178
186
  **`athena security`** runs the audit tools your project's ecosystems provide (`npm audit`, `pnpm audit`, `pip-audit`, `govulncheck`, `cargo audit`, `composer audit`) and reports what they find, attributed to the tool. Athena has no vulnerability database of its own: a tool that isn't installed is reported as **unknown**, never as "no problems". Results are stored in `.athena/security-scan.json` and recorded in `security.md` on the next `athena sync`.
179
187
 
188
+ **`athena scan`** puts every check into one findings model. Each finding has a rule id, severity (`unrated` when the tool gave none — never treated as low), confidence, a FACT/DETECTED/INFERRED label, CWE, a location or package, and a fingerprint that never depends on a secret's value. Every report lists what was *not* covered (tools not installed, engines that failed, categories nothing scanned), and a failed engine fails the gate — it checked nothing. Optional committed files tune it: `.athena/policy.json` (gate thresholds, rule overrides, path excludes), `.athena/baseline.json`, `.athena/triage.json`, and inline `athena-ignore <ruleId> -- <reason>` comments (a reason is required). In CI, `--policy-from <base>` evaluates with the base branch's policy and reports any change to it on the branch as `review/policy-weakened`. Output formats: text, JSON, Markdown, GitHub annotations and SARIF 2.1.0 (for code scanning). Ratings A–E per category come with their basis; there is no numeric score.
189
+
180
190
  **`athena review`** checks facts about your current diff: secrets in added lines (a blocker, exit 1), committed env files, new dependencies, source changed without tests, API/schema/auth touchpoints, large files, debug leftovers, and whether Athena knowledge is stale. It then lists your enabled rules and the project's review checklist for you or your agent to apply — Athena does not claim to judge whether they are met, and it runs no AI.
181
191
 
182
192
  ## Use in CI and git hooks
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  listAgents
4
- } from "./chunk-QLW657NT.js";
4
+ } from "./chunk-RQKGT2EK.js";
5
5
  import {
6
6
  readRecentEvents,
7
7
  summarizeSessions
8
- } from "./chunk-65TSRDQ3.js";
8
+ } from "./chunk-P4TRQMES.js";
9
9
  import {
10
10
  c,
11
11
  dim,
@@ -20,7 +20,7 @@ import {
20
20
  requireProjectRoot
21
21
  } from "./chunk-XFN7UZCS.js";
22
22
  import "./chunk-KBO7XRBW.js";
23
- import "./chunk-JCGDR4UV.js";
23
+ import "./chunk-WWQ5NKEC.js";
24
24
  import "./chunk-YRIS7ZS6.js";
25
25
  import "./chunk-GHFRQAYT.js";
26
26
  import "./chunk-INT3IGMQ.js";
@@ -74,4 +74,4 @@ async function activityCommand(opts) {
74
74
  export {
75
75
  activityCommand
76
76
  };
77
- //# sourceMappingURL=activity-BKW7G4I5.js.map
77
+ //# sourceMappingURL=activity-XCHKBZTG.js.map
@@ -3,8 +3,8 @@ import {
3
3
  configureAgents,
4
4
  listAgents,
5
5
  removeAgents
6
- } from "./chunk-QLW657NT.js";
7
- import "./chunk-65TSRDQ3.js";
6
+ } from "./chunk-RQKGT2EK.js";
7
+ import "./chunk-P4TRQMES.js";
8
8
  import {
9
9
  c,
10
10
  dim,
@@ -19,7 +19,7 @@ import {
19
19
  requireProjectRoot
20
20
  } from "./chunk-XFN7UZCS.js";
21
21
  import "./chunk-KBO7XRBW.js";
22
- import "./chunk-JCGDR4UV.js";
22
+ import "./chunk-WWQ5NKEC.js";
23
23
  import "./chunk-YRIS7ZS6.js";
24
24
  import "./chunk-GHFRQAYT.js";
25
25
  import "./chunk-INT3IGMQ.js";
@@ -66,4 +66,4 @@ export {
66
66
  agentsListCommand,
67
67
  agentsRemoveCommand
68
68
  };
69
- //# sourceMappingURL=agents-DFSNEOP5.js.map
69
+ //# sourceMappingURL=agents-HUJ4JXIB.js.map
@@ -3,7 +3,7 @@ import {
3
3
  aiStatus,
4
4
  buildEnrichPayload,
5
5
  enrich
6
- } from "./chunk-RXNRMIZP.js";
6
+ } from "./chunk-WUIC65QO.js";
7
7
  import {
8
8
  c,
9
9
  dim,
@@ -20,7 +20,7 @@ import {
20
20
  } from "./chunk-XFN7UZCS.js";
21
21
  import "./chunk-KBO7XRBW.js";
22
22
  import "./chunk-PVCIGUIT.js";
23
- import "./chunk-JCGDR4UV.js";
23
+ import "./chunk-WWQ5NKEC.js";
24
24
  import "./chunk-FQEGEVLY.js";
25
25
  import "./chunk-2DY4PUR4.js";
26
26
  import "./chunk-IQQBAWSM.js";
@@ -93,4 +93,4 @@ export {
93
93
  aiEnrichCommand,
94
94
  aiStatusCommand
95
95
  };
96
- //# sourceMappingURL=ai-ASL4FFQ7.js.map
96
+ //# sourceMappingURL=ai-LGTLQKL4.js.map
@@ -4,7 +4,7 @@ import {
4
4
  printSummary,
5
5
  runWithProgress,
6
6
  summarize
7
- } from "./chunk-XDCKZ5TV.js";
7
+ } from "./chunk-R5AX7VST.js";
8
8
  import {
9
9
  printHeader
10
10
  } from "./chunk-SHKXELUX.js";
@@ -16,14 +16,14 @@ import {
16
16
  requireProjectRoot
17
17
  } from "./chunk-XFN7UZCS.js";
18
18
  import "./chunk-KBO7XRBW.js";
19
- import "./chunk-GGXPG6OE.js";
20
- import "./chunk-MPG3IEGB.js";
21
- import "./chunk-ELKFRTZT.js";
19
+ import "./chunk-4FETKOY3.js";
22
20
  import "./chunk-2R6PXY6Z.js";
21
+ import "./chunk-44EGCEB2.js";
23
22
  import "./chunk-PVCIGUIT.js";
24
- import "./chunk-OLPSEQS5.js";
23
+ import "./chunk-ELKFRTZT.js";
24
+ import "./chunk-AKOI4VTD.js";
25
25
  import "./chunk-QCC4VJUH.js";
26
- import "./chunk-JCGDR4UV.js";
26
+ import "./chunk-WWQ5NKEC.js";
27
27
  import "./chunk-YRIS7ZS6.js";
28
28
  import "./chunk-GHFRQAYT.js";
29
29
  import "./chunk-INT3IGMQ.js";
@@ -47,4 +47,4 @@ async function analyzeCommand(opts) {
47
47
  export {
48
48
  analyzeCommand
49
49
  };
50
- //# sourceMappingURL=analyze-ZY3MXBOI.js.map
50
+ //# sourceMappingURL=analyze-AP4XJT7D.js.map
@@ -13,7 +13,7 @@ import {
13
13
  git,
14
14
  statusV2,
15
15
  workingChanges
16
- } from "./chunk-OLPSEQS5.js";
16
+ } from "./chunk-AKOI4VTD.js";
17
17
  import {
18
18
  isBlockModified,
19
19
  parseBlocks
@@ -218,4 +218,4 @@ async function buildStatus(root, signal, opts = {}) {
218
218
  export {
219
219
  buildStatus
220
220
  };
221
- //# sourceMappingURL=chunk-ZB7LC6FP.js.map
221
+ //# sourceMappingURL=chunk-3KFOI2X4.js.map
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getOrCreateSalt,
4
+ keyedFingerprint,
5
+ readLocalConfig
6
+ } from "./chunk-PVCIGUIT.js";
2
7
  import {
3
8
  IgnoreMatcher,
4
9
  SOURCE_FILE,
@@ -8,14 +13,9 @@ import {
8
13
  sniffBinary,
9
14
  walkProject
10
15
  } from "./chunk-ELKFRTZT.js";
11
- import {
12
- getOrCreateSalt,
13
- keyedFingerprint,
14
- readLocalConfig
15
- } from "./chunk-PVCIGUIT.js";
16
16
  import {
17
17
  gitInfo
18
- } from "./chunk-OLPSEQS5.js";
18
+ } from "./chunk-AKOI4VTD.js";
19
19
  import {
20
20
  ProjectModel,
21
21
  detected,
@@ -28,7 +28,7 @@ import {
28
28
  isSecretLikeName,
29
29
  redact,
30
30
  scanText
31
- } from "./chunk-JCGDR4UV.js";
31
+ } from "./chunk-WWQ5NKEC.js";
32
32
  import {
33
33
  FactsCache,
34
34
  athenaDir,
@@ -2379,9 +2379,6 @@ import path4 from "path";
2379
2379
  import crypto2 from "crypto";
2380
2380
  var SKIP = /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lock|poetry\.lock|uv\.lock|Cargo\.lock|go\.sum|composer\.lock|Gemfile\.lock|pubspec\.lock)$|\.(min\.js|map|svg|snap)$/;
2381
2381
  var MAX_SECRET_FINDINGS = 500;
2382
- async function secretFingerprinter(root) {
2383
- return (await secretFingerprintKey(root)).fingerprint;
2384
- }
2385
2382
  async function secretFingerprintKey(root, saltOverride) {
2386
2383
  try {
2387
2384
  let salt = saltOverride;
@@ -2398,12 +2395,20 @@ async function secretFingerprintKey(root, saltOverride) {
2398
2395
  }
2399
2396
  return { fingerprint: ephemeralFingerprint, fingerprintKeyId: null };
2400
2397
  }
2398
+ var MINIFIED = /\.(min\.js|map)$/;
2401
2399
  async function scanFilesForSecrets(files, read, opts) {
2402
2400
  const findings = opts.into ?? [];
2403
2401
  const max = opts.max ?? MAX_SECRET_FINDINGS;
2404
2402
  const stats = { skippedLongLines: 0 };
2403
+ const counts = { scanned: 0, binary: 0, tooLarge: 0, minified: 0, excluded: 0, unreadable: 0 };
2405
2404
  let truncated = false;
2406
2405
  for (const f of files) {
2406
+ if (f.binary) counts.binary++;
2407
+ else if (f.large) counts.tooLarge++;
2408
+ else if (SKIP.test(f.path)) {
2409
+ if (MINIFIED.test(f.path)) counts.minified++;
2410
+ else counts.excluded++;
2411
+ }
2407
2412
  if (f.binary || f.large || SKIP.test(f.path)) continue;
2408
2413
  if (findings.length >= max) {
2409
2414
  truncated = true;
@@ -2411,10 +2416,14 @@ async function scanFilesForSecrets(files, read, opts) {
2411
2416
  }
2412
2417
  opts.signal?.throwIfAborted();
2413
2418
  const text = await read(f.path);
2419
+ if (text === null) counts.unreadable++;
2420
+ else counts.scanned++;
2414
2421
  if (!text) continue;
2415
- for (const m of scanText(text, { fingerprint: opts.fingerprint, stats })) findings.push({ type: m.type, file: f.path, line: m.line, fingerprint: m.fingerprint });
2422
+ const matches = scanText(text, { fingerprint: opts.fingerprint, stats });
2423
+ for (const m of matches) findings.push({ type: m.type, file: f.path, line: m.line, fingerprint: m.fingerprint });
2424
+ if (matches.length) opts.onFile?.(f.path, text, matches);
2416
2425
  }
2417
- return { findings, truncated, skippedLongLines: stats.skippedLongLines };
2426
+ return { findings, truncated, skippedLongLines: stats.skippedLongLines, files: counts };
2418
2427
  }
2419
2428
  var secretsFact = {
2420
2429
  id: "secrets",
@@ -2673,7 +2682,7 @@ export {
2673
2682
  readExclude,
2674
2683
  manifestDependencyNames,
2675
2684
  REAL_ENV_NAMES,
2676
- secretFingerprinter,
2685
+ MAX_SECRET_FINDINGS,
2677
2686
  scanFilesForSecrets,
2678
2687
  analyzeProject,
2679
2688
  persistAnalysisCache,
@@ -2688,4 +2697,4 @@ export {
2688
2697
  unratedFindings,
2689
2698
  meetsThreshold
2690
2699
  };
2691
- //# sourceMappingURL=chunk-MPG3IEGB.js.map
2700
+ //# sourceMappingURL=chunk-44EGCEB2.js.map
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ATHENA_VERSION
4
+ } from "./chunk-2R6PXY6Z.js";
2
5
  import {
3
6
  analyzeProject,
4
7
  loadScanFromDir,
5
8
  persistAnalysisCache
6
- } from "./chunk-MPG3IEGB.js";
7
- import {
8
- ATHENA_VERSION
9
- } from "./chunk-2R6PXY6Z.js";
9
+ } from "./chunk-44EGCEB2.js";
10
10
  import {
11
11
  LOCAL_CONFIG_FILE
12
12
  } from "./chunk-PVCIGUIT.js";
13
13
  import {
14
14
  redact
15
- } from "./chunk-JCGDR4UV.js";
15
+ } from "./chunk-WWQ5NKEC.js";
16
16
  import {
17
17
  ADAPTERS,
18
18
  applyChanges
@@ -752,6 +752,7 @@ var ATHENA_GITIGNORE = [
752
752
  "model.json",
753
753
  "graph.json",
754
754
  "security-scan.json",
755
+ "findings.json",
755
756
  "ai-suggestions.md",
756
757
  ".backup/",
757
758
  ".server.json",
@@ -764,7 +765,7 @@ var ATHENA_GITIGNORE = [
764
765
  "local.json",
765
766
  ""
766
767
  ].join("\n");
767
- var GITIGNORE_ENTRIES = ["state.json", "model.json", "graph.json", "security-scan.json", "ai-suggestions.md", ".backup/", ".server.json", ".sync-ignore.json", ".agent-events.jsonl", ".agent-events.1.jsonl", ".agent-events.rotate.lock", "cache/", "local.json"];
768
+ var GITIGNORE_ENTRIES = ["state.json", "model.json", "graph.json", "security-scan.json", "findings.json", "ai-suggestions.md", ".backup/", ".server.json", ".sync-ignore.json", ".agent-events.jsonl", ".agent-events.1.jsonl", ".agent-events.rotate.lock", "cache/", "local.json"];
768
769
  async function ensureGitignoreEntries(file) {
769
770
  const existing = await readTextIfExists(file);
770
771
  if (existing === null) {
@@ -901,4 +902,4 @@ export {
901
902
  composeState,
902
903
  runPipeline
903
904
  };
904
- //# sourceMappingURL=chunk-GGXPG6OE.js.map
905
+ //# sourceMappingURL=chunk-4FETKOY3.js.map
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ auditDependencies,
4
+ legacyToolResult,
5
+ saveFindings
6
+ } from "./chunk-IDODPGTX.js";
7
+ import {
8
+ Coverage,
9
+ Finding,
10
+ dedupeFindings,
11
+ scanSecrets
12
+ } from "./chunk-Y6SDMZET.js";
13
+ import {
14
+ saveScan
15
+ } from "./chunk-44EGCEB2.js";
16
+ import {
17
+ walkProject
18
+ } from "./chunk-ELKFRTZT.js";
19
+ import {
20
+ loadConfig
21
+ } from "./chunk-2DY4PUR4.js";
22
+
23
+ // src/services/security.ts
24
+ var resultsByScan = /* @__PURE__ */ new WeakMap();
25
+ async function runSecurityScanWithFindings(root, model, opts = {}) {
26
+ const started = Date.now();
27
+ const { config } = await loadConfig(root);
28
+ const walk = await walkProject(root, { config, signal: opts.signal });
29
+ const ctx = { root, config, mode: "all", signal: opts.signal };
30
+ const deps = opts.skipAudit ? null : await auditDependencies(ctx, { model, walk: walk.files, timeoutMs: opts.timeoutMs, onTool: opts.onTool });
31
+ const tools = (deps?.runs ?? []).map(legacyToolResult);
32
+ const counts = { critical: 0, high: 0, moderate: 0, low: 0, unrated: 0 };
33
+ for (const t of tools) for (const f of t.findings) counts[f.severity]++;
34
+ const secrets = await scanSecrets(ctx, { walk: walk.files });
35
+ const scannedAt = (/* @__PURE__ */ new Date()).toISOString();
36
+ const durationMs = Date.now() - started;
37
+ const scan = {
38
+ schemaVersion: 1,
39
+ scannedAt,
40
+ durationMs,
41
+ tools,
42
+ counts,
43
+ secrets: {
44
+ count: secrets.summary.count,
45
+ files: secrets.summary.files.slice(0, 50),
46
+ skippedLongLines: secrets.summary.skippedLongLines,
47
+ ...secrets.summary.truncated ? { truncated: true } : {}
48
+ }
49
+ };
50
+ const outputs = [secrets, ...deps ? [deps] : []];
51
+ const result = {
52
+ schemaVersion: 1,
53
+ scannedAt,
54
+ durationMs,
55
+ scope: { mode: "all" },
56
+ findings: dedupeFindings(outputs.flatMap((o) => o.findings.map((f) => Finding.parse(f)))),
57
+ coverage: [...deps?.coverage ?? [], ...secrets.coverage].map((c) => Coverage.parse(c))
58
+ };
59
+ resultsByScan.set(scan, result);
60
+ return { scan, result };
61
+ }
62
+ async function runSecurityScan(root, model, opts = {}) {
63
+ return (await runSecurityScanWithFindings(root, model, opts)).scan;
64
+ }
65
+ async function saveScan2(root, scan) {
66
+ await saveScan(root, scan);
67
+ const result = resultsByScan.get(scan);
68
+ if (result) await saveFindings(root, result);
69
+ }
70
+
71
+ export {
72
+ runSecurityScanWithFindings,
73
+ runSecurityScan,
74
+ saveScan2 as saveScan
75
+ };
76
+ //# sourceMappingURL=chunk-4IIBVTBS.js.map
@@ -9,10 +9,10 @@ import {
9
9
  } from "./chunk-3IS2X2Z7.js";
10
10
  import {
11
11
  git
12
- } from "./chunk-OLPSEQS5.js";
12
+ } from "./chunk-AKOI4VTD.js";
13
13
  import {
14
14
  scanText
15
- } from "./chunk-JCGDR4UV.js";
15
+ } from "./chunk-WWQ5NKEC.js";
16
16
  import {
17
17
  isBlockModified,
18
18
  parseBlocks
@@ -206,4 +206,4 @@ export {
206
206
  rulesUpdate,
207
207
  rulesRemove
208
208
  };
209
- //# sourceMappingURL=chunk-LEMPCNAY.js.map
209
+ //# sourceMappingURL=chunk-56EXAGTM.js.map
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AnalysisScheduler
4
- } from "./chunk-RVTNSNGK.js";
4
+ } from "./chunk-X7EYOAEX.js";
5
5
  import {
6
6
  applySync,
7
7
  needsIndexRefreshOnly
8
- } from "./chunk-KHMBNT66.js";
8
+ } from "./chunk-PZPMWCXS.js";
9
9
  import {
10
10
  readExclude
11
- } from "./chunk-MPG3IEGB.js";
11
+ } from "./chunk-44EGCEB2.js";
12
12
  import {
13
13
  IgnoreMatcher,
14
14
  factsConfigHash
15
15
  } from "./chunk-ELKFRTZT.js";
16
16
  import {
17
17
  git
18
- } from "./chunk-OLPSEQS5.js";
18
+ } from "./chunk-AKOI4VTD.js";
19
19
  import {
20
20
  loadConfig
21
21
  } from "./chunk-2DY4PUR4.js";
@@ -166,4 +166,4 @@ async function watchProject(root, opts) {
166
166
  export {
167
167
  watchProject
168
168
  };
169
- //# sourceMappingURL=chunk-DZDI2HRT.js.map
169
+ //# sourceMappingURL=chunk-6OSQMXUP.js.map
@@ -184,6 +184,14 @@ function stripPrefix(files, prefix) {
184
184
  if (!prefix) return files;
185
185
  return files.filter((f) => f.path.startsWith(prefix)).map((f) => ({ ...f, path: f.path.slice(prefix.length), from: f.from?.startsWith(prefix) ? f.from.slice(prefix.length) : f.from }));
186
186
  }
187
+ async function changesSince(cwd, fromRef, opts = {}) {
188
+ if (!isSafeRef(fromRef)) return null;
189
+ const mb = await git(cwd, ["merge-base", fromRef, "HEAD"], { signal: opts.signal });
190
+ if (!mb.ok || !mb.stdout.trim()) return null;
191
+ const r = await git(cwd, ["diff", "--name-status", "-M", "-z", "--relative", "--no-color", mb.stdout.trim(), "HEAD", "--", "."], { signal: opts.signal });
192
+ if (!r.ok) return null;
193
+ return parseNameStatusZ(r.stdout);
194
+ }
187
195
  function parseNameStatusZ(stdout) {
188
196
  const out = [];
189
197
  const parts = stdout.split("\0").filter(Boolean);
@@ -256,9 +264,10 @@ export {
256
264
  isGitRepo,
257
265
  gitInfo,
258
266
  workingChanges,
267
+ changesSince,
259
268
  parseNameStatusZ,
260
269
  isSafeRef,
261
270
  headMovement,
262
271
  statusV2
263
272
  };
264
- //# sourceMappingURL=chunk-OLPSEQS5.js.map
273
+ //# sourceMappingURL=chunk-AKOI4VTD.js.map