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,399 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ projectSession
4
+ } from "./chunk-PDFQ5BFG.js";
5
+ import {
6
+ impactOf
7
+ } from "./chunk-GR4DL2VM.js";
8
+ import {
9
+ composeState,
10
+ ensureRules,
11
+ indexWithIntegrations,
12
+ planKnowledge,
13
+ writePlannedKnowledge
14
+ } from "./chunk-SJOOXO34.js";
15
+ import {
16
+ analyzeProject
17
+ } from "./chunk-PANVPNYF.js";
18
+ import {
19
+ headMovement
20
+ } from "./chunk-OLPSEQS5.js";
21
+ import {
22
+ KNOWLEDGE_DOCS
23
+ } from "./chunk-FQEGEVLY.js";
24
+ import {
25
+ athenaDir,
26
+ backupCorruptedState,
27
+ buildFileIndex,
28
+ diffFileIndex,
29
+ readFileIndex,
30
+ readState,
31
+ writeState
32
+ } from "./chunk-2DY4PUR4.js";
33
+ import {
34
+ readTextIfExists,
35
+ sha256,
36
+ writeFileAtomic
37
+ } from "./chunk-IQQBAWSM.js";
38
+ import {
39
+ AthenaError,
40
+ conflict
41
+ } from "./chunk-AD4OZCU2.js";
42
+
43
+ // src/services/sync.ts
44
+ import { promises as fs } from "fs";
45
+ import path from "path";
46
+ import { createTwoFilesPatch } from "diff";
47
+
48
+ // src/core/impact/model-diff.ts
49
+ var named = (i) => String(i.name ?? i.path ?? JSON.stringify(i));
50
+ var MODEL_SECTIONS = [
51
+ { label: "Project identity", keys: ["name", "description"], docs: ["project"] },
52
+ { label: "Languages", keys: ["languages"], docs: ["project"], identity: named, unit: ["language", "languages"] },
53
+ { label: "Project structure", keys: ["topLevelDirs", "stats"], docs: ["project"] },
54
+ { label: "Dependencies & manifests", keys: ["manifests"], docs: ["project", "security"], identity: (i) => String(i.path), unit: ["manifest", "manifests"] },
55
+ { label: "Package managers & build tools", keys: ["packageManagers", "buildSystems"], docs: ["project"], identity: named, unit: ["tool", "tools"] },
56
+ { label: "Workspace packages", keys: ["workspace"], docs: ["project", "architecture", "code-review", "testing"] },
57
+ { label: "Frameworks", keys: ["frameworks"], docs: ["project", "architecture", "api", "security", "code-review", "performance"], identity: (i) => `${i.name}@${i.root}`, display: (i) => String(i.name), unit: ["framework", "frameworks"] },
58
+ { label: "Entry points", keys: ["entryPoints"], docs: ["project"], identity: (i) => String(i.path), unit: ["entry point", "entry points"] },
59
+ { label: "Commands", keys: ["commands"], docs: ["project", "testing", "debugging", "deployment", "code-review", "database"], identity: (i) => `${i.source}:${i.name}`, display: (i) => String(i.name), unit: ["command", "commands"] },
60
+ { label: "Database technology", keys: ["databases"], docs: ["database", "architecture", "security", "code-review", "performance"], identity: named, unit: ["technology", "technologies"] },
61
+ { label: "Database schema", keys: ["dbEntities"], docs: ["database", "auth", "performance"], identity: (i) => `${i.name}@${i.file}`, display: (i) => String(i.name), unit: ["entity", "entities"] },
62
+ { label: "Migrations", keys: ["migrations"], docs: ["database", "deployment"], identity: (i) => `${i.tool}:${i.path}`, display: (i) => String(i.path), unit: ["migration directory", "migration directories"] },
63
+ { label: "API routes", keys: ["routes"], docs: ["api", "security", "testing", "code-review"], identity: (i) => `${i.method} ${i.path} ${i.file}`, display: (i) => `${i.method} ${i.path}`, unit: ["route", "routes"] },
64
+ { label: "API specifications", keys: ["apiSpecs"], docs: ["api", "security"], identity: (i) => String(i.path), unit: ["spec", "specs"] },
65
+ { label: "Authentication", keys: ["auth"], docs: ["auth", "security", "architecture"], identity: named, unit: ["auth signal", "auth signals"] },
66
+ { label: "Containers", keys: ["containers"], docs: ["deployment", "architecture", "security"] },
67
+ { label: "Infrastructure", keys: ["infrastructure"], docs: ["deployment", "architecture"], identity: (i) => `${i.name}@${i.file}`, display: (i) => String(i.name), unit: ["resource", "resources"] },
68
+ { label: "CI/CD", keys: ["ci"], docs: ["deployment"], identity: (i) => `${i.system}:${i.file}:${i.name}`, unit: ["CI job", "CI jobs"] },
69
+ { label: "Tests", keys: ["tests"], docs: ["testing", "code-review"] },
70
+ { label: "Environment variables", keys: ["env"], docs: ["deployment", "project", "security"] },
71
+ { label: "Security findings & controls", keys: ["security"], docs: ["security"] },
72
+ { label: "Caching & queues", keys: ["caching", "queues"], docs: ["performance", "architecture", "security"], identity: named, unit: ["technology", "technologies"] },
73
+ { label: "Observability", keys: ["observability"], docs: ["debugging", "architecture"], identity: named, unit: ["tool", "tools"] },
74
+ { label: "Git hotspots", keys: ["git"], docs: ["debugging"] },
75
+ { label: "Analysis warnings", keys: ["warnings"], docs: ["project"] }
76
+ ];
77
+ function stable(v) {
78
+ return JSON.stringify(v, (_k, val) => val && typeof val === "object" && !Array.isArray(val) ? Object.fromEntries(Object.entries(val).sort(([a], [b]) => a.localeCompare(b))) : val);
79
+ }
80
+ function normalize(key, value) {
81
+ if (key === "git" && value && typeof value === "object") return { hotspots: value.hotspots };
82
+ if (key === "root") return null;
83
+ return value;
84
+ }
85
+ function diffModels(prev, next) {
86
+ if (!prev) return [{ label: "Previous analysis", summary: "no previous model.json to compare against \u2014 full comparison of rendered documents used", docs: [] }];
87
+ const changes = [];
88
+ for (const spec of MODEL_SECTIONS) {
89
+ const differing = spec.keys.filter((k) => stable(normalize(k, prev[k])) !== stable(normalize(k, next[k])));
90
+ if (!differing.length) continue;
91
+ const parts = [];
92
+ for (const k of differing) {
93
+ const a = prev[k];
94
+ const b = next[k];
95
+ if (Array.isArray(a) && Array.isArray(b) && spec.identity) {
96
+ const labels = /* @__PURE__ */ new Map();
97
+ const ids = (xs) => new Map(
98
+ xs.map((x) => {
99
+ const item = x;
100
+ const id = spec.identity(item);
101
+ labels.set(id, (spec.display ?? spec.identity)(item));
102
+ return [id, stable(x)];
103
+ })
104
+ );
105
+ const ma = ids(a);
106
+ const mb = ids(b);
107
+ const show = (xs) => [...new Set(xs.map((x) => labels.get(x) ?? x))];
108
+ const added = show([...mb.keys()].filter((x) => !ma.has(x)));
109
+ const removed = show([...ma.keys()].filter((x) => !mb.has(x)));
110
+ const changed = show([...mb.keys()].filter((x) => ma.has(x) && ma.get(x) !== mb.get(x)));
111
+ const [one, many] = spec.unit ?? ["item", "items"];
112
+ const fmt = (n, verb, list) => n ? `${n} ${n === 1 ? one : many} ${verb}${list.length <= 3 ? ` (${list.join(", ")})` : ""}` : "";
113
+ parts.push(...[fmt(added.length, "added", added), fmt(removed.length, "removed", removed), fmt(changed.length, "changed", changed)].filter(Boolean));
114
+ } else if (k === "stats") {
115
+ const sa = a;
116
+ const sb = b;
117
+ if (sa.filesScanned !== sb.filesScanned) parts.push(`files analyzed ${sa.filesScanned} \u2192 ${sb.filesScanned}`);
118
+ else parts.push("file statistics changed");
119
+ } else {
120
+ parts.push(describeObjectChange(k, a, b));
121
+ }
122
+ }
123
+ changes.push({ label: spec.label, summary: parts.join("; ") || "changed", docs: spec.docs });
124
+ }
125
+ return changes;
126
+ }
127
+ function names(xs) {
128
+ return new Set(xs.map((x) => x.name));
129
+ }
130
+ function setDelta(label, a, b) {
131
+ const added = [...b].filter((x) => !a.has(x));
132
+ const removed = [...a].filter((x) => !b.has(x));
133
+ const out = [];
134
+ const list = (xs) => xs.length <= 3 ? ` (${xs.join(", ")})` : "";
135
+ if (added.length) out.push(`${added.length} ${label} added${list(added)}`);
136
+ if (removed.length) out.push(`${removed.length} ${label} removed${list(removed)}`);
137
+ return out;
138
+ }
139
+ function describeObjectChange(key, a, b) {
140
+ const parts = [];
141
+ switch (key) {
142
+ case "env": {
143
+ const ea = a;
144
+ const eb = b;
145
+ parts.push(...setDelta("variable name(s)", names(ea.vars), names(eb.vars)));
146
+ parts.push(...setDelta("env file(s)", new Set(ea.envFilesPresent), new Set(eb.envFilesPresent)));
147
+ if (!parts.length) parts.push("variable references moved");
148
+ break;
149
+ }
150
+ case "security": {
151
+ const sa = a;
152
+ const sb = b;
153
+ if (sa.secrets.length !== sb.secrets.length) parts.push(`potential secrets ${sa.secrets.length} \u2192 ${sb.secrets.length}`);
154
+ parts.push(...setDelta("control(s)", names(sa.controls), names(sb.controls)));
155
+ parts.push(...setDelta("tool(s)", names(sa.tooling), names(sb.tooling)));
156
+ if (!parts.length) parts.push("evidence locations changed");
157
+ break;
158
+ }
159
+ case "tests": {
160
+ const ta = a;
161
+ const tb = b;
162
+ if (ta.testFileCount !== tb.testFileCount) parts.push(`test files ${ta.testFileCount} \u2192 ${tb.testFileCount}`);
163
+ parts.push(...setDelta("framework(s)", names(ta.frameworks), names(tb.frameworks)));
164
+ if (!parts.length) parts.push("test layout changed");
165
+ break;
166
+ }
167
+ case "containers": {
168
+ const ca = a;
169
+ const cb = b;
170
+ parts.push(...setDelta("Dockerfile(s)", new Set(ca.dockerfiles.map((d) => d.path)), new Set(cb.dockerfiles.map((d) => d.path))));
171
+ parts.push(...setDelta("service(s)", names(ca.services), names(cb.services)));
172
+ if (!parts.length) parts.push("container configuration changed");
173
+ break;
174
+ }
175
+ case "workspace": {
176
+ const wa = a;
177
+ const wb = b;
178
+ parts.push(...setDelta("package(s)", names(wa.packages), names(wb.packages)));
179
+ if (!parts.length) parts.push("package dependencies changed");
180
+ break;
181
+ }
182
+ case "topLevelDirs":
183
+ parts.push(...setDelta("top-level director(ies)", new Set(a), new Set(b)));
184
+ break;
185
+ case "git":
186
+ parts.push("change hotspots updated from recent commits");
187
+ break;
188
+ case "name":
189
+ case "description":
190
+ parts.push(`${key} changed`);
191
+ break;
192
+ default:
193
+ parts.push(`${String(key)} changed`);
194
+ }
195
+ return parts.join("; ");
196
+ }
197
+
198
+ // src/services/sync.ts
199
+ var IGNORE_FILE = ".sync-ignore.json";
200
+ var MAX_DIFF_CHARS = 6e4;
201
+ var internals = /* @__PURE__ */ new WeakMap();
202
+ var hashOf = (text) => text === null ? null : sha256(text).slice(0, 16);
203
+ function countLines(patch) {
204
+ let additions = 0;
205
+ let deletions = 0;
206
+ for (const line of patch.split("\n")) {
207
+ if (line.startsWith("+") && !line.startsWith("+++")) additions++;
208
+ else if (line.startsWith("-") && !line.startsWith("---")) deletions++;
209
+ }
210
+ return { additions, deletions };
211
+ }
212
+ function detectRenames(prev, next, added, deleted) {
213
+ const byHash = /* @__PURE__ */ new Map();
214
+ for (const d of deleted) {
215
+ const h = prev[d]?.h.slice(0, 16);
216
+ if (h && !h.startsWith("meta:")) byHash.set(h, [...byHash.get(h) ?? [], d]);
217
+ }
218
+ const renamed = [];
219
+ const remainingAdded = [];
220
+ const usedDeleted = /* @__PURE__ */ new Set();
221
+ for (const a of added) {
222
+ const candidates = byHash.get(next[a]?.h.slice(0, 16) ?? "")?.filter((d) => !usedDeleted.has(d));
223
+ if (candidates?.length) {
224
+ usedDeleted.add(candidates[0]);
225
+ renamed.push({ from: candidates[0], to: a });
226
+ } else remainingAdded.push(a);
227
+ }
228
+ return { added: remainingAdded, modified: [], deleted: deleted.filter((d) => !usedDeleted.has(d)), renamed };
229
+ }
230
+ async function readIgnored(dir) {
231
+ const raw = await readTextIfExists(path.join(dir, IGNORE_FILE));
232
+ if (!raw) return null;
233
+ try {
234
+ const j = JSON.parse(raw);
235
+ return typeof j.planId === "string" ? j.planId : null;
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+ async function planSync(root, opts = {}) {
241
+ const started = Date.now();
242
+ const dir = athenaDir(root);
243
+ const st = await readState(dir);
244
+ if (st.kind === "missing" && !await readTextIfExists(path.join(dir, "rules.md"))) {
245
+ throw new AthenaError("Athena is not initialized in this project.", "Run `athena init` first.", 3);
246
+ }
247
+ const prevState = st.kind === "ok" ? st.state : null;
248
+ const warnings = [];
249
+ if (st.kind === "corrupted") warnings.push(`state.json is corrupted (${st.reason}); it will be rebuilt on apply.`);
250
+ const prevIndex = prevState ? await readFileIndex(dir) : {};
251
+ const [prevModel, analysis] = await Promise.all([projectSession(root).model(), analyzeProject(root, { signal: opts.signal, reuse: prevIndex, incremental: opts.incremental })]);
252
+ opts.signal?.throwIfAborted();
253
+ const previousBlocks = prevState ? Object.fromEntries(Object.entries(prevState.documents).map(([k, v]) => [k, v.blocks])) : void 0;
254
+ const plans = await planKnowledge(dir, analysis.model, { force: opts.force, previousBlocks });
255
+ const nextIndex = buildFileIndex(analysis.files);
256
+ const rawDiff = diffFileIndex(prevIndex, nextIndex);
257
+ const managed = new Set(Object.values(prevState?.agents ?? {}).flatMap((a) => a.files));
258
+ const idxChanges = { added: rawDiff.added.filter((p) => !managed.has(p)), modified: rawDiff.modified.filter((p) => !managed.has(p)), deleted: rawDiff.deleted.filter((p) => !managed.has(p)) };
259
+ const renames = detectRenames(prevIndex, nextIndex, idxChanges.added, idxChanges.deleted);
260
+ const fileChanges = { ...renames, modified: idxChanges.modified };
261
+ const modelChanges = diffModels(prevModel, analysis.model);
262
+ const allPaths = [...fileChanges.added, ...fileChanges.modified, ...fileChanges.deleted, ...fileChanges.renamed.flatMap((r) => [r.from, r.to])];
263
+ const pathImpact = impactOf(allPaths, { structural: [...fileChanges.added, ...fileChanges.deleted, ...fileChanges.renamed.map((r) => r.to)] });
264
+ const documents = [];
265
+ for (const p of plans) {
266
+ if (p.status === "unchanged") continue;
267
+ const doc = KNOWLEDGE_DOCS.find((d) => d.id === p.id);
268
+ const reasons = [
269
+ ...modelChanges.filter((c) => c.docs.includes(p.id)).map((c) => `${c.label}: ${c.summary}`),
270
+ ...(pathImpact.reasons[p.id] ?? []).map((r) => `Changed files (${r})`)
271
+ ];
272
+ if (p.status === "created") reasons.unshift("Document is missing and will be recreated");
273
+ if (!reasons.length) reasons.push("Rendered content differs from the file on disk (for example after an Athena upgrade or a manual edit outside sections)");
274
+ let patch = createTwoFilesPatch(`a/.athena/${p.file}`, `b/.athena/${p.file}`, p.before ?? "", p.after, void 0, void 0, { context: 3 });
275
+ const counts = countLines(patch);
276
+ const diffTruncated = patch.length > MAX_DIFF_CHARS;
277
+ if (diffTruncated) patch = `${patch.slice(0, MAX_DIFF_CHARS)}
278
+ \u2026 diff truncated \u2026
279
+ `;
280
+ documents.push({
281
+ id: p.id,
282
+ file: p.file,
283
+ title: doc.title,
284
+ status: p.status,
285
+ reasons: [...new Set(reasons)],
286
+ changedSections: p.changedBlocks,
287
+ preservedSections: p.preservedModified,
288
+ diff: patch,
289
+ diffTruncated,
290
+ ...counts,
291
+ baseHash: hashOf(p.before)
292
+ });
293
+ }
294
+ const git = {
295
+ isRepo: analysis.model.git.isRepo,
296
+ previousHead: prevState?.git.head ?? null,
297
+ head: analysis.model.git.head ?? null,
298
+ previousBranch: prevState?.git.branch ?? null,
299
+ branch: analysis.model.git.branch ?? null,
300
+ branchChanged: Boolean(prevState?.git.branch && analysis.model.git.branch && prevState.git.branch !== analysis.model.git.branch),
301
+ commits: [],
302
+ diverged: false,
303
+ truncated: false
304
+ };
305
+ if (git.isRepo && git.previousHead && git.head && git.previousHead !== git.head) {
306
+ const mv = await headMovement(root, git.previousHead, git.head);
307
+ if (mv) Object.assign(git, { commits: mv.commits, diverged: mv.diverged, truncated: mv.truncated });
308
+ }
309
+ const id = sha256(JSON.stringify(documents.map((d) => [d.file, d.baseHash, hashOf(plans.find((p) => p.id === d.id).after)]))).slice(0, 16);
310
+ const upToDate = documents.length === 0;
311
+ const plan = {
312
+ id,
313
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
314
+ previousAnalysisAt: prevState?.analyzedAt ?? null,
315
+ upToDate,
316
+ fileChanges,
317
+ git,
318
+ modelChanges: prevModel ? modelChanges : [],
319
+ documents,
320
+ checkedUnchanged: pathImpact.docs.map((d) => KNOWLEDGE_DOCS.find((k) => k.id === d).file).filter((f) => !documents.some((d) => d.file === f)),
321
+ ignored: !upToDate && await readIgnored(dir) === id,
322
+ durationMs: Date.now() - started,
323
+ warnings
324
+ };
325
+ internals.set(plan, { analysis, plans, prevState });
326
+ return plan;
327
+ }
328
+ function planSnapshot(plan) {
329
+ const a = internals.get(plan)?.analysis;
330
+ return a ? { snapshot: a.snapshot, walkMode: a.walkMode } : null;
331
+ }
332
+ async function applySync(root, plan) {
333
+ const inner = internals.get(plan);
334
+ if (!inner) throw new AthenaError("This sync plan is no longer available.", "Create a new plan and try again.", 1, "conflict");
335
+ const dir = athenaDir(root);
336
+ for (const d of plan.documents) {
337
+ const current = hashOf(await readTextIfExists(path.join(dir, d.file)));
338
+ if (current !== d.baseHash) {
339
+ throw conflict(`${d.file} changed after the sync plan was created.`, "Review the new plan before applying.", { file: d.file });
340
+ }
341
+ }
342
+ const st = await readState(dir);
343
+ if (st.kind === "corrupted") await backupCorruptedState(dir);
344
+ const prevState = st.kind === "ok" ? st.state : inner.prevState;
345
+ await fs.mkdir(dir, { recursive: true });
346
+ const docs = await writePlannedKnowledge(dir, inner.plans);
347
+ docs.push(await ensureRules(dir, inner.analysis.model));
348
+ await writeFileAtomic(path.join(dir, "model.json"), `${JSON.stringify(inner.analysis.model, null, 2)}
349
+ `);
350
+ const managedFiles = Object.values(prevState?.agents ?? {}).flatMap((a) => a.files);
351
+ const state = composeState({
352
+ prevState,
353
+ analysis: inner.analysis,
354
+ docs,
355
+ agents: prevState?.agents ?? {},
356
+ fileIndex: await indexWithIntegrations(root, inner.analysis.files, managedFiles)
357
+ });
358
+ await writeState(dir, state);
359
+ await fs.rm(path.join(dir, IGNORE_FILE), { force: true });
360
+ internals.delete(plan);
361
+ return {
362
+ applied: plan.documents.map((d) => d.file),
363
+ preserved: plan.documents.filter((d) => d.preservedSections.length).map((d) => ({ file: d.file, sections: d.preservedSections })),
364
+ analyzedAt: state.analyzedAt
365
+ };
366
+ }
367
+ async function ignorePlan(root, planId) {
368
+ if (!/^[a-f0-9]{16}$/.test(planId)) throw new AthenaError("Invalid plan id");
369
+ await writeFileAtomic(path.join(athenaDir(root), IGNORE_FILE), `${JSON.stringify({ planId, ignoredAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
370
+ `);
371
+ }
372
+ function fileChangeCount(plan) {
373
+ const c = plan.fileChanges;
374
+ return c.added.length + c.modified.length + c.deleted.length + c.renamed.length;
375
+ }
376
+ function needsIndexRefreshOnly(plan) {
377
+ return plan.upToDate && (fileChangeCount(plan) > 0 || plan.git.head !== plan.git.previousHead);
378
+ }
379
+ function summarizePlan(plan) {
380
+ const { documents, ...rest } = plan;
381
+ return { ...rest, documents: documents.map(({ diff: _diff, ...d }) => d) };
382
+ }
383
+ var HISTORY_SECTIONS = /* @__PURE__ */ new Set(["hotspots"]);
384
+ function staleForCheck(plan) {
385
+ return plan.documents.filter((d) => d.status === "created" || !d.changedSections.length || d.changedSections.some((s) => !HISTORY_SECTIONS.has(s)));
386
+ }
387
+
388
+ export {
389
+ planSync,
390
+ planSnapshot,
391
+ applySync,
392
+ ignorePlan,
393
+ fileChangeCount,
394
+ needsIndexRefreshOnly,
395
+ summarizePlan,
396
+ HISTORY_SECTIONS,
397
+ staleForCheck
398
+ };
399
+ //# sourceMappingURL=chunk-TGJUUB44.js.map
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/services/which.ts
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ async function whichExecutable(command) {
7
+ if (command.includes("/") || command.includes("\\")) {
8
+ return await fs.access(command).then(() => true).catch(() => false) ? command : null;
9
+ }
10
+ const dirs = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
11
+ const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
12
+ for (const dir of dirs) {
13
+ for (const ext of exts) {
14
+ const candidate = path.join(dir, command + ext.toLowerCase());
15
+ try {
16
+ const st = await fs.stat(candidate);
17
+ if (st.isFile()) return candidate;
18
+ } catch {
19
+ }
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+
25
+ export {
26
+ whichExecutable
27
+ };
28
+ //# sourceMappingURL=chunk-VSYBC3VY.js.map
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ planSnapshot,
4
+ planSync
5
+ } from "./chunk-TGJUUB44.js";
6
+
7
+ // src/services/scheduler.ts
8
+ var DEFAULT_FULL_WALK_EVERY_MS = 5 * 6e4;
9
+ function abortError() {
10
+ const e = new Error("The analysis was cancelled.");
11
+ e.name = "AbortError";
12
+ return e;
13
+ }
14
+ var defaultPlanner = async (root, input) => {
15
+ const plan = await planSync(root, { signal: input.signal, incremental: input.incremental });
16
+ return { plan, snapshot: planSnapshot(plan)?.snapshot ?? null };
17
+ };
18
+ var AnalysisScheduler = class {
19
+ constructor(root, opts = {}) {
20
+ this.root = root;
21
+ this.planner = opts.planner ?? defaultPlanner;
22
+ this.maxConsecutiveAborts = opts.maxConsecutiveAborts ?? 2;
23
+ this.fullWalkEveryMs = opts.fullWalkEveryMs ?? DEFAULT_FULL_WALK_EVERY_MS;
24
+ }
25
+ root;
26
+ hooks = {};
27
+ planner;
28
+ maxConsecutiveAborts;
29
+ fullWalkEveryMs;
30
+ current = null;
31
+ next = null;
32
+ /** Serializes runs and exclusive writers. */
33
+ lock = Promise.resolve();
34
+ closed = false;
35
+ consecutiveAborts = 0;
36
+ // Change tracking (incremental analysis).
37
+ tracking = false;
38
+ /** What the tracker (watcher) was set up with: a walk made under other ignore rules is not trusted. */
39
+ guard = null;
40
+ epoch = 0;
41
+ noted = /* @__PURE__ */ new Set();
42
+ baseline = null;
43
+ forceFullNext = false;
44
+ /** Counters (tests, diagnostics). */
45
+ stats = { runs: 0, aborted: 0, incremental: 0, coalesced: 0 };
46
+ get busy() {
47
+ return this.current !== null;
48
+ }
49
+ /**
50
+ * Turn change tracking on (a watcher that is ready and will report every change)
51
+ * or off. Either way the baseline is dropped: the next run walks the whole tree.
52
+ */
53
+ setTracking(on, guard) {
54
+ this.tracking = on;
55
+ this.guard = on ? guard ?? null : null;
56
+ this.resetBaseline();
57
+ }
58
+ /** Forget the previous walk (watcher error, missed events): the next run walks the whole tree. */
59
+ resetBaseline() {
60
+ this.epoch++;
61
+ this.baseline = null;
62
+ this.noted.clear();
63
+ }
64
+ /** Record changed paths without requesting a plan (a watcher calls this on every event). */
65
+ noteChanges(paths) {
66
+ for (const p of paths) this.noted.add(p);
67
+ }
68
+ /** Request a plan. Resolves with the plan of the run that covers this request. */
69
+ request(req) {
70
+ if (this.closed) return Promise.reject(abortError());
71
+ if (req.paths?.length) this.noteChanges(req.paths);
72
+ if (req.full) this.forceFullNext = true;
73
+ return new Promise((resolve, reject) => {
74
+ const waiter = { resolve, reject };
75
+ if (!this.current) {
76
+ const run = this.newRun(req.reason);
77
+ run.waiters.push(waiter);
78
+ this.start(run);
79
+ return;
80
+ }
81
+ this.stats.coalesced++;
82
+ if (this.current.phase === "queued" && !this.current.superseded) {
83
+ this.current.reasons.push(req.reason);
84
+ this.current.waiters.push(waiter);
85
+ return;
86
+ }
87
+ if (!this.next) this.next = this.newRun(req.reason);
88
+ else this.next.reasons.push(req.reason);
89
+ this.next.waiters.push(waiter);
90
+ const cur = this.current;
91
+ if (req.supersede && (req.paths?.length || this.noted.size) && cur.phase === "planning" && !cur.superseded && this.consecutiveAborts < this.maxConsecutiveAborts) {
92
+ cur.superseded = true;
93
+ this.consecutiveAborts++;
94
+ this.stats.aborted++;
95
+ this.next.waiters.unshift(...cur.waiters.splice(0));
96
+ cur.controller.abort();
97
+ }
98
+ });
99
+ }
100
+ /**
101
+ * Run `fn` with no plan in flight; plans requested meanwhile wait for it.
102
+ * For writers (apply, full analysis) that must not interleave with planning.
103
+ */
104
+ exclusive(fn) {
105
+ const result = this.lock.then(fn);
106
+ this.lock = result.catch(() => {
107
+ });
108
+ return result;
109
+ }
110
+ /** Abort everything and reject pending requests. */
111
+ close() {
112
+ if (this.closed) return;
113
+ this.closed = true;
114
+ const err = abortError();
115
+ for (const run of [this.current, this.next]) {
116
+ if (!run) continue;
117
+ run.superseded = true;
118
+ for (const w of run.waiters.splice(0)) w.reject(err);
119
+ run.controller.abort();
120
+ }
121
+ this.next = null;
122
+ }
123
+ newRun(reason) {
124
+ return { reasons: [reason], waiters: [], controller: new AbortController(), phase: "queued", superseded: false, claimed: [], forceFull: false };
125
+ }
126
+ start(run) {
127
+ this.current = run;
128
+ const task = this.lock.then(() => this.execute(run));
129
+ this.lock = task.catch(() => {
130
+ });
131
+ }
132
+ /** The run is over: free the slot (starting the follow-up), then settle its waiters. */
133
+ finish(run, outcome) {
134
+ if (this.current === run) this.current = null;
135
+ const next = this.next;
136
+ this.next = null;
137
+ if (next && !this.closed) this.start(next);
138
+ for (const w of run.waiters.splice(0)) {
139
+ if ("plan" in outcome) w.resolve(outcome.plan);
140
+ else w.reject(outcome.error);
141
+ }
142
+ }
143
+ async execute(run) {
144
+ if (run.superseded || this.closed) {
145
+ this.finish(run, { error: abortError() });
146
+ return;
147
+ }
148
+ this.stats.runs++;
149
+ const epoch = this.epoch;
150
+ run.claimed = [...this.noted];
151
+ this.noted.clear();
152
+ run.forceFull = this.forceFullNext;
153
+ this.forceFullNext = false;
154
+ const base = this.baseline;
155
+ const guard = this.guard;
156
+ const useIncremental = this.tracking && !run.forceFull && base !== null && base.epoch === epoch && Date.now() - base.snapshot.takenAt < this.fullWalkEveryMs && (!guard || guard.configHash === base.snapshot.configHash && guard.excludeText === base.snapshot.excludeText);
157
+ const info = { reason: run.reasons.join(","), paths: run.claimed, incremental: useIncremental };
158
+ run.phase = "planning";
159
+ try {
160
+ this.hooks.onStart?.(info);
161
+ const out = await this.planner(this.root, {
162
+ signal: run.controller.signal,
163
+ incremental: useIncremental ? { snapshot: base.snapshot, changedPaths: run.claimed } : void 0
164
+ });
165
+ run.controller.signal.throwIfAborted();
166
+ if (useIncremental && out.snapshot?.takenAt === base.snapshot.takenAt) this.stats.incremental++;
167
+ if (this.tracking && out.snapshot && this.epoch === epoch) this.baseline = { snapshot: out.snapshot, epoch };
168
+ run.phase = "finishing";
169
+ await this.hooks.onPlan?.(out.plan, info);
170
+ this.consecutiveAborts = 0;
171
+ this.finish(run, { plan: out.plan });
172
+ } catch (err) {
173
+ if (this.epoch === epoch) for (const p of run.claimed) this.noted.add(p);
174
+ this.finish(run, { error: run.superseded ? abortError() : err });
175
+ }
176
+ }
177
+ };
178
+
179
+ export {
180
+ AnalysisScheduler
181
+ };
182
+ //# sourceMappingURL=chunk-XB66PZ6Z.js.map
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ findProjectRoot
4
+ } from "./chunk-KBO7XRBW.js";
5
+ import {
6
+ AthenaError,
7
+ EXIT
8
+ } from "./chunk-AD4OZCU2.js";
9
+
10
+ // src/cli/context.ts
11
+ import { promises as fs } from "fs";
12
+ import path from "path";
13
+ async function resolveCwd(opts) {
14
+ const dir = path.resolve(opts.cwd ?? process.cwd());
15
+ try {
16
+ const st = await fs.stat(dir);
17
+ if (!st.isDirectory()) throw new AthenaError(`Not a directory: ${dir}`);
18
+ return await fs.realpath(dir);
19
+ } catch (err) {
20
+ if (err instanceof AthenaError) throw err;
21
+ throw new AthenaError(`Directory not accessible: ${dir}`, "Check the path and your permissions.");
22
+ }
23
+ }
24
+ async function requireProjectRoot(opts) {
25
+ const cwd = await resolveCwd(opts);
26
+ const root = await findProjectRoot(cwd);
27
+ if (!root) throw new AthenaError("Athena is not initialized in this project.", "Run `athena init` in the project root.", EXIT.NOT_INITIALIZED);
28
+ return root;
29
+ }
30
+
31
+ export {
32
+ resolveCwd,
33
+ requireProjectRoot
34
+ };
35
+ //# sourceMappingURL=chunk-XFN7UZCS.js.map
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/util/paths.ts
4
+ import path from "path";
5
+ function toPosix(p) {
6
+ return p.split(path.sep).join("/").replace(/\\/g, "/");
7
+ }
8
+ function resolveInside(root, candidate) {
9
+ if (candidate.includes("\0")) return null;
10
+ const resolvedRoot = path.resolve(root);
11
+ const resolved = path.resolve(resolvedRoot, candidate);
12
+ const rel = path.relative(resolvedRoot, resolved);
13
+ if (rel === "") return resolved;
14
+ if (rel.startsWith("..") || path.isAbsolute(rel)) return null;
15
+ return resolved;
16
+ }
17
+ function dirOf(posixPath) {
18
+ const i = posixPath.lastIndexOf("/");
19
+ return i === -1 ? "." : posixPath.slice(0, i);
20
+ }
21
+ function baseName(posixPath) {
22
+ const i = posixPath.lastIndexOf("/");
23
+ return i === -1 ? posixPath : posixPath.slice(i + 1);
24
+ }
25
+
26
+ export {
27
+ toPosix,
28
+ resolveInside,
29
+ dirOf,
30
+ baseName
31
+ };
32
+ //# sourceMappingURL=chunk-XW3MLHI2.js.map