project-athena 0.2.1 → 0.3.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 (147) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +15 -8
  3. package/dist/activity-P4UUFHWV.js +77 -0
  4. package/dist/agents-3HTSQGL2.js +69 -0
  5. package/dist/ai-ASL4FFQ7.js +96 -0
  6. package/dist/analyze-F3YOILNL.js +49 -0
  7. package/dist/chunk-2DY4PUR4.js +465 -0
  8. package/dist/chunk-2R6PXY6Z.js +33 -0
  9. package/dist/chunk-2RS6TA3D.js +908 -0
  10. package/dist/chunk-32PRS2MG.js +169 -0
  11. package/dist/chunk-3IS2X2Z7.js +89 -0
  12. package/dist/chunk-65TSRDQ3.js +124 -0
  13. package/dist/chunk-673EPYYH.js +105 -0
  14. package/dist/chunk-AD4OZCU2.js +34 -0
  15. package/dist/chunk-CXUTYCZX.js +238 -0
  16. package/dist/chunk-DOQMAZL5.js +436 -0
  17. package/dist/chunk-EF6GHOIN.js +343 -0
  18. package/dist/chunk-ELKFRTZT.js +221 -0
  19. package/dist/chunk-EUPETUWA.js +14 -0
  20. package/dist/chunk-FQEGEVLY.js +38 -0
  21. package/dist/chunk-GHFRQAYT.js +112 -0
  22. package/dist/chunk-GR4DL2VM.js +47 -0
  23. package/dist/chunk-INT3IGMQ.js +111 -0
  24. package/dist/chunk-IQQBAWSM.js +90 -0
  25. package/dist/chunk-JCGDR4UV.js +182 -0
  26. package/dist/chunk-JY6BFIJJ.js +95 -0
  27. package/dist/chunk-KBO7XRBW.js +26 -0
  28. package/dist/chunk-LEMPCNAY.js +209 -0
  29. package/dist/chunk-LMOFZNTQ.js +379 -0
  30. package/dist/chunk-OLPSEQS5.js +264 -0
  31. package/dist/chunk-PANVPNYF.js +2892 -0
  32. package/dist/chunk-PDFQ5BFG.js +378 -0
  33. package/dist/chunk-PR53DT7I.js +179 -0
  34. package/dist/chunk-PVCIGUIT.js +64 -0
  35. package/dist/chunk-RXNRMIZP.js +390 -0
  36. package/dist/chunk-S7T6VX56.js +61 -0
  37. package/dist/chunk-SHKXELUX.js +295 -0
  38. package/dist/chunk-SJOOXO34.js +904 -0
  39. package/dist/chunk-TGJUUB44.js +399 -0
  40. package/dist/chunk-VSYBC3VY.js +28 -0
  41. package/dist/chunk-XB66PZ6Z.js +182 -0
  42. package/dist/chunk-XFN7UZCS.js +35 -0
  43. package/dist/chunk-XW3MLHI2.js +32 -0
  44. package/dist/chunk-Z6QWMJLK.js +124 -0
  45. package/dist/chunk-ZB7LC6FP.js +221 -0
  46. package/dist/clean-S7PKCOLM.js +23 -0
  47. package/dist/cli.js +18 -10716
  48. package/dist/context-AH5MFHRG.js +147 -0
  49. package/dist/doctor-RSJMLE2P.js +62 -0
  50. package/dist/event-TNT6CQZR.js +101 -0
  51. package/dist/git-hook-YZNZGAAP.js +252 -0
  52. package/dist/init-2V34DVRQ.js +37 -0
  53. package/dist/mcp-T2GRTEX5.js +255 -0
  54. package/dist/open-2QSZKKUG.js +1259 -0
  55. package/dist/program-V2USHGXN.js +148 -0
  56. package/dist/review-R3R2J5BJ.js +115 -0
  57. package/dist/rules-4FXJUCXB.js +83 -0
  58. package/dist/security-PI4LXCOB.js +175 -0
  59. package/dist/staged-check-HHIBKKMR.js +500 -0
  60. package/dist/status-6M73WX5I.js +75 -0
  61. package/dist/sync-3ZNJDSSU.js +216 -0
  62. package/dist/watch-USN5CQIV.js +121 -0
  63. package/dist/web/assets/{Editor-DAXvniWB.js → Editor-BI56-HX5.js} +1 -1
  64. package/dist/web/assets/{abnfDiagram-O67JEVCF-Az7EW1BC.js → abnfDiagram-O67JEVCF-y_9px2ob.js} +1 -1
  65. package/dist/web/assets/architecture-7GRP2DOG-DyxkwYiW.js +1 -0
  66. package/dist/web/assets/{architectureDiagram-NJMV4G6O-BxeXqSTn.js → architectureDiagram-NJMV4G6O-lB6fcVzU.js} +1 -1
  67. package/dist/web/assets/{blockDiagram-BEXU5L5S-aeKZl9lJ.js → blockDiagram-BEXU5L5S-DcM5pEjM.js} +1 -1
  68. package/dist/web/assets/{c4Diagram-YGBWAQC7-CgmntYsG.js → c4Diagram-YGBWAQC7-CJgqxTY2.js} +1 -1
  69. package/dist/web/assets/channel-BvDD7AMO.js +1 -0
  70. package/dist/web/assets/{chunk-3FUC2YCW-BpmRD2yo.js → chunk-3FUC2YCW-B8l_VuWR.js} +1 -1
  71. package/dist/web/assets/{chunk-5DYCD2WN-CRIFDwod.js → chunk-5DYCD2WN-CZecgNhf.js} +1 -1
  72. package/dist/web/assets/{chunk-742MDFTN-hRW9VeNV.js → chunk-742MDFTN-BeZ7P5jB.js} +1 -1
  73. package/dist/web/assets/{chunk-7INBJB4K-C6VEpT0Y.js → chunk-7INBJB4K-IKy0pr5m.js} +1 -1
  74. package/dist/web/assets/{chunk-7M6MHVWA-Dnajov2w.js → chunk-7M6MHVWA-Dg_QqJEX.js} +1 -1
  75. package/dist/web/assets/{chunk-7PRAP22T-DSAc0_CZ.js → chunk-7PRAP22T-DqQNS8oq.js} +1 -1
  76. package/dist/web/assets/{chunk-GTNCS2PH-CefpYUjb.js → chunk-GTNCS2PH-x4sM51nJ.js} +1 -1
  77. package/dist/web/assets/{chunk-GWA4HPMP-MLNflMmF.js → chunk-GWA4HPMP-CE5GcwVN.js} +1 -1
  78. package/dist/web/assets/{chunk-MBY4JIJT-D3GmmEiU.js → chunk-MBY4JIJT-DA_D1hjF.js} +1 -1
  79. package/dist/web/assets/{chunk-NETBCI7D-DzTj6xVk.js → chunk-NETBCI7D-CeJ3XBZ2.js} +1 -1
  80. package/dist/web/assets/{chunk-O7XYJQB3-D1d0hCko.js → chunk-O7XYJQB3-BH2gESvO.js} +1 -1
  81. package/dist/web/assets/{chunk-UA2S7LBM-C9hIFeWR.js → chunk-UA2S7LBM-BWr0xCux.js} +1 -1
  82. package/dist/web/assets/{chunk-WEXAMYUT-BFGtcjsi.js → chunk-WEXAMYUT-B4QwUZPM.js} +1 -1
  83. package/dist/web/assets/{chunk-Z7XXMR3K-BRZ8_wtE.js → chunk-Z7XXMR3K-BxcA-y5B.js} +1 -1
  84. package/dist/web/assets/{chunk-ZIGJFQKS-CTKOioal.js → chunk-ZIGJFQKS-BZYvP63G.js} +1 -1
  85. package/dist/web/assets/{classDiagram-v2-NBCMYWYE-DJGKtWJv.js → classDiagram-v2-NBCMYWYE-BS3P_NXb.js} +1 -1
  86. package/dist/web/assets/{cynefin-OW5HDTMX-hNq1kGmA.js → cynefin-OW5HDTMX-BALD4Pve.js} +1 -1
  87. package/dist/web/assets/{cynefinDiagram-VND7K2PF-sODQM_ZZ.js → cynefinDiagram-VND7K2PF-BM2y4W6d.js} +1 -1
  88. package/dist/web/assets/{dagre-6A5THRUB-DEzTThIf.js → dagre-6A5THRUB-PTfOrIdF.js} +1 -1
  89. package/dist/web/assets/{diagram-22UHCM2B-s_eellsa.js → diagram-22UHCM2B-CpXEHhUl.js} +1 -1
  90. package/dist/web/assets/{diagram-3UASUU5V-B2onY_Xw.js → diagram-3UASUU5V-DPHfXzr6.js} +1 -1
  91. package/dist/web/assets/{diagram-ATOU4E4O-B8V6s13s.js → diagram-ATOU4E4O-DMBNXvAC.js} +1 -1
  92. package/dist/web/assets/{diagram-CDSNMT55-CJxiE6mf.js → diagram-CDSNMT55-CDLe1M7G.js} +1 -1
  93. package/dist/web/assets/{diagram-MLGK6HIB-m5OHPLAp.js → diagram-MLGK6HIB-D6L8oE7j.js} +1 -1
  94. package/dist/web/assets/{diagram-MPIPVDR6-Buvvrk_A.js → diagram-MPIPVDR6-CP-S7rrQ.js} +1 -1
  95. package/dist/web/assets/{ebnfDiagram-ZINNZB2B-CpL7Wt3R.js → ebnfDiagram-ZINNZB2B-CkUc63TK.js} +1 -1
  96. package/dist/web/assets/{elk-276RUBZZ-CwYhbsXa.js → elk-276RUBZZ-C1CAJLH_.js} +1 -1
  97. package/dist/web/assets/{erDiagram-OPXOYQCR-DNikci2r.js → erDiagram-OPXOYQCR-DXBnaTHm.js} +1 -1
  98. package/dist/web/assets/eventmodeling-NTZA5JFV-cLIzvgDp.js +1 -0
  99. package/dist/web/assets/flowDiagram-KWPJA3E3-D7Zq63VQ.js +1 -0
  100. package/dist/web/assets/{ganttDiagram-FUAMR5RP-d-ynDlvJ.js → ganttDiagram-FUAMR5RP-BVm8SiGg.js} +1 -1
  101. package/dist/web/assets/{gitGraph-4MIJSDKK-DwIeBI91.js → gitGraph-4MIJSDKK-Dm7xBrW0.js} +1 -1
  102. package/dist/web/assets/{gitGraphDiagram-X574FWY7-04RZccu6.js → gitGraphDiagram-X574FWY7-D_PIKKhx.js} +1 -1
  103. package/dist/web/assets/{index-C3-8uYXr.js → index-CeOIBpu2.js} +3 -3
  104. package/dist/web/assets/{info-A6RAGUB7-xf4Nzgv-.js → info-A6RAGUB7-CLGrx8pn.js} +1 -1
  105. package/dist/web/assets/{infoDiagram-VRGFBTTK-BYjw0eiK.js → infoDiagram-VRGFBTTK-BlBjaKm7.js} +1 -1
  106. package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-Bx9s4nPM.js → ishikawaDiagram-OU5B5YK6-Kxe7GGS3.js} +1 -1
  107. package/dist/web/assets/{journeyDiagram-ZHPQQLJL-DrdHs7B-.js → journeyDiagram-ZHPQQLJL-wCMoHK4M.js} +1 -1
  108. package/dist/web/assets/{kanban-definition-PNTS6WVX-CVd8v2Te.js → kanban-definition-PNTS6WVX-BxdCz9Ko.js} +1 -1
  109. package/dist/web/assets/{line-V_WrJ-6y.js → line-Zf11FnWF.js} +1 -1
  110. package/dist/web/assets/{mermaid-parser.core-B-ilyEpO.js → mermaid-parser.core-BvFFklev.js} +3 -3
  111. package/dist/web/assets/{mermaid.core-Bnyaa0nF.js → mermaid.core-Cd_Ssti9.js} +4 -4
  112. package/dist/web/assets/{mindmap-definition-NLK3R4M7-B3AbvHPR.js → mindmap-definition-NLK3R4M7-BGPQl-GY.js} +1 -1
  113. package/dist/web/assets/{packet-AYTQ26CC-CBAIUXIc.js → packet-AYTQ26CC-6lI3cEX_.js} +1 -1
  114. package/dist/web/assets/{pegDiagram-GJSIUBJH-DIdeELUM.js → pegDiagram-GJSIUBJH-BPIjlSAj.js} +1 -1
  115. package/dist/web/assets/{pie-WAS4IAKB-a1r3tzPR.js → pie-WAS4IAKB-D6Dzj3Ja.js} +1 -1
  116. package/dist/web/assets/{pieDiagram-5QR66LMP-pQBEio0x.js → pieDiagram-5QR66LMP-DtA-vZmv.js} +1 -1
  117. package/dist/web/assets/{quadrantDiagram-O4NWA36T-CkJK5Y03.js → quadrantDiagram-O4NWA36T-EettyqQn.js} +1 -1
  118. package/dist/web/assets/{radar-RG4KPBEZ-BZfLYjx5.js → radar-RG4KPBEZ-xmRc3Dl8.js} +1 -1
  119. package/dist/web/assets/{railroad-74A4TZTK-CuIW1ZAU.js → railroad-74A4TZTK-BLB7dhOn.js} +1 -1
  120. package/dist/web/assets/railroad-abnf-HS5TGJTU-DBFym6g8.js +1 -0
  121. package/dist/web/assets/railroad-ebnf-LZEXJU2U-CkG28ig8.js +1 -0
  122. package/dist/web/assets/railroad-peg-WCYAUIDC-Df_Ifaad.js +1 -0
  123. package/dist/web/assets/{railroadDiagram-XR7U4H2S-_BpGE8aJ.js → railroadDiagram-XR7U4H2S-PBw4We6b.js} +1 -1
  124. package/dist/web/assets/{requirementDiagram-PLB6GJNP-CnzF90GS.js → requirementDiagram-PLB6GJNP-DjQpaUFA.js} +1 -1
  125. package/dist/web/assets/{sankeyDiagram-IPEJSGJF-DYeisDj_.js → sankeyDiagram-IPEJSGJF-CBW-_AoF.js} +1 -1
  126. package/dist/web/assets/{sequenceDiagram-PO4LG4MO-ll9cBvca.js → sequenceDiagram-PO4LG4MO-DEoPYqY9.js} +1 -1
  127. package/dist/web/assets/{stateDiagram-v2-GCMORJYK-CdKcOIKQ.js → stateDiagram-v2-GCMORJYK-UZq5rPud.js} +1 -1
  128. package/dist/web/assets/{swimlanes-2SLR337P-y6piTWEW.js → swimlanes-2SLR337P-CACfuXJR.js} +1 -1
  129. package/dist/web/assets/swimlanesDiagram-TC7HE7FX-CDSrqdgx.js +8 -0
  130. package/dist/web/assets/{timeline-definition-EJHVYXUP-DuC_4xK0.js → timeline-definition-EJHVYXUP-CYJl7mP-.js} +1 -1
  131. package/dist/web/assets/{treeView-Q6P3EWNA-gCQFmND0.js → treeView-Q6P3EWNA-DtZ5nkPQ.js} +1 -1
  132. package/dist/web/assets/{treemap-WGGIJYW6-BQ2010OY.js → treemap-WGGIJYW6-m49GDM-M.js} +1 -1
  133. package/dist/web/assets/{usecaseDiagram-POWQR4AR-CCTqzHz6.js → usecaseDiagram-POWQR4AR-C2Vg20UK.js} +1 -1
  134. package/dist/web/assets/{vennDiagram-UO4OBE2U-0_O7guNq.js → vennDiagram-UO4OBE2U-C07v5OGI.js} +1 -1
  135. package/dist/web/assets/{wardley-WFR3VGLG-SsX7uMDs.js → wardley-WFR3VGLG-C9Z7EZtz.js} +1 -1
  136. package/dist/web/assets/{wardleyDiagram-VNRHLVJA-DlqH6Hox.js → wardleyDiagram-VNRHLVJA-DdA8CcSC.js} +1 -1
  137. package/dist/web/assets/{xychartDiagram-PMCCYNJV-nr4UYDtd.js → xychartDiagram-PMCCYNJV-C9EZw07b.js} +1 -1
  138. package/dist/web/index.html +1 -1
  139. package/package.json +4 -2
  140. package/dist/web/assets/architecture-7GRP2DOG-blXBarB2.js +0 -1
  141. package/dist/web/assets/channel-f4QzewnN.js +0 -1
  142. package/dist/web/assets/eventmodeling-NTZA5JFV-CA_ZFPZ2.js +0 -1
  143. package/dist/web/assets/flowDiagram-KWPJA3E3-DObsBuWT.js +0 -1
  144. package/dist/web/assets/railroad-abnf-HS5TGJTU-6B4Ffbxn.js +0 -1
  145. package/dist/web/assets/railroad-ebnf-LZEXJU2U-BGL9lIQc.js +0 -1
  146. package/dist/web/assets/railroad-peg-WCYAUIDC-DcqxTem7.js +0 -1
  147. package/dist/web/assets/swimlanesDiagram-TC7HE7FX-DFS8a3jO.js +0 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 — 2026-09-30
