project-athena 0.2.1 → 0.4.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 (152) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +61 -12
  3. package/dist/activity-BKW7G4I5.js +77 -0
  4. package/dist/agents-DFSNEOP5.js +69 -0
  5. package/dist/ai-ASL4FFQ7.js +96 -0
  6. package/dist/analyze-ZY3MXBOI.js +50 -0
  7. package/dist/chunk-2DY4PUR4.js +465 -0
  8. package/dist/chunk-2R6PXY6Z.js +33 -0
  9. package/dist/chunk-3IS2X2Z7.js +89 -0
  10. package/dist/chunk-3XSDI5WF.js +179 -0
  11. package/dist/chunk-65TSRDQ3.js +124 -0
  12. package/dist/chunk-673EPYYH.js +105 -0
  13. package/dist/chunk-7DZK6WSQ.js +65 -0
  14. package/dist/chunk-AD4OZCU2.js +34 -0
  15. package/dist/chunk-DZDI2HRT.js +169 -0
  16. package/dist/chunk-ELKFRTZT.js +221 -0
  17. package/dist/chunk-EUPETUWA.js +14 -0
  18. package/dist/chunk-FQEGEVLY.js +38 -0
  19. package/dist/chunk-FVCDMQUQ.js +343 -0
  20. package/dist/chunk-GGXPG6OE.js +904 -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-KBO7XRBW.js +26 -0
  27. package/dist/chunk-KHMBNT66.js +399 -0
  28. package/dist/chunk-LEMPCNAY.js +209 -0
  29. package/dist/chunk-MPG3IEGB.js +2691 -0
  30. package/dist/chunk-OLPSEQS5.js +264 -0
  31. package/dist/chunk-PVCIGUIT.js +64 -0
  32. package/dist/chunk-QCC4VJUH.js +217 -0
  33. package/dist/chunk-QLW657NT.js +95 -0
  34. package/dist/chunk-RVTNSNGK.js +182 -0
  35. package/dist/chunk-RXNRMIZP.js +390 -0
  36. package/dist/chunk-SHKXELUX.js +295 -0
  37. package/dist/chunk-SJ4DQFIJ.js +379 -0
  38. package/dist/chunk-SVTEJYMS.js +227 -0
  39. package/dist/chunk-TDNZ2RYA.js +702 -0
  40. package/dist/chunk-UAZLPNVC.js +108 -0
  41. package/dist/chunk-VSYBC3VY.js +28 -0
  42. package/dist/chunk-WVQC2EGO.js +166 -0
  43. package/dist/chunk-XDCKZ5TV.js +436 -0
  44. package/dist/chunk-XFN7UZCS.js +35 -0
  45. package/dist/chunk-XW3MLHI2.js +32 -0
  46. package/dist/chunk-YRIS7ZS6.js +919 -0
  47. package/dist/chunk-Z6QWMJLK.js +124 -0
  48. package/dist/chunk-ZB7LC6FP.js +221 -0
  49. package/dist/clean-MDBYH62P.js +23 -0
  50. package/dist/cli.js +18 -10716
  51. package/dist/context-PLPCAPCO.js +161 -0
  52. package/dist/doctor-BX677KRE.js +69 -0
  53. package/dist/event-TNT6CQZR.js +101 -0
  54. package/dist/git-hook-YZNZGAAP.js +252 -0
  55. package/dist/init-WQCAOJ2R.js +38 -0
  56. package/dist/mcp-U7EPH43L.js +393 -0
  57. package/dist/memory-VSVNLEJC.js +393 -0
  58. package/dist/open-UJZRYF4E.js +1450 -0
  59. package/dist/program-CWJNGCTZ.js +165 -0
  60. package/dist/review-OOKXSYMP.js +117 -0
  61. package/dist/rules-4FXJUCXB.js +83 -0
  62. package/dist/security-LME3XFL3.js +177 -0
  63. package/dist/staged-check-X24C3YRD.js +502 -0
  64. package/dist/status-RR57ZNGI.js +81 -0
  65. package/dist/sync-KFJANRXF.js +218 -0
  66. package/dist/watch-CY7QHIYN.js +123 -0
  67. package/dist/web/assets/{Editor-DAXvniWB.js → Editor-Bnilgch0.js} +1 -1
  68. package/dist/web/assets/{abnfDiagram-O67JEVCF-Az7EW1BC.js → abnfDiagram-O67JEVCF-DW9LWr8f.js} +1 -1
  69. package/dist/web/assets/architecture-7GRP2DOG-BWDZV2yn.js +1 -0
  70. package/dist/web/assets/{architectureDiagram-NJMV4G6O-BxeXqSTn.js → architectureDiagram-NJMV4G6O-0eKeQRhj.js} +1 -1
  71. package/dist/web/assets/{blockDiagram-BEXU5L5S-aeKZl9lJ.js → blockDiagram-BEXU5L5S-D5i3-ogI.js} +1 -1
  72. package/dist/web/assets/{c4Diagram-YGBWAQC7-CgmntYsG.js → c4Diagram-YGBWAQC7-BAWQZrEu.js} +1 -1
  73. package/dist/web/assets/channel-Bkh05NKd.js +1 -0
  74. package/dist/web/assets/{chunk-3FUC2YCW-BpmRD2yo.js → chunk-3FUC2YCW-fKRGOZF3.js} +1 -1
  75. package/dist/web/assets/{chunk-5DYCD2WN-CRIFDwod.js → chunk-5DYCD2WN-C5jOlx42.js} +1 -1
  76. package/dist/web/assets/{chunk-742MDFTN-hRW9VeNV.js → chunk-742MDFTN-urY4RP_A.js} +1 -1
  77. package/dist/web/assets/{chunk-7INBJB4K-C6VEpT0Y.js → chunk-7INBJB4K-nk48sKqZ.js} +1 -1
  78. package/dist/web/assets/{chunk-7M6MHVWA-Dnajov2w.js → chunk-7M6MHVWA-BfBD9X0F.js} +1 -1
  79. package/dist/web/assets/{chunk-7PRAP22T-DSAc0_CZ.js → chunk-7PRAP22T-jYf59QlI.js} +1 -1
  80. package/dist/web/assets/{chunk-GTNCS2PH-CefpYUjb.js → chunk-GTNCS2PH-BOCxryKf.js} +1 -1
  81. package/dist/web/assets/{chunk-GWA4HPMP-MLNflMmF.js → chunk-GWA4HPMP-CFpaW0ru.js} +1 -1
  82. package/dist/web/assets/{chunk-MBY4JIJT-D3GmmEiU.js → chunk-MBY4JIJT-BIB8HctN.js} +1 -1
  83. package/dist/web/assets/{chunk-NETBCI7D-DzTj6xVk.js → chunk-NETBCI7D-CpUUK6rh.js} +1 -1
  84. package/dist/web/assets/{chunk-O7XYJQB3-D1d0hCko.js → chunk-O7XYJQB3-BNx_V4Oi.js} +1 -1
  85. package/dist/web/assets/{chunk-UA2S7LBM-C9hIFeWR.js → chunk-UA2S7LBM-D3tMiIOY.js} +1 -1
  86. package/dist/web/assets/{chunk-WEXAMYUT-BFGtcjsi.js → chunk-WEXAMYUT-CC-3C32l.js} +1 -1
  87. package/dist/web/assets/{chunk-Z7XXMR3K-BRZ8_wtE.js → chunk-Z7XXMR3K-BzhxKQUc.js} +1 -1
  88. package/dist/web/assets/{chunk-ZIGJFQKS-CTKOioal.js → chunk-ZIGJFQKS-D4tBHzxg.js} +1 -1
  89. package/dist/web/assets/{classDiagram-v2-NBCMYWYE-DJGKtWJv.js → classDiagram-v2-NBCMYWYE-BbtyanXR.js} +1 -1
  90. package/dist/web/assets/{cynefin-OW5HDTMX-hNq1kGmA.js → cynefin-OW5HDTMX-BIArb8W1.js} +1 -1
  91. package/dist/web/assets/{cynefinDiagram-VND7K2PF-sODQM_ZZ.js → cynefinDiagram-VND7K2PF-BrAcfkXL.js} +1 -1
  92. package/dist/web/assets/{dagre-6A5THRUB-DEzTThIf.js → dagre-6A5THRUB-BcHXRl4s.js} +1 -1
  93. package/dist/web/assets/{diagram-22UHCM2B-s_eellsa.js → diagram-22UHCM2B-Dx9tSCdE.js} +1 -1
  94. package/dist/web/assets/{diagram-3UASUU5V-B2onY_Xw.js → diagram-3UASUU5V-CLLDP9yH.js} +1 -1
  95. package/dist/web/assets/{diagram-ATOU4E4O-B8V6s13s.js → diagram-ATOU4E4O-B46H9iPt.js} +1 -1
  96. package/dist/web/assets/{diagram-CDSNMT55-CJxiE6mf.js → diagram-CDSNMT55-CYtvB6aG.js} +1 -1
  97. package/dist/web/assets/{diagram-MLGK6HIB-m5OHPLAp.js → diagram-MLGK6HIB-Dar7ktoX.js} +1 -1
  98. package/dist/web/assets/{diagram-MPIPVDR6-Buvvrk_A.js → diagram-MPIPVDR6-C-DljnYN.js} +1 -1
  99. package/dist/web/assets/{ebnfDiagram-ZINNZB2B-CpL7Wt3R.js → ebnfDiagram-ZINNZB2B-Dv_3jWnh.js} +1 -1
  100. package/dist/web/assets/{elk-276RUBZZ-CwYhbsXa.js → elk-276RUBZZ-DnHciE9k.js} +1 -1
  101. package/dist/web/assets/{erDiagram-OPXOYQCR-DNikci2r.js → erDiagram-OPXOYQCR-Bn46KrHz.js} +1 -1
  102. package/dist/web/assets/eventmodeling-NTZA5JFV-DYwoptMd.js +1 -0
  103. package/dist/web/assets/flowDiagram-KWPJA3E3-y4rS0gIB.js +1 -0
  104. package/dist/web/assets/{ganttDiagram-FUAMR5RP-d-ynDlvJ.js → ganttDiagram-FUAMR5RP-DOjjJtqz.js} +1 -1
  105. package/dist/web/assets/{gitGraph-4MIJSDKK-DwIeBI91.js → gitGraph-4MIJSDKK-BIoMXJRQ.js} +1 -1
  106. package/dist/web/assets/{gitGraphDiagram-X574FWY7-04RZccu6.js → gitGraphDiagram-X574FWY7-BsJ01R0v.js} +1 -1
  107. package/dist/web/assets/{index-C3-8uYXr.js → index-Cen0jOXj.js} +5 -4
  108. package/dist/web/assets/{index-DKE3ojTe.css → index-WwRY6n68.css} +1 -1
  109. package/dist/web/assets/{info-A6RAGUB7-xf4Nzgv-.js → info-A6RAGUB7-BmlXzRWj.js} +1 -1
  110. package/dist/web/assets/{infoDiagram-VRGFBTTK-BYjw0eiK.js → infoDiagram-VRGFBTTK-CYPJPmBP.js} +1 -1
  111. package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-Bx9s4nPM.js → ishikawaDiagram-OU5B5YK6-YN96geky.js} +1 -1
  112. package/dist/web/assets/{journeyDiagram-ZHPQQLJL-DrdHs7B-.js → journeyDiagram-ZHPQQLJL-20juojKY.js} +1 -1
  113. package/dist/web/assets/{kanban-definition-PNTS6WVX-CVd8v2Te.js → kanban-definition-PNTS6WVX-BhCKIEbP.js} +1 -1
  114. package/dist/web/assets/{line-V_WrJ-6y.js → line-BERsMFG6.js} +1 -1
  115. package/dist/web/assets/{mermaid-parser.core-B-ilyEpO.js → mermaid-parser.core-Bis_wTB-.js} +3 -3
  116. package/dist/web/assets/{mermaid.core-Bnyaa0nF.js → mermaid.core-DMsM3ZX7.js} +4 -4
  117. package/dist/web/assets/{mindmap-definition-NLK3R4M7-B3AbvHPR.js → mindmap-definition-NLK3R4M7-BkcGdOfv.js} +1 -1
  118. package/dist/web/assets/{packet-AYTQ26CC-CBAIUXIc.js → packet-AYTQ26CC-Bh30-JJX.js} +1 -1
  119. package/dist/web/assets/{pegDiagram-GJSIUBJH-DIdeELUM.js → pegDiagram-GJSIUBJH-DxnIo9RF.js} +1 -1
  120. package/dist/web/assets/{pie-WAS4IAKB-a1r3tzPR.js → pie-WAS4IAKB-ChaTr-GD.js} +1 -1
  121. package/dist/web/assets/{pieDiagram-5QR66LMP-pQBEio0x.js → pieDiagram-5QR66LMP-CzAp78FM.js} +1 -1
  122. package/dist/web/assets/{quadrantDiagram-O4NWA36T-CkJK5Y03.js → quadrantDiagram-O4NWA36T-BDSE-cfY.js} +1 -1
  123. package/dist/web/assets/{radar-RG4KPBEZ-BZfLYjx5.js → radar-RG4KPBEZ-DgJ-o_dq.js} +1 -1
  124. package/dist/web/assets/{railroad-74A4TZTK-CuIW1ZAU.js → railroad-74A4TZTK-C8MmcBKz.js} +1 -1
  125. package/dist/web/assets/railroad-abnf-HS5TGJTU-CTLXjcdP.js +1 -0
  126. package/dist/web/assets/railroad-ebnf-LZEXJU2U-ggWsTTL0.js +1 -0
  127. package/dist/web/assets/railroad-peg-WCYAUIDC-D1mCX705.js +1 -0
  128. package/dist/web/assets/{railroadDiagram-XR7U4H2S-_BpGE8aJ.js → railroadDiagram-XR7U4H2S-DfbcWRNh.js} +1 -1
  129. package/dist/web/assets/{requirementDiagram-PLB6GJNP-CnzF90GS.js → requirementDiagram-PLB6GJNP-DM3nksxs.js} +1 -1
  130. package/dist/web/assets/{sankeyDiagram-IPEJSGJF-DYeisDj_.js → sankeyDiagram-IPEJSGJF-DthUpiiC.js} +1 -1
  131. package/dist/web/assets/{sequenceDiagram-PO4LG4MO-ll9cBvca.js → sequenceDiagram-PO4LG4MO-DLA3nuvA.js} +1 -1
  132. package/dist/web/assets/{stateDiagram-v2-GCMORJYK-CdKcOIKQ.js → stateDiagram-v2-GCMORJYK-B0X6ihBW.js} +1 -1
  133. package/dist/web/assets/{swimlanes-2SLR337P-y6piTWEW.js → swimlanes-2SLR337P-B4qWe6vE.js} +1 -1
  134. package/dist/web/assets/swimlanesDiagram-TC7HE7FX-ClIk3e7S.js +8 -0
  135. package/dist/web/assets/{timeline-definition-EJHVYXUP-DuC_4xK0.js → timeline-definition-EJHVYXUP-F-cDzYrj.js} +1 -1
  136. package/dist/web/assets/{treeView-Q6P3EWNA-gCQFmND0.js → treeView-Q6P3EWNA-BaI30nLZ.js} +1 -1
  137. package/dist/web/assets/{treemap-WGGIJYW6-BQ2010OY.js → treemap-WGGIJYW6-D2jGGoMl.js} +1 -1
  138. package/dist/web/assets/{usecaseDiagram-POWQR4AR-CCTqzHz6.js → usecaseDiagram-POWQR4AR-B9qoAQCy.js} +1 -1
  139. package/dist/web/assets/{vennDiagram-UO4OBE2U-0_O7guNq.js → vennDiagram-UO4OBE2U-DGVNOaKO.js} +1 -1
  140. package/dist/web/assets/{wardley-WFR3VGLG-SsX7uMDs.js → wardley-WFR3VGLG-DbKqhNAh.js} +1 -1
  141. package/dist/web/assets/{wardleyDiagram-VNRHLVJA-DlqH6Hox.js → wardleyDiagram-VNRHLVJA-BgZ1gprw.js} +1 -1
  142. package/dist/web/assets/{xychartDiagram-PMCCYNJV-nr4UYDtd.js → xychartDiagram-PMCCYNJV-D73RYid1.js} +1 -1
  143. package/dist/web/index.html +2 -2
  144. package/package.json +4 -2
  145. package/dist/web/assets/architecture-7GRP2DOG-blXBarB2.js +0 -1
  146. package/dist/web/assets/channel-f4QzewnN.js +0 -1
  147. package/dist/web/assets/eventmodeling-NTZA5JFV-CA_ZFPZ2.js +0 -1
  148. package/dist/web/assets/flowDiagram-KWPJA3E3-DObsBuWT.js +0 -1
  149. package/dist/web/assets/railroad-abnf-HS5TGJTU-6B4Ffbxn.js +0 -1
  150. package/dist/web/assets/railroad-ebnf-LZEXJU2U-BGL9lIQc.js +0 -1
  151. package/dist/web/assets/railroad-peg-WCYAUIDC-DcqxTem7.js +0 -1
  152. package/dist/web/assets/swimlanesDiagram-TC7HE7FX-DFS8a3jO.js +0 -8
