project-athena 0.2.0 → 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 (148) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +19 -11
  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 -9795
  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-CL6A82dQ.js → Editor-BI56-HX5.js} +1 -1
  64. package/dist/web/assets/{abnfDiagram-O67JEVCF-BuFfjTfx.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-BTsv87pA.js → architectureDiagram-NJMV4G6O-lB6fcVzU.js} +1 -1
  67. package/dist/web/assets/{blockDiagram-BEXU5L5S-CNXqyKIx.js → blockDiagram-BEXU5L5S-DcM5pEjM.js} +1 -1
  68. package/dist/web/assets/{c4Diagram-YGBWAQC7-kksFXQoJ.js → c4Diagram-YGBWAQC7-CJgqxTY2.js} +1 -1
  69. package/dist/web/assets/channel-BvDD7AMO.js +1 -0
  70. package/dist/web/assets/{chunk-3FUC2YCW-BY99XYBs.js → chunk-3FUC2YCW-B8l_VuWR.js} +1 -1
  71. package/dist/web/assets/{chunk-5DYCD2WN-h-PRThMP.js → chunk-5DYCD2WN-CZecgNhf.js} +1 -1
  72. package/dist/web/assets/{chunk-742MDFTN-BIxfyBUj.js → chunk-742MDFTN-BeZ7P5jB.js} +1 -1
  73. package/dist/web/assets/{chunk-7INBJB4K-DZtctDdk.js → chunk-7INBJB4K-IKy0pr5m.js} +1 -1
  74. package/dist/web/assets/{chunk-7M6MHVWA-BX9fUUbL.js → chunk-7M6MHVWA-Dg_QqJEX.js} +1 -1
  75. package/dist/web/assets/{chunk-7PRAP22T-X1s3e81n.js → chunk-7PRAP22T-DqQNS8oq.js} +1 -1
  76. package/dist/web/assets/{chunk-GTNCS2PH-Bh8eluU-.js → chunk-GTNCS2PH-x4sM51nJ.js} +1 -1
  77. package/dist/web/assets/{chunk-GWA4HPMP-D1KATAi0.js → chunk-GWA4HPMP-CE5GcwVN.js} +1 -1
  78. package/dist/web/assets/{chunk-MBY4JIJT-CEErv5kP.js → chunk-MBY4JIJT-DA_D1hjF.js} +1 -1
  79. package/dist/web/assets/{chunk-NETBCI7D-BXiH4aPa.js → chunk-NETBCI7D-CeJ3XBZ2.js} +1 -1
  80. package/dist/web/assets/{chunk-O7XYJQB3-Dede62yi.js → chunk-O7XYJQB3-BH2gESvO.js} +1 -1
  81. package/dist/web/assets/{chunk-UA2S7LBM-KZ9aWRTa.js → chunk-UA2S7LBM-BWr0xCux.js} +1 -1
  82. package/dist/web/assets/{chunk-WEXAMYUT-xf-3L2zU.js → chunk-WEXAMYUT-B4QwUZPM.js} +1 -1
  83. package/dist/web/assets/{chunk-Z7XXMR3K-DLpRdZ8n.js → chunk-Z7XXMR3K-BxcA-y5B.js} +1 -1
  84. package/dist/web/assets/{chunk-ZIGJFQKS-BdJ0_J-U.js → chunk-ZIGJFQKS-BZYvP63G.js} +1 -1
  85. package/dist/web/assets/{classDiagram-v2-NBCMYWYE-C5VzGDGq.js → classDiagram-v2-NBCMYWYE-BS3P_NXb.js} +1 -1
  86. package/dist/web/assets/{cynefin-OW5HDTMX-BqOgbE7U.js → cynefin-OW5HDTMX-BALD4Pve.js} +1 -1
  87. package/dist/web/assets/{cynefinDiagram-VND7K2PF-CCC_feG5.js → cynefinDiagram-VND7K2PF-BM2y4W6d.js} +1 -1
  88. package/dist/web/assets/{dagre-6A5THRUB-SLP-94sd.js → dagre-6A5THRUB-PTfOrIdF.js} +1 -1
  89. package/dist/web/assets/{diagram-22UHCM2B-DpHwEBYM.js → diagram-22UHCM2B-CpXEHhUl.js} +1 -1
  90. package/dist/web/assets/{diagram-3UASUU5V-iGFwuMEk.js → diagram-3UASUU5V-DPHfXzr6.js} +1 -1
  91. package/dist/web/assets/{diagram-ATOU4E4O-Csx9hj0z.js → diagram-ATOU4E4O-DMBNXvAC.js} +1 -1
  92. package/dist/web/assets/{diagram-CDSNMT55-yGKrLRO5.js → diagram-CDSNMT55-CDLe1M7G.js} +1 -1
  93. package/dist/web/assets/{diagram-MLGK6HIB-mDpCormD.js → diagram-MLGK6HIB-D6L8oE7j.js} +1 -1
  94. package/dist/web/assets/{diagram-MPIPVDR6-BJHdatrF.js → diagram-MPIPVDR6-CP-S7rrQ.js} +1 -1
  95. package/dist/web/assets/{ebnfDiagram-ZINNZB2B-ClJbCBBu.js → ebnfDiagram-ZINNZB2B-CkUc63TK.js} +1 -1
  96. package/dist/web/assets/{elk-276RUBZZ-C5tFZMYm.js → elk-276RUBZZ-C1CAJLH_.js} +1 -1
  97. package/dist/web/assets/{erDiagram-OPXOYQCR-u7_UFZot.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-B4hrLcLf.js → ganttDiagram-FUAMR5RP-BVm8SiGg.js} +1 -1
  101. package/dist/web/assets/{gitGraph-4MIJSDKK-Dxx-Bl-U.js → gitGraph-4MIJSDKK-Dm7xBrW0.js} +1 -1
  102. package/dist/web/assets/{gitGraphDiagram-X574FWY7-DKjDV5Kr.js → gitGraphDiagram-X574FWY7-D_PIKKhx.js} +1 -1
  103. package/dist/web/assets/index-CeOIBpu2.js +45 -0
  104. package/dist/web/assets/{info-A6RAGUB7-CgqIdgQA.js → info-A6RAGUB7-CLGrx8pn.js} +1 -1
  105. package/dist/web/assets/{infoDiagram-VRGFBTTK-D-qjWkwy.js → infoDiagram-VRGFBTTK-BlBjaKm7.js} +1 -1
  106. package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-CzpqV6WN.js → ishikawaDiagram-OU5B5YK6-Kxe7GGS3.js} +1 -1
  107. package/dist/web/assets/{journeyDiagram-ZHPQQLJL-BBnOUDzw.js → journeyDiagram-ZHPQQLJL-wCMoHK4M.js} +1 -1
  108. package/dist/web/assets/{kanban-definition-PNTS6WVX-Di9sTFB1.js → kanban-definition-PNTS6WVX-BxdCz9Ko.js} +1 -1
  109. package/dist/web/assets/{line-B8ehGiwF.js → line-Zf11FnWF.js} +1 -1
  110. package/dist/web/assets/{mermaid-parser.core-DWsT24e5.js → mermaid-parser.core-BvFFklev.js} +3 -3
  111. package/dist/web/assets/{mermaid.core-DzYpaVzI.js → mermaid.core-Cd_Ssti9.js} +4 -4
  112. package/dist/web/assets/{mindmap-definition-NLK3R4M7-CxagBsH3.js → mindmap-definition-NLK3R4M7-BGPQl-GY.js} +1 -1
  113. package/dist/web/assets/{packet-AYTQ26CC-CmxbS0qf.js → packet-AYTQ26CC-6lI3cEX_.js} +1 -1
  114. package/dist/web/assets/{pegDiagram-GJSIUBJH-_3jnD3q8.js → pegDiagram-GJSIUBJH-BPIjlSAj.js} +1 -1
  115. package/dist/web/assets/{pie-WAS4IAKB-DppCWOqP.js → pie-WAS4IAKB-D6Dzj3Ja.js} +1 -1
  116. package/dist/web/assets/{pieDiagram-5QR66LMP-DTVThTqz.js → pieDiagram-5QR66LMP-DtA-vZmv.js} +1 -1
  117. package/dist/web/assets/{quadrantDiagram-O4NWA36T-BJ_OFnht.js → quadrantDiagram-O4NWA36T-EettyqQn.js} +1 -1
  118. package/dist/web/assets/{radar-RG4KPBEZ-B6quSMjs.js → radar-RG4KPBEZ-xmRc3Dl8.js} +1 -1
  119. package/dist/web/assets/{railroad-74A4TZTK-CErU7RG3.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-Zu0N_D-D.js → railroadDiagram-XR7U4H2S-PBw4We6b.js} +1 -1
  124. package/dist/web/assets/{requirementDiagram-PLB6GJNP-Bl6CN3ul.js → requirementDiagram-PLB6GJNP-DjQpaUFA.js} +1 -1
  125. package/dist/web/assets/{sankeyDiagram-IPEJSGJF-DdpKzZV8.js → sankeyDiagram-IPEJSGJF-CBW-_AoF.js} +1 -1
  126. package/dist/web/assets/{sequenceDiagram-PO4LG4MO-BwUqrl9F.js → sequenceDiagram-PO4LG4MO-DEoPYqY9.js} +1 -1
  127. package/dist/web/assets/{stateDiagram-v2-GCMORJYK-C3vZGpAi.js → stateDiagram-v2-GCMORJYK-UZq5rPud.js} +1 -1
  128. package/dist/web/assets/{swimlanes-2SLR337P-BjoMa2HW.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-0apcf2l2.js → timeline-definition-EJHVYXUP-CYJl7mP-.js} +1 -1
  131. package/dist/web/assets/{treeView-Q6P3EWNA-BOPWzyg3.js → treeView-Q6P3EWNA-DtZ5nkPQ.js} +1 -1
  132. package/dist/web/assets/{treemap-WGGIJYW6-CC6rU6dD.js → treemap-WGGIJYW6-m49GDM-M.js} +1 -1
  133. package/dist/web/assets/{usecaseDiagram-POWQR4AR-BFclVNBG.js → usecaseDiagram-POWQR4AR-C2Vg20UK.js} +1 -1
  134. package/dist/web/assets/{vennDiagram-UO4OBE2U-D2XPv-zb.js → vennDiagram-UO4OBE2U-C07v5OGI.js} +1 -1
  135. package/dist/web/assets/{wardley-WFR3VGLG-Btz6ID8P.js → wardley-WFR3VGLG-C9Z7EZtz.js} +1 -1
  136. package/dist/web/assets/{wardleyDiagram-VNRHLVJA-DFYPvGpS.js → wardleyDiagram-VNRHLVJA-DdA8CcSC.js} +1 -1
  137. package/dist/web/assets/{xychartDiagram-PMCCYNJV-DMkQD-sV.js → xychartDiagram-PMCCYNJV-C9EZw07b.js} +1 -1
  138. package/dist/web/index.html +1 -1
  139. package/package.json +5 -2
  140. package/dist/web/assets/architecture-7GRP2DOG-CGSEE8po.js +0 -1
  141. package/dist/web/assets/channel-Dneho6u-.js +0 -1
  142. package/dist/web/assets/eventmodeling-NTZA5JFV-CA-vzJb3.js +0 -1
  143. package/dist/web/assets/flowDiagram-KWPJA3E3-Ca_1cbbS.js +0 -1
  144. package/dist/web/assets/index-s6lwoj84.js +0 -45
  145. package/dist/web/assets/railroad-abnf-HS5TGJTU-CWsEvTRR.js +0 -1
  146. package/dist/web/assets/railroad-ebnf-LZEXJU2U-Cyq9ELNn.js +0 -1
  147. package/dist/web/assets/railroad-peg-WCYAUIDC-BX1hI1hV.js +0 -1
  148. package/dist/web/assets/swimlanesDiagram-TC7HE7FX-BGNz1HGG.js +0 -8
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ addRule,
4
+ listRules,
5
+ parseRules,
6
+ removeRule,
7
+ serializeRules,
8
+ updateRule
9
+ } from "./chunk-3IS2X2Z7.js";
10
+ import {
11
+ git
12
+ } from "./chunk-OLPSEQS5.js";
13
+ import {
14
+ scanText
15
+ } from "./chunk-JCGDR4UV.js";
16
+ import {
17
+ isBlockModified,
18
+ parseBlocks
19
+ } from "./chunk-673EPYYH.js";
20
+ import {
21
+ KNOWLEDGE_DOCS
22
+ } from "./chunk-FQEGEVLY.js";
23
+ import {
24
+ athenaDir,
25
+ readState
26
+ } from "./chunk-2DY4PUR4.js";
27
+ import {
28
+ readTextIfExists,
29
+ sha256,
30
+ writeFileAtomic
31
+ } from "./chunk-IQQBAWSM.js";
32
+ import {
33
+ AthenaError,
34
+ conflict,
35
+ notFound
36
+ } from "./chunk-AD4OZCU2.js";
37
+
38
+ // src/services/rules.ts
39
+ import path2 from "path";
40
+
41
+ // src/services/knowledge.ts
42
+ import path from "path";
43
+ var MAX_DOC_BYTES = 1e6;
44
+ function resolveDoc(id) {
45
+ const doc = KNOWLEDGE_DOCS.find((d) => d.id === id);
46
+ if (!doc) throw notFound(`Unknown knowledge document: ${id.slice(0, 64)}`);
47
+ return doc;
48
+ }
49
+ function contentHash(text) {
50
+ return sha256(text).slice(0, 16);
51
+ }
52
+ function segmentDocument(text) {
53
+ const segments = [];
54
+ let cursor = 0;
55
+ for (const b of parseBlocks(text)) {
56
+ const before = text.slice(cursor, b.start);
57
+ if (before.trim()) segments.push({ kind: "developer", content: before });
58
+ segments.push({ kind: "generated", id: b.id, modified: isBlockModified(b), content: b.content });
59
+ cursor = b.end;
60
+ }
61
+ const rest = text.slice(cursor);
62
+ if (rest.trim()) segments.push({ kind: "developer", content: rest });
63
+ return segments;
64
+ }
65
+ async function meta(root, id, text) {
66
+ const doc = resolveDoc(id);
67
+ const st = await readState(athenaDir(root));
68
+ const ds = st.kind === "ok" ? st.state.documents[id] : void 0;
69
+ const blocks = text ? parseBlocks(text) : [];
70
+ return {
71
+ id,
72
+ file: doc.file,
73
+ title: doc.title,
74
+ purpose: doc.purpose,
75
+ present: text !== null,
76
+ bytes: text ? Buffer.byteLength(text) : 0,
77
+ hash: text === null ? null : contentHash(text),
78
+ lastGeneratedAt: ds?.lastGeneratedAt ?? (st.kind === "ok" && id === "rules" ? st.state.createdAt : null),
79
+ lastChangedAt: ds?.lastChangedAt ?? null,
80
+ generatedSections: blocks.length,
81
+ editedSections: blocks.filter(isBlockModified).map((b) => b.id)
82
+ };
83
+ }
84
+ async function listDocs(root) {
85
+ const out = [];
86
+ for (const d of KNOWLEDGE_DOCS) out.push(await meta(root, d.id, await readTextIfExists(path.join(athenaDir(root), d.file))));
87
+ return out;
88
+ }
89
+ async function readDoc(root, id) {
90
+ const doc = resolveDoc(id);
91
+ const text = await readTextIfExists(path.join(athenaDir(root), doc.file));
92
+ if (text === null) throw notFound(`${doc.file} does not exist. Run \`athena analyze\` to regenerate it.`);
93
+ return { ...await meta(root, doc.id, text), content: text, segments: segmentDocument(text) };
94
+ }
95
+ async function saveDoc(root, id, content, baseHash) {
96
+ const doc = resolveDoc(id);
97
+ if (typeof content !== "string") throw new AthenaError("content must be a string");
98
+ if (Buffer.byteLength(content) > MAX_DOC_BYTES) throw new AthenaError(`Document exceeds ${MAX_DOC_BYTES} bytes`, void 0, 1, "unprocessable");
99
+ const file = path.join(athenaDir(root), doc.file);
100
+ const current = await readTextIfExists(file);
101
+ const currentHash = current === null ? null : contentHash(current);
102
+ if (currentHash !== baseHash) {
103
+ throw conflict(`${doc.file} was changed on disk since you opened it.`, "Reload to see the latest version, then re-apply your edits.", { currentHash });
104
+ }
105
+ const secrets = scanText(content);
106
+ if (secrets.length) {
107
+ throw new AthenaError(
108
+ `Refusing to save: ${secrets.length} likely secret(s) found (${[...new Set(secrets.map((s) => s.type))].join(", ")}).`,
109
+ "Knowledge files must never contain secrets. Replace values with placeholders such as <configured externally>.",
110
+ 1,
111
+ "unprocessable",
112
+ { secrets: secrets.map((s) => ({ type: s.type, line: s.line })) }
113
+ );
114
+ }
115
+ const normalized = content.replace(/\r\n/g, "\n");
116
+ await writeFileAtomic(file, normalized.endsWith("\n") ? normalized : `${normalized}
117
+ `);
118
+ return readDoc(root, doc.id);
119
+ }
120
+ async function searchDocs(root, query, limit = 50) {
121
+ const q = query.trim().toLowerCase();
122
+ if (q.length < 2) return [];
123
+ const hits = [];
124
+ for (const d of KNOWLEDGE_DOCS) {
125
+ const text = await readTextIfExists(path.join(athenaDir(root), d.file));
126
+ if (!text) continue;
127
+ const lines = text.split("\n");
128
+ for (let i = 0; i < lines.length && hits.length < limit; i++) {
129
+ const line = lines[i];
130
+ if (line.startsWith("<!-- athena:")) continue;
131
+ if (line.toLowerCase().includes(q)) hits.push({ id: d.id, file: d.file, title: d.title, line: i + 1, text: line.trim().slice(0, 240) });
132
+ }
133
+ if (d.title.toLowerCase().includes(q) && !hits.some((h) => h.id === d.id)) hits.unshift({ id: d.id, file: d.file, title: d.title, line: 1, text: d.purpose });
134
+ }
135
+ return hits.slice(0, limit);
136
+ }
137
+ async function docHistory(root, id, limit = 50) {
138
+ const doc = resolveDoc(id);
139
+ const rel = `.athena/${doc.file}`;
140
+ const r = await git(root, ["log", `-n${limit}`, "--follow", "--format=%H%x1f%cI%x1f%s", "--", rel]);
141
+ if (!r.ok) return { available: false, reason: "Git history is not available (not a Git repository, or git is not installed).", entries: [] };
142
+ const entries = r.stdout.split("\n").filter(Boolean).map((l) => {
143
+ const [sha, date, subject] = l.split("");
144
+ return { sha, date, subject: subject ?? "" };
145
+ });
146
+ if (!entries.length) return { available: true, reason: `${rel} has not been committed yet.`, entries };
147
+ return { available: true, entries };
148
+ }
149
+ async function docAtRevision(root, id, sha) {
150
+ const doc = resolveDoc(id);
151
+ if (!/^[0-9a-f]{7,64}$/i.test(sha)) throw new AthenaError("Invalid revision");
152
+ const prefix = (await git(root, ["rev-parse", "--show-prefix"])).stdout.trim();
153
+ const r = await git(root, ["show", `${sha}:${prefix}.athena/${doc.file}`]);
154
+ if (!r.ok) throw notFound(`${doc.file} does not exist at revision ${sha.slice(0, 12)}`);
155
+ return r.stdout;
156
+ }
157
+
158
+ // src/services/rules.ts
159
+ async function load(root) {
160
+ const file = path2.join(athenaDir(root), "rules.md");
161
+ const text = await readTextIfExists(file);
162
+ if (text === null) throw notFound(".athena/rules.md not found. Run `athena analyze` to recreate it.");
163
+ return { file, text, doc: parseRules(text) };
164
+ }
165
+ function view(text, doc) {
166
+ const rules = listRules(doc);
167
+ const sections = [.../* @__PURE__ */ new Set([...doc.lines.flatMap((l) => l.kind === "other" ? /^##\s+(.+?)\s*#*\s*$/.exec(l.raw)?.[1] ?? [] : []), ...rules.map((r) => r.section)])];
168
+ return { hash: contentHash(text), rules, sections };
169
+ }
170
+ async function getRules(root) {
171
+ const { text, doc } = await load(root);
172
+ return view(text, doc);
173
+ }
174
+ async function mutateRules(root, baseHash, fn) {
175
+ const { file, text, doc } = await load(root);
176
+ if (baseHash !== void 0 && baseHash !== contentHash(text)) {
177
+ throw conflict("rules.md changed since it was loaded.", "Reload the rules and try again.", { currentHash: contentHash(text) });
178
+ }
179
+ let next;
180
+ try {
181
+ next = fn(doc);
182
+ } catch (err) {
183
+ throw new AthenaError(err.message);
184
+ }
185
+ const out = serializeRules(next);
186
+ const secrets = scanText(out).filter((s) => !scanText(text).some((o) => o.fingerprint === s.fingerprint));
187
+ if (secrets.length) throw new AthenaError("Refusing to save a rule that appears to contain a secret.", "Rules must never contain credentials.", 1, "unprocessable");
188
+ await writeFileAtomic(file, out);
189
+ return view(out, next);
190
+ }
191
+ var rulesAdd = (root, baseHash, section, text) => mutateRules(root, baseHash, (d) => addRule(d, section, text));
192
+ var rulesUpdate = (root, baseHash, index, patch) => mutateRules(root, baseHash, (d) => updateRule(d, index, patch));
193
+ var rulesRemove = (root, baseHash, index) => mutateRules(root, baseHash, (d) => removeRule(d, index));
194
+
195
+ export {
196
+ contentHash,
197
+ listDocs,
198
+ readDoc,
199
+ saveDoc,
200
+ searchDocs,
201
+ docHistory,
202
+ docAtRevision,
203
+ getRules,
204
+ mutateRules,
205
+ rulesAdd,
206
+ rulesUpdate,
207
+ rulesRemove
208
+ };
209
+ //# sourceMappingURL=chunk-LEMPCNAY.js.map
@@ -0,0 +1,379 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ whichExecutable
4
+ } from "./chunk-VSYBC3VY.js";
5
+ import {
6
+ scanFilesForSecrets,
7
+ secretFingerprinter
8
+ } from "./chunk-PANVPNYF.js";
9
+ import {
10
+ walkProject
11
+ } from "./chunk-ELKFRTZT.js";
12
+ import {
13
+ loadConfig
14
+ } from "./chunk-2DY4PUR4.js";
15
+
16
+ // src/services/security.ts
17
+ import { execFile } from "child_process";
18
+ import { promises as fs } from "fs";
19
+ import path from "path";
20
+ var AuditToolError = class extends Error {
21
+ };
22
+ var asRecord = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
23
+ var isObject = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
24
+ var sev = (s) => {
25
+ const v = String(s ?? "").toLowerCase();
26
+ if (v === "critical") return "critical";
27
+ if (v === "high") return "high";
28
+ if (v === "moderate" || v === "medium") return "moderate";
29
+ if (v === "low" || v === "info") return "low";
30
+ return "unrated";
31
+ };
32
+ function parseJsonObject(stdout, tool) {
33
+ const text = stdout.trim();
34
+ if (!text) throw new AuditToolError(`${tool} produced no output`);
35
+ const json = JSON.parse(text);
36
+ if (!isObject(json)) throw new AuditToolError(`${tool} produced unexpected output`);
37
+ return json;
38
+ }
39
+ function throwIfToolError(json, tool) {
40
+ if (json.error === void 0) return;
41
+ const e = asRecord(json.error);
42
+ const parts = [e.code, e.summary ?? e.message].filter((x) => typeof x === "string" && x.trim());
43
+ const text = parts.length ? parts.join(": ") : typeof json.error === "string" ? json.error : "unknown error";
44
+ throw new AuditToolError(`${tool} reported an error: ${text.split("\n")[0].slice(0, 200)}`);
45
+ }
46
+ function parseAdvisories(advisories) {
47
+ const out = [];
48
+ for (const [key, raw] of Object.entries(advisories)) {
49
+ const a = asRecord(raw);
50
+ const versions = [...new Set((Array.isArray(a.findings) ? a.findings : []).map((f) => asRecord(f).version).filter((v) => typeof v === "string"))];
51
+ const patched = typeof a.patched_versions === "string" ? a.patched_versions.trim() : "";
52
+ out.push({
53
+ package: String(a.module_name ?? "unknown"),
54
+ severity: sev(a.severity),
55
+ title: String(a.title ?? `Vulnerable dependency: ${String(a.module_name ?? key)}`),
56
+ id: typeof a.github_advisory_id === "string" ? a.github_advisory_id : Array.isArray(a.cves) && typeof a.cves[0] === "string" ? a.cves[0] : String(a.id ?? key),
57
+ url: typeof a.url === "string" ? a.url : void 0,
58
+ vulnerableRange: typeof a.vulnerable_versions === "string" ? a.vulnerable_versions + (versions.length ? ` (installed ${versions.join(", ")})` : "") : void 0,
59
+ fixAvailable: Boolean(patched) && patched !== "<0.0.0"
60
+ });
61
+ }
62
+ return out;
63
+ }
64
+ function parseNpmAudit(stdout) {
65
+ const json = parseJsonObject(stdout, "npm audit");
66
+ throwIfToolError(json, "npm audit");
67
+ if (!isObject(json.vulnerabilities)) {
68
+ if (isObject(json.advisories)) return parseAdvisories(json.advisories);
69
+ throw new AuditToolError('npm audit output has no "vulnerabilities" report');
70
+ }
71
+ const out = [];
72
+ for (const [name, raw] of Object.entries(json.vulnerabilities)) {
73
+ const v = asRecord(raw);
74
+ const via = Array.isArray(v.via) ? v.via : [];
75
+ const advisory = via.find((x) => x && typeof x === "object");
76
+ const viaNames = via.filter((x) => typeof x === "string");
77
+ out.push({
78
+ package: name,
79
+ severity: sev(v.severity),
80
+ title: String(advisory?.title ?? (viaNames.length ? `Depends on vulnerable ${viaNames.join(", ")}` : `Vulnerable dependency: ${name}`)),
81
+ id: advisory?.source !== void 0 ? String(advisory.source) : void 0,
82
+ url: typeof advisory?.url === "string" ? advisory.url : void 0,
83
+ vulnerableRange: typeof v.range === "string" ? v.range : void 0,
84
+ fixAvailable: Boolean(v.fixAvailable)
85
+ });
86
+ }
87
+ return out;
88
+ }
89
+ function parsePnpmAudit(stdout) {
90
+ const json = parseJsonObject(stdout, "pnpm audit");
91
+ throwIfToolError(json, "pnpm audit");
92
+ if (!isObject(json.advisories)) throw new AuditToolError('pnpm audit output has no "advisories" report');
93
+ return parseAdvisories(json.advisories);
94
+ }
95
+ function parsePipAudit(stdout) {
96
+ const text = stdout.trim();
97
+ if (!text) throw new AuditToolError("pip-audit produced no output");
98
+ const json = JSON.parse(text);
99
+ const deps = isObject(json) && Array.isArray(json.dependencies) ? json.dependencies : Array.isArray(json) ? json : null;
100
+ if (!deps) throw new AuditToolError('pip-audit output has no "dependencies" list');
101
+ const out = [];
102
+ for (const raw of deps) {
103
+ const d = asRecord(raw);
104
+ for (const vRaw of Array.isArray(d.vulns) ? d.vulns : []) {
105
+ const v = asRecord(vRaw);
106
+ const aliases = Array.isArray(v.aliases) ? v.aliases.filter((a) => typeof a === "string") : [];
107
+ const id = typeof v.id === "string" ? v.id : void 0;
108
+ out.push({
109
+ package: String(d.name ?? "unknown"),
110
+ severity: "unrated",
111
+ // pip-audit does not report severity
112
+ title: String(v.description ?? id ?? "Known vulnerability").split("\n")[0].slice(0, 300),
113
+ id: id ?? aliases[0],
114
+ url: id ? `https://osv.dev/vulnerability/${id}` : void 0,
115
+ vulnerableRange: typeof d.version === "string" ? `installed ${d.version}` : void 0,
116
+ fixAvailable: Array.isArray(v.fix_versions) && v.fix_versions.length > 0
117
+ });
118
+ }
119
+ }
120
+ return out;
121
+ }
122
+ function splitJsonStream(text) {
123
+ const out = [];
124
+ let depth = 0;
125
+ let inString = false;
126
+ let escaped = false;
127
+ let start = -1;
128
+ for (let i = 0; i < text.length; i++) {
129
+ const ch = text[i];
130
+ if (inString) {
131
+ if (escaped) escaped = false;
132
+ else if (ch === "\\") escaped = true;
133
+ else if (ch === '"') inString = false;
134
+ continue;
135
+ }
136
+ if (ch === '"') {
137
+ if (depth > 0) inString = true;
138
+ } else if (ch === "{") {
139
+ if (depth === 0) start = i;
140
+ depth++;
141
+ } else if (ch === "}" && depth > 0) {
142
+ depth--;
143
+ if (depth === 0) {
144
+ out.push(JSON.parse(text.slice(start, i + 1)));
145
+ start = -1;
146
+ }
147
+ }
148
+ }
149
+ if (depth !== 0) throw new AuditToolError("truncated JSON output");
150
+ return out;
151
+ }
152
+ var REACH_RANK = { required: 0, imported: 1, called: 2 };
153
+ function parseGovulncheck(stdout) {
154
+ const messages = splitJsonStream(stdout).map(asRecord);
155
+ if (!messages.some((m) => isObject(m.config))) throw new AuditToolError("govulncheck produced no JSON report");
156
+ const osvs = /* @__PURE__ */ new Map();
157
+ for (const m of messages) {
158
+ const osv = asRecord(m.osv);
159
+ if (typeof osv.id === "string") osvs.set(osv.id, osv);
160
+ }
161
+ const found = /* @__PURE__ */ new Map();
162
+ for (const m of messages) {
163
+ const f = asRecord(m.finding);
164
+ if (typeof f.osv !== "string") continue;
165
+ const trace = (Array.isArray(f.trace) ? f.trace : []).map(asRecord);
166
+ const reach = trace.some((t) => typeof t.function === "string") ? "called" : trace.some((t) => typeof t.package === "string") ? "imported" : "required";
167
+ const first = trace[0] ?? {};
168
+ const prev = found.get(f.osv);
169
+ if (prev && REACH_RANK[prev.reach] >= REACH_RANK[reach]) continue;
170
+ found.set(f.osv, {
171
+ module: typeof first.module === "string" ? first.module : prev?.module,
172
+ version: typeof first.version === "string" ? first.version : prev?.version,
173
+ fixed: typeof f.fixed_version === "string" ? f.fixed_version : prev?.fixed,
174
+ reach
175
+ });
176
+ }
177
+ const label = { called: "called by your code", imported: "package imported, vulnerable symbol not called", required: "module required, package not imported" };
178
+ return [...found].map(([id, f]) => {
179
+ const osv = osvs.get(id) ?? {};
180
+ const affected = Array.isArray(osv.affected) ? asRecord(osv.affected[0]) : {};
181
+ return {
182
+ package: f.module ?? String(asRecord(affected.package).name ?? "unknown"),
183
+ severity: "unrated",
184
+ // the Go vulnerability database does not assign severities
185
+ title: `${String(osv.summary ?? id)} (${label[f.reach]})`,
186
+ id,
187
+ url: `https://pkg.go.dev/vuln/${id}`,
188
+ vulnerableRange: f.version ? `installed ${f.version}` : void 0,
189
+ fixAvailable: Boolean(f.fixed)
190
+ };
191
+ });
192
+ }
193
+ function parseCargoAudit(stdout) {
194
+ const json = parseJsonObject(stdout, "cargo audit");
195
+ if (!isObject(json.vulnerabilities)) throw new AuditToolError('cargo audit output has no "vulnerabilities" report');
196
+ const list = Array.isArray(json.vulnerabilities.list) ? json.vulnerabilities.list : [];
197
+ return list.map((raw) => {
198
+ const v = asRecord(raw);
199
+ const advisory = asRecord(v.advisory);
200
+ const pkg = asRecord(v.package);
201
+ return {
202
+ package: String(pkg.name ?? "unknown"),
203
+ severity: sev(asRecord(advisory.cvss).severity ?? advisory.severity),
204
+ title: String(advisory.title ?? "Known vulnerability"),
205
+ id: typeof advisory.id === "string" ? advisory.id : void 0,
206
+ url: typeof advisory.url === "string" ? advisory.url : void 0
207
+ };
208
+ });
209
+ }
210
+ function parseComposerAudit(stdout) {
211
+ const json = parseJsonObject(stdout, "composer audit");
212
+ if (json.advisories === void 0) throw new AuditToolError('composer audit output has no "advisories" report');
213
+ const advisories = asRecord(json.advisories);
214
+ const out = [];
215
+ for (const [pkg, raw] of Object.entries(advisories)) {
216
+ for (const aRaw of Array.isArray(raw) ? raw : Object.values(asRecord(raw))) {
217
+ const a = asRecord(aRaw);
218
+ out.push({
219
+ package: pkg,
220
+ severity: sev(a.severity),
221
+ title: String(a.title ?? "Known vulnerability"),
222
+ id: typeof a.cve === "string" ? a.cve : typeof a.advisoryId === "string" ? a.advisoryId : void 0,
223
+ url: typeof a.link === "string" ? a.link : void 0,
224
+ vulnerableRange: typeof a.affectedVersions === "string" ? a.affectedVersions : void 0
225
+ });
226
+ }
227
+ }
228
+ return out;
229
+ }
230
+ var MAX_PIP_TARGETS = 10;
231
+ var REQUIREMENTS_FILE = /(^|\/)requirements[\w.-]*\.txt$/i;
232
+ async function planPipAudit(ctx) {
233
+ const common = ["--format", "json", "--progress-spinner", "off"];
234
+ const reqs = ctx.files.filter((f) => REQUIREMENTS_FILE.test(f)).sort();
235
+ const reqDirs = new Set(reqs.map((f) => path.posix.dirname(f)));
236
+ const out = reqs.map((f) => ({ target: f, args: ["-r", f, ...common] }));
237
+ for (const f of ctx.files.filter((x) => x === "pyproject.toml" || x.endsWith("/pyproject.toml")).sort()) {
238
+ const dir = path.posix.dirname(f);
239
+ if (reqDirs.has(dir)) continue;
240
+ const text = await fs.readFile(path.join(ctx.root, f), "utf8").catch(() => "");
241
+ if (!/^\s*\[project\]\s*$/m.test(text)) continue;
242
+ out.push({ target: f, args: [...common, dir === "." ? "." : `./${dir}`] });
243
+ }
244
+ if (!out.length) return "no requirements file to audit (pip-audit needs requirements*.txt or a PEP 621 pyproject.toml; the active Python environment is never audited)";
245
+ return out.slice(0, MAX_PIP_TARGETS);
246
+ }
247
+ var single = (args) => async () => [{ args }];
248
+ var RUNNERS = [
249
+ {
250
+ tool: "npm audit",
251
+ ecosystem: "npm",
252
+ command: "npm",
253
+ applies: (_m, has) => has("package-lock.json") || has("npm-shrinkwrap.json"),
254
+ plan: single(["audit", "--json", "--audit-level=low"]),
255
+ parse: parseNpmAudit
256
+ },
257
+ {
258
+ tool: "pnpm audit",
259
+ ecosystem: "npm",
260
+ command: "pnpm",
261
+ applies: (_m, has) => has("pnpm-lock.yaml"),
262
+ plan: single(["audit", "--json"]),
263
+ parse: parsePnpmAudit
264
+ },
265
+ {
266
+ tool: "pip-audit",
267
+ ecosystem: "python",
268
+ command: "pip-audit",
269
+ applies: (m, _has, ctx) => m.manifests.some((x) => x.ecosystem === "python") || ctx.files.some((f) => REQUIREMENTS_FILE.test(f)),
270
+ plan: planPipAudit,
271
+ parse: parsePipAudit
272
+ },
273
+ {
274
+ tool: "govulncheck",
275
+ ecosystem: "go",
276
+ command: "govulncheck",
277
+ applies: (_m, has) => has("go.mod"),
278
+ plan: single(["-format=json", "./..."]),
279
+ parse: parseGovulncheck
280
+ },
281
+ {
282
+ tool: "cargo audit",
283
+ ecosystem: "cargo",
284
+ command: "cargo",
285
+ applies: (_m, has) => has("Cargo.lock"),
286
+ plan: single(["audit", "--json"]),
287
+ parse: parseCargoAudit
288
+ },
289
+ {
290
+ tool: "composer audit",
291
+ ecosystem: "composer",
292
+ command: "composer",
293
+ applies: (_m, has) => has("composer.lock"),
294
+ plan: single(["audit", "--format=json"]),
295
+ parse: parseComposerAudit
296
+ }
297
+ ];
298
+ function run(command, args, cwd, timeoutMs) {
299
+ return new Promise((resolve) => {
300
+ execFile(command, args, { cwd, timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024, windowsHide: true, env: { ...process.env, NO_COLOR: "1", npm_config_color: "false" } }, (error, stdout, stderr) => {
301
+ const timedOut = Boolean(error && (error.code === "ETIMEDOUT" || error.killed));
302
+ resolve({ stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), timedOut, error: error ?? null });
303
+ });
304
+ });
305
+ }
306
+ async function runTool(runner, inv, root, timeoutMs) {
307
+ const t = Date.now();
308
+ const base = { tool: runner.tool, ecosystem: runner.ecosystem, ...inv.target ? { target: inv.target } : {} };
309
+ const r = await run(runner.command, inv.args, root, timeoutMs);
310
+ if (r.timedOut) return { ...base, status: "timeout", message: `Timed out after ${timeoutMs / 1e3}s`, durationMs: Date.now() - t, findings: [] };
311
+ try {
312
+ const findings = runner.parse(r.stdout, r.stderr);
313
+ return { ...base, status: "ok", durationMs: Date.now() - t, findings };
314
+ } catch (err) {
315
+ const reason = err instanceof AuditToolError ? err.message : `Could not parse output: ${r.stderr.trim().split("\n")[0] || r.error?.message || err.message || "unreadable output"}`;
316
+ return { ...base, status: "failed", message: reason.slice(0, 300), durationMs: Date.now() - t, findings: [] };
317
+ }
318
+ }
319
+ async function runSecurityScan(root, model, opts = {}) {
320
+ const started = Date.now();
321
+ const timeoutMs = opts.timeoutMs ?? 12e4;
322
+ const { config } = await loadConfig(root);
323
+ const walk = await walkProject(root, { config, signal: opts.signal });
324
+ const ctx = { root, files: walk.files.map((f) => f.path) };
325
+ const files = new Set(model.manifests.map((m) => m.path));
326
+ for (const pm of model.packageManagers) for (const e of pm.provenance.evidence) files.add(e.file);
327
+ for (const f of ctx.files) files.add(f);
328
+ const has = (file) => files.has(file);
329
+ const tools = [];
330
+ if (!opts.skipAudit) {
331
+ for (const runner of RUNNERS) {
332
+ if (!runner.applies(model, has, ctx)) continue;
333
+ opts.signal?.throwIfAborted();
334
+ const t = Date.now();
335
+ opts.onTool?.(runner.tool);
336
+ const bin = await whichExecutable(runner.command);
337
+ if (!bin) {
338
+ tools.push({ tool: runner.tool, ecosystem: runner.ecosystem, status: "unavailable", message: `${runner.command} is not installed or not on PATH`, durationMs: Date.now() - t, findings: [] });
339
+ continue;
340
+ }
341
+ const plan = await runner.plan(ctx);
342
+ if (typeof plan === "string") {
343
+ tools.push({ tool: runner.tool, ecosystem: runner.ecosystem, status: "unavailable", message: plan, durationMs: Date.now() - t, findings: [] });
344
+ continue;
345
+ }
346
+ for (const inv of plan) {
347
+ opts.signal?.throwIfAborted();
348
+ tools.push(await runTool(runner, inv, root, timeoutMs));
349
+ }
350
+ }
351
+ }
352
+ const counts = { critical: 0, high: 0, moderate: 0, low: 0, unrated: 0 };
353
+ for (const t of tools) for (const f of t.findings) counts[f.severity]++;
354
+ const secrets = await scanProjectSecrets(root, walk.files, opts.signal);
355
+ const scan = {
356
+ schemaVersion: 1,
357
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
358
+ durationMs: Date.now() - started,
359
+ tools,
360
+ counts,
361
+ secrets: {
362
+ count: secrets.findings.length,
363
+ files: [...new Set(secrets.findings.map((s) => s.file))].slice(0, 50),
364
+ skippedLongLines: secrets.skippedLongLines,
365
+ ...secrets.truncated ? { truncated: true } : {}
366
+ }
367
+ };
368
+ return scan;
369
+ }
370
+ async function scanProjectSecrets(root, files, signal) {
371
+ const fingerprint = await secretFingerprinter(root);
372
+ const read = async (rel) => fs.readFile(path.join(root, rel), "utf8").catch(() => null);
373
+ return scanFilesForSecrets(files, read, { fingerprint, signal });
374
+ }
375
+
376
+ export {
377
+ runSecurityScan
378
+ };
379
+ //# sourceMappingURL=chunk-LMOFZNTQ.js.map