4
+
5
+ Performance release: work is now proportional to what changed. On a 20,000-file repository (vs 0.2.1): a `sync --check` with nothing changed takes ~0.42 s instead of 2.9 s, `status` 0.14 s instead of 0.34 s, agent hooks start in ~28 ms instead of 112 ms, and peak memory is 20–52% lower in every scenario. The generated `model.json` is unchanged (golden-tested against the 0.2.1 analyzer). Benchmarks: `npm run bench`.
6
+
7
+ After upgrading, the first run rebuilds `.athena/state.json` in the new format automatically. If you use the pre-commit hook, run `athena git-hook install` again so it checks staged content.
8
+
9
+ ### Performance
10
+
11
+ - `athena open` reuses parsed `.athena` artifacts: a new `ProjectSession` loads `model.json`, `graph.json`, `security-scan.json` and `state.json` once and re-reads them only when the file changes (inode, size or mtime). The overview, graph, context, security and `athena security` paths share it, and the graph summary checks for `model.json` with a stat instead of parsing and validating the whole model.
12
+ - The event stream (`/api/events`) coalesces bursts into one write per 250 ms (intermediate activity states collapse to the latest), serializes each event once for all clients, and applies backpressure: a client that stops reading gets nothing more until its socket drains, keeps at most 100 queued events (oldest dropped) plus the latest activity, and then receives a `resync` frame with the number of events it missed. At most 16 event streams are open at once (503 beyond that).
13
+ - `GET /api/sync` no longer includes each proposed document's full diff; the Sync page fetches a diff from the new `GET /api/sync/:doc` only when the document is expanded.
14
+ - `athena event` (run by agent hooks on every tool call) starts about 2.5× faster and uses about 40% less memory (p50 112 ms / 100 MB → ~45 ms / 57 MB on the 20k-file benchmark): it no longer loads commander or any other command's dependencies. All other commands load their code on demand, which also lowers memory for `status`, `review` and `sync`.
15
+ - `athena event` no longer loads zod: the paths it needs moved to a dependency-free module (`src/core/paths.ts`). p50 of 10 on an Apple M-series laptop: ~43 ms / 57 MB → ~26 ms / 49 MB. The startup test now fails if zod reaches the event chunk.
16
+ - The CLI is now built with code splitting: `dist/cli.js` is a small entry that loads chunks from `dist/` as needed.
17
+ - Project graph building is linear in the number of edges (edge de-duplication, import traversal and package lookups no longer rescan everything); 50k import edges now build in about 0.1 s instead of 3.2 s, and 100k in about 0.25 s. Graph output is unchanged. `athena graph --node` and task-context expansion use a per-graph index.
18
+ - Git metadata is collected with concurrent `git` processes, the `git --version` check runs once per process, and history is read once, bounded to the last 180 days / 2,000 commits. The contributor count in `model.json` (`git.contributorCount`) now counts authors in that window instead of the whole history.
19
+ - Nested `.gitignore` matching only consults the path's own ancestor directories instead of every `.gitignore` in the project.
20
+ - Analysis is staged and incremental: a stat-only walk, one content pass in which every new or changed file is read exactly once (hashing and all per-file detector work share the same bytes), then an aggregate stage that builds the model from cached per-file facts. Unchanged files are not read at all. The generated `model.json` is unchanged (guarded by a golden test against the 0.2.1 analyzer).
21
+ - 20,000-file benchmark (Apple M-series, vs 0.2.1): warm `sync --check` 2.9 s → 0.50 s (×5.8), `sync --check` after a one-file change 2.9 s → 0.49 s, cold `init` 3.5 s → 2.3 s; peak memory lower in every scenario (warm sync 195 → 162 MB). 200,000 files: warm `sync --check` 46 s → 4.1 s, cold `init` 51 s → 40 s, peak memory 730 → 589 MB (init).
22
+ - File text is no longer cached without limit during analysis: a 64 MB byte-budgeted LRU serves the rare aggregate-stage lookup, and text is dropped as soon as a file's facts are computed.
23
+ - Sync planning goes through one scheduler per project (`src/services/scheduler.ts`): at most one analysis runs at a time, requests that arrive meanwhile share a single follow-up run, and new file changes can cancel a plan that is still in progress (planning is read-only). The file watcher, the local server (`POST /api/sync/check`, review, re-plans after edits) and the MCP server use it, so concurrent checks no longer start concurrent full analyses; applying a proposal and a full re-analysis from the web UI never interleave with planning.
24
+ - While `athena open` (or another watcher) is running, a plan after file changes re-checks only the paths the watcher reported instead of walking the whole tree (about 265 → 170 ms for a one-file change in the 20k-file benchmark, in-process). It walks the whole tree on the first plan, after a `.gitignore`, `.athena/config.json` or `.git/info/exclude` change, a directory add/delete, a watcher error, when the project contains symbolic links, and at least every 5 minutes.
25
+ - `athena status` (and the server's status, the MCP `get_project_changes` tool) no longer walks the tree: it stats the files in the cached index and asks Git for new files (`git ls-files`, `git status --porcelain=v2 -z`). 254 → ~140 ms on the 20k-file benchmark; ~0.85 s for 200k files. The output is unchanged; it falls back to walking when Git can't give the same answer (no Git or no index, `include` patterns, ignore rules changed since the last analysis, submodules, nested repositories, symlinked directories).
26
+ - The server's status report is cached until the watcher reports a change (with a 30 s safety TTL, instead of a flat 3 s), and concurrent requests share one computation.
27
+
28
+ ### Changed
29
+
30
+ - **The pre-commit hook checks what is being committed.** New `athena sync --check --staged` compares the `.athena/` knowledge as staged with what Athena generates from the code as staged, so untracked files (a scratch file, a local `.env`) and unstaged edits no longer block a commit, and a staged change reverted in the working tree still counts. It mirrors the index into `.athena/cache/staged/` (gitignored, updated incrementally): files that match the working tree are sparse stand-ins that reuse the last analysis's hashes and the facts cache, so only staged changes are read (20k files, warm: ~0.54 s vs ~0.44 s for the working-tree check). When `athena sync` already wrote a document that isn't staged, the output says which files to `git add`; JSON output has `staged`, `inSync`, `stale` and per-document details. Exit code 3 when `.athena/` is not in the index; 2 when the index has unresolved conflicts. `athena git-hook install` writes the new command (re-run it to update an existing hook; `athena git-hook status` shows `staged: false` and a hint for old hooks), and the suggested husky/lefthook line is now `npx --no-install athena sync --check --staged`.
31
+ - The manifest detector is split into one module per ecosystem (`src/core/analyzer/detectors/manifests/`); `model.json` output is byte-identical.
32
+ - Files modified within 2 seconds of the file index being written are re-read on the next run instead of trusted by size and mtime (Git's "racily clean" rule), so a same-size edit in the same clock tick is never missed.
33
+ - The local server is split into route modules (`src/server/routes/*`) sharing one server context; routes, authentication, Host/Origin checks, security headers, CSP and rate limiting are unchanged.
34
+ - `GET /api/sync` returns the proposal without `documents[].diff` (reasons, changed and preserved sections, additions/deletions and `diffTruncated` are still included). New `GET /api/sync/:doc[?planId=…]` returns one proposed document's diff; the id must be a knowledge document in the current proposal (404 otherwise, 409 when `planId` is stale). `POST /api/sync/check` still returns the full plan.
35
+ - A corrupted `model.json` now gives a clear error with a hint (`model.json is corrupted (…)`, run `athena analyze`) instead of an internal error when starting a security scan from the web UI.
36
+ - Test, source, auth, API and schema path patterns now come from one module (`src/core/patterns.ts`). `athena review` recognizes more test layouts (`integration_tests/`, `androidTest/`, `test_driver/`, `*_test.dart`), and change impact recognizes the same test files as review plus test runner configs.
37
+ - New per-file facts cache in `.athena/cache/v1/` (gitignored; `cache/` is added to `.athena/.gitignore`, also for existing projects). Facts are keyed by content hash and discarded automatically when detector versions or the `ignore`/`include`/`maxFileBytes` settings change. Safe to delete; `athena clean` removes it with `.athena/`.
38
+ - `state.json` schema 2: the file index moved to `.athena/cache/v1/files.json` (full SHA-256 hashes), so `state.json` stays small regardless of repository size. Version-1 files are migrated automatically on first read; older Athena versions will treat a v2 `state.json` as corrupted and rebuild it.
39
+ - `athena init` creates the machine-local fingerprint salt (`.athena/local.json`) together with `.athena/`, so secret-finding fingerprints in the first `model.json` already use the project's stable key.
40
+
3
41
  ## 0.2.1 — 2026-09-30
4
42
 
5
43
  Security release. Upgrading is recommended for everyone. After upgrading, run `athena sync` once (the `security.md` format changed) and, if you use the pre-commit hook with `--review`, run `athena git-hook install --review` again so it reviews staged changes.
package/README.md CHANGED
@@ -104,6 +104,7 @@ Apply updates to 2 documents? [y/N]
104
104
  - **Your edits are still safe.** Developer-edited sections are preserved and listed. Applying is refused if a document changed after the proposal was made.
105
105
  - When files change but no knowledge is affected, only the local index is refreshed.
106
106
  - `athena sync --check` exits with 1 when knowledge is out of date, which is useful in CI or a pre-commit hook (see [Use in CI and git hooks](#use-in-ci-and-git-hooks)). Change hotspots come from Git history and move with every commit, so the check ignores them; `athena sync` still refreshes them.
107
+ - `athena sync --check --staged` checks what a commit would contain instead of the working tree: the `.athena/` knowledge as staged against the code as staged. Untracked files and unstaged edits don't count. This is what the pre-commit hook runs.
107
108
  - Ignore rules: defaults, root and nested `.gitignore` files, `.git/info/exclude`, and `.athena/config.json`.
108
109
 
109
110
  ## Watching what agents do
@@ -192,16 +193,22 @@ A complete workflow is in [examples/github-workflow.yml](examples/github-workflo
192
193
  ```text
193
194
  $ athena git-hook install
194
195
  ✓ Installed the pre-commit hook (knowledge check) at .git/hooks/pre-commit
195
- Commits are blocked when .athena/ knowledge is out of date. Teammates without Athena installed are not blocked.
196
- Using husky, lefthook or pre-commit? Add `npx --no-install athena sync --check` to its pre-commit config instead.
196
+ Commits are blocked when the staged .athena/ knowledge is out of date for the staged code (untracked and unstaged files are ignored). Teammates without Athena installed are not blocked.
197
+ Using husky, lefthook or pre-commit? Add `npx --no-install athena sync --check --staged` to its pre-commit config instead.
197
198
 
198
199
  $ git commit -m "Add refunds"
199
- Proposed: api.md, security.md
200
- Knowledge is out of date. Run `athena sync` to review and apply.
201
- athena: commit blocked: .athena/ knowledge is out of date. Run `athena sync`, stage .athena/ and commit again (or skip once with `git commit --no-verify`).
200
+ ~ api.md endpoints
201
+ ~ security.md attack-surface
202
+ Staged knowledge is out of date for the staged code. Run `athena sync`, then stage .athena/.
203
+ athena: commit blocked: staged .athena/ knowledge is out of date. Run `athena sync`, stage .athena/ and commit again (or skip once with `git commit --no-verify`).
202
204
  ```
203
205
 
204
- - `athena git-hook install --review` also runs `athena review` and blocks commits that add possible secrets or env files. It checks the whole working tree (staged, unstaged and untracked files that aren't ignored), not only what is staged.
206
+ - The hook checks the commit, not the working tree: an untracked scratch file, a local `.env` or an unstaged edit never blocks a commit, and a change you staged and then reverted in the working tree still counts. If `athena sync` already updated a document but it isn't staged, the output says which files to `git add`.
207
+ - The check mirrors the index into `.athena/cache/staged/` (gitignored). Files that match the working tree are stand-ins that reuse the last analysis and the facts cache, so only staged changes are read; the directory is updated incrementally and is safe to delete. Submodules are checked as they are in the working tree. Local env files that aren't committed (such as `.env`) are machine-local: the check accepts staged knowledge written with or without them.
208
+ - Knowledge generated by `athena sync` describes the working tree. If untracked files that are not gitignored change it (a new route in a file you don't commit), the staged check reports the difference; commit, ignore or stash those files before syncing.
209
+ - Hooks installed by earlier versions check the working tree; `athena git-hook status` points them out and `athena git-hook install` updates them.
210
+
211
+ - `athena git-hook install --review` also runs `athena review --staged` and blocks commits that add possible secrets or env files. Like the knowledge check, it only looks at what is staged.
205
212
  - The hook is a `#!/bin/sh` script in the directory Git actually uses (`git rev-parse --git-path hooks`), so `core.hooksPath` and linked worktrees work. For a project in a subfolder of the repository, the hook runs Athena there.
206
213
  - If `athena` is not on `PATH`, the hook prints a note and lets the commit through, so it never blocks teammates who don't use Athena.
207
214
  - **An existing pre-commit hook is kept.** If it is a shell script, Athena adds its block between `# athena:start` and `# athena:end` right after the shebang, so it runs before your own commands. Athena refuses to touch hooks written in other languages and hooks it recognizes as managed by husky, lefthook or pre-commit (from `core.hooksPath` or the hook's contents), and prints the line to add instead.
@@ -211,7 +218,7 @@ athena: commit blocked: .athena/ knowledge is out of date. Run `athena sync`, st
211
218
 
212
219
  ```bash
213
220
  # .husky/pre-commit
214
- npx --no-install athena sync --check
221
+ npx --no-install athena sync --check --staged
215
222
  ```
216
223
 
217
224
  ```yaml
@@ -219,7 +226,7 @@ npx --no-install athena sync --check
219
226
  pre-commit:
220
227
  commands:
221
228
  athena:
222
- run: npx --no-install athena sync --check
229
+ run: npx --no-install athena sync --check --staged
223
230
  ```
224
231
 
225
232
  ### Example project
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ listAgents
4
+ } from "./chunk-JY6BFIJJ.js";
5
+ import {
6
+ readRecentEvents,
7
+ summarizeSessions
8
+ } from "./chunk-65TSRDQ3.js";
9
+ import {
10
+ c,
11
+ dim,
12
+ heading,
13
+ isJson,
14
+ json,
15
+ line,
16
+ relativeTime,
17
+ sym
18
+ } from "./chunk-Z6QWMJLK.js";
19
+ import {
20
+ requireProjectRoot
21
+ } from "./chunk-XFN7UZCS.js";
22
+ import "./chunk-KBO7XRBW.js";
23
+ import "./chunk-JCGDR4UV.js";
24
+ import "./chunk-2RS6TA3D.js";
25
+ import "./chunk-GHFRQAYT.js";
26
+ import "./chunk-673EPYYH.js";
27
+ import "./chunk-FQEGEVLY.js";
28
+ import "./chunk-2DY4PUR4.js";
29
+ import "./chunk-IQQBAWSM.js";
30
+ import "./chunk-AD4OZCU2.js";
31
+ import "./chunk-INT3IGMQ.js";
32
+ import "./chunk-XW3MLHI2.js";
33
+ import "./chunk-EUPETUWA.js";
34
+
35
+ // src/cli/commands/activity.ts
36
+ var STATE_COLOR = {
37
+ ANALYZING: c.cyan,
38
+ PLANNING: c.magenta,
39
+ CODING: c.blue,
40
+ TESTING: c.yellow,
41
+ REVIEWING: c.cyan,
42
+ SUCCESS: c.green,
43
+ IDLE: c.dim
44
+ };
45
+ async function activityCommand(opts) {
46
+ const root = await requireProjectRoot(opts);
47
+ const limit = Math.min(Math.max(Number(opts.limit ?? 30) || 30, 1), 500);
48
+ const all = await readRecentEvents(root, 500);
49
+ const events = (opts.agent ? all.filter((e) => e.agent === opts.agent) : all).slice(-limit);
50
+ if (isJson()) {
51
+ json({ events, sessions: summarizeSessions(all) });
52
+ return;
53
+ }
54
+ heading("AI Agent Activity");
55
+ line();
56
+ if (!events.length) {
57
+ const agents = await listAgents(root);
58
+ const reporting = agents.filter((a) => a.activityObservation === "hooks");
59
+ line(dim("No agent activity recorded yet."));
60
+ line(dim(reporting.length ? `Hooks are installed for: ${reporting.map((a) => a.name).join(", ")}. Events appear once an agent runs in this project.` : "Install hooks with `athena agents add claude-code` (or cursor, codex, copilot, gemini-cli) to record agent activity."));
61
+ return;
62
+ }
63
+ for (const e of events) {
64
+ const time = new Date(e.ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false });
65
+ const color = STATE_COLOR[e.state] ?? ((s) => s);
66
+ line(`${dim(time)} ${color(sym.dot)} ${c.bold(e.agent)} ${dim("\xB7")} ${e.message}`);
67
+ }
68
+ line();
69
+ for (const s of summarizeSessions(all).slice(0, 5)) {
70
+ line(dim(`${s.agent} ${s.session ? `(${s.session.slice(0, 8)})` : ""}: ${s.events} events, last ${relativeTime(s.lastEventAt)}`));
71
+ }
72
+ line(dim("Only hook-reported tool use is shown. Athena cannot see agent reasoning."));
73
+ }
74
+ export {
75
+ activityCommand
76
+ };
77
+ //# sourceMappingURL=activity-P4UUFHWV.js.map
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ configureAgents,
4
+ listAgents,
5
+ removeAgents
6
+ } from "./chunk-JY6BFIJJ.js";
7
+ import "./chunk-65TSRDQ3.js";
8
+ import {
9
+ c,
10
+ dim,
11
+ heading,
12
+ isJson,
13
+ json,
14
+ line,
15
+ ok,
16
+ sym
17
+ } from "./chunk-Z6QWMJLK.js";
18
+ import {
19
+ requireProjectRoot
20
+ } from "./chunk-XFN7UZCS.js";
21
+ import "./chunk-KBO7XRBW.js";
22
+ import "./chunk-JCGDR4UV.js";
23
+ import "./chunk-2RS6TA3D.js";
24
+ import "./chunk-GHFRQAYT.js";
25
+ import "./chunk-673EPYYH.js";
26
+ import "./chunk-FQEGEVLY.js";
27
+ import "./chunk-2DY4PUR4.js";
28
+ import "./chunk-IQQBAWSM.js";
29
+ import "./chunk-AD4OZCU2.js";
30
+ import "./chunk-INT3IGMQ.js";
31
+ import "./chunk-XW3MLHI2.js";
32
+ import "./chunk-EUPETUWA.js";
33
+
34
+ // src/cli/commands/agents.ts
35
+ async function agentsListCommand(opts) {
36
+ const root = await requireProjectRoot(opts);
37
+ const rows = await listAgents(root);
38
+ if (isJson()) {
39
+ json(rows);
40
+ return;
41
+ }
42
+ heading("AI Agent Integrations");
43
+ line();
44
+ for (const r of rows) {
45
+ const icon = r.configured ? c.green(sym.dot) : c.dim(sym.ring);
46
+ line(`${icon} ${c.bold(r.name)} ${dim(`(${r.id})`)} \u2014 ${r.configured ? "configured" : "not configured"}${r.detectedInProject ? dim(` \xB7 detected: ${r.evidence.join(", ")}`) : ""}`);
47
+ line(` ${dim(r.note)}`);
48
+ }
49
+ line();
50
+ line(dim('"Configured" means Athena instruction files are in place. Agents with hooks also report their tool use \u2014 see `athena activity`.'));
51
+ }
52
+ async function agentsAddCommand(names, opts) {
53
+ const root = await requireProjectRoot(opts);
54
+ const results = await configureAgents(root, names);
55
+ if (isJson()) json({ ok: true, agents: results });
56
+ else for (const r of results) ok(`${r.id}: ${r.files.map((f) => `${f.path} (${f.action})`).join(", ")}`);
57
+ }
58
+ async function agentsRemoveCommand(names, opts) {
59
+ const root = await requireProjectRoot(opts);
60
+ const removed = await removeAgents(root, names);
61
+ if (isJson()) json({ ok: true, removed });
62
+ else ok(removed.length ? `Removed Athena integration from ${removed.join(", ")}` : "Nothing to remove");
63
+ }
64
+ export {
65
+ agentsAddCommand,
66
+ agentsListCommand,
67
+ agentsRemoveCommand
68
+ };
69
+ //# sourceMappingURL=agents-3HTSQGL2.js.map
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ aiStatus,
4
+ buildEnrichPayload,
5
+ enrich
6
+ } from "./chunk-RXNRMIZP.js";
7
+ import {
8
+ c,
9
+ dim,
10
+ heading,
11
+ isJson,
12
+ json,
13
+ line,
14
+ spinner,
15
+ sym,
16
+ warn
17
+ } from "./chunk-Z6QWMJLK.js";
18
+ import {
19
+ requireProjectRoot
20
+ } from "./chunk-XFN7UZCS.js";
21
+ import "./chunk-KBO7XRBW.js";
22
+ import "./chunk-PVCIGUIT.js";
23
+ import "./chunk-JCGDR4UV.js";
24
+ import "./chunk-FQEGEVLY.js";
25
+ import "./chunk-2DY4PUR4.js";
26
+ import "./chunk-IQQBAWSM.js";
27
+ import "./chunk-AD4OZCU2.js";
28
+ import "./chunk-EUPETUWA.js";
29
+
30
+ // src/cli/commands/ai.ts
31
+ var CONSENT_SOURCE = { env: "ATHENA_AI_CONSENT", local: ".athena/local.json", user: "user config" };
32
+ async function aiStatusCommand(opts) {
33
+ const root = await requireProjectRoot(opts);
34
+ const status = await aiStatus(root);
35
+ if (isJson()) {
36
+ json(status);
37
+ return;
38
+ }
39
+ heading("AI providers");
40
+ line();
41
+ for (const w of status.warnings) warn(c.yellow(w));
42
+ if (status.warnings.length) line();
43
+ for (const p of status.providers) {
44
+ const icon = p.availability.ok ? c.green(sym.ok) : c.dim(sym.ring);
45
+ line(`${icon} ${c.bold(p.name)}${p.selected ? c.cyan(" (selected)") : ""}`);
46
+ line(` ${dim(`${p.remote ? "sends data to" : "runs locally at"} ${p.availability.endpoint} \xB7 default model ${p.defaultModel}`)}`);
47
+ if (!p.availability.ok) line(` ${dim(p.availability.reason ?? "")}`);
48
+ }
49
+ line();
50
+ line(`Consent to send project knowledge: ${status.consent ? c.green(`granted (${CONSENT_SOURCE[status.consentSource ?? "env"]})`) : c.yellow("not granted")}`);
51
+ line(dim("Athena never needs AI: analysis, sync, context and MCP are deterministic. AI only produces INFERRED suggestions you review."));
52
+ line(dim('Configure in .athena/config.json \u2192 "ai": { "provider": "ollama" } for a fully local setup.'));
53
+ line(dim("Endpoint (baseUrl) and consent are machine-local only: ATHENA_AI_BASE_URL / ATHENA_AI_CONSENT=1, .athena/local.json, or ~/.config/athena/config.json."));
54
+ }
55
+ async function aiEnrichCommand(opts) {
56
+ const root = await requireProjectRoot(opts);
57
+ const docs = opts.docs?.split(",").map((d) => d.trim()).filter(Boolean);
58
+ const payload = await buildEnrichPayload(root, { docs });
59
+ if (!isJson()) {
60
+ heading("AI enrichment");
61
+ line();
62
+ for (const w of payload.warnings) warn(c.yellow(w));
63
+ if (payload.warnings.length) line();
64
+ line(`Provider: ${c.bold(payload.provider.name)} ${dim(`(${payload.model})`)}`);
65
+ line(`Sends to: ${payload.remote ? c.yellow(payload.endpoint) : c.green(`${payload.endpoint} (local)`)}`);
66
+ line(`Content: ${payload.documents.join(", ")} ${dim(`(${payload.chars} characters, redacted knowledge only \u2014 no source code)`)}`);
67
+ line();
68
+ }
69
+ if (opts.dryRun) {
70
+ if (isJson()) json({ ...payload, provider: payload.provider.id });
71
+ else line(dim("Dry run \u2014 nothing was sent."));
72
+ return 0;
73
+ }
74
+ const sp = spinner(`Asking ${payload.provider.name}...`);
75
+ let result;
76
+ try {
77
+ result = await enrich(root, { docs, consent: opts.consent, signal: opts.signal });
78
+ sp.succeed(`Suggestions written to .athena/${result.file}`);
79
+ } catch (err) {
80
+ sp.stop();
81
+ throw err;
82
+ }
83
+ if (isJson()) json(result);
84
+ else {
85
+ line();
86
+ line(result.markdown.split("\n").slice(0, 40).join("\n"));
87
+ line();
88
+ line(dim(`Full suggestions: .athena/${result.file} (gitignored). These are INFERRED \u2014 nothing was added to your knowledge base.`));
89
+ }
90
+ return 0;
91
+ }
92
+ export {
93
+ aiEnrichCommand,
94
+ aiStatusCommand
95
+ };
96
+ //# sourceMappingURL=ai-ASL4FFQ7.js.map
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ parseAgentsOption,
4
+ printSummary,
5
+ runWithProgress,
6
+ summarize
7
+ } from "./chunk-DOQMAZL5.js";
8
+ import {
9
+ printHeader
10
+ } from "./chunk-SHKXELUX.js";
11
+ import {
12
+ isJson,
13
+ json
14
+ } from "./chunk-Z6QWMJLK.js";
15
+ import {
16
+ requireProjectRoot
17
+ } from "./chunk-XFN7UZCS.js";
18
+ import "./chunk-KBO7XRBW.js";
19
+ import "./chunk-SJOOXO34.js";
20
+ import "./chunk-PANVPNYF.js";
21
+ import "./chunk-ELKFRTZT.js";
22
+ import "./chunk-2R6PXY6Z.js";
23
+ import "./chunk-PVCIGUIT.js";
24
+ import "./chunk-OLPSEQS5.js";
25
+ import "./chunk-JCGDR4UV.js";
26
+ import "./chunk-2RS6TA3D.js";
27
+ import "./chunk-GHFRQAYT.js";
28
+ import "./chunk-673EPYYH.js";
29
+ import "./chunk-FQEGEVLY.js";
30
+ import "./chunk-2DY4PUR4.js";
31
+ import "./chunk-IQQBAWSM.js";
32
+ import "./chunk-AD4OZCU2.js";
33
+ import "./chunk-INT3IGMQ.js";
34
+ import "./chunk-XW3MLHI2.js";
35
+ import "./chunk-EUPETUWA.js";
36
+
37
+ // src/cli/commands/analyze.ts
38
+ async function analyzeCommand(opts) {
39
+ const root = await requireProjectRoot(opts);
40
+ const agents = parseAgentsOption(opts.agents, false);
41
+ printHeader();
42
+ const result = await runWithProgress(root, { mode: "analyze", force: opts.force, agents, signal: opts.signal });
43
+ if (isJson()) json(summarize(result, root, false));
44
+ else printSummary(result, false, "analyze");
45
+ }
46
+ export {
47
+ analyzeCommand
48
+ };
49
+ //# sourceMappingURL=analyze-F3YOILNL.js.map