@@ -0,0 +1,919 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ATHENA_MARK,
4
+ hookArgs,
5
+ hookCommand,
6
+ hookScript,
7
+ isAthenaEntry,
8
+ mergeMcpServers,
9
+ planJsonChange,
10
+ pruneEmpty,
11
+ readJsonFile,
12
+ removeJsonHooks,
13
+ stripMcpServers
14
+ } from "./chunk-GHFRQAYT.js";
15
+ import {
16
+ CLAUDE_HOOK_EVENTS,
17
+ CODEX_HOOK_EVENTS,
18
+ COPILOT_HOOK_EVENTS,
19
+ CURSOR_HOOK_EVENTS,
20
+ GEMINI_HOOK_EVENTS
21
+ } from "./chunk-INT3IGMQ.js";
22
+ import {
23
+ INTEGRATION_END,
24
+ INTEGRATION_START,
25
+ removeIntegrationBlock,
26
+ upsertIntegrationBlock
27
+ } from "./chunk-673EPYYH.js";
28
+ import {
29
+ KNOWLEDGE_DOCS,
30
+ RELEVANCE_MAP
31
+ } from "./chunk-FQEGEVLY.js";
32
+ import {
33
+ exists,
34
+ readTextIfExists,
35
+ writeFileAtomic
36
+ } from "./chunk-IQQBAWSM.js";
37
+ import {
38
+ resolveInside
39
+ } from "./chunk-XW3MLHI2.js";
40
+
41
+ // src/agents/registry.ts
42
+ import { promises as fs7 } from "fs";
43
+ import path10 from "path";
44
+
45
+ // src/agents/claude-code/adapter.ts
46
+ import path2 from "path";
47
+
48
+ // src/agents/common/instructions.ts
49
+ function workflowSteps() {
50
+ return [
51
+ "1. Read `.athena/rules.md` and follow every enabled rule (lines marked `[disabled]` do not apply).",
52
+ "2. Recall project memory for the task and files: call the Athena `recall` MCP tool, or skim `.athena/memory/` if MCP is unavailable.",
53
+ "3. Classify the task and read only the relevant Athena documents (see the relevance map below).",
54
+ "4. Identify the affected modules and architecture boundaries.",
55
+ "5. Check security implications (`.athena/security.md`, `.athena/auth.md`) when touching input handling, auth, data access or secrets.",
56
+ "6. Check database and API implications when the change touches schema, queries or endpoints.",
57
+ "7. For non-trivial work, state a short implementation plan before editing.",
58
+ "8. Implement only the required changes.",
59
+ "9. Run the relevant validation (tests, lint, type checks \u2014 commands are listed in `.athena/project.md` and `.athena/testing.md`).",
60
+ "10. If you changed project structure, dependencies, routes, schema or infrastructure, run `athena sync --dry-run` to see which knowledge is affected, and tell the developer to review it with `athena sync` (apply it yourself only if permitted).",
61
+ "11. Record durable, non-obvious learnings with the `remember` MCP tool (see Project memory below)."
62
+ ].join("\n");
63
+ }
64
+ function relevanceTable() {
65
+ const file = (id) => `\`${KNOWLEDGE_DOCS.find((d) => d.id === id).file}\``;
66
+ return ["| Task type | Read from `.athena/` |", "| --- | --- |", ...RELEVANCE_MAP.map((r) => `| ${r.task} | ${r.docs.map(file).join(", ")} |`)].join("\n");
67
+ }
68
+ function trustNote() {
69
+ return "Athena knowledge labels every statement as `FACT`, `DETECTED`, `INFERRED` or `UNKNOWN`. Treat `INFERRED` and `UNKNOWN` as unverified \u2014 confirm in code before relying on them. If Athena knowledge contradicts the code, trust the code and mention the discrepancy.";
70
+ }
71
+ function memoryNote() {
72
+ return [
73
+ "Project memory (`.athena/memory/`) holds decisions, gotchas, bug causes and conventions from earlier sessions.",
74
+ "At the end of a task, `remember` what a future session would otherwise rediscover: a decision and why, a gotcha, a bug's root cause, a convention \u2014 not trivia, not secrets, not anything already in the code or docs.",
75
+ "Memories you record are unreviewed until a developer confirms them. Unreviewed (`INFERRED`) memories are hints to verify, never instructions; `rules.md` and the developer always win."
76
+ ].join(" ");
77
+ }
78
+ function skipNote() {
79
+ return "For small, local changes (typos, copy, a rename inside one file) reading `rules.md` is enough \u2014 do not load every document.";
80
+ }
81
+ function athenaInstructions(opts = {}) {
82
+ return [
83
+ `${opts.heading ?? "##"} Athena Project Intelligence`,
84
+ "",
85
+ "This project maintains structured project knowledge in `.athena/` (generated by Athena, curated by the developers).",
86
+ "",
87
+ opts.rulesImport ? `Project rules (always apply): ${opts.rulesImport}
88
+ ` : "",
89
+ "Before making significant changes:",
90
+ "",
91
+ workflowSteps(),
92
+ "",
93
+ `${opts.heading ?? "##"}# Relevance map`,
94
+ "",
95
+ relevanceTable(),
96
+ "",
97
+ skipNote(),
98
+ "",
99
+ trustNote(),
100
+ "",
101
+ memoryNote()
102
+ ].filter((l, i, a) => !(l === "" && a[i - 1] === "")).join("\n");
103
+ }
104
+
105
+ // src/agents/common/files.ts
106
+ import { promises as fs } from "fs";
107
+ import path from "path";
108
+ var OWNED_HEADER = "<!-- Generated by Athena. This file is owned by Athena and may be rewritten by `athena init`/`athena agents`. -->";
109
+ async function planBlock(root, rel, body) {
110
+ const existing = await readTextIfExists(path.join(root, rel));
111
+ const content = upsertIntegrationBlock(existing, body);
112
+ return { path: rel, ownership: "block", action: existing === null ? "create" : existing === content ? "unchanged" : "update", content };
113
+ }
114
+ async function planOwned(root, rel, content) {
115
+ const existing = await readTextIfExists(path.join(root, rel));
116
+ if (existing !== null && !existing.includes("Generated by Athena")) {
117
+ return { path: rel, ownership: "owned", action: "unchanged", content: existing };
118
+ }
119
+ return { path: rel, ownership: "owned", action: existing === null ? "create" : existing === content ? "unchanged" : "update", content };
120
+ }
121
+ async function removeBlockFrom(root, rel) {
122
+ const file = path.join(root, rel);
123
+ const existing = await readTextIfExists(file);
124
+ if (existing === null || !existing.includes(INTEGRATION_START)) return false;
125
+ const next = removeIntegrationBlock(existing);
126
+ if (next.trim() === "") await fs.rm(file);
127
+ else await fs.writeFile(file, next, "utf8");
128
+ return true;
129
+ }
130
+ async function removeOwned(root, rel) {
131
+ const file = path.join(root, rel);
132
+ const existing = await readTextIfExists(file);
133
+ if (existing === null || !existing.includes("Generated by Athena")) return false;
134
+ await fs.rm(file);
135
+ return true;
136
+ }
137
+ async function checkBlock(root, rel, label) {
138
+ const text = await readTextIfExists(path.join(root, rel));
139
+ if (text === null) return { ok: false, level: "warn", message: `${label}: ${rel} not found (run \`athena init\` or \`athena analyze\`)` };
140
+ const s = text.indexOf(INTEGRATION_START);
141
+ const e = text.indexOf(INTEGRATION_END);
142
+ if (s === -1 && e === -1) return { ok: false, level: "warn", message: `${label}: ${rel} has no Athena block` };
143
+ if (s === -1 || e < s) return { ok: false, level: "error", message: `${label}: ${rel} has a malformed Athena block (unmatched markers)` };
144
+ return { ok: true, level: "ok", message: `${label}: ${rel}` };
145
+ }
146
+ async function userEvidence(root, spec) {
147
+ const found = [];
148
+ const owned = /* @__PURE__ */ new Set();
149
+ for (const rel of spec.athenaOwned ?? []) {
150
+ const text = await readTextIfExists(path.join(root, rel)).catch(() => null);
151
+ if (text !== null && /Generated by Athena|managed by Athena/.test(text)) owned.add(rel);
152
+ }
153
+ for (const rel of spec.athenaOwnedJson ?? []) {
154
+ if (await jsonOnlyAthena(path.join(root, rel))) owned.add(rel);
155
+ }
156
+ for (const rel of spec.files ?? []) {
157
+ const text = await readTextIfExists(path.join(root, rel)).catch(() => null);
158
+ if (text === null) continue;
159
+ if (text.includes(OWNED_HEADER)) continue;
160
+ if (removeIntegrationBlock(text).trim()) found.push(rel);
161
+ }
162
+ for (const rel of spec.dirs ?? []) {
163
+ if (await hasNonAthenaEntries(path.join(root, rel), root, owned)) found.push(rel);
164
+ }
165
+ return found;
166
+ }
167
+ async function hasNonAthenaEntries(dir, root, owned) {
168
+ let entries;
169
+ try {
170
+ entries = await fs.readdir(dir, { withFileTypes: true });
171
+ } catch {
172
+ return false;
173
+ }
174
+ for (const e of entries) {
175
+ const abs = path.join(dir, e.name);
176
+ const rel = path.relative(root, abs).split(path.sep).join("/");
177
+ if (owned.has(rel)) continue;
178
+ if (e.isDirectory()) {
179
+ if (await hasNonAthenaEntries(abs, root, owned)) return true;
180
+ } else return true;
181
+ }
182
+ return false;
183
+ }
184
+ async function jsonOnlyAthena(file) {
185
+ const text = await readTextIfExists(file).catch(() => null);
186
+ if (text === null) return false;
187
+ let data;
188
+ try {
189
+ data = JSON.parse(text);
190
+ } catch {
191
+ return false;
192
+ }
193
+ const record = data;
194
+ let hadAthenaMcp = false;
195
+ for (const key of ["mcpServers", "servers"]) {
196
+ const servers = record?.[key];
197
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) continue;
198
+ if (!("athena" in servers)) continue;
199
+ hadAthenaMcp = true;
200
+ delete servers.athena;
201
+ if (Object.keys(servers).length === 0) delete record[key];
202
+ }
203
+ if (!text.includes(ATHENA_MARK) && !hadAthenaMcp) return false;
204
+ const strip = (value) => {
205
+ if (Array.isArray(value)) {
206
+ const kept = value.filter((v) => !JSON.stringify(v ?? "").includes(ATHENA_MARK)).map(strip);
207
+ return kept.length ? kept : void 0;
208
+ }
209
+ if (value && typeof value === "object") {
210
+ const out = {};
211
+ for (const [k, v] of Object.entries(value)) {
212
+ if (JSON.stringify(v ?? "").includes(ATHENA_MARK)) {
213
+ const cleaned = strip(v);
214
+ if (cleaned !== void 0) out[k] = cleaned;
215
+ continue;
216
+ }
217
+ if (k === "version") continue;
218
+ out[k] = v;
219
+ }
220
+ return Object.keys(out).length ? out : void 0;
221
+ }
222
+ return value;
223
+ };
224
+ return strip(data) === void 0;
225
+ }
226
+
227
+ // src/agents/claude-code/adapter.ts
228
+ var SETTINGS = ".claude/settings.json";
229
+ var MCP_FILE = ".mcp.json";
230
+ var claudeCodeAdapter = {
231
+ id: "claude-code",
232
+ displayName: "Claude Code",
233
+ capabilities: { instructionsFile: true, scopedRules: false, hooks: true, mcp: true },
234
+ supportNote: "Uses CLAUDE.md with an @-import of .athena/rules.md, hooks in .claude/settings.json that report activity, and an MCP server entry in .mcp.json.",
235
+ async detectPresence(root) {
236
+ const evidence = await userEvidence(root, { files: ["CLAUDE.md", "CLAUDE.local.md"], dirs: [".claude"], athenaOwnedJson: [SETTINGS, MCP_FILE] });
237
+ return { detectedInProject: evidence.length > 0, evidence };
238
+ },
239
+ async plan(ctx) {
240
+ const changes = [await planBlock(ctx.root, "CLAUDE.md", athenaInstructions({ heading: "##", rulesImport: "@.athena/rules.md" }))];
241
+ changes.push(
242
+ await planJsonChange(ctx.root, SETTINGS, (data) => {
243
+ const hooks = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks) ? data.hooks : {};
244
+ for (const event of CLAUDE_HOOK_EVENTS) {
245
+ const list = (Array.isArray(hooks[event]) ? hooks[event] : []).filter((e) => !isAthenaEntry(e));
246
+ list.push({
247
+ ...event === "PreToolUse" ? { matcher: "*" } : {},
248
+ hooks: [{ type: "command", command: hookCommand(), args: hookArgs("claude-code", event), async: true, timeout: 10 }]
249
+ });
250
+ hooks[event] = list;
251
+ }
252
+ data.hooks = hooks;
253
+ })
254
+ );
255
+ changes.push(await planJsonChange(ctx.root, MCP_FILE, mergeMcpServers));
256
+ return changes;
257
+ },
258
+ async remove(root) {
259
+ const touched = [];
260
+ if (await removeBlockFrom(root, "CLAUDE.md")) touched.push("CLAUDE.md");
261
+ if (await removeJsonHooks(root, MCP_FILE, stripMcpServers, (d) => Object.keys(d).length === 0)) touched.push(MCP_FILE);
262
+ const removed = await removeJsonHooks(
263
+ root,
264
+ SETTINGS,
265
+ (data) => {
266
+ const hooks = data.hooks;
267
+ if (!hooks) return;
268
+ for (const event of Object.keys(hooks)) {
269
+ hooks[event] = (hooks[event] ?? []).filter((e) => !isAthenaEntry(e));
270
+ if (!hooks[event].length) delete hooks[event];
271
+ }
272
+ pruneEmpty(data, "hooks");
273
+ },
274
+ (data) => Object.keys(data).length === 0
275
+ );
276
+ if (removed) touched.push(SETTINGS);
277
+ return touched;
278
+ },
279
+ async check(root) {
280
+ const checks = [await checkBlock(root, "CLAUDE.md", "Claude Code")];
281
+ const text = await readTextIfExists(path2.join(root, SETTINGS));
282
+ if (text === null) checks.push({ ok: false, level: "warn", message: `Claude Code: ${SETTINGS} not found \u2014 activity reporting is off` });
283
+ else {
284
+ const configured = CLAUDE_HOOK_EVENTS.filter((e) => new RegExp(`"${e}"`).test(text) && text.includes("--athena-hook"));
285
+ checks.push(
286
+ configured.length === CLAUDE_HOOK_EVENTS.length ? { ok: true, level: "ok", message: `Claude Code: activity hooks installed (${configured.length} events)` } : { ok: false, level: "warn", message: `Claude Code: only ${configured.length}/${CLAUDE_HOOK_EVENTS.length} activity hooks installed \u2014 re-run \`athena agents add claude-code\`` }
287
+ );
288
+ }
289
+ return checks;
290
+ }
291
+ };
292
+
293
+ // src/agents/cursor/adapter.ts
294
+ import { promises as fs2 } from "fs";
295
+ import path3 from "path";
296
+ var RULE_FILE = ".cursor/rules/athena.mdc";
297
+ var HOOKS_FILE = ".cursor/hooks.json";
298
+ var HOOK_DIR = ".cursor/hooks";
299
+ var MCP_FILE2 = ".cursor/mcp.json";
300
+ var cursorAdapter = {
301
+ id: "cursor",
302
+ displayName: "Cursor",
303
+ capabilities: { instructionsFile: true, scopedRules: true, hooks: true, mcp: true },
304
+ supportNote: "Uses an Athena-owned project rule at .cursor/rules/athena.mdc, hooks in .cursor/hooks.json that report activity, and an MCP server entry in .cursor/mcp.json.",
305
+ async detectPresence(root) {
306
+ const evidence = await userEvidence(root, { files: [".cursorrules"], dirs: [".cursor"], athenaOwned: [RULE_FILE, `${HOOK_DIR}/athena-hook.sh`, `${HOOK_DIR}/athena-hook.cmd`], athenaOwnedJson: [HOOKS_FILE, MCP_FILE2] });
307
+ return { detectedInProject: evidence.length > 0, evidence };
308
+ },
309
+ async plan(ctx) {
310
+ const content = ["---", "description: Athena project intelligence \u2014 how to use .athena/ knowledge and project rules", "globs:", "alwaysApply: true", "---", OWNED_HEADER, "", athenaInstructions({ heading: "#" }), ""].join("\n");
311
+ const changes = [await planOwned(ctx.root, RULE_FILE, content)];
312
+ const script = hookScript("cursor");
313
+ const scriptRel = `${HOOK_DIR}/${script.file}`;
314
+ changes.push(await planOwned(ctx.root, scriptRel, script.content));
315
+ changes.push(
316
+ await planJsonChange(ctx.root, HOOKS_FILE, (data) => {
317
+ data.version = typeof data.version === "number" ? data.version : 1;
318
+ const hooks = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks) ? data.hooks : {};
319
+ for (const event of CURSOR_HOOK_EVENTS) {
320
+ const list = (Array.isArray(hooks[event]) ? hooks[event] : []).filter((e) => !isAthenaEntry(e));
321
+ list.push({ command: `./${scriptRel} ${event} --athena-hook`, timeout: 10 });
322
+ hooks[event] = list;
323
+ }
324
+ data.hooks = hooks;
325
+ })
326
+ );
327
+ changes.push(await planJsonChange(ctx.root, MCP_FILE2, mergeMcpServers));
328
+ return changes;
329
+ },
330
+ async remove(root) {
331
+ const touched = [];
332
+ if (await removeOwned(root, RULE_FILE)) touched.push(RULE_FILE);
333
+ if (await removeJsonHooks(root, MCP_FILE2, stripMcpServers, (d) => Object.keys(d).length === 0)) touched.push(MCP_FILE2);
334
+ for (const f of ["athena-hook.sh", "athena-hook.cmd"]) {
335
+ if (await removeOwned(root, `${HOOK_DIR}/${f}`)) touched.push(`${HOOK_DIR}/${f}`);
336
+ }
337
+ const removed = await removeJsonHooks(
338
+ root,
339
+ HOOKS_FILE,
340
+ (data) => {
341
+ const hooks = data.hooks;
342
+ if (!hooks) return;
343
+ for (const event of Object.keys(hooks)) {
344
+ hooks[event] = (hooks[event] ?? []).filter((e) => !isAthenaEntry(e));
345
+ if (!hooks[event].length) delete hooks[event];
346
+ }
347
+ pruneEmpty(data, "hooks");
348
+ if (Object.keys(data).length === 1 && "version" in data) delete data.version;
349
+ },
350
+ (data) => Object.keys(data).length === 0
351
+ );
352
+ if (removed) touched.push(HOOKS_FILE);
353
+ await fs2.rm(path3.join(root, HOOK_DIR), { recursive: true, force: true }).catch(() => {
354
+ });
355
+ return touched;
356
+ },
357
+ async check(root) {
358
+ const text = await readTextIfExists(path3.join(root, RULE_FILE));
359
+ const checks = [];
360
+ if (text === null) checks.push({ ok: false, level: "warn", message: `Cursor: ${RULE_FILE} not found` });
361
+ else if (!/^---[\s\S]*?alwaysApply:\s*true[\s\S]*?---/.test(text)) checks.push({ ok: false, level: "warn", message: `Cursor: ${RULE_FILE} frontmatter was modified (alwaysApply is not true)` });
362
+ else checks.push({ ok: true, level: "ok", message: `Cursor: ${RULE_FILE}` });
363
+ const hooks = await readTextIfExists(path3.join(root, HOOKS_FILE));
364
+ if (hooks === null) checks.push({ ok: false, level: "warn", message: `Cursor: ${HOOKS_FILE} not found \u2014 activity reporting is off` });
365
+ else if (!hooks.includes("--athena-hook")) checks.push({ ok: false, level: "warn", message: `Cursor: no Athena hooks in ${HOOKS_FILE}` });
366
+ else checks.push({ ok: true, level: "ok", message: `Cursor: activity hooks installed (${CURSOR_HOOK_EVENTS.length} events)` });
367
+ return checks;
368
+ }
369
+ };
370
+
371
+ // src/agents/antigravity/adapter.ts
372
+ import path4 from "path";
373
+ var RULE_FILE2 = ".agents/rules/athena.md";
374
+ var ANTIGRAVITY_RULE_LIMIT = 12e3;
375
+ var antigravityAdapter = {
376
+ id: "antigravity",
377
+ displayName: "Antigravity",
378
+ capabilities: { instructionsFile: true, scopedRules: true, hooks: false, mcp: true },
379
+ supportNote: 'Uses an Athena-owned workspace rule at .agents/rules/athena.md. Set its activation to "Always On" in Antigravity if it is not applied automatically.',
380
+ async detectPresence(root) {
381
+ const evidence = await userEvidence(root, { dirs: [".agents", ".agent"], athenaOwned: [RULE_FILE2] });
382
+ return { detectedInProject: evidence.length > 0, evidence };
383
+ },
384
+ async plan(ctx) {
385
+ const content = [OWNED_HEADER, "", athenaInstructions({ heading: "#" }), ""].join("\n");
386
+ if (content.length > ANTIGRAVITY_RULE_LIMIT) throw new Error(`Antigravity rule exceeds ${ANTIGRAVITY_RULE_LIMIT} characters`);
387
+ return [await planOwned(ctx.root, RULE_FILE2, content)];
388
+ },
389
+ async remove(root) {
390
+ return await removeOwned(root, RULE_FILE2) ? [RULE_FILE2] : [];
391
+ },
392
+ async check(root) {
393
+ const text = await readTextIfExists(path4.join(root, RULE_FILE2));
394
+ if (text === null) return [{ ok: false, level: "warn", message: `Antigravity: ${RULE_FILE2} not found` }];
395
+ if (text.length > ANTIGRAVITY_RULE_LIMIT) return [{ ok: false, level: "error", message: `Antigravity: ${RULE_FILE2} exceeds the 12,000 character rule limit` }];
396
+ return [
397
+ { ok: true, level: "ok", message: `Antigravity: ${RULE_FILE2}` },
398
+ { ok: true, level: "warn", message: 'Antigravity: activation mode cannot be verified from files \u2014 confirm the rule is "Always On" in Antigravity' }
399
+ ];
400
+ }
401
+ };
402
+
403
+ // src/agents/codex/adapter.ts
404
+ import { promises as fs3 } from "fs";
405
+ import os from "os";
406
+ import path5 from "path";
407
+ import { parse as parseToml } from "smol-toml";
408
+ var INSTRUCTIONS = "AGENTS.md";
409
+ var CONFIG = ".codex/config.toml";
410
+ var HOOKS_FILE2 = ".codex/hooks.json";
411
+ var TOML_START = "# athena:start (managed by Athena; `athena agents remove codex` removes it)";
412
+ var TOML_END = "# athena:end";
413
+ var codexAdapter = {
414
+ id: "codex",
415
+ displayName: "Codex",
416
+ capabilities: { instructionsFile: true, scopedRules: false, hooks: true, mcp: true },
417
+ supportNote: "Uses the Athena block in AGENTS.md, an MCP server entry in .codex/config.toml, and hooks in .codex/hooks.json that report activity. Codex loads .codex/ only for trusted projects.",
418
+ async detectPresence(root) {
419
+ const athenaOwned = await tomlOnlyAthena(path5.join(root, CONFIG)) ? [CONFIG] : [];
420
+ const evidence = await userEvidence(root, { dirs: [".codex"], athenaOwned, athenaOwnedJson: [HOOKS_FILE2] });
421
+ return { detectedInProject: evidence.length > 0, evidence };
422
+ },
423
+ async plan(ctx) {
424
+ const changes = [await planBlock(ctx.root, INSTRUCTIONS, athenaInstructions({ heading: "##" }))];
425
+ changes.push(await planConfig(ctx.root));
426
+ changes.push(
427
+ await planJsonChange(ctx.root, HOOKS_FILE2, (data) => {
428
+ const hooks = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks) ? data.hooks : {};
429
+ for (const event of CODEX_HOOK_EVENTS) {
430
+ const list = (Array.isArray(hooks[event]) ? hooks[event] : []).filter((e) => !isAthenaEntry(e));
431
+ list.push({ hooks: [{ type: "command", command: codexHookCommand(event), timeout: 10 }] });
432
+ hooks[event] = list;
433
+ }
434
+ data.hooks = hooks;
435
+ })
436
+ );
437
+ return changes;
438
+ },
439
+ async remove(root) {
440
+ const touched = [];
441
+ if (await removeBlockFrom(root, INSTRUCTIONS)) touched.push(INSTRUCTIONS);
442
+ if (await removeConfigBlock(root)) touched.push(CONFIG);
443
+ const removed = await removeJsonHooks(
444
+ root,
445
+ HOOKS_FILE2,
446
+ (data) => {
447
+ const hooks = data.hooks;
448
+ if (!hooks) return;
449
+ for (const event of Object.keys(hooks)) {
450
+ hooks[event] = (hooks[event] ?? []).filter((e) => !isAthenaEntry(e));
451
+ if (!hooks[event].length) delete hooks[event];
452
+ }
453
+ pruneEmpty(data, "hooks");
454
+ },
455
+ (data) => Object.keys(data).length === 0
456
+ );
457
+ if (removed) touched.push(HOOKS_FILE2);
458
+ return touched;
459
+ },
460
+ async check(root) {
461
+ const checks = [await checkBlock(root, INSTRUCTIONS, "Codex")];
462
+ const config = await readTextIfExists(path5.join(root, CONFIG));
463
+ checks.push(
464
+ config?.includes(TOML_START) && config.includes("[mcp_servers.athena]") ? { ok: true, level: "ok", message: `Codex: MCP server registered in ${CONFIG}` } : { ok: false, level: "warn", message: `Codex: no Athena MCP entry in ${CONFIG} \u2014 re-run \`athena agents add codex\`` }
465
+ );
466
+ const hooks = await readTextIfExists(path5.join(root, HOOKS_FILE2));
467
+ if (hooks === null) checks.push({ ok: false, level: "warn", message: `Codex: ${HOOKS_FILE2} not found \u2014 activity reporting is off` });
468
+ else {
469
+ const configured = CODEX_HOOK_EVENTS.filter((e) => new RegExp(`"${e}"`).test(hooks) && hooks.includes(ATHENA_MARK));
470
+ checks.push(
471
+ configured.length === CODEX_HOOK_EVENTS.length ? { ok: true, level: "ok", message: `Codex: activity hooks installed (${configured.length} events)` } : { ok: false, level: "warn", message: `Codex: only ${configured.length}/${CODEX_HOOK_EVENTS.length} activity hooks installed \u2014 re-run \`athena agents add codex\`` }
472
+ );
473
+ }
474
+ checks.push(await trustCheck(root));
475
+ return checks;
476
+ }
477
+ };
478
+ function codexHookCommand(event) {
479
+ const cmd = hookCommand();
480
+ const exe = /[\s"'$`\\]/.test(cmd) ? JSON.stringify(cmd) : cmd;
481
+ return `${exe} event ${ATHENA_MARK} --agent codex --hook ${event}`;
482
+ }
483
+ function mcpBlock() {
484
+ return [TOML_START, "[mcp_servers.athena]", `command = ${JSON.stringify(hookCommand())}`, 'args = ["mcp"]', TOML_END].join("\n");
485
+ }
486
+ function stripTomlBlock(text) {
487
+ const lines = text.split("\n");
488
+ const out = [];
489
+ let inside = false;
490
+ for (const line of lines) {
491
+ if (!inside && line.startsWith("# athena:start")) inside = true;
492
+ else if (inside && line.trim() === TOML_END) inside = false;
493
+ else if (!inside) out.push(line);
494
+ }
495
+ return out.join("\n");
496
+ }
497
+ function parseTomlOrThrow(text) {
498
+ try {
499
+ return parseToml(text);
500
+ } catch {
501
+ throw new Error(`${CONFIG} is not valid TOML \u2014 fix it before configuring Codex`);
502
+ }
503
+ }
504
+ async function planConfig(root) {
505
+ const existing = await readTextIfExists(path5.join(root, CONFIG));
506
+ const base = existing === null ? "" : stripTomlBlock(existing);
507
+ const parsed = parseTomlOrThrow(base);
508
+ const servers = parsed.mcp_servers;
509
+ if (servers && "athena" in servers) return { path: CONFIG, ownership: "block", action: "unchanged", content: existing ?? "" };
510
+ const head = base.trimEnd();
511
+ const content = `${head ? `${head}
512
+
513
+ ` : ""}${mcpBlock()}
514
+ `;
515
+ return { path: CONFIG, ownership: "block", action: existing === null ? "create" : existing === content ? "unchanged" : "update", content };
516
+ }
517
+ async function removeConfigBlock(root) {
518
+ const file = path5.join(root, CONFIG);
519
+ const existing = await readTextIfExists(file);
520
+ if (existing === null || !existing.includes(TOML_START)) return false;
521
+ const next = stripTomlBlock(existing).trimEnd();
522
+ if (next.trim() === "") await fs3.rm(file, { force: true });
523
+ else await fs3.writeFile(file, `${next}
524
+ `, "utf8");
525
+ return true;
526
+ }
527
+ async function tomlOnlyAthena(file) {
528
+ const text = await readTextIfExists(file).catch(() => null);
529
+ return text !== null && text.includes(TOML_START) && stripTomlBlock(text).trim() === "";
530
+ }
531
+ async function trustCheck(root) {
532
+ const home = process.env.CODEX_HOME?.trim() || path5.join(os.homedir(), ".codex");
533
+ const hint = `Codex loads .codex/ only for trusted projects: trust this folder when Codex asks, or add [projects."${root}"] trust_level = "trusted" to ${path5.join(home, "config.toml")}`;
534
+ const text = await readTextIfExists(path5.join(home, "config.toml")).catch(() => null);
535
+ if (text === null) return { ok: false, level: "warn", message: `Codex: could not confirm this project is trusted. ${hint}` };
536
+ let projects = {};
537
+ try {
538
+ projects = parseToml(text).projects ?? {};
539
+ } catch {
540
+ return { ok: false, level: "warn", message: `Codex: could not read ${path5.join(home, "config.toml")} to confirm trust. ${hint}` };
541
+ }
542
+ for (let dir = root; ; dir = path5.dirname(dir)) {
543
+ const entry = projects[dir];
544
+ if (entry?.trust_level === "trusted") return { ok: true, level: "ok", message: `Codex: project is trusted${dir === root ? "" : ` (via ${dir})`}` };
545
+ if (entry?.trust_level === "untrusted") return { ok: false, level: "warn", message: `Codex: ${dir} is marked untrusted, so Codex ignores .codex/. ${hint}` };
546
+ if (path5.dirname(dir) === dir) break;
547
+ }
548
+ return { ok: false, level: "warn", message: `Codex: this project is not trusted yet. ${hint}` };
549
+ }
550
+
551
+ // src/agents/copilot/adapter.ts
552
+ import { promises as fs4 } from "fs";
553
+ import path6 from "path";
554
+ var INSTRUCTIONS2 = ".github/copilot-instructions.md";
555
+ var MCP_FILE3 = ".vscode/mcp.json";
556
+ var HOOKS_DIR = ".github/hooks";
557
+ var HOOKS_FILE3 = `${HOOKS_DIR}/athena.json`;
558
+ var copilotAdapter = {
559
+ id: "copilot",
560
+ displayName: "GitHub Copilot",
561
+ capabilities: { instructionsFile: true, scopedRules: false, hooks: true, mcp: true },
562
+ supportNote: "Uses the Athena block in .github/copilot-instructions.md, an MCP server entry in .vscode/mcp.json (VS Code), and hooks in .github/hooks/athena.json that report activity (VS Code hooks are in Preview; Copilot CLI reads the same file).",
563
+ async detectPresence(root) {
564
+ const evidence = await userEvidence(root, { files: [INSTRUCTIONS2], dirs: [".github/instructions", HOOKS_DIR], athenaOwnedJson: [HOOKS_FILE3] });
565
+ if (await exists(path6.join(root, MCP_FILE3)) && !await jsonOnlyAthena(path6.join(root, MCP_FILE3))) evidence.push(MCP_FILE3);
566
+ return { detectedInProject: evidence.length > 0, evidence };
567
+ },
568
+ async plan(ctx) {
569
+ const changes = [await planBlock(ctx.root, INSTRUCTIONS2, athenaInstructions({ heading: "##" }))];
570
+ changes.push(
571
+ await planJsonChange(ctx.root, MCP_FILE3, (data) => {
572
+ const servers = data.servers && typeof data.servers === "object" && !Array.isArray(data.servers) ? data.servers : {};
573
+ servers.athena = { type: "stdio", command: hookCommand(), args: ["mcp"] };
574
+ data.servers = servers;
575
+ })
576
+ );
577
+ changes.push(
578
+ await planJsonChange(ctx.root, HOOKS_FILE3, (data) => {
579
+ data.version = typeof data.version === "number" ? data.version : 1;
580
+ const hooks = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks) ? data.hooks : {};
581
+ for (const event of COPILOT_HOOK_EVENTS) {
582
+ const list = (Array.isArray(hooks[event]) ? hooks[event] : []).filter((e) => !isAthenaEntry(e));
583
+ list.push({ type: "command", bash: posixCommand(event), powershell: powershellCommand(event), timeoutSec: 10 });
584
+ hooks[event] = list;
585
+ }
586
+ data.hooks = hooks;
587
+ })
588
+ );
589
+ return changes;
590
+ },
591
+ async remove(root) {
592
+ const touched = [];
593
+ if (await removeBlockFrom(root, INSTRUCTIONS2)) touched.push(INSTRUCTIONS2);
594
+ const mcpRemoved = await removeJsonHooks(
595
+ root,
596
+ MCP_FILE3,
597
+ (data) => {
598
+ const servers = data.servers;
599
+ if (!servers || typeof servers !== "object") return;
600
+ delete servers.athena;
601
+ pruneEmpty(data, "servers");
602
+ },
603
+ (d) => Object.keys(d).length === 0
604
+ );
605
+ if (mcpRemoved) touched.push(MCP_FILE3);
606
+ const hooksRemoved = await removeJsonHooks(
607
+ root,
608
+ HOOKS_FILE3,
609
+ (data) => {
610
+ const hooks = data.hooks;
611
+ if (!hooks || typeof hooks !== "object") return;
612
+ for (const event of Object.keys(hooks)) {
613
+ if (!Array.isArray(hooks[event])) continue;
614
+ hooks[event] = hooks[event].filter((e) => !isAthenaEntry(e));
615
+ if (!hooks[event].length) delete hooks[event];
616
+ }
617
+ pruneEmpty(data, "hooks");
618
+ if (Object.keys(data).length === 1 && "version" in data) delete data.version;
619
+ },
620
+ (d) => Object.keys(d).length === 0
621
+ );
622
+ if (hooksRemoved) touched.push(HOOKS_FILE3);
623
+ for (const dir of [HOOKS_DIR, ".vscode"]) await fs4.rmdir(path6.join(root, dir)).catch(() => {
624
+ });
625
+ return touched;
626
+ },
627
+ async check(root) {
628
+ const checks = [await checkBlock(root, INSTRUCTIONS2, "GitHub Copilot")];
629
+ const mcp = await readJsonFile(path6.join(root, MCP_FILE3)).catch(() => null);
630
+ const servers = mcp?.data.servers;
631
+ if (mcp?.malformed) checks.push({ ok: false, level: "error", message: `GitHub Copilot: ${MCP_FILE3} is not valid JSON` });
632
+ else
633
+ checks.push(
634
+ servers && typeof servers === "object" && "athena" in servers ? { ok: true, level: "ok", message: `GitHub Copilot: MCP server registered in ${MCP_FILE3} (VS Code)` } : { ok: false, level: "warn", message: `GitHub Copilot: no Athena MCP entry in ${MCP_FILE3} \u2014 re-run \`athena agents add copilot\`` }
635
+ );
636
+ const hooks = await readTextIfExists(path6.join(root, HOOKS_FILE3));
637
+ if (hooks === null) checks.push({ ok: false, level: "warn", message: `GitHub Copilot: ${HOOKS_FILE3} not found \u2014 activity reporting is off` });
638
+ else {
639
+ const configured = COPILOT_HOOK_EVENTS.filter((e) => new RegExp(`"${e}"`).test(hooks) && hooks.includes(ATHENA_MARK));
640
+ checks.push(
641
+ configured.length === COPILOT_HOOK_EVENTS.length ? { ok: true, level: "ok", message: `GitHub Copilot: activity hooks installed (${configured.length} events)` } : { ok: false, level: "warn", message: `GitHub Copilot: only ${configured.length}/${COPILOT_HOOK_EVENTS.length} activity hooks installed \u2014 re-run \`athena agents add copilot\`` }
642
+ );
643
+ }
644
+ return checks;
645
+ }
646
+ };
647
+ function posixCommand(event) {
648
+ const cmd = hookCommand();
649
+ const exe = /[\s"'$`\\]/.test(cmd) ? `'${cmd.replace(/'/g, `'\\''`)}'` : cmd;
650
+ return `${exe} event ${ATHENA_MARK} --agent copilot --hook ${event} 2>/dev/null || true`;
651
+ }
652
+ function powershellCommand(event) {
653
+ const exe = `'${hookCommand().replace(/'/g, "''")}'`;
654
+ return `& ${exe} event ${ATHENA_MARK} --agent copilot --hook ${event} 2>$null; exit 0`;
655
+ }
656
+
657
+ // src/agents/gemini-cli/adapter.ts
658
+ import { promises as fs5 } from "fs";
659
+ import path7 from "path";
660
+ var CONTEXT = "GEMINI.md";
661
+ var SETTINGS2 = ".gemini/settings.json";
662
+ var geminiCliAdapter = {
663
+ id: "gemini-cli",
664
+ displayName: "Gemini CLI",
665
+ capabilities: { instructionsFile: true, scopedRules: false, hooks: true, mcp: true },
666
+ supportNote: "Uses the Athena block in GEMINI.md, plus an MCP server entry and hooks that report activity in .gemini/settings.json. Gemini CLI ignores .gemini/settings.json in folders it does not trust (when folder trust is enabled).",
667
+ async detectPresence(root) {
668
+ const evidence = await userEvidence(root, { files: [CONTEXT], dirs: [".gemini"], athenaOwnedJson: [SETTINGS2] });
669
+ return { detectedInProject: evidence.length > 0, evidence };
670
+ },
671
+ async plan(ctx) {
672
+ const changes = [await planBlock(ctx.root, CONTEXT, athenaInstructions({ heading: "##" }))];
673
+ changes.push(
674
+ await planJsonChange(ctx.root, SETTINGS2, (data) => {
675
+ mergeMcpServers(data);
676
+ const hooks = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks) ? data.hooks : {};
677
+ for (const event of GEMINI_HOOK_EVENTS) {
678
+ const list = (Array.isArray(hooks[event]) ? hooks[event] : []).filter((e) => !isAthenaEntry(e));
679
+ list.push({
680
+ ...event === "BeforeTool" ? { matcher: "*" } : {},
681
+ hooks: [{ name: "athena-activity", type: "command", command: geminiHookCommand(event), timeout: 1e4 }]
682
+ });
683
+ hooks[event] = list;
684
+ }
685
+ data.hooks = hooks;
686
+ })
687
+ );
688
+ return changes;
689
+ },
690
+ async remove(root) {
691
+ const touched = [];
692
+ if (await removeBlockFrom(root, CONTEXT)) touched.push(CONTEXT);
693
+ const removed = await removeJsonHooks(
694
+ root,
695
+ SETTINGS2,
696
+ (data) => {
697
+ stripMcpServers(data);
698
+ const hooks = data.hooks;
699
+ if (!hooks || typeof hooks !== "object") return;
700
+ for (const event of Object.keys(hooks)) {
701
+ if (!Array.isArray(hooks[event])) continue;
702
+ hooks[event] = hooks[event].filter((e) => !isAthenaEntry(e));
703
+ if (!hooks[event].length) delete hooks[event];
704
+ }
705
+ pruneEmpty(data, "hooks");
706
+ },
707
+ (d) => Object.keys(d).length === 0
708
+ );
709
+ if (removed) touched.push(SETTINGS2);
710
+ await fs5.rmdir(path7.join(root, ".gemini")).catch(() => {
711
+ });
712
+ return touched;
713
+ },
714
+ async check(root) {
715
+ const checks = [await checkBlock(root, CONTEXT, "Gemini CLI")];
716
+ const settings = await readJsonFile(path7.join(root, SETTINGS2)).catch(() => null);
717
+ if (!settings?.existed) {
718
+ checks.push({ ok: false, level: "warn", message: `Gemini CLI: ${SETTINGS2} not found \u2014 no MCP server and activity reporting is off` });
719
+ return checks;
720
+ }
721
+ if (settings.malformed) {
722
+ checks.push({ ok: false, level: "error", message: `Gemini CLI: ${SETTINGS2} is not valid JSON` });
723
+ return checks;
724
+ }
725
+ const servers = settings.data.mcpServers;
726
+ checks.push(
727
+ servers && typeof servers === "object" && "athena" in servers ? { ok: true, level: "ok", message: `Gemini CLI: MCP server registered in ${SETTINGS2}` } : { ok: false, level: "warn", message: `Gemini CLI: no Athena MCP entry in ${SETTINGS2} \u2014 re-run \`athena agents add gemini-cli\`` }
728
+ );
729
+ const text = await readTextIfExists(path7.join(root, SETTINGS2)) ?? "";
730
+ const hooks = settings.data.hooks ?? {};
731
+ const configured = GEMINI_HOOK_EVENTS.filter((e) => isAthenaEntry(hooks[e]));
732
+ checks.push(
733
+ configured.length === GEMINI_HOOK_EVENTS.length && text.includes(ATHENA_MARK) ? { ok: true, level: "ok", message: `Gemini CLI: activity hooks installed (${configured.length} events)` } : { ok: false, level: "warn", message: `Gemini CLI: only ${configured.length}/${GEMINI_HOOK_EVENTS.length} activity hooks installed \u2014 re-run \`athena agents add gemini-cli\`` }
734
+ );
735
+ checks.push({ ok: true, level: "warn", message: "Gemini CLI: if folder trust is enabled, trust this folder \u2014 Gemini CLI ignores .gemini/settings.json (MCP, hooks) in untrusted folders" });
736
+ return checks;
737
+ }
738
+ };
739
+ function geminiHookCommand(event) {
740
+ const cmd = hookCommand();
741
+ const exe = /[\s"'$`\\]/.test(cmd) ? JSON.stringify(cmd) : cmd;
742
+ return `${exe} event ${ATHENA_MARK} --agent gemini-cli --hook ${event}`;
743
+ }
744
+
745
+ // src/agents/windsurf/adapter.ts
746
+ import path8 from "path";
747
+ var RULE_FILE3 = ".windsurf/rules/athena.md";
748
+ var WINDSURF_RULE_LIMIT = 12e3;
749
+ var windsurfAdapter = {
750
+ id: "windsurf",
751
+ displayName: "Windsurf",
752
+ capabilities: { instructionsFile: true, scopedRules: true, hooks: false, mcp: false },
753
+ supportNote: 'Uses an Athena-owned workspace rule at .windsurf/rules/athena.md (trigger: always_on), which Windsurf and Devin Desktop read. MCP is configured per user in Windsurf, so Athena does not register it: add `{ "mcpServers": { "athena": { "command": "athena", "args": ["mcp"] } } }` to your mcp_config.json yourself. No activity hooks.',
754
+ async detectPresence(root) {
755
+ const evidence = await userEvidence(root, { files: [".windsurfrules"], dirs: [".windsurf"], athenaOwned: await generatedByAthena(root, [RULE_FILE3]) });
756
+ return { detectedInProject: evidence.length > 0, evidence };
757
+ },
758
+ async plan(ctx) {
759
+ const content = ["---", "trigger: always_on", "---", OWNED_HEADER, "", athenaInstructions({ heading: "#" }), ""].join("\n");
760
+ if (content.length > WINDSURF_RULE_LIMIT) throw new Error(`Windsurf rule exceeds ${WINDSURF_RULE_LIMIT} characters`);
761
+ return [await planOwned(ctx.root, RULE_FILE3, content)];
762
+ },
763
+ async remove(root) {
764
+ return await removeOwned(root, RULE_FILE3) ? [RULE_FILE3] : [];
765
+ },
766
+ async check(root) {
767
+ const text = await readTextIfExists(path8.join(root, RULE_FILE3));
768
+ if (text === null) return [{ ok: false, level: "warn", message: `Windsurf: ${RULE_FILE3} not found` }];
769
+ if (!text.includes("Generated by Athena")) return [{ ok: false, level: "warn", message: `Windsurf: ${RULE_FILE3} is a developer file, so Athena did not write its rule there` }];
770
+ const checks = [];
771
+ if (text.length > WINDSURF_RULE_LIMIT) checks.push({ ok: false, level: "error", message: `Windsurf: ${RULE_FILE3} exceeds the 12,000 character rule limit` });
772
+ else if (!/^---\r?\n[\s\S]*?trigger:\s*always_on[\s\S]*?\r?\n---/.test(text)) checks.push({ ok: false, level: "warn", message: `Windsurf: ${RULE_FILE3} frontmatter was modified (trigger is not always_on)` });
773
+ else checks.push({ ok: true, level: "ok", message: `Windsurf: ${RULE_FILE3}` });
774
+ return checks;
775
+ }
776
+ };
777
+ async function generatedByAthena(root, rels) {
778
+ const out = [];
779
+ for (const rel of rels) {
780
+ const text = await readTextIfExists(path8.join(root, rel)).catch(() => null);
781
+ if (text?.includes("Generated by Athena")) out.push(rel);
782
+ }
783
+ return out;
784
+ }
785
+
786
+ // src/agents/cline/adapter.ts
787
+ import { promises as fs6 } from "fs";
788
+ import path9 from "path";
789
+ var RULES_DIR = ".clinerules";
790
+ var RULE_FILE4 = ".clinerules/athena.md";
791
+ var ALT_RULE_FILE = ".cline/rules/athena.md";
792
+ var clineAdapter = {
793
+ id: "cline",
794
+ displayName: "Cline",
795
+ capabilities: { instructionsFile: true, scopedRules: true, hooks: false, mcp: false },
796
+ supportNote: 'Uses an Athena-owned workspace rule at .clinerules/athena.md (or .cline/rules/athena.md when .clinerules is a single file). MCP servers are configured per user in Cline, so Athena does not register it: add an "athena" server with command `athena` and args ["mcp"] in Cline\'s MCP settings yourself. No activity hooks.',
797
+ async detectPresence(root) {
798
+ const evidence = await userEvidence(root, { files: [RULES_DIR], dirs: [RULES_DIR, ".cline"], athenaOwned: await generatedByAthena2(root, [RULE_FILE4, ALT_RULE_FILE]) });
799
+ return { detectedInProject: evidence.length > 0, evidence };
800
+ },
801
+ async plan(ctx) {
802
+ const content = [OWNED_HEADER, "", athenaInstructions({ heading: "#" }), ""].join("\n");
803
+ return [await planOwned(ctx.root, await rulePath(ctx.root), content)];
804
+ },
805
+ async remove(root) {
806
+ const touched = [];
807
+ for (const rel of [RULE_FILE4, ALT_RULE_FILE]) {
808
+ if (!await isDirectory(path9.join(root, path9.dirname(rel)))) continue;
809
+ if (await removeOwned(root, rel)) touched.push(rel);
810
+ }
811
+ return touched;
812
+ },
813
+ async check(root) {
814
+ const rel = await rulePath(root);
815
+ const text = await readTextIfExists(path9.join(root, rel));
816
+ if (text === null) return [{ ok: false, level: "warn", message: `Cline: ${rel} not found` }];
817
+ if (!text.includes("Generated by Athena")) return [{ ok: false, level: "warn", message: `Cline: ${rel} is a developer file, so Athena did not write its rule there` }];
818
+ const checks = [{ ok: true, level: "ok", message: `Cline: ${rel}` }];
819
+ if (rel === ALT_RULE_FILE && !await isDirectory(path9.join(root, RULES_DIR)) && await exists2(path9.join(root, RULES_DIR))) {
820
+ checks.push({ ok: true, level: "warn", message: `Cline: .clinerules is a single file, so Athena's rule is in ${ALT_RULE_FILE}; older Cline versions that only read .clinerules will not see it` });
821
+ }
822
+ return checks;
823
+ }
824
+ };
825
+ async function rulePath(root) {
826
+ const clinerules = path9.join(root, RULES_DIR);
827
+ if (await exists2(clinerules) && !await isDirectory(clinerules)) return ALT_RULE_FILE;
828
+ const alt = await readTextIfExists(path9.join(root, ALT_RULE_FILE)).catch(() => null);
829
+ if (alt?.includes("Generated by Athena")) return ALT_RULE_FILE;
830
+ return RULE_FILE4;
831
+ }
832
+ async function exists2(p) {
833
+ return await fs6.lstat(p).catch(() => null) !== null;
834
+ }
835
+ async function isDirectory(p) {
836
+ return (await fs6.lstat(p).catch(() => null))?.isDirectory() ?? false;
837
+ }
838
+ async function generatedByAthena2(root, rels) {
839
+ const out = [];
840
+ for (const rel of rels) {
841
+ const text = await readTextIfExists(path9.join(root, rel)).catch(() => null);
842
+ if (text?.includes("Generated by Athena")) out.push(rel);
843
+ }
844
+ return out;
845
+ }
846
+
847
+ // src/agents/agents-md/adapter.ts
848
+ var agentsMdAdapter = {
849
+ id: "agents-md",
850
+ displayName: "AGENTS.md (cross-agent)",
851
+ capabilities: { instructionsFile: true, scopedRules: false, hooks: false, mcp: false },
852
+ supportNote: "Manages a marked block in AGENTS.md for agents that read the AGENTS.md convention.",
853
+ async detectPresence(root) {
854
+ const evidence = await userEvidence(root, { files: ["AGENTS.md"] });
855
+ return { detectedInProject: evidence.length > 0, evidence };
856
+ },
857
+ async plan(ctx) {
858
+ return [await planBlock(ctx.root, "AGENTS.md", athenaInstructions({ heading: "##" }))];
859
+ },
860
+ async remove(root) {
861
+ return await removeBlockFrom(root, "AGENTS.md") ? ["AGENTS.md"] : [];
862
+ },
863
+ async check(root) {
864
+ return [await checkBlock(root, "AGENTS.md", "AGENTS.md")];
865
+ }
866
+ };
867
+
868
+ // src/agents/registry.ts
869
+ var ADAPTERS = [claudeCodeAdapter, cursorAdapter, codexAdapter, copilotAdapter, geminiCliAdapter, antigravityAdapter, windsurfAdapter, clineAdapter, agentsMdAdapter];
870
+ function resolveAdapters(names) {
871
+ const alias = {
872
+ claude: "claude-code",
873
+ claudecode: "claude-code",
874
+ agents: "agents-md",
875
+ "agents.md": "agents-md",
876
+ gemini: "gemini-cli",
877
+ geminicli: "gemini-cli",
878
+ "openai-codex": "codex",
879
+ "codex-cli": "codex",
880
+ "github-copilot": "copilot",
881
+ "gh-copilot": "copilot",
882
+ "copilot-cli": "copilot",
883
+ "google-antigravity": "antigravity",
884
+ codeium: "windsurf"
885
+ };
886
+ const out = [];
887
+ for (const raw of names) {
888
+ const n = raw.trim().toLowerCase();
889
+ if (!n) continue;
890
+ const id = alias[n] ?? n;
891
+ const a = ADAPTERS.find((x) => x.id === id);
892
+ if (!a) throw new Error(`Unknown agent "${raw}". Supported: ${ADAPTERS.map((x) => x.id).join(", ")}`);
893
+ if (!out.includes(a)) out.push(a);
894
+ }
895
+ return out;
896
+ }
897
+ async function applyChanges(root, changes) {
898
+ const applied = [];
899
+ for (const c of changes) {
900
+ if (c.action === "unchanged") continue;
901
+ const abs = resolveInside(root, c.path);
902
+ if (!abs) throw new Error(`Refusing to write outside the project: ${c.path}`);
903
+ const st = await fs7.lstat(abs).catch(() => null);
904
+ if (st?.isSymbolicLink()) throw new Error(`Refusing to write through symlink: ${c.path}`);
905
+ await fs7.mkdir(path10.dirname(abs), { recursive: true });
906
+ await writeFileAtomic(abs, c.content);
907
+ if (abs.endsWith(".sh")) await fs7.chmod(abs, 493).catch(() => {
908
+ });
909
+ applied.push(c);
910
+ }
911
+ return applied;
912
+ }
913
+
914
+ export {
915
+ ADAPTERS,
916
+ resolveAdapters,
917
+ applyChanges
918
+ };
919
+ //# sourceMappingURL=chunk-YRIS7ZS6.js.map