project-athena 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +15 -8
- package/dist/activity-P4UUFHWV.js +77 -0
- package/dist/agents-3HTSQGL2.js +69 -0
- package/dist/ai-ASL4FFQ7.js +96 -0
- package/dist/analyze-F3YOILNL.js +49 -0
- package/dist/chunk-2DY4PUR4.js +465 -0
- package/dist/chunk-2R6PXY6Z.js +33 -0
- package/dist/chunk-2RS6TA3D.js +908 -0
- package/dist/chunk-32PRS2MG.js +169 -0
- package/dist/chunk-3IS2X2Z7.js +89 -0
- package/dist/chunk-65TSRDQ3.js +124 -0
- package/dist/chunk-673EPYYH.js +105 -0
- package/dist/chunk-AD4OZCU2.js +34 -0
- package/dist/chunk-CXUTYCZX.js +238 -0
- package/dist/chunk-DOQMAZL5.js +436 -0
- package/dist/chunk-EF6GHOIN.js +343 -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-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-JY6BFIJJ.js +95 -0
- package/dist/chunk-KBO7XRBW.js +26 -0
- package/dist/chunk-LEMPCNAY.js +209 -0
- package/dist/chunk-LMOFZNTQ.js +379 -0
- package/dist/chunk-OLPSEQS5.js +264 -0
- package/dist/chunk-PANVPNYF.js +2892 -0
- package/dist/chunk-PDFQ5BFG.js +378 -0
- package/dist/chunk-PR53DT7I.js +179 -0
- package/dist/chunk-PVCIGUIT.js +64 -0
- package/dist/chunk-RXNRMIZP.js +390 -0
- package/dist/chunk-S7T6VX56.js +61 -0
- package/dist/chunk-SHKXELUX.js +295 -0
- package/dist/chunk-SJOOXO34.js +904 -0
- package/dist/chunk-TGJUUB44.js +399 -0
- package/dist/chunk-VSYBC3VY.js +28 -0
- package/dist/chunk-XB66PZ6Z.js +182 -0
- package/dist/chunk-XFN7UZCS.js +35 -0
- package/dist/chunk-XW3MLHI2.js +32 -0
- package/dist/chunk-Z6QWMJLK.js +124 -0
- package/dist/chunk-ZB7LC6FP.js +221 -0
- package/dist/clean-S7PKCOLM.js +23 -0
- package/dist/cli.js +18 -10716
- package/dist/context-AH5MFHRG.js +147 -0
- package/dist/doctor-RSJMLE2P.js +62 -0
- package/dist/event-TNT6CQZR.js +101 -0
- package/dist/git-hook-YZNZGAAP.js +252 -0
- package/dist/init-2V34DVRQ.js +37 -0
- package/dist/mcp-T2GRTEX5.js +255 -0
- package/dist/open-2QSZKKUG.js +1259 -0
- package/dist/program-V2USHGXN.js +148 -0
- package/dist/review-R3R2J5BJ.js +115 -0
- package/dist/rules-4FXJUCXB.js +83 -0
- package/dist/security-PI4LXCOB.js +175 -0
- package/dist/staged-check-HHIBKKMR.js +500 -0
- package/dist/status-6M73WX5I.js +75 -0
- package/dist/sync-3ZNJDSSU.js +216 -0
- package/dist/watch-USN5CQIV.js +121 -0
- package/dist/web/assets/{Editor-DAXvniWB.js → Editor-BI56-HX5.js} +1 -1
- package/dist/web/assets/{abnfDiagram-O67JEVCF-Az7EW1BC.js → abnfDiagram-O67JEVCF-y_9px2ob.js} +1 -1
- package/dist/web/assets/architecture-7GRP2DOG-DyxkwYiW.js +1 -0
- package/dist/web/assets/{architectureDiagram-NJMV4G6O-BxeXqSTn.js → architectureDiagram-NJMV4G6O-lB6fcVzU.js} +1 -1
- package/dist/web/assets/{blockDiagram-BEXU5L5S-aeKZl9lJ.js → blockDiagram-BEXU5L5S-DcM5pEjM.js} +1 -1
- package/dist/web/assets/{c4Diagram-YGBWAQC7-CgmntYsG.js → c4Diagram-YGBWAQC7-CJgqxTY2.js} +1 -1
- package/dist/web/assets/channel-BvDD7AMO.js +1 -0
- package/dist/web/assets/{chunk-3FUC2YCW-BpmRD2yo.js → chunk-3FUC2YCW-B8l_VuWR.js} +1 -1
- package/dist/web/assets/{chunk-5DYCD2WN-CRIFDwod.js → chunk-5DYCD2WN-CZecgNhf.js} +1 -1
- package/dist/web/assets/{chunk-742MDFTN-hRW9VeNV.js → chunk-742MDFTN-BeZ7P5jB.js} +1 -1
- package/dist/web/assets/{chunk-7INBJB4K-C6VEpT0Y.js → chunk-7INBJB4K-IKy0pr5m.js} +1 -1
- package/dist/web/assets/{chunk-7M6MHVWA-Dnajov2w.js → chunk-7M6MHVWA-Dg_QqJEX.js} +1 -1
- package/dist/web/assets/{chunk-7PRAP22T-DSAc0_CZ.js → chunk-7PRAP22T-DqQNS8oq.js} +1 -1
- package/dist/web/assets/{chunk-GTNCS2PH-CefpYUjb.js → chunk-GTNCS2PH-x4sM51nJ.js} +1 -1
- package/dist/web/assets/{chunk-GWA4HPMP-MLNflMmF.js → chunk-GWA4HPMP-CE5GcwVN.js} +1 -1
- package/dist/web/assets/{chunk-MBY4JIJT-D3GmmEiU.js → chunk-MBY4JIJT-DA_D1hjF.js} +1 -1
- package/dist/web/assets/{chunk-NETBCI7D-DzTj6xVk.js → chunk-NETBCI7D-CeJ3XBZ2.js} +1 -1
- package/dist/web/assets/{chunk-O7XYJQB3-D1d0hCko.js → chunk-O7XYJQB3-BH2gESvO.js} +1 -1
- package/dist/web/assets/{chunk-UA2S7LBM-C9hIFeWR.js → chunk-UA2S7LBM-BWr0xCux.js} +1 -1
- package/dist/web/assets/{chunk-WEXAMYUT-BFGtcjsi.js → chunk-WEXAMYUT-B4QwUZPM.js} +1 -1
- package/dist/web/assets/{chunk-Z7XXMR3K-BRZ8_wtE.js → chunk-Z7XXMR3K-BxcA-y5B.js} +1 -1
- package/dist/web/assets/{chunk-ZIGJFQKS-CTKOioal.js → chunk-ZIGJFQKS-BZYvP63G.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-NBCMYWYE-DJGKtWJv.js → classDiagram-v2-NBCMYWYE-BS3P_NXb.js} +1 -1
- package/dist/web/assets/{cynefin-OW5HDTMX-hNq1kGmA.js → cynefin-OW5HDTMX-BALD4Pve.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-VND7K2PF-sODQM_ZZ.js → cynefinDiagram-VND7K2PF-BM2y4W6d.js} +1 -1
- package/dist/web/assets/{dagre-6A5THRUB-DEzTThIf.js → dagre-6A5THRUB-PTfOrIdF.js} +1 -1
- package/dist/web/assets/{diagram-22UHCM2B-s_eellsa.js → diagram-22UHCM2B-CpXEHhUl.js} +1 -1
- package/dist/web/assets/{diagram-3UASUU5V-B2onY_Xw.js → diagram-3UASUU5V-DPHfXzr6.js} +1 -1
- package/dist/web/assets/{diagram-ATOU4E4O-B8V6s13s.js → diagram-ATOU4E4O-DMBNXvAC.js} +1 -1
- package/dist/web/assets/{diagram-CDSNMT55-CJxiE6mf.js → diagram-CDSNMT55-CDLe1M7G.js} +1 -1
- package/dist/web/assets/{diagram-MLGK6HIB-m5OHPLAp.js → diagram-MLGK6HIB-D6L8oE7j.js} +1 -1
- package/dist/web/assets/{diagram-MPIPVDR6-Buvvrk_A.js → diagram-MPIPVDR6-CP-S7rrQ.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-ZINNZB2B-CpL7Wt3R.js → ebnfDiagram-ZINNZB2B-CkUc63TK.js} +1 -1
- package/dist/web/assets/{elk-276RUBZZ-CwYhbsXa.js → elk-276RUBZZ-C1CAJLH_.js} +1 -1
- package/dist/web/assets/{erDiagram-OPXOYQCR-DNikci2r.js → erDiagram-OPXOYQCR-DXBnaTHm.js} +1 -1
- package/dist/web/assets/eventmodeling-NTZA5JFV-cLIzvgDp.js +1 -0
- package/dist/web/assets/flowDiagram-KWPJA3E3-D7Zq63VQ.js +1 -0
- package/dist/web/assets/{ganttDiagram-FUAMR5RP-d-ynDlvJ.js → ganttDiagram-FUAMR5RP-BVm8SiGg.js} +1 -1
- package/dist/web/assets/{gitGraph-4MIJSDKK-DwIeBI91.js → gitGraph-4MIJSDKK-Dm7xBrW0.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-X574FWY7-04RZccu6.js → gitGraphDiagram-X574FWY7-D_PIKKhx.js} +1 -1
- package/dist/web/assets/{index-C3-8uYXr.js → index-CeOIBpu2.js} +3 -3
- package/dist/web/assets/{info-A6RAGUB7-xf4Nzgv-.js → info-A6RAGUB7-CLGrx8pn.js} +1 -1
- package/dist/web/assets/{infoDiagram-VRGFBTTK-BYjw0eiK.js → infoDiagram-VRGFBTTK-BlBjaKm7.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-Bx9s4nPM.js → ishikawaDiagram-OU5B5YK6-Kxe7GGS3.js} +1 -1
- package/dist/web/assets/{journeyDiagram-ZHPQQLJL-DrdHs7B-.js → journeyDiagram-ZHPQQLJL-wCMoHK4M.js} +1 -1
- package/dist/web/assets/{kanban-definition-PNTS6WVX-CVd8v2Te.js → kanban-definition-PNTS6WVX-BxdCz9Ko.js} +1 -1
- package/dist/web/assets/{line-V_WrJ-6y.js → line-Zf11FnWF.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-B-ilyEpO.js → mermaid-parser.core-BvFFklev.js} +3 -3
- package/dist/web/assets/{mermaid.core-Bnyaa0nF.js → mermaid.core-Cd_Ssti9.js} +4 -4
- package/dist/web/assets/{mindmap-definition-NLK3R4M7-B3AbvHPR.js → mindmap-definition-NLK3R4M7-BGPQl-GY.js} +1 -1
- package/dist/web/assets/{packet-AYTQ26CC-CBAIUXIc.js → packet-AYTQ26CC-6lI3cEX_.js} +1 -1
- package/dist/web/assets/{pegDiagram-GJSIUBJH-DIdeELUM.js → pegDiagram-GJSIUBJH-BPIjlSAj.js} +1 -1
- package/dist/web/assets/{pie-WAS4IAKB-a1r3tzPR.js → pie-WAS4IAKB-D6Dzj3Ja.js} +1 -1
- package/dist/web/assets/{pieDiagram-5QR66LMP-pQBEio0x.js → pieDiagram-5QR66LMP-DtA-vZmv.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-O4NWA36T-CkJK5Y03.js → quadrantDiagram-O4NWA36T-EettyqQn.js} +1 -1
- package/dist/web/assets/{radar-RG4KPBEZ-BZfLYjx5.js → radar-RG4KPBEZ-xmRc3Dl8.js} +1 -1
- package/dist/web/assets/{railroad-74A4TZTK-CuIW1ZAU.js → railroad-74A4TZTK-BLB7dhOn.js} +1 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-DBFym6g8.js +1 -0
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-CkG28ig8.js +1 -0
- package/dist/web/assets/railroad-peg-WCYAUIDC-Df_Ifaad.js +1 -0
- package/dist/web/assets/{railroadDiagram-XR7U4H2S-_BpGE8aJ.js → railroadDiagram-XR7U4H2S-PBw4We6b.js} +1 -1
- package/dist/web/assets/{requirementDiagram-PLB6GJNP-CnzF90GS.js → requirementDiagram-PLB6GJNP-DjQpaUFA.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-IPEJSGJF-DYeisDj_.js → sankeyDiagram-IPEJSGJF-CBW-_AoF.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-PO4LG4MO-ll9cBvca.js → sequenceDiagram-PO4LG4MO-DEoPYqY9.js} +1 -1
- package/dist/web/assets/{stateDiagram-v2-GCMORJYK-CdKcOIKQ.js → stateDiagram-v2-GCMORJYK-UZq5rPud.js} +1 -1
- package/dist/web/assets/{swimlanes-2SLR337P-y6piTWEW.js → swimlanes-2SLR337P-CACfuXJR.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-CDSrqdgx.js +8 -0
- package/dist/web/assets/{timeline-definition-EJHVYXUP-DuC_4xK0.js → timeline-definition-EJHVYXUP-CYJl7mP-.js} +1 -1
- package/dist/web/assets/{treeView-Q6P3EWNA-gCQFmND0.js → treeView-Q6P3EWNA-DtZ5nkPQ.js} +1 -1
- package/dist/web/assets/{treemap-WGGIJYW6-BQ2010OY.js → treemap-WGGIJYW6-m49GDM-M.js} +1 -1
- package/dist/web/assets/{usecaseDiagram-POWQR4AR-CCTqzHz6.js → usecaseDiagram-POWQR4AR-C2Vg20UK.js} +1 -1
- package/dist/web/assets/{vennDiagram-UO4OBE2U-0_O7guNq.js → vennDiagram-UO4OBE2U-C07v5OGI.js} +1 -1
- package/dist/web/assets/{wardley-WFR3VGLG-SsX7uMDs.js → wardley-WFR3VGLG-C9Z7EZtz.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-VNRHLVJA-DlqH6Hox.js → wardleyDiagram-VNRHLVJA-DdA8CcSC.js} +1 -1
- package/dist/web/assets/{xychartDiagram-PMCCYNJV-nr4UYDtd.js → xychartDiagram-PMCCYNJV-C9EZw07b.js} +1 -1
- package/dist/web/index.html +1 -1
- 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,378 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ProjectModel,
|
|
4
|
+
SCAN_FILE,
|
|
5
|
+
SecurityScan
|
|
6
|
+
} from "./chunk-PANVPNYF.js";
|
|
7
|
+
import {
|
|
8
|
+
athenaDir,
|
|
9
|
+
readState
|
|
10
|
+
} from "./chunk-2DY4PUR4.js";
|
|
11
|
+
import {
|
|
12
|
+
readTextIfExists,
|
|
13
|
+
writeFileAtomic
|
|
14
|
+
} from "./chunk-IQQBAWSM.js";
|
|
15
|
+
import {
|
|
16
|
+
AthenaError
|
|
17
|
+
} from "./chunk-AD4OZCU2.js";
|
|
18
|
+
import {
|
|
19
|
+
toPosix
|
|
20
|
+
} from "./chunk-XW3MLHI2.js";
|
|
21
|
+
|
|
22
|
+
// src/core/graph/graph.ts
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
import path from "path";
|
|
25
|
+
var NodeKind = z.enum(["package", "file", "route", "entity", "framework", "command", "infra", "doc"]);
|
|
26
|
+
var EdgeKind = z.enum(["contains", "imports", "handles", "defines", "depends_on", "documents", "runs"]);
|
|
27
|
+
var GraphNode = z.object({
|
|
28
|
+
id: z.string(),
|
|
29
|
+
kind: NodeKind,
|
|
30
|
+
name: z.string(),
|
|
31
|
+
path: z.string().optional(),
|
|
32
|
+
meta: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).default({})
|
|
33
|
+
});
|
|
34
|
+
var GraphEdge = z.object({ from: z.string(), to: z.string(), kind: EdgeKind });
|
|
35
|
+
var ProjectGraph = z.object({
|
|
36
|
+
schemaVersion: z.literal(1),
|
|
37
|
+
builtAt: z.string(),
|
|
38
|
+
nodes: z.array(GraphNode),
|
|
39
|
+
edges: z.array(GraphEdge),
|
|
40
|
+
stats: z.object({ nodes: z.number(), edges: z.number(), byKind: z.record(z.string(), z.number()) })
|
|
41
|
+
});
|
|
42
|
+
var GRAPH_FILE = "graph.json";
|
|
43
|
+
var nodeId = (kind, key) => `${kind}:${key}`;
|
|
44
|
+
var IMPORT_RE = /^\s*(?:import\s[^'"]*from\s*['"]([^'"]+)['"]|import\s*['"]([^'"]+)['"]|export\s[^'"]*from\s*['"]([^'"]+)['"]|(?:const|let|var)\s[^=]*=\s*require\(\s*['"]([^'"]+)['"]\s*\))/gm;
|
|
45
|
+
var PY_IMPORT_RE = /^\s*(?:from\s+([.\w]+)\s+import|import\s+([.\w]+))/gm;
|
|
46
|
+
var RESOLVE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".py", ".vue", ".svelte"];
|
|
47
|
+
function resolveImport(fromFile, spec, fileSet) {
|
|
48
|
+
if (!spec.startsWith(".")) return null;
|
|
49
|
+
const base = toPosix(path.posix.normalize(path.posix.join(path.posix.dirname(fromFile), spec)));
|
|
50
|
+
if (fileSet.has(base)) return base;
|
|
51
|
+
for (const ext of RESOLVE_EXTS) {
|
|
52
|
+
for (const c of [`${base}${ext}`, `${base}/index${ext}`, `${base}/__init__${ext}`]) if (fileSet.has(c)) return c;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
var compare = new Intl.Collator().compare;
|
|
57
|
+
async function buildGraph(model, opts) {
|
|
58
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
59
|
+
const edges = [];
|
|
60
|
+
const edgeKeys = /* @__PURE__ */ new Set();
|
|
61
|
+
const fileSet = new Set(opts.files);
|
|
62
|
+
const addNode = (n) => {
|
|
63
|
+
if (!nodes.has(n.id)) nodes.set(n.id, n);
|
|
64
|
+
return n.id;
|
|
65
|
+
};
|
|
66
|
+
const addEdge = (from, to, kind) => {
|
|
67
|
+
if (from === to || !nodes.has(from) || !nodes.has(to)) return;
|
|
68
|
+
const key = `${from}\0${to}\0${kind}`;
|
|
69
|
+
if (edgeKeys.has(key)) return;
|
|
70
|
+
edgeKeys.add(key);
|
|
71
|
+
edges.push({ from, to, kind });
|
|
72
|
+
};
|
|
73
|
+
const fileNode = (p) => addNode({ id: nodeId("file", p), kind: "file", name: path.posix.basename(p), path: p, meta: {} });
|
|
74
|
+
for (const pkg of model.workspace.packages) {
|
|
75
|
+
addNode({ id: nodeId("package", pkg.name), kind: "package", name: pkg.name, path: pkg.path, meta: { kind: pkg.kind, ecosystem: pkg.ecosystem } });
|
|
76
|
+
}
|
|
77
|
+
for (const pkg of model.workspace.packages) {
|
|
78
|
+
for (const dep of pkg.internalDependencies) addEdge(nodeId("package", pkg.name), nodeId("package", dep), "depends_on");
|
|
79
|
+
}
|
|
80
|
+
const packageAtPath = /* @__PURE__ */ new Map();
|
|
81
|
+
for (const pkg of model.workspace.packages) if (!packageAtPath.has(pkg.path)) packageAtPath.set(pkg.path, pkg);
|
|
82
|
+
for (const f of model.frameworks) {
|
|
83
|
+
const id = addNode({ id: nodeId("framework", `${f.name}@${f.root}`), kind: "framework", name: f.name, path: f.root, meta: { category: f.category, status: f.provenance.status, confidence: f.provenance.confidence } });
|
|
84
|
+
const owner = packageAtPath.get(f.root);
|
|
85
|
+
if (owner) addEdge(nodeId("package", owner.name), id, "contains");
|
|
86
|
+
}
|
|
87
|
+
for (const r of model.routes) {
|
|
88
|
+
const id = addNode({ id: nodeId("route", `${r.method} ${r.path} ${r.file}`), kind: "route", name: `${r.method} ${r.path}`, path: r.file, meta: { method: r.method, framework: r.framework, ...r.line ? { line: r.line } : {} } });
|
|
89
|
+
addEdge(id, fileNode(r.file), "handles");
|
|
90
|
+
}
|
|
91
|
+
const entityByName = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const x of model.dbEntities) {
|
|
93
|
+
const k = x.name.toLowerCase();
|
|
94
|
+
if (!entityByName.has(k)) entityByName.set(k, x);
|
|
95
|
+
}
|
|
96
|
+
for (const e of model.dbEntities) {
|
|
97
|
+
const id = addNode({ id: nodeId("entity", `${e.name}@${e.file}`), kind: "entity", name: e.name, path: e.file, meta: { kind: e.kind, fields: e.fields.length, indexes: e.indexes.length } });
|
|
98
|
+
addEdge(id, fileNode(e.file), "defines");
|
|
99
|
+
for (const rel of e.relations) {
|
|
100
|
+
const target = rel.split("\u2192")[1]?.trim();
|
|
101
|
+
const other = target ? entityByName.get(target.toLowerCase()) : void 0;
|
|
102
|
+
if (other) addEdge(id, nodeId("entity", `${other.name}@${other.file}`), "depends_on");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const e of model.entryPoints) fileNode(e.path);
|
|
106
|
+
for (const c of model.commands) {
|
|
107
|
+
const id = addNode({ id: nodeId("command", `${c.source}:${c.name}`), kind: "command", name: c.name, path: c.source, meta: { purpose: c.purpose, command: c.command.slice(0, 120) } });
|
|
108
|
+
addEdge(id, fileNode(c.source), "runs");
|
|
109
|
+
}
|
|
110
|
+
for (const i of model.infrastructure) addNode({ id: nodeId("infra", `${i.name}@${i.file}`), kind: "infra", name: i.name, path: i.file, meta: { kind: i.kind } });
|
|
111
|
+
for (const d of model.containers.dockerfiles) addNode({ id: nodeId("infra", `Dockerfile@${d.path}`), kind: "infra", name: "Dockerfile", path: d.path, meta: { kind: "container" } });
|
|
112
|
+
for (const s of model.containers.services) addNode({ id: nodeId("infra", `service:${s.name}@${s.file}`), kind: "infra", name: s.name, path: s.file, meta: { kind: "service", image: s.image ?? "" } });
|
|
113
|
+
for (const n of [...nodes.values()].filter((x) => x.kind === "file" && x.path)) {
|
|
114
|
+
const p = n.path;
|
|
115
|
+
let owner;
|
|
116
|
+
for (let i = p.lastIndexOf("/"); i >= 0 && !owner; i = i > 0 ? p.lastIndexOf("/", i - 1) : -1) {
|
|
117
|
+
const prefix = p.slice(0, i);
|
|
118
|
+
if (prefix !== ".") owner = packageAtPath.get(prefix);
|
|
119
|
+
}
|
|
120
|
+
if (owner) addEdge(nodeId("package", owner.name), n.id, "contains");
|
|
121
|
+
}
|
|
122
|
+
const maxImportFiles = opts.maxImportFiles ?? 2e3;
|
|
123
|
+
const queue = [...nodes.values()].filter((n) => n.kind === "file").map((n) => n.path);
|
|
124
|
+
const parsed = /* @__PURE__ */ new Set();
|
|
125
|
+
let head = 0;
|
|
126
|
+
while (head < queue.length && parsed.size < maxImportFiles) {
|
|
127
|
+
opts.signal?.throwIfAborted();
|
|
128
|
+
const p = queue[head++];
|
|
129
|
+
if (parsed.has(p)) continue;
|
|
130
|
+
parsed.add(p);
|
|
131
|
+
const n = { id: nodeId("file", p) };
|
|
132
|
+
if (!/\.(m|c)?(t|j)sx?$|\.py$/.test(p)) continue;
|
|
133
|
+
const text = await opts.read(p);
|
|
134
|
+
if (!text) continue;
|
|
135
|
+
const specs = [];
|
|
136
|
+
for (const m of text.matchAll(IMPORT_RE)) specs.push(m[1] ?? m[2] ?? m[3] ?? m[4]);
|
|
137
|
+
if (p.endsWith(".py")) for (const m of text.matchAll(PY_IMPORT_RE)) specs.push((m[1] ?? m[2]).replace(/\./g, "/"));
|
|
138
|
+
for (const spec of specs.slice(0, 200)) {
|
|
139
|
+
const target = resolveImport(p, spec, fileSet);
|
|
140
|
+
if (!target) continue;
|
|
141
|
+
addEdge(n.id, fileNode(target), "imports");
|
|
142
|
+
if (!parsed.has(target)) queue.push(target);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const byKind = {};
|
|
146
|
+
for (const n of nodes.values()) byKind[n.kind] = (byKind[n.kind] ?? 0) + 1;
|
|
147
|
+
return {
|
|
148
|
+
schemaVersion: 1,
|
|
149
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
150
|
+
nodes: [...nodes.values()].sort((a, b) => compare(a.id, b.id)),
|
|
151
|
+
edges: edges.sort((a, b) => compare(a.from, b.from) || compare(a.to, b.to)),
|
|
152
|
+
stats: { nodes: nodes.size, edges: edges.length, byKind }
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
async function saveGraph(root, graph) {
|
|
156
|
+
await writeFileAtomic(path.join(athenaDir(root), GRAPH_FILE), `${JSON.stringify(graph, null, 2)}
|
|
157
|
+
`);
|
|
158
|
+
}
|
|
159
|
+
async function loadGraph(root) {
|
|
160
|
+
const raw = await readTextIfExists(path.join(athenaDir(root), GRAPH_FILE));
|
|
161
|
+
if (!raw) return null;
|
|
162
|
+
try {
|
|
163
|
+
const parsed = ProjectGraph.safeParse(JSON.parse(raw));
|
|
164
|
+
return parsed.success ? parsed.data : null;
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
170
|
+
function graphIndex(graph) {
|
|
171
|
+
let idx = indexes.get(graph);
|
|
172
|
+
if (idx && idx.nodeCount === graph.nodes.length && idx.edgeCount === graph.edges.length) return idx;
|
|
173
|
+
const byId = /* @__PURE__ */ new Map();
|
|
174
|
+
for (const n of graph.nodes) byId.set(n.id, n);
|
|
175
|
+
const incident = /* @__PURE__ */ new Map();
|
|
176
|
+
const add = (id, i) => {
|
|
177
|
+
const list = incident.get(id);
|
|
178
|
+
if (!list) incident.set(id, [i]);
|
|
179
|
+
else if (list[list.length - 1] !== i) list.push(i);
|
|
180
|
+
};
|
|
181
|
+
graph.edges.forEach((e, i) => {
|
|
182
|
+
add(e.from, i);
|
|
183
|
+
add(e.to, i);
|
|
184
|
+
});
|
|
185
|
+
idx = { byId, incident, nodeCount: graph.nodes.length, edgeCount: graph.edges.length };
|
|
186
|
+
indexes.set(graph, idx);
|
|
187
|
+
return idx;
|
|
188
|
+
}
|
|
189
|
+
function neighbors(graph, id) {
|
|
190
|
+
const node = graph.nodes.find((n) => n.id === id);
|
|
191
|
+
if (!node) return null;
|
|
192
|
+
const { byId, incident } = graphIndex(graph);
|
|
193
|
+
const out = [];
|
|
194
|
+
for (const i of incident.get(id) ?? []) {
|
|
195
|
+
const e = graph.edges[i];
|
|
196
|
+
if (e.from === id && byId.has(e.to)) out.push({ edge: e, other: byId.get(e.to), direction: "out" });
|
|
197
|
+
else if (e.to === id && byId.has(e.from)) out.push({ edge: e, other: byId.get(e.from), direction: "in" });
|
|
198
|
+
}
|
|
199
|
+
return { node, edges: out };
|
|
200
|
+
}
|
|
201
|
+
function expandFromFiles(graph, files, depth = 1, limit = 40) {
|
|
202
|
+
const { byId, incident } = graphIndex(graph);
|
|
203
|
+
const fileSet = new Set(files);
|
|
204
|
+
const seed = graph.nodes.filter((n) => n.path && fileSet.has(n.path));
|
|
205
|
+
const seen = new Set(seed.map((n) => n.id));
|
|
206
|
+
let frontier = new Set(seen);
|
|
207
|
+
for (let d = 0; d < depth; d++) {
|
|
208
|
+
const touched = /* @__PURE__ */ new Set();
|
|
209
|
+
for (const id of frontier) for (const i of incident.get(id) ?? []) touched.add(i);
|
|
210
|
+
const next = /* @__PURE__ */ new Set();
|
|
211
|
+
for (const i of [...touched].sort((a, b) => a - b)) {
|
|
212
|
+
const e = graph.edges[i];
|
|
213
|
+
if (frontier.has(e.from) && !seen.has(e.to)) {
|
|
214
|
+
seen.add(e.to);
|
|
215
|
+
next.add(e.to);
|
|
216
|
+
} else if (frontier.has(e.to) && !seen.has(e.from)) {
|
|
217
|
+
seen.add(e.from);
|
|
218
|
+
next.add(e.from);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
frontier = next;
|
|
222
|
+
if (!frontier.size) break;
|
|
223
|
+
}
|
|
224
|
+
return [...seen].map((id) => byId.get(id)).filter((n) => Boolean(n)).slice(0, limit);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/services/project-session.ts
|
|
228
|
+
import { promises as fs } from "fs";
|
|
229
|
+
import path2 from "path";
|
|
230
|
+
var fileKey = (st) => `${st.ino}:${st.size}:${st.mtimeNs}`;
|
|
231
|
+
var ProjectSession = class {
|
|
232
|
+
constructor(root) {
|
|
233
|
+
this.root = root;
|
|
234
|
+
}
|
|
235
|
+
root;
|
|
236
|
+
cache = /* @__PURE__ */ new Map();
|
|
237
|
+
inflight = /* @__PURE__ */ new Map();
|
|
238
|
+
stateEntry = null;
|
|
239
|
+
get dir() {
|
|
240
|
+
return athenaDir(this.root);
|
|
241
|
+
}
|
|
242
|
+
/** model.json, or why it can't be used. */
|
|
243
|
+
readModel() {
|
|
244
|
+
return this.read("model.json", ProjectModel);
|
|
245
|
+
}
|
|
246
|
+
/** The parsed model, or null when missing or unusable. */
|
|
247
|
+
async model() {
|
|
248
|
+
const r = await this.readModel();
|
|
249
|
+
return r.kind === "ok" ? r.value : null;
|
|
250
|
+
}
|
|
251
|
+
/** The parsed model; throws an AthenaError with a hint when it is missing or corrupted. */
|
|
252
|
+
async requireModel() {
|
|
253
|
+
const r = await this.readModel();
|
|
254
|
+
if (r.kind === "ok") return r.value;
|
|
255
|
+
if (r.kind === "missing") throw new AthenaError("model.json is missing.", "Run `athena analyze` (or re-analyze from the overview) first.");
|
|
256
|
+
throw new AthenaError(`model.json is corrupted (${r.reason}).`, "Run `athena analyze` to rebuild it.");
|
|
257
|
+
}
|
|
258
|
+
/** Cheap existence check (a stat, no parsing). */
|
|
259
|
+
async hasModel() {
|
|
260
|
+
return this.exists("model.json");
|
|
261
|
+
}
|
|
262
|
+
readGraph() {
|
|
263
|
+
return this.read(GRAPH_FILE, ProjectGraph);
|
|
264
|
+
}
|
|
265
|
+
async graph() {
|
|
266
|
+
const r = await this.readGraph();
|
|
267
|
+
return r.kind === "ok" ? r.value : null;
|
|
268
|
+
}
|
|
269
|
+
readScan() {
|
|
270
|
+
return this.read(SCAN_FILE, SecurityScan);
|
|
271
|
+
}
|
|
272
|
+
async scan() {
|
|
273
|
+
const r = await this.readScan();
|
|
274
|
+
return r.kind === "ok" ? r.value : null;
|
|
275
|
+
}
|
|
276
|
+
/** state.json via `readState`, memoized like the other artifacts. */
|
|
277
|
+
async state() {
|
|
278
|
+
const file = path2.join(this.dir, "state.json");
|
|
279
|
+
const st = await fs.stat(file, { bigint: true }).catch(() => null);
|
|
280
|
+
if (!st) return readState(this.dir);
|
|
281
|
+
const key = fileKey(st);
|
|
282
|
+
if (this.stateEntry?.key === key) return this.stateEntry.state;
|
|
283
|
+
const state = await readState(this.dir);
|
|
284
|
+
const after = await fs.stat(file, { bigint: true }).catch(() => null);
|
|
285
|
+
if (after && fileKey(after) === key) this.stateEntry = { key, state };
|
|
286
|
+
return state;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Record a value this process just wrote to `.athena/<name>`, so the next
|
|
290
|
+
* read doesn't parse it again. The file must already be on disk.
|
|
291
|
+
*/
|
|
292
|
+
async remember(name, value) {
|
|
293
|
+
const st = await fs.stat(path2.join(this.dir, name), { bigint: true }).catch(() => null);
|
|
294
|
+
if (st) this.cache.set(name, { key: fileKey(st), result: { kind: "ok", value } });
|
|
295
|
+
else this.cache.delete(name);
|
|
296
|
+
}
|
|
297
|
+
/** Drop memoized results (all, or one file). */
|
|
298
|
+
invalidate(name) {
|
|
299
|
+
if (!name || name === "state.json") this.stateEntry = null;
|
|
300
|
+
if (name) this.cache.delete(name);
|
|
301
|
+
else this.cache.clear();
|
|
302
|
+
}
|
|
303
|
+
async exists(name) {
|
|
304
|
+
const st = await fs.stat(path2.join(this.dir, name)).catch(() => null);
|
|
305
|
+
return Boolean(st?.isFile());
|
|
306
|
+
}
|
|
307
|
+
read(name, schema) {
|
|
308
|
+
const running = this.inflight.get(name);
|
|
309
|
+
if (running) return running;
|
|
310
|
+
const p = this.load(name, schema).finally(() => this.inflight.delete(name));
|
|
311
|
+
this.inflight.set(name, p);
|
|
312
|
+
return p;
|
|
313
|
+
}
|
|
314
|
+
async load(name, schema) {
|
|
315
|
+
const file = path2.join(this.dir, name);
|
|
316
|
+
let st;
|
|
317
|
+
try {
|
|
318
|
+
st = await fs.stat(file, { bigint: true });
|
|
319
|
+
} catch (err) {
|
|
320
|
+
const code = err.code;
|
|
321
|
+
this.cache.delete(name);
|
|
322
|
+
if (code === "ENOENT" || code === "ENOTDIR") return { kind: "missing" };
|
|
323
|
+
return { kind: "corrupted", reason: `unreadable (${code})` };
|
|
324
|
+
}
|
|
325
|
+
const key = fileKey(st);
|
|
326
|
+
const hit = this.cache.get(name);
|
|
327
|
+
if (hit?.key === key) return hit.result;
|
|
328
|
+
let raw;
|
|
329
|
+
try {
|
|
330
|
+
raw = await fs.readFile(file, "utf8");
|
|
331
|
+
} catch (err) {
|
|
332
|
+
const code = err.code;
|
|
333
|
+
if (code === "ENOENT") return { kind: "missing" };
|
|
334
|
+
return { kind: "corrupted", reason: `unreadable (${code})` };
|
|
335
|
+
}
|
|
336
|
+
const result = parseArtifact(raw, schema);
|
|
337
|
+
const after = await fs.stat(file, { bigint: true }).catch(() => null);
|
|
338
|
+
if (after && fileKey(after) === key) this.cache.set(name, { key, result });
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
function parseArtifact(raw, schema) {
|
|
343
|
+
let json;
|
|
344
|
+
try {
|
|
345
|
+
json = JSON.parse(raw);
|
|
346
|
+
} catch {
|
|
347
|
+
return { kind: "corrupted", reason: "invalid JSON" };
|
|
348
|
+
}
|
|
349
|
+
const parsed = schema.safeParse(json);
|
|
350
|
+
if (parsed.success) return { kind: "ok", value: parsed.data };
|
|
351
|
+
const issue = parsed.error.issues[0];
|
|
352
|
+
return { kind: "corrupted", reason: `schema mismatch at ${issue?.path.map(String).join(".") || "(root)"}: ${issue?.message}` };
|
|
353
|
+
}
|
|
354
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
355
|
+
function projectSession(root) {
|
|
356
|
+
const key = path2.resolve(root);
|
|
357
|
+
let s = sessions.get(key);
|
|
358
|
+
if (!s) {
|
|
359
|
+
s = new ProjectSession(key);
|
|
360
|
+
sessions.set(key, s);
|
|
361
|
+
}
|
|
362
|
+
return s;
|
|
363
|
+
}
|
|
364
|
+
function releaseProjectSession(root) {
|
|
365
|
+
sessions.delete(path2.resolve(root));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export {
|
|
369
|
+
GRAPH_FILE,
|
|
370
|
+
buildGraph,
|
|
371
|
+
saveGraph,
|
|
372
|
+
loadGraph,
|
|
373
|
+
neighbors,
|
|
374
|
+
expandFromFiles,
|
|
375
|
+
projectSession,
|
|
376
|
+
releaseProjectSession
|
|
377
|
+
};
|
|
378
|
+
//# sourceMappingURL=chunk-PDFQ5BFG.js.map
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
committedAiWarning
|
|
4
|
+
} from "./chunk-RXNRMIZP.js";
|
|
5
|
+
import {
|
|
6
|
+
whichExecutable
|
|
7
|
+
} from "./chunk-VSYBC3VY.js";
|
|
8
|
+
import {
|
|
9
|
+
listRules,
|
|
10
|
+
parseRules
|
|
11
|
+
} from "./chunk-3IS2X2Z7.js";
|
|
12
|
+
import {
|
|
13
|
+
readRecentEvents
|
|
14
|
+
} from "./chunk-65TSRDQ3.js";
|
|
15
|
+
import {
|
|
16
|
+
findProjectRoot
|
|
17
|
+
} from "./chunk-KBO7XRBW.js";
|
|
18
|
+
import {
|
|
19
|
+
ATHENA_VERSION
|
|
20
|
+
} from "./chunk-2R6PXY6Z.js";
|
|
21
|
+
import {
|
|
22
|
+
gitAvailable,
|
|
23
|
+
isGitRepo
|
|
24
|
+
} from "./chunk-OLPSEQS5.js";
|
|
25
|
+
import {
|
|
26
|
+
ADAPTERS
|
|
27
|
+
} from "./chunk-2RS6TA3D.js";
|
|
28
|
+
import {
|
|
29
|
+
hookCommand
|
|
30
|
+
} from "./chunk-GHFRQAYT.js";
|
|
31
|
+
import {
|
|
32
|
+
parseBlocks
|
|
33
|
+
} from "./chunk-673EPYYH.js";
|
|
34
|
+
import {
|
|
35
|
+
KNOWLEDGE_DOCS
|
|
36
|
+
} from "./chunk-FQEGEVLY.js";
|
|
37
|
+
import {
|
|
38
|
+
athenaDir,
|
|
39
|
+
loadConfig,
|
|
40
|
+
readState
|
|
41
|
+
} from "./chunk-2DY4PUR4.js";
|
|
42
|
+
import {
|
|
43
|
+
readTextIfExists
|
|
44
|
+
} from "./chunk-IQQBAWSM.js";
|
|
45
|
+
|
|
46
|
+
// src/services/instance.ts
|
|
47
|
+
import { promises as fs } from "fs";
|
|
48
|
+
import path from "path";
|
|
49
|
+
var INSTANCE_FILE = ".server.json";
|
|
50
|
+
async function findRunningInstance(root) {
|
|
51
|
+
const file = path.join(athenaDir(root), INSTANCE_FILE);
|
|
52
|
+
const raw = await readTextIfExists(file);
|
|
53
|
+
if (!raw) return null;
|
|
54
|
+
let info;
|
|
55
|
+
try {
|
|
56
|
+
info = JSON.parse(raw);
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (typeof info.pid !== "number" || typeof info.port !== "number" || typeof info.token !== "string") return null;
|
|
61
|
+
try {
|
|
62
|
+
process.kill(info.pid, 0);
|
|
63
|
+
} catch {
|
|
64
|
+
await fs.rm(file, { force: true }).catch(() => {
|
|
65
|
+
});
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const host = info.host.includes(":") ? `[${info.host}]` : info.host;
|
|
70
|
+
const res = await fetch(`http://${host}:${info.port}/api/health`, { signal: AbortSignal.timeout(1500) });
|
|
71
|
+
const body = await res.json();
|
|
72
|
+
return body.instanceId === info.instanceId ? info : null;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/services/doctor.ts
|
|
79
|
+
import { constants, promises as fs2 } from "fs";
|
|
80
|
+
import path2 from "path";
|
|
81
|
+
|
|
82
|
+
// src/services/time.ts
|
|
83
|
+
function relativeTime(iso, now = Date.now()) {
|
|
84
|
+
const ms = now - Date.parse(iso);
|
|
85
|
+
if (Number.isNaN(ms)) return "unknown";
|
|
86
|
+
const s = Math.round(ms / 1e3);
|
|
87
|
+
if (s < 60) return `${s}s ago`;
|
|
88
|
+
const m = Math.round(s / 60);
|
|
89
|
+
if (m < 60) return `${m} min ago`;
|
|
90
|
+
const h = Math.round(m / 60);
|
|
91
|
+
if (h < 48) return `${h} h ago`;
|
|
92
|
+
return `${Math.round(h / 24)} days ago`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/services/doctor.ts
|
|
96
|
+
async function runDoctor(cwd) {
|
|
97
|
+
const checks = [];
|
|
98
|
+
const add = (c) => checks.push(c);
|
|
99
|
+
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
100
|
+
const nodeOk = major > 22 || major === 22 && minor >= 12;
|
|
101
|
+
add({ area: "CLI", level: nodeOk ? "ok" : "error", message: `Athena ${ATHENA_VERSION} on Node.js ${process.versions.node} (${process.platform}/${process.arch})`, hint: nodeOk ? void 0 : "Athena requires Node.js >= 22.12" });
|
|
102
|
+
const root = await findProjectRoot(cwd);
|
|
103
|
+
const projectRoot = root ?? cwd;
|
|
104
|
+
if (await gitAvailable()) {
|
|
105
|
+
const repo = await isGitRepo(projectRoot);
|
|
106
|
+
add({ area: "Git", level: repo ? "ok" : "warn", message: repo ? "Git repository detected" : "Not a Git repository", hint: repo ? void 0 : "Change detection and knowledge history work best in a Git repository." });
|
|
107
|
+
} else {
|
|
108
|
+
add({ area: "Git", level: "warn", message: "git executable not found", hint: "Install Git to enable change detection and knowledge history." });
|
|
109
|
+
}
|
|
110
|
+
if (!root) {
|
|
111
|
+
add({ area: "Project", level: "error", message: "Athena is not initialized here", hint: "Run `athena init` in the project root." });
|
|
112
|
+
return { checks, root };
|
|
113
|
+
}
|
|
114
|
+
add({ area: "Project", level: "ok", message: root });
|
|
115
|
+
const dir = athenaDir(root);
|
|
116
|
+
try {
|
|
117
|
+
await fs2.access(dir, constants.R_OK | constants.W_OK);
|
|
118
|
+
add({ area: "Knowledge directory", level: "ok", message: ".athena/ is readable and writable" });
|
|
119
|
+
} catch {
|
|
120
|
+
add({ area: "Knowledge directory", level: "error", message: ".athena/ is not writable", hint: "Check file permissions." });
|
|
121
|
+
}
|
|
122
|
+
const cfg = await loadConfig(root);
|
|
123
|
+
if (cfg.warning) add({ area: "Configuration", level: "warn", message: cfg.warning });
|
|
124
|
+
const aiWarning = await committedAiWarning(root);
|
|
125
|
+
if (aiWarning) add({ area: "Configuration", level: "warn", message: aiWarning.split(": ")[0], hint: aiWarning.slice(aiWarning.indexOf(": ") + 2) });
|
|
126
|
+
const st = await readState(dir);
|
|
127
|
+
if (st.kind === "ok") add({ area: "State", level: "ok", message: `state.json valid (analyzed ${relativeTime(st.state.analyzedAt)})` });
|
|
128
|
+
else if (st.kind === "missing") add({ area: "State", level: "error", message: "state.json missing", hint: "Run `athena analyze`." });
|
|
129
|
+
else add({ area: "State", level: "error", message: `state.json corrupted: ${st.reason}`, hint: "Run `athena analyze` (the file is backed up and rebuilt)." });
|
|
130
|
+
const missing = [];
|
|
131
|
+
const malformed = [];
|
|
132
|
+
for (const d of KNOWLEDGE_DOCS) {
|
|
133
|
+
const text = await readTextIfExists(path2.join(dir, d.file));
|
|
134
|
+
if (text === null) missing.push(d.file);
|
|
135
|
+
else if (d.id !== "rules" && (text.match(/athena:generated:start/g)?.length ?? 0) !== parseBlocks(text).length) malformed.push(d.file);
|
|
136
|
+
}
|
|
137
|
+
add({ area: "Knowledge files", level: missing.length ? "error" : "ok", message: missing.length ? `Missing: ${missing.join(", ")}` : `${KNOWLEDGE_DOCS.length} knowledge files present`, hint: missing.length ? "Run `athena analyze` to regenerate missing files." : void 0 });
|
|
138
|
+
if (malformed.length) add({ area: "Knowledge files", level: "warn", message: `Unmatched generated-section markers in: ${malformed.join(", ")}`, hint: "Those sections are treated as developer content and will not be refreshed." });
|
|
139
|
+
const rulesText = await readTextIfExists(path2.join(dir, "rules.md"));
|
|
140
|
+
if (rulesText !== null) {
|
|
141
|
+
const rules = listRules(parseRules(rulesText));
|
|
142
|
+
const enabled = rules.filter((r) => r.enabled).length;
|
|
143
|
+
add({ area: "Rules", level: rules.length ? "ok" : "warn", message: `${enabled} enabled, ${rules.length - enabled} disabled`, hint: rules.length ? void 0 : "rules.md has no rules. Add some with `athena rules add`." });
|
|
144
|
+
}
|
|
145
|
+
const stateAgents = st.kind === "ok" ? st.state.agents : {};
|
|
146
|
+
for (const a of ADAPTERS) {
|
|
147
|
+
const configured = stateAgents[a.id]?.configured;
|
|
148
|
+
if (!configured) {
|
|
149
|
+
add({ area: a.displayName, level: "info", message: "Integration not configured", hint: `Enable with \`athena agents add ${a.id}\`.` });
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
for (const c of await a.check(root)) add({ area: a.displayName, level: c.level, message: c.message.replace(/^[^:]+:\s*/, "") });
|
|
153
|
+
}
|
|
154
|
+
const hooksConfigured = ADAPTERS.some((a) => a.capabilities.hooks && stateAgents[a.id]?.configured);
|
|
155
|
+
if (hooksConfigured) {
|
|
156
|
+
const cmd = hookCommand();
|
|
157
|
+
const resolved = await whichExecutable(cmd);
|
|
158
|
+
add(
|
|
159
|
+
resolved ? { area: "Agent hooks", level: "ok", message: `\`${cmd}\` resolves to ${resolved}` } : { area: "Agent hooks", level: "warn", message: `\`${cmd}\` is not on PATH \u2014 agent hooks cannot report activity`, hint: "Install globally (`npm i -g project-athena`) or set ATHENA_HOOK_COMMAND to an absolute path and re-run `athena agents add`." }
|
|
160
|
+
);
|
|
161
|
+
const events = await readRecentEvents(root, 1);
|
|
162
|
+
add({ area: "Agent hooks", level: "info", message: events.length ? `Last agent event: ${relativeTime(events[0].ts)}` : "No agent activity recorded yet (hooks fire when an agent runs)" });
|
|
163
|
+
}
|
|
164
|
+
const running = await findRunningInstance(root);
|
|
165
|
+
add(
|
|
166
|
+
running ? { area: "Local server", level: "ok", message: `Running on ${running.host}:${running.port} (pid ${running.pid})`, hint: "Run `athena open` to get the access link." } : { area: "Local server", level: "info", message: "Not running", hint: "Start the web UI with `athena open`." }
|
|
167
|
+
);
|
|
168
|
+
if (running && !["127.0.0.1", "::1", "localhost"].includes(running.host)) {
|
|
169
|
+
add({ area: "Local server", level: "warn", message: `Bound to ${running.host} \u2014 reachable beyond this machine` });
|
|
170
|
+
}
|
|
171
|
+
return { checks, root };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
INSTANCE_FILE,
|
|
176
|
+
findRunningInstance,
|
|
177
|
+
runDoctor
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=chunk-PR53DT7I.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readTextIfExists,
|
|
4
|
+
writeFileAtomic
|
|
5
|
+
} from "./chunk-IQQBAWSM.js";
|
|
6
|
+
import {
|
|
7
|
+
ATHENA_DIR
|
|
8
|
+
} from "./chunk-EUPETUWA.js";
|
|
9
|
+
|
|
10
|
+
// src/core/local-config.ts
|
|
11
|
+
import crypto from "crypto";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
var LOCAL_CONFIG_FILE = "local.json";
|
|
15
|
+
var LocalConfig = z.object({
|
|
16
|
+
/** Random per-machine salt for keyed fingerprints of secret findings. Never leaves this machine. */
|
|
17
|
+
salt: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
18
|
+
/** AI settings trusted only from machine-local sources (see src/services/ai.ts). */
|
|
19
|
+
ai: z.object({
|
|
20
|
+
baseUrl: z.string().optional(),
|
|
21
|
+
consent: z.boolean().optional()
|
|
22
|
+
}).optional()
|
|
23
|
+
}).passthrough();
|
|
24
|
+
function localPath(root) {
|
|
25
|
+
return path.join(root, ATHENA_DIR, LOCAL_CONFIG_FILE);
|
|
26
|
+
}
|
|
27
|
+
async function readLocalConfig(root) {
|
|
28
|
+
const text = await readTextIfExists(localPath(root)).catch(() => null);
|
|
29
|
+
if (text === null) return {};
|
|
30
|
+
try {
|
|
31
|
+
const parsed = LocalConfig.safeParse(JSON.parse(text));
|
|
32
|
+
return parsed.success ? parsed.data : {};
|
|
33
|
+
} catch {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function writeLocalConfig(root, cfg) {
|
|
38
|
+
const ignoreFile = path.join(root, ATHENA_DIR, ".gitignore");
|
|
39
|
+
const ignore = await readTextIfExists(ignoreFile);
|
|
40
|
+
if (!ignore?.split(/\r?\n/).includes(LOCAL_CONFIG_FILE)) {
|
|
41
|
+
await writeFileAtomic(ignoreFile, `${(ignore ?? "").replace(/\s*$/, "")}${ignore ? "\n" : ""}${LOCAL_CONFIG_FILE}
|
|
42
|
+
`);
|
|
43
|
+
}
|
|
44
|
+
await writeFileAtomic(localPath(root), `${JSON.stringify(cfg, null, 2)}
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
async function getOrCreateSalt(root) {
|
|
48
|
+
const cfg = await readLocalConfig(root);
|
|
49
|
+
if (cfg.salt) return cfg.salt;
|
|
50
|
+
const salt = crypto.randomBytes(32).toString("hex");
|
|
51
|
+
await writeLocalConfig(root, { ...cfg, salt });
|
|
52
|
+
return salt;
|
|
53
|
+
}
|
|
54
|
+
function keyedFingerprint(salt, value, length = 16) {
|
|
55
|
+
return crypto.createHmac("sha256", salt).update(value).digest("hex").slice(0, length);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
LOCAL_CONFIG_FILE,
|
|
60
|
+
readLocalConfig,
|
|
61
|
+
getOrCreateSalt,
|
|
62
|
+
keyedFingerprint
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=chunk-PVCIGUIT.js.map
|