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.
- package/CHANGELOG.md +63 -0
- package/README.md +61 -12
- package/dist/activity-BKW7G4I5.js +77 -0
- package/dist/agents-DFSNEOP5.js +69 -0
- package/dist/ai-ASL4FFQ7.js +96 -0
- package/dist/analyze-ZY3MXBOI.js +50 -0
- package/dist/chunk-2DY4PUR4.js +465 -0
- package/dist/chunk-2R6PXY6Z.js +33 -0
- package/dist/chunk-3IS2X2Z7.js +89 -0
- package/dist/chunk-3XSDI5WF.js +179 -0
- package/dist/chunk-65TSRDQ3.js +124 -0
- package/dist/chunk-673EPYYH.js +105 -0
- package/dist/chunk-7DZK6WSQ.js +65 -0
- package/dist/chunk-AD4OZCU2.js +34 -0
- package/dist/chunk-DZDI2HRT.js +169 -0
- package/dist/chunk-ELKFRTZT.js +221 -0
- package/dist/chunk-EUPETUWA.js +14 -0
- package/dist/chunk-FQEGEVLY.js +38 -0
- package/dist/chunk-FVCDMQUQ.js +343 -0
- package/dist/chunk-GGXPG6OE.js +904 -0
- package/dist/chunk-GHFRQAYT.js +112 -0
- package/dist/chunk-GR4DL2VM.js +47 -0
- package/dist/chunk-INT3IGMQ.js +111 -0
- package/dist/chunk-IQQBAWSM.js +90 -0
- package/dist/chunk-JCGDR4UV.js +182 -0
- package/dist/chunk-KBO7XRBW.js +26 -0
- package/dist/chunk-KHMBNT66.js +399 -0
- package/dist/chunk-LEMPCNAY.js +209 -0
- package/dist/chunk-MPG3IEGB.js +2691 -0
- package/dist/chunk-OLPSEQS5.js +264 -0
- package/dist/chunk-PVCIGUIT.js +64 -0
- package/dist/chunk-QCC4VJUH.js +217 -0
- package/dist/chunk-QLW657NT.js +95 -0
- package/dist/chunk-RVTNSNGK.js +182 -0
- package/dist/chunk-RXNRMIZP.js +390 -0
- package/dist/chunk-SHKXELUX.js +295 -0
- package/dist/chunk-SJ4DQFIJ.js +379 -0
- package/dist/chunk-SVTEJYMS.js +227 -0
- package/dist/chunk-TDNZ2RYA.js +702 -0
- package/dist/chunk-UAZLPNVC.js +108 -0
- package/dist/chunk-VSYBC3VY.js +28 -0
- package/dist/chunk-WVQC2EGO.js +166 -0
- package/dist/chunk-XDCKZ5TV.js +436 -0
- package/dist/chunk-XFN7UZCS.js +35 -0
- package/dist/chunk-XW3MLHI2.js +32 -0
- package/dist/chunk-YRIS7ZS6.js +919 -0
- package/dist/chunk-Z6QWMJLK.js +124 -0
- package/dist/chunk-ZB7LC6FP.js +221 -0
- package/dist/clean-MDBYH62P.js +23 -0
- package/dist/cli.js +18 -10716
- package/dist/context-PLPCAPCO.js +161 -0
- package/dist/doctor-BX677KRE.js +69 -0
- package/dist/event-TNT6CQZR.js +101 -0
- package/dist/git-hook-YZNZGAAP.js +252 -0
- package/dist/init-WQCAOJ2R.js +38 -0
- package/dist/mcp-U7EPH43L.js +393 -0
- package/dist/memory-VSVNLEJC.js +393 -0
- package/dist/open-UJZRYF4E.js +1450 -0
- package/dist/program-CWJNGCTZ.js +165 -0
- package/dist/review-OOKXSYMP.js +117 -0
- package/dist/rules-4FXJUCXB.js +83 -0
- package/dist/security-LME3XFL3.js +177 -0
- package/dist/staged-check-X24C3YRD.js +502 -0
- package/dist/status-RR57ZNGI.js +81 -0
- package/dist/sync-KFJANRXF.js +218 -0
- package/dist/watch-CY7QHIYN.js +123 -0
- package/dist/web/assets/{Editor-DAXvniWB.js → Editor-Bnilgch0.js} +1 -1
- package/dist/web/assets/{abnfDiagram-O67JEVCF-Az7EW1BC.js → abnfDiagram-O67JEVCF-DW9LWr8f.js} +1 -1
- package/dist/web/assets/architecture-7GRP2DOG-BWDZV2yn.js +1 -0
- package/dist/web/assets/{architectureDiagram-NJMV4G6O-BxeXqSTn.js → architectureDiagram-NJMV4G6O-0eKeQRhj.js} +1 -1
- package/dist/web/assets/{blockDiagram-BEXU5L5S-aeKZl9lJ.js → blockDiagram-BEXU5L5S-D5i3-ogI.js} +1 -1
- package/dist/web/assets/{c4Diagram-YGBWAQC7-CgmntYsG.js → c4Diagram-YGBWAQC7-BAWQZrEu.js} +1 -1
- package/dist/web/assets/channel-Bkh05NKd.js +1 -0
- package/dist/web/assets/{chunk-3FUC2YCW-BpmRD2yo.js → chunk-3FUC2YCW-fKRGOZF3.js} +1 -1
- package/dist/web/assets/{chunk-5DYCD2WN-CRIFDwod.js → chunk-5DYCD2WN-C5jOlx42.js} +1 -1
- package/dist/web/assets/{chunk-742MDFTN-hRW9VeNV.js → chunk-742MDFTN-urY4RP_A.js} +1 -1
- package/dist/web/assets/{chunk-7INBJB4K-C6VEpT0Y.js → chunk-7INBJB4K-nk48sKqZ.js} +1 -1
- package/dist/web/assets/{chunk-7M6MHVWA-Dnajov2w.js → chunk-7M6MHVWA-BfBD9X0F.js} +1 -1
- package/dist/web/assets/{chunk-7PRAP22T-DSAc0_CZ.js → chunk-7PRAP22T-jYf59QlI.js} +1 -1
- package/dist/web/assets/{chunk-GTNCS2PH-CefpYUjb.js → chunk-GTNCS2PH-BOCxryKf.js} +1 -1
- package/dist/web/assets/{chunk-GWA4HPMP-MLNflMmF.js → chunk-GWA4HPMP-CFpaW0ru.js} +1 -1
- package/dist/web/assets/{chunk-MBY4JIJT-D3GmmEiU.js → chunk-MBY4JIJT-BIB8HctN.js} +1 -1
- package/dist/web/assets/{chunk-NETBCI7D-DzTj6xVk.js → chunk-NETBCI7D-CpUUK6rh.js} +1 -1
- package/dist/web/assets/{chunk-O7XYJQB3-D1d0hCko.js → chunk-O7XYJQB3-BNx_V4Oi.js} +1 -1
- package/dist/web/assets/{chunk-UA2S7LBM-C9hIFeWR.js → chunk-UA2S7LBM-D3tMiIOY.js} +1 -1
- package/dist/web/assets/{chunk-WEXAMYUT-BFGtcjsi.js → chunk-WEXAMYUT-CC-3C32l.js} +1 -1
- package/dist/web/assets/{chunk-Z7XXMR3K-BRZ8_wtE.js → chunk-Z7XXMR3K-BzhxKQUc.js} +1 -1
- package/dist/web/assets/{chunk-ZIGJFQKS-CTKOioal.js → chunk-ZIGJFQKS-D4tBHzxg.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-NBCMYWYE-DJGKtWJv.js → classDiagram-v2-NBCMYWYE-BbtyanXR.js} +1 -1
- package/dist/web/assets/{cynefin-OW5HDTMX-hNq1kGmA.js → cynefin-OW5HDTMX-BIArb8W1.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-VND7K2PF-sODQM_ZZ.js → cynefinDiagram-VND7K2PF-BrAcfkXL.js} +1 -1
- package/dist/web/assets/{dagre-6A5THRUB-DEzTThIf.js → dagre-6A5THRUB-BcHXRl4s.js} +1 -1
- package/dist/web/assets/{diagram-22UHCM2B-s_eellsa.js → diagram-22UHCM2B-Dx9tSCdE.js} +1 -1
- package/dist/web/assets/{diagram-3UASUU5V-B2onY_Xw.js → diagram-3UASUU5V-CLLDP9yH.js} +1 -1
- package/dist/web/assets/{diagram-ATOU4E4O-B8V6s13s.js → diagram-ATOU4E4O-B46H9iPt.js} +1 -1
- package/dist/web/assets/{diagram-CDSNMT55-CJxiE6mf.js → diagram-CDSNMT55-CYtvB6aG.js} +1 -1
- package/dist/web/assets/{diagram-MLGK6HIB-m5OHPLAp.js → diagram-MLGK6HIB-Dar7ktoX.js} +1 -1
- package/dist/web/assets/{diagram-MPIPVDR6-Buvvrk_A.js → diagram-MPIPVDR6-C-DljnYN.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-ZINNZB2B-CpL7Wt3R.js → ebnfDiagram-ZINNZB2B-Dv_3jWnh.js} +1 -1
- package/dist/web/assets/{elk-276RUBZZ-CwYhbsXa.js → elk-276RUBZZ-DnHciE9k.js} +1 -1
- package/dist/web/assets/{erDiagram-OPXOYQCR-DNikci2r.js → erDiagram-OPXOYQCR-Bn46KrHz.js} +1 -1
- package/dist/web/assets/eventmodeling-NTZA5JFV-DYwoptMd.js +1 -0
- package/dist/web/assets/flowDiagram-KWPJA3E3-y4rS0gIB.js +1 -0
- package/dist/web/assets/{ganttDiagram-FUAMR5RP-d-ynDlvJ.js → ganttDiagram-FUAMR5RP-DOjjJtqz.js} +1 -1
- package/dist/web/assets/{gitGraph-4MIJSDKK-DwIeBI91.js → gitGraph-4MIJSDKK-BIoMXJRQ.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-X574FWY7-04RZccu6.js → gitGraphDiagram-X574FWY7-BsJ01R0v.js} +1 -1
- package/dist/web/assets/{index-C3-8uYXr.js → index-Cen0jOXj.js} +5 -4
- package/dist/web/assets/{index-DKE3ojTe.css → index-WwRY6n68.css} +1 -1
- package/dist/web/assets/{info-A6RAGUB7-xf4Nzgv-.js → info-A6RAGUB7-BmlXzRWj.js} +1 -1
- package/dist/web/assets/{infoDiagram-VRGFBTTK-BYjw0eiK.js → infoDiagram-VRGFBTTK-CYPJPmBP.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-Bx9s4nPM.js → ishikawaDiagram-OU5B5YK6-YN96geky.js} +1 -1
- package/dist/web/assets/{journeyDiagram-ZHPQQLJL-DrdHs7B-.js → journeyDiagram-ZHPQQLJL-20juojKY.js} +1 -1
- package/dist/web/assets/{kanban-definition-PNTS6WVX-CVd8v2Te.js → kanban-definition-PNTS6WVX-BhCKIEbP.js} +1 -1
- package/dist/web/assets/{line-V_WrJ-6y.js → line-BERsMFG6.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-B-ilyEpO.js → mermaid-parser.core-Bis_wTB-.js} +3 -3
- package/dist/web/assets/{mermaid.core-Bnyaa0nF.js → mermaid.core-DMsM3ZX7.js} +4 -4
- package/dist/web/assets/{mindmap-definition-NLK3R4M7-B3AbvHPR.js → mindmap-definition-NLK3R4M7-BkcGdOfv.js} +1 -1
- package/dist/web/assets/{packet-AYTQ26CC-CBAIUXIc.js → packet-AYTQ26CC-Bh30-JJX.js} +1 -1
- package/dist/web/assets/{pegDiagram-GJSIUBJH-DIdeELUM.js → pegDiagram-GJSIUBJH-DxnIo9RF.js} +1 -1
- package/dist/web/assets/{pie-WAS4IAKB-a1r3tzPR.js → pie-WAS4IAKB-ChaTr-GD.js} +1 -1
- package/dist/web/assets/{pieDiagram-5QR66LMP-pQBEio0x.js → pieDiagram-5QR66LMP-CzAp78FM.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-O4NWA36T-CkJK5Y03.js → quadrantDiagram-O4NWA36T-BDSE-cfY.js} +1 -1
- package/dist/web/assets/{radar-RG4KPBEZ-BZfLYjx5.js → radar-RG4KPBEZ-DgJ-o_dq.js} +1 -1
- package/dist/web/assets/{railroad-74A4TZTK-CuIW1ZAU.js → railroad-74A4TZTK-C8MmcBKz.js} +1 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-CTLXjcdP.js +1 -0
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-ggWsTTL0.js +1 -0
- package/dist/web/assets/railroad-peg-WCYAUIDC-D1mCX705.js +1 -0
- package/dist/web/assets/{railroadDiagram-XR7U4H2S-_BpGE8aJ.js → railroadDiagram-XR7U4H2S-DfbcWRNh.js} +1 -1
- package/dist/web/assets/{requirementDiagram-PLB6GJNP-CnzF90GS.js → requirementDiagram-PLB6GJNP-DM3nksxs.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-IPEJSGJF-DYeisDj_.js → sankeyDiagram-IPEJSGJF-DthUpiiC.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-PO4LG4MO-ll9cBvca.js → sequenceDiagram-PO4LG4MO-DLA3nuvA.js} +1 -1
- package/dist/web/assets/{stateDiagram-v2-GCMORJYK-CdKcOIKQ.js → stateDiagram-v2-GCMORJYK-B0X6ihBW.js} +1 -1
- package/dist/web/assets/{swimlanes-2SLR337P-y6piTWEW.js → swimlanes-2SLR337P-B4qWe6vE.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-ClIk3e7S.js +8 -0
- package/dist/web/assets/{timeline-definition-EJHVYXUP-DuC_4xK0.js → timeline-definition-EJHVYXUP-F-cDzYrj.js} +1 -1
- package/dist/web/assets/{treeView-Q6P3EWNA-gCQFmND0.js → treeView-Q6P3EWNA-BaI30nLZ.js} +1 -1
- package/dist/web/assets/{treemap-WGGIJYW6-BQ2010OY.js → treemap-WGGIJYW6-D2jGGoMl.js} +1 -1
- package/dist/web/assets/{usecaseDiagram-POWQR4AR-CCTqzHz6.js → usecaseDiagram-POWQR4AR-B9qoAQCy.js} +1 -1
- package/dist/web/assets/{vennDiagram-UO4OBE2U-0_O7guNq.js → vennDiagram-UO4OBE2U-DGVNOaKO.js} +1 -1
- package/dist/web/assets/{wardley-WFR3VGLG-SsX7uMDs.js → wardley-WFR3VGLG-DbKqhNAh.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-VNRHLVJA-DlqH6Hox.js → wardleyDiagram-VNRHLVJA-BgZ1gprw.js} +1 -1
- package/dist/web/assets/{xychartDiagram-PMCCYNJV-nr4UYDtd.js → xychartDiagram-PMCCYNJV-D73RYid1.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -2
- package/dist/web/assets/architecture-7GRP2DOG-blXBarB2.js +0 -1
- package/dist/web/assets/channel-f4QzewnN.js +0 -1
- package/dist/web/assets/eventmodeling-NTZA5JFV-CA_ZFPZ2.js +0 -1
- package/dist/web/assets/flowDiagram-KWPJA3E3-DObsBuWT.js +0 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-6B4Ffbxn.js +0 -1
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-BGL9lIQc.js +0 -1
- package/dist/web/assets/railroad-peg-WCYAUIDC-DcqxTem7.js +0 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-DFS8a3jO.js +0 -8
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
projectSession
|
|
4
|
+
} from "./chunk-WVQC2EGO.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-GGXPG6OE.js";
|
|
15
|
+
import {
|
|
16
|
+
analyzeProject
|
|
17
|
+
} from "./chunk-MPG3IEGB.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-KHMBNT66.js.map
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
addRule,
|
|
4
|
+
listRules,
|
|
5
|
+
parseRules,
|
|
6
|
+
removeRule,
|
|
7
|
+
serializeRules,
|
|
8
|
+
updateRule
|
|
9
|
+
} from "./chunk-3IS2X2Z7.js";
|
|
10
|
+
import {
|
|
11
|
+
git
|
|
12
|
+
} from "./chunk-OLPSEQS5.js";
|
|
13
|
+
import {
|
|
14
|
+
scanText
|
|
15
|
+
} from "./chunk-JCGDR4UV.js";
|
|
16
|
+
import {
|
|
17
|
+
isBlockModified,
|
|
18
|
+
parseBlocks
|
|
19
|
+
} from "./chunk-673EPYYH.js";
|
|
20
|
+
import {
|
|
21
|
+
KNOWLEDGE_DOCS
|
|
22
|
+
} from "./chunk-FQEGEVLY.js";
|
|
23
|
+
import {
|
|
24
|
+
athenaDir,
|
|
25
|
+
readState
|
|
26
|
+
} from "./chunk-2DY4PUR4.js";
|
|
27
|
+
import {
|
|
28
|
+
readTextIfExists,
|
|
29
|
+
sha256,
|
|
30
|
+
writeFileAtomic
|
|
31
|
+
} from "./chunk-IQQBAWSM.js";
|
|
32
|
+
import {
|
|
33
|
+
AthenaError,
|
|
34
|
+
conflict,
|
|
35
|
+
notFound
|
|
36
|
+
} from "./chunk-AD4OZCU2.js";
|
|
37
|
+
|
|
38
|
+
// src/services/rules.ts
|
|
39
|
+
import path2 from "path";
|
|
40
|
+
|
|
41
|
+
// src/services/knowledge.ts
|
|
42
|
+
import path from "path";
|
|
43
|
+
var MAX_DOC_BYTES = 1e6;
|
|
44
|
+
function resolveDoc(id) {
|
|
45
|
+
const doc = KNOWLEDGE_DOCS.find((d) => d.id === id);
|
|
46
|
+
if (!doc) throw notFound(`Unknown knowledge document: ${id.slice(0, 64)}`);
|
|
47
|
+
return doc;
|
|
48
|
+
}
|
|
49
|
+
function contentHash(text) {
|
|
50
|
+
return sha256(text).slice(0, 16);
|
|
51
|
+
}
|
|
52
|
+
function segmentDocument(text) {
|
|
53
|
+
const segments = [];
|
|
54
|
+
let cursor = 0;
|
|
55
|
+
for (const b of parseBlocks(text)) {
|
|
56
|
+
const before = text.slice(cursor, b.start);
|
|
57
|
+
if (before.trim()) segments.push({ kind: "developer", content: before });
|
|
58
|
+
segments.push({ kind: "generated", id: b.id, modified: isBlockModified(b), content: b.content });
|
|
59
|
+
cursor = b.end;
|
|
60
|
+
}
|
|
61
|
+
const rest = text.slice(cursor);
|
|
62
|
+
if (rest.trim()) segments.push({ kind: "developer", content: rest });
|
|
63
|
+
return segments;
|
|
64
|
+
}
|
|
65
|
+
async function meta(root, id, text) {
|
|
66
|
+
const doc = resolveDoc(id);
|
|
67
|
+
const st = await readState(athenaDir(root));
|
|
68
|
+
const ds = st.kind === "ok" ? st.state.documents[id] : void 0;
|
|
69
|
+
const blocks = text ? parseBlocks(text) : [];
|
|
70
|
+
return {
|
|
71
|
+
id,
|
|
72
|
+
file: doc.file,
|
|
73
|
+
title: doc.title,
|
|
74
|
+
purpose: doc.purpose,
|
|
75
|
+
present: text !== null,
|
|
76
|
+
bytes: text ? Buffer.byteLength(text) : 0,
|
|
77
|
+
hash: text === null ? null : contentHash(text),
|
|
78
|
+
lastGeneratedAt: ds?.lastGeneratedAt ?? (st.kind === "ok" && id === "rules" ? st.state.createdAt : null),
|
|
79
|
+
lastChangedAt: ds?.lastChangedAt ?? null,
|
|
80
|
+
generatedSections: blocks.length,
|
|
81
|
+
editedSections: blocks.filter(isBlockModified).map((b) => b.id)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function listDocs(root) {
|
|
85
|
+
const out = [];
|
|
86
|
+
for (const d of KNOWLEDGE_DOCS) out.push(await meta(root, d.id, await readTextIfExists(path.join(athenaDir(root), d.file))));
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
async function readDoc(root, id) {
|
|
90
|
+
const doc = resolveDoc(id);
|
|
91
|
+
const text = await readTextIfExists(path.join(athenaDir(root), doc.file));
|
|
92
|
+
if (text === null) throw notFound(`${doc.file} does not exist. Run \`athena analyze\` to regenerate it.`);
|
|
93
|
+
return { ...await meta(root, doc.id, text), content: text, segments: segmentDocument(text) };
|
|
94
|
+
}
|
|
95
|
+
async function saveDoc(root, id, content, baseHash) {
|
|
96
|
+
const doc = resolveDoc(id);
|
|
97
|
+
if (typeof content !== "string") throw new AthenaError("content must be a string");
|
|
98
|
+
if (Buffer.byteLength(content) > MAX_DOC_BYTES) throw new AthenaError(`Document exceeds ${MAX_DOC_BYTES} bytes`, void 0, 1, "unprocessable");
|
|
99
|
+
const file = path.join(athenaDir(root), doc.file);
|
|
100
|
+
const current = await readTextIfExists(file);
|
|
101
|
+
const currentHash = current === null ? null : contentHash(current);
|
|
102
|
+
if (currentHash !== baseHash) {
|
|
103
|
+
throw conflict(`${doc.file} was changed on disk since you opened it.`, "Reload to see the latest version, then re-apply your edits.", { currentHash });
|
|
104
|
+
}
|
|
105
|
+
const secrets = scanText(content);
|
|
106
|
+
if (secrets.length) {
|
|
107
|
+
throw new AthenaError(
|
|
108
|
+
`Refusing to save: ${secrets.length} likely secret(s) found (${[...new Set(secrets.map((s) => s.type))].join(", ")}).`,
|
|
109
|
+
"Knowledge files must never contain secrets. Replace values with placeholders such as <configured externally>.",
|
|
110
|
+
1,
|
|
111
|
+
"unprocessable",
|
|
112
|
+
{ secrets: secrets.map((s) => ({ type: s.type, line: s.line })) }
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
116
|
+
await writeFileAtomic(file, normalized.endsWith("\n") ? normalized : `${normalized}
|
|
117
|
+
`);
|
|
118
|
+
return readDoc(root, doc.id);
|
|
119
|
+
}
|
|
120
|
+
async function searchDocs(root, query, limit = 50) {
|
|
121
|
+
const q = query.trim().toLowerCase();
|
|
122
|
+
if (q.length < 2) return [];
|
|
123
|
+
const hits = [];
|
|
124
|
+
for (const d of KNOWLEDGE_DOCS) {
|
|
125
|
+
const text = await readTextIfExists(path.join(athenaDir(root), d.file));
|
|
126
|
+
if (!text) continue;
|
|
127
|
+
const lines = text.split("\n");
|
|
128
|
+
for (let i = 0; i < lines.length && hits.length < limit; i++) {
|
|
129
|
+
const line = lines[i];
|
|
130
|
+
if (line.startsWith("<!-- athena:")) continue;
|
|
131
|
+
if (line.toLowerCase().includes(q)) hits.push({ id: d.id, file: d.file, title: d.title, line: i + 1, text: line.trim().slice(0, 240) });
|
|
132
|
+
}
|
|
133
|
+
if (d.title.toLowerCase().includes(q) && !hits.some((h) => h.id === d.id)) hits.unshift({ id: d.id, file: d.file, title: d.title, line: 1, text: d.purpose });
|
|
134
|
+
}
|
|
135
|
+
return hits.slice(0, limit);
|
|
136
|
+
}
|
|
137
|
+
async function docHistory(root, id, limit = 50) {
|
|
138
|
+
const doc = resolveDoc(id);
|
|
139
|
+
const rel = `.athena/${doc.file}`;
|
|
140
|
+
const r = await git(root, ["log", `-n${limit}`, "--follow", "--format=%H%x1f%cI%x1f%s", "--", rel]);
|
|
141
|
+
if (!r.ok) return { available: false, reason: "Git history is not available (not a Git repository, or git is not installed).", entries: [] };
|
|
142
|
+
const entries = r.stdout.split("\n").filter(Boolean).map((l) => {
|
|
143
|
+
const [sha, date, subject] = l.split("");
|
|
144
|
+
return { sha, date, subject: subject ?? "" };
|
|
145
|
+
});
|
|
146
|
+
if (!entries.length) return { available: true, reason: `${rel} has not been committed yet.`, entries };
|
|
147
|
+
return { available: true, entries };
|
|
148
|
+
}
|
|
149
|
+
async function docAtRevision(root, id, sha) {
|
|
150
|
+
const doc = resolveDoc(id);
|
|
151
|
+
if (!/^[0-9a-f]{7,64}$/i.test(sha)) throw new AthenaError("Invalid revision");
|
|
152
|
+
const prefix = (await git(root, ["rev-parse", "--show-prefix"])).stdout.trim();
|
|
153
|
+
const r = await git(root, ["show", `${sha}:${prefix}.athena/${doc.file}`]);
|
|
154
|
+
if (!r.ok) throw notFound(`${doc.file} does not exist at revision ${sha.slice(0, 12)}`);
|
|
155
|
+
return r.stdout;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/services/rules.ts
|
|
159
|
+
async function load(root) {
|
|
160
|
+
const file = path2.join(athenaDir(root), "rules.md");
|
|
161
|
+
const text = await readTextIfExists(file);
|
|
162
|
+
if (text === null) throw notFound(".athena/rules.md not found. Run `athena analyze` to recreate it.");
|
|
163
|
+
return { file, text, doc: parseRules(text) };
|
|
164
|
+
}
|
|
165
|
+
function view(text, doc) {
|
|
166
|
+
const rules = listRules(doc);
|
|
167
|
+
const sections = [.../* @__PURE__ */ new Set([...doc.lines.flatMap((l) => l.kind === "other" ? /^##\s+(.+?)\s*#*\s*$/.exec(l.raw)?.[1] ?? [] : []), ...rules.map((r) => r.section)])];
|
|
168
|
+
return { hash: contentHash(text), rules, sections };
|
|
169
|
+
}
|
|
170
|
+
async function getRules(root) {
|
|
171
|
+
const { text, doc } = await load(root);
|
|
172
|
+
return view(text, doc);
|
|
173
|
+
}
|
|
174
|
+
async function mutateRules(root, baseHash, fn) {
|
|
175
|
+
const { file, text, doc } = await load(root);
|
|
176
|
+
if (baseHash !== void 0 && baseHash !== contentHash(text)) {
|
|
177
|
+
throw conflict("rules.md changed since it was loaded.", "Reload the rules and try again.", { currentHash: contentHash(text) });
|
|
178
|
+
}
|
|
179
|
+
let next;
|
|
180
|
+
try {
|
|
181
|
+
next = fn(doc);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
throw new AthenaError(err.message);
|
|
184
|
+
}
|
|
185
|
+
const out = serializeRules(next);
|
|
186
|
+
const secrets = scanText(out).filter((s) => !scanText(text).some((o) => o.fingerprint === s.fingerprint));
|
|
187
|
+
if (secrets.length) throw new AthenaError("Refusing to save a rule that appears to contain a secret.", "Rules must never contain credentials.", 1, "unprocessable");
|
|
188
|
+
await writeFileAtomic(file, out);
|
|
189
|
+
return view(out, next);
|
|
190
|
+
}
|
|
191
|
+
var rulesAdd = (root, baseHash, section, text) => mutateRules(root, baseHash, (d) => addRule(d, section, text));
|
|
192
|
+
var rulesUpdate = (root, baseHash, index, patch) => mutateRules(root, baseHash, (d) => updateRule(d, index, patch));
|
|
193
|
+
var rulesRemove = (root, baseHash, index) => mutateRules(root, baseHash, (d) => removeRule(d, index));
|
|
194
|
+
|
|
195
|
+
export {
|
|
196
|
+
contentHash,
|
|
197
|
+
listDocs,
|
|
198
|
+
readDoc,
|
|
199
|
+
saveDoc,
|
|
200
|
+
searchDocs,
|
|
201
|
+
docHistory,
|
|
202
|
+
docAtRevision,
|
|
203
|
+
getRules,
|
|
204
|
+
mutateRules,
|
|
205
|
+
rulesAdd,
|
|
206
|
+
rulesUpdate,
|
|
207
|
+
rulesRemove
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=chunk-LEMPCNAY.js.map
|