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,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_IGNORES
|
|
4
|
+
} from "./chunk-2DY4PUR4.js";
|
|
5
|
+
import {
|
|
6
|
+
toPosix
|
|
7
|
+
} from "./chunk-XW3MLHI2.js";
|
|
8
|
+
|
|
9
|
+
// src/core/fs/ignore.ts
|
|
10
|
+
import { promises as fs } from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import ignoreFactory from "ignore";
|
|
13
|
+
var IgnoreMatcher = class _IgnoreMatcher {
|
|
14
|
+
base;
|
|
15
|
+
include;
|
|
16
|
+
/** Directory (repo-relative POSIX, "" for root) → rules from its .gitignore. */
|
|
17
|
+
scoped = /* @__PURE__ */ new Map();
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.base = ignoreFactory().add(DEFAULT_IGNORES.map((d) => `${d}/`)).add(config.ignore);
|
|
20
|
+
this.include = config.include.length ? ignoreFactory().add(config.include) : null;
|
|
21
|
+
}
|
|
22
|
+
static async load(root, config) {
|
|
23
|
+
const m = new _IgnoreMatcher(config);
|
|
24
|
+
const exclude = await fs.readFile(path.join(root, ".git", "info", "exclude"), "utf8").catch(() => null);
|
|
25
|
+
if (exclude) m.setGitignore("", exclude);
|
|
26
|
+
const rootGi = await fs.readFile(path.join(root, ".gitignore"), "utf8").catch(() => null);
|
|
27
|
+
if (rootGi) m.setGitignore("", rootGi, true);
|
|
28
|
+
return m;
|
|
29
|
+
}
|
|
30
|
+
/** Register (or replace) the rules of `<dir>/.gitignore`. `append` merges with existing rules for that dir. */
|
|
31
|
+
setGitignore(dir, content, append = false) {
|
|
32
|
+
const existing = append ? this.scoped.get(dir) : void 0;
|
|
33
|
+
this.scoped.set(dir, (existing ?? ignoreFactory()).add(content));
|
|
34
|
+
}
|
|
35
|
+
removeGitignore(dir) {
|
|
36
|
+
this.scoped.delete(dir);
|
|
37
|
+
}
|
|
38
|
+
/** Is the repo-relative POSIX path ignored? `isDir` enables directory-only patterns ("build/"). */
|
|
39
|
+
ignores(rel, isDir = false) {
|
|
40
|
+
if (!rel || rel === ".") return false;
|
|
41
|
+
const test = isDir ? `${rel}/` : rel;
|
|
42
|
+
if (this.include?.ignores(test)) return false;
|
|
43
|
+
if (this.base.ignores(test)) return true;
|
|
44
|
+
if (!this.scoped.size) return false;
|
|
45
|
+
const rootRules = this.scoped.get("");
|
|
46
|
+
if (rootRules?.ignores(test)) return true;
|
|
47
|
+
for (let i = rel.indexOf("/"); i > 0; i = rel.indexOf("/", i + 1)) {
|
|
48
|
+
const ig = this.scoped.get(rel.slice(0, i));
|
|
49
|
+
if (!ig) continue;
|
|
50
|
+
const sub = rel.slice(i + 1);
|
|
51
|
+
if (ig.ignores(isDir ? `${sub}/` : sub)) return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/core/fs/walker.ts
|
|
58
|
+
import { promises as fs2, statSync } from "fs";
|
|
59
|
+
import path2 from "path";
|
|
60
|
+
import crypto from "crypto";
|
|
61
|
+
var SNIFF_BYTES = 8e3;
|
|
62
|
+
function sniffBinary(buf) {
|
|
63
|
+
return buf.subarray(0, SNIFF_BYTES).includes(0);
|
|
64
|
+
}
|
|
65
|
+
async function walkProject(root, opts) {
|
|
66
|
+
const rootReal = await fs2.realpath(root);
|
|
67
|
+
const matcher = opts.matcher ?? await IgnoreMatcher.load(rootReal, opts.config);
|
|
68
|
+
const result = { files: [], skippedBinary: 0, skippedLarge: 0, skippedUnreadable: 0, truncated: false, warnings: [], symlinks: 0 };
|
|
69
|
+
const visitedDirs = /* @__PURE__ */ new Set([rootReal]);
|
|
70
|
+
const pending = [];
|
|
71
|
+
const isIgnored = (rel, isDir) => matcher.ignores(rel, isDir);
|
|
72
|
+
const stack = [rootReal];
|
|
73
|
+
while (stack.length) {
|
|
74
|
+
opts.signal?.throwIfAborted();
|
|
75
|
+
const dir = stack.pop();
|
|
76
|
+
let entries;
|
|
77
|
+
try {
|
|
78
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
79
|
+
} catch (err) {
|
|
80
|
+
result.skippedUnreadable++;
|
|
81
|
+
result.warnings.push(`Cannot read directory ${toPosix(path2.relative(rootReal, dir)) || "."}: ${err.code ?? "error"}`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
85
|
+
const dirRel = toPosix(path2.relative(rootReal, dir));
|
|
86
|
+
if (dirRel && entries.some((e) => e.name === ".gitignore" && e.isFile())) {
|
|
87
|
+
const gi = await fs2.readFile(path2.join(dir, ".gitignore"), "utf8").catch(() => null);
|
|
88
|
+
if (gi !== null) matcher.setGitignore(dirRel, gi);
|
|
89
|
+
}
|
|
90
|
+
for (const ent of entries) {
|
|
91
|
+
const abs = path2.join(dir, ent.name);
|
|
92
|
+
const rel = dirRel ? `${dirRel}/${ent.name}` : toPosix(ent.name);
|
|
93
|
+
let isDir = ent.isDirectory();
|
|
94
|
+
let isFile = ent.isFile();
|
|
95
|
+
if (ent.isSymbolicLink()) {
|
|
96
|
+
try {
|
|
97
|
+
const target = await fs2.realpath(abs);
|
|
98
|
+
const relTarget = path2.relative(rootReal, target);
|
|
99
|
+
if (relTarget.startsWith("..") || path2.isAbsolute(relTarget)) continue;
|
|
100
|
+
const st = await fs2.stat(target);
|
|
101
|
+
isDir = st.isDirectory();
|
|
102
|
+
isFile = st.isFile();
|
|
103
|
+
if (isDir) {
|
|
104
|
+
if (visitedDirs.has(target)) continue;
|
|
105
|
+
}
|
|
106
|
+
result.symlinks++;
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (isDir) {
|
|
112
|
+
if (isIgnored(rel, true)) continue;
|
|
113
|
+
const real = await fs2.realpath(abs).catch(() => abs);
|
|
114
|
+
if (visitedDirs.has(real)) continue;
|
|
115
|
+
visitedDirs.add(real);
|
|
116
|
+
stack.push(abs);
|
|
117
|
+
} else if (isFile) {
|
|
118
|
+
if (isIgnored(rel, false)) continue;
|
|
119
|
+
if (result.files.length + pending.length >= opts.config.maxFiles) {
|
|
120
|
+
result.truncated = true;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
pending.push(rel);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (result.truncated) break;
|
|
127
|
+
if (pending.length >= 256) await flush();
|
|
128
|
+
}
|
|
129
|
+
await flush();
|
|
130
|
+
if (result.truncated) {
|
|
131
|
+
result.warnings.push(`File limit reached (${opts.config.maxFiles}); analysis is partial. Raise maxFiles in .athena/config.json or add ignores.`);
|
|
132
|
+
}
|
|
133
|
+
result.files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
134
|
+
return result;
|
|
135
|
+
async function flush() {
|
|
136
|
+
const batch = pending.splice(0, pending.length);
|
|
137
|
+
await mapLimit(batch, 32, async (rel) => {
|
|
138
|
+
opts.signal?.throwIfAborted();
|
|
139
|
+
const entry = await statEntry(rootReal, rel, opts.config.maxFileBytes, opts.reuse?.[rel], opts.deferRead);
|
|
140
|
+
if (!entry) {
|
|
141
|
+
result.skippedUnreadable++;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (entry.binary) result.skippedBinary++;
|
|
145
|
+
if (entry.large) result.skippedLarge++;
|
|
146
|
+
result.files.push(entry);
|
|
147
|
+
});
|
|
148
|
+
opts.onProgress?.(result.files.length);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function statEntry(root, rel, maxBytes, prev, deferRead) {
|
|
152
|
+
const abs = path2.join(root, rel);
|
|
153
|
+
try {
|
|
154
|
+
const st = deferRead ? statSync(abs) : await fs2.stat(abs);
|
|
155
|
+
if (prev && prev.s === st.size && prev.m === Math.floor(st.mtimeMs) && prev.h.startsWith("meta:") === st.size > maxBytes) {
|
|
156
|
+
return { path: rel, size: st.size, mtimeMs: prev.m, hash: prev.h, binary: prev.b === 1, large: prev.h.startsWith("meta:") };
|
|
157
|
+
}
|
|
158
|
+
if (st.size > maxBytes) {
|
|
159
|
+
return { path: rel, size: st.size, mtimeMs: Math.floor(st.mtimeMs), hash: `meta:${st.size}:${Math.floor(st.mtimeMs)}`, binary: false, large: true };
|
|
160
|
+
}
|
|
161
|
+
if (deferRead) return { path: rel, size: st.size, mtimeMs: Math.floor(st.mtimeMs), hash: "", binary: false, large: false };
|
|
162
|
+
const buf = await fs2.readFile(abs);
|
|
163
|
+
const binary = sniffBinary(buf);
|
|
164
|
+
return {
|
|
165
|
+
path: rel,
|
|
166
|
+
size: st.size,
|
|
167
|
+
mtimeMs: Math.floor(st.mtimeMs),
|
|
168
|
+
hash: crypto.createHash("sha256").update(buf).digest("hex"),
|
|
169
|
+
binary,
|
|
170
|
+
large: false
|
|
171
|
+
};
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function mapLimit(items, limit, fn) {
|
|
177
|
+
const out = new Array(items.length);
|
|
178
|
+
let i = 0;
|
|
179
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
180
|
+
while (i < items.length) {
|
|
181
|
+
const idx = i++;
|
|
182
|
+
out[idx] = await fn(items[idx]);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
await Promise.all(workers);
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/core/analyzer/config-hash.ts
|
|
190
|
+
import crypto2 from "crypto";
|
|
191
|
+
function factsConfigHash(config) {
|
|
192
|
+
return crypto2.createHash("sha256").update(JSON.stringify({ ignore: config.ignore, include: config.include, maxFileBytes: config.maxFileBytes })).digest("hex").slice(0, 16);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/core/patterns.ts
|
|
196
|
+
var TEST_FILE = /(^|\/)(__tests__|tests?|spec|specs|e2e|integration_tests|androidTest|test_driver)\/|\.(test|spec|e2e)\.[cm]?[jt]sx?$|(^|\/)test_[^/]+\.py$|_test\.(py|go|exs)$|_spec\.rb$|Tests?\.(java|kt|cs)$|_test\.dart$/;
|
|
197
|
+
var TEST_RUNNER_CONFIG = /(^|\/)(jest|vitest|playwright|cypress)\.config\./;
|
|
198
|
+
var SOURCE_FILE = /\.(m|c)?(t|j)sx?$|\.(py|go|rs|java|kt|cs|php|rb|dart|ex|swift|vue|svelte)$/;
|
|
199
|
+
var AUTH_PATH = /(auth|session|login|oauth|jwt|guard|permission|rbac|acl|polic(y|ies)|middleware)/i;
|
|
200
|
+
var API_ROUTE_PATH = /(^|\/)(routes?|controllers?|handlers?|api|endpoints?|resolvers?)\/|(^|\/)(urls\.py|routes\.rb)$|(^|\/)app\/(.+\/)?route\.(t|j)sx?$/i;
|
|
201
|
+
var API_SURFACE_PATH = /(^|\/)(routes?|controllers?|handlers?|api|endpoints?|views|resolvers?)\/|(^|\/)(urls\.py|routes\.rb)$|(^|\/)app\/(.+\/)?route\.(t|j)sx?$|\.(graphql|gql|proto)$|(openapi|swagger)[^/]*\.(json|ya?ml)$/i;
|
|
202
|
+
var SCHEMA_PATH = /\.prisma$|\.sql$|(^|\/)(migrations?|migrate|alembic)\/|(^|\/)models(\.py|\/)/i;
|
|
203
|
+
var SCHEMA_RELATED_PATH = /\.prisma$|\.sql$|(^|\/)(migrations?|migrate|alembic|changelog)\/|(^|\/)models(\.py|\/)|(^|\/)(schema|entities|entity)\b/i;
|
|
204
|
+
|
|
205
|
+
export {
|
|
206
|
+
IgnoreMatcher,
|
|
207
|
+
sniffBinary,
|
|
208
|
+
walkProject,
|
|
209
|
+
statEntry,
|
|
210
|
+
mapLimit,
|
|
211
|
+
factsConfigHash,
|
|
212
|
+
TEST_FILE,
|
|
213
|
+
TEST_RUNNER_CONFIG,
|
|
214
|
+
SOURCE_FILE,
|
|
215
|
+
AUTH_PATH,
|
|
216
|
+
API_ROUTE_PATH,
|
|
217
|
+
API_SURFACE_PATH,
|
|
218
|
+
SCHEMA_PATH,
|
|
219
|
+
SCHEMA_RELATED_PATH
|
|
220
|
+
};
|
|
221
|
+
//# sourceMappingURL=chunk-ELKFRTZT.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/paths.ts
|
|
4
|
+
import path from "path";
|
|
5
|
+
var ATHENA_DIR = ".athena";
|
|
6
|
+
function athenaDir(root) {
|
|
7
|
+
return path.join(root, ATHENA_DIR);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
ATHENA_DIR,
|
|
12
|
+
athenaDir
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=chunk-EUPETUWA.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/knowledge/documents.ts
|
|
4
|
+
var KNOWLEDGE_DOCS = [
|
|
5
|
+
{ id: "project", file: "project.md", title: "Project", purpose: "What this project is, its technologies, structure, entry points, commands and development workflow." },
|
|
6
|
+
{ id: "architecture", file: "architecture.md", title: "Architecture", purpose: "System and module architecture, service relationships, data flow and external services." },
|
|
7
|
+
{ id: "database", file: "database.md", title: "Database", purpose: "Database technologies, schemas, entities, relationships, indexes and migrations." },
|
|
8
|
+
{ id: "api", file: "api.md", title: "API", purpose: "API style, endpoints, specifications, and authentication/error-handling expectations." },
|
|
9
|
+
{ id: "auth", file: "auth.md", title: "Authentication & Authorization", purpose: "Authentication mechanisms, sessions/tokens, roles and permissions, and security assumptions." },
|
|
10
|
+
{ id: "security", file: "security.md", title: "Security", purpose: "Attack surface, security controls, secret handling, tooling, and known/potential issues." },
|
|
11
|
+
{ id: "testing", file: "testing.md", title: "Testing", purpose: "Test frameworks, commands, layout, coverage configuration and detectable gaps." },
|
|
12
|
+
{ id: "debugging", file: "debugging.md", title: "Debugging", purpose: "Known bugs, common errors, debugging commands, logs and troubleshooting procedures." },
|
|
13
|
+
{ id: "performance", file: "performance.md", title: "Performance", purpose: "Performance-sensitive areas, caching, queues, background jobs and performance rules." },
|
|
14
|
+
{ id: "code-review", file: "code-review.md", title: "Code Review", purpose: "Project-specific review checklist covering architecture, security, testing and performance." },
|
|
15
|
+
{ id: "deployment", file: "deployment.md", title: "Deployment", purpose: "Deployment architecture, environments, containers, CI/CD, hosting and environment variables." },
|
|
16
|
+
{ id: "rules", file: "rules.md", title: "Project Rules", purpose: "Developer-defined rules that AI agents must follow. Owned by the developer." }
|
|
17
|
+
];
|
|
18
|
+
var DOC_FILES = new Set(KNOWLEDGE_DOCS.map((d) => d.file));
|
|
19
|
+
var RELEVANCE_MAP = [
|
|
20
|
+
{ task: "Database / schema / migrations / queries", docs: ["database", "architecture", "api", "security", "testing", "rules"] },
|
|
21
|
+
{ task: "API endpoints / controllers / request handling", docs: ["api", "auth", "security", "architecture", "testing", "rules"] },
|
|
22
|
+
{ task: "Authentication / authorization / sessions / permissions", docs: ["auth", "security", "api", "database", "testing", "rules"] },
|
|
23
|
+
{ task: "Frontend UI / styling / components", docs: ["project", "architecture", "rules"] },
|
|
24
|
+
{ task: "New feature spanning multiple layers", docs: ["project", "architecture", "database", "api", "auth", "security", "testing", "rules"] },
|
|
25
|
+
{ task: "Bug fix / debugging", docs: ["debugging", "architecture", "testing", "rules"] },
|
|
26
|
+
{ task: "Performance work / caching / background jobs", docs: ["performance", "architecture", "database", "rules"] },
|
|
27
|
+
{ task: "Tests", docs: ["testing", "project", "rules"] },
|
|
28
|
+
{ task: "Build / CI / Docker / deployment / infrastructure", docs: ["deployment", "project", "security", "rules"] },
|
|
29
|
+
{ task: "Dependency changes", docs: ["project", "security", "rules"] },
|
|
30
|
+
{ task: "Code review", docs: ["code-review", "rules", "security", "testing"] },
|
|
31
|
+
{ task: "Small, local change (typo, rename, copy)", docs: ["rules"] }
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
KNOWLEDGE_DOCS,
|
|
36
|
+
RELEVANCE_MAP
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=chunk-FQEGEVLY.js.map
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
listRules,
|
|
4
|
+
parseRules
|
|
5
|
+
} from "./chunk-3IS2X2Z7.js";
|
|
6
|
+
import {
|
|
7
|
+
loadScan,
|
|
8
|
+
manifestDependencyNames
|
|
9
|
+
} from "./chunk-MPG3IEGB.js";
|
|
10
|
+
import {
|
|
11
|
+
API_ROUTE_PATH,
|
|
12
|
+
AUTH_PATH,
|
|
13
|
+
SCHEMA_PATH,
|
|
14
|
+
SOURCE_FILE,
|
|
15
|
+
TEST_FILE
|
|
16
|
+
} from "./chunk-ELKFRTZT.js";
|
|
17
|
+
import {
|
|
18
|
+
GitCommandError,
|
|
19
|
+
git,
|
|
20
|
+
gitOrThrow,
|
|
21
|
+
isSafeRef,
|
|
22
|
+
parseNameStatusZ,
|
|
23
|
+
workingChanges
|
|
24
|
+
} from "./chunk-OLPSEQS5.js";
|
|
25
|
+
import {
|
|
26
|
+
scanText
|
|
27
|
+
} from "./chunk-JCGDR4UV.js";
|
|
28
|
+
import {
|
|
29
|
+
athenaDir
|
|
30
|
+
} from "./chunk-2DY4PUR4.js";
|
|
31
|
+
import {
|
|
32
|
+
readTextIfExists,
|
|
33
|
+
readTextInsideRoot
|
|
34
|
+
} from "./chunk-IQQBAWSM.js";
|
|
35
|
+
import {
|
|
36
|
+
AthenaError,
|
|
37
|
+
EXIT
|
|
38
|
+
} from "./chunk-AD4OZCU2.js";
|
|
39
|
+
|
|
40
|
+
// src/services/review.ts
|
|
41
|
+
import { promises as fs } from "fs";
|
|
42
|
+
import path from "path";
|
|
43
|
+
var ENV_FILE = /(^|\/)\.env(\.(local|production|development|prod|dev|staging))?$/;
|
|
44
|
+
var MANIFEST_FILE = /(^|\/)(package\.json|pyproject\.toml|requirements[^/]*\.txt|go\.mod|Cargo\.toml|composer\.json|Gemfile|pubspec\.yaml|pom\.xml|build\.gradle(\.kts)?)$/;
|
|
45
|
+
var LARGE_ADDED_BYTES = 512 * 1024;
|
|
46
|
+
var MAX_DIFF_BYTES = 64 * 1024 * 1024;
|
|
47
|
+
var REVIEW_GIT_TIMEOUT_MS = 12e4;
|
|
48
|
+
var FETCH_HINT = "Make sure the base commit and the history back to the merge base are present. In GitHub Actions use `actions/checkout` with `fetch-depth: 0`; elsewhere run `git fetch --unshallow` or fetch the base branch.";
|
|
49
|
+
function unquoteGitPath(p) {
|
|
50
|
+
if (!p.startsWith('"') || !p.endsWith('"')) return p;
|
|
51
|
+
const bytes = [];
|
|
52
|
+
const body = p.slice(1, -1);
|
|
53
|
+
const esc = { a: 7, b: 8, t: 9, n: 10, v: 11, f: 12, r: 13, '"': 34, "\\": 92 };
|
|
54
|
+
for (let i = 0; i < body.length; i++) {
|
|
55
|
+
const ch = body[i];
|
|
56
|
+
if (ch !== "\\") {
|
|
57
|
+
bytes.push(...Buffer.from(ch, "utf8"));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const next = body[i + 1] ?? "";
|
|
61
|
+
if (/[0-7]/.test(next)) {
|
|
62
|
+
bytes.push(parseInt(body.slice(i + 1, i + 4), 8));
|
|
63
|
+
i += 3;
|
|
64
|
+
} else {
|
|
65
|
+
bytes.push(esc[next] ?? next.charCodeAt(0));
|
|
66
|
+
i += 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return Buffer.from(bytes).toString("utf8");
|
|
70
|
+
}
|
|
71
|
+
function addedLines(patch) {
|
|
72
|
+
const out = [];
|
|
73
|
+
let file = "";
|
|
74
|
+
let lineNo = 0;
|
|
75
|
+
let oldLeft = 0;
|
|
76
|
+
let newLeft = 0;
|
|
77
|
+
for (const raw of patch.split("\n")) {
|
|
78
|
+
if (oldLeft > 0 || newLeft > 0) {
|
|
79
|
+
const c = raw[0];
|
|
80
|
+
if (c === "+") {
|
|
81
|
+
out.push({ file, line: lineNo++, text: raw.slice(1) });
|
|
82
|
+
newLeft--;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (c === "-") {
|
|
86
|
+
oldLeft--;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (c === " ") {
|
|
90
|
+
lineNo++;
|
|
91
|
+
oldLeft--;
|
|
92
|
+
newLeft--;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (c === "\\") continue;
|
|
96
|
+
oldLeft = newLeft = 0;
|
|
97
|
+
} else if (raw.startsWith("\\")) continue;
|
|
98
|
+
if (raw.startsWith("diff --git ")) {
|
|
99
|
+
file = "";
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (raw.startsWith("+++ ")) {
|
|
103
|
+
const p = unquoteGitPath(raw.slice(4).replace(/\t$/, ""));
|
|
104
|
+
file = p === "/dev/null" ? "" : p.startsWith("b/") ? p.slice(2) : p;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const h = /^@@ -\d+(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(raw);
|
|
108
|
+
if (h) {
|
|
109
|
+
oldLeft = h[1] === void 0 ? 1 : Number(h[1]);
|
|
110
|
+
lineNo = Number(h[2]);
|
|
111
|
+
newLeft = h[3] === void 0 ? 1 : Number(h[3]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function scanAddedLines(lines) {
|
|
117
|
+
const hits = [];
|
|
118
|
+
let block = [];
|
|
119
|
+
const flush = () => {
|
|
120
|
+
if (!block.length) return;
|
|
121
|
+
const b = block;
|
|
122
|
+
block = [];
|
|
123
|
+
for (const m of scanText(b.map((l) => l.text).join("\n"))) {
|
|
124
|
+
const at = b[m.line - 1] ?? b[0];
|
|
125
|
+
hits.push({ type: m.type, file: at.file, line: at.line });
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
for (const l of lines) {
|
|
129
|
+
const prev = block[block.length - 1];
|
|
130
|
+
if (prev && (prev.file !== l.file || prev.line + 1 !== l.line)) flush();
|
|
131
|
+
block.push(l);
|
|
132
|
+
}
|
|
133
|
+
flush();
|
|
134
|
+
return hits;
|
|
135
|
+
}
|
|
136
|
+
var DIFF_FLAGS = ["--no-color", "--no-ext-diff", "--no-textconv", "--relative", "--src-prefix=a/", "--dst-prefix=b/", "-M"];
|
|
137
|
+
async function reviewChanges(root, opts = {}) {
|
|
138
|
+
try {
|
|
139
|
+
return await review(root, opts);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (err instanceof GitCommandError) {
|
|
142
|
+
throw new AthenaError(`Could not complete the review: ${err.message}`, "Athena did not check this change. Fix the Git problem above and run `athena review` again.", EXIT.NOT_AVAILABLE);
|
|
143
|
+
}
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function review(root, opts) {
|
|
148
|
+
const signal = opts.signal;
|
|
149
|
+
signal?.throwIfAborted();
|
|
150
|
+
const repoCheck = await git(root, ["rev-parse", "--is-inside-work-tree"], { signal });
|
|
151
|
+
if (repoCheck.aborted) throw signal?.reason ?? new Error("Aborted");
|
|
152
|
+
const isRepo = repoCheck.stdout.trim() === "true";
|
|
153
|
+
if (!isRepo) throw new AthenaError("`athena review` needs a Git repository.", "Initialize Git, or review changes manually.");
|
|
154
|
+
if (opts.base !== void 0 && opts.staged) throw new AthenaError("`--staged` and `--base` cannot be combined.", "Use `--staged` before committing, or `--base <ref>` to review commits.");
|
|
155
|
+
if (opts.base !== void 0 && !isSafeRef(opts.base)) throw new AthenaError(`Invalid base ref: ${opts.base}`);
|
|
156
|
+
const runOpts = { signal, timeoutMs: REVIEW_GIT_TIMEOUT_MS };
|
|
157
|
+
const g = (args, extra = {}) => gitOrThrow(root, args, { ...runOpts, ...extra });
|
|
158
|
+
const mode = opts.base !== void 0 ? "base" : opts.staged ? "staged" : "working";
|
|
159
|
+
const base = opts.base ?? "HEAD";
|
|
160
|
+
const headCommit = await git(root, ["rev-parse", "--verify", "-q", "HEAD^{commit}"], runOpts);
|
|
161
|
+
if (headCommit.aborted) throw signal?.reason ?? new Error("Aborted");
|
|
162
|
+
let before;
|
|
163
|
+
let diffArgs;
|
|
164
|
+
let mergeBase;
|
|
165
|
+
if (mode === "base") {
|
|
166
|
+
if (!headCommit.ok) throw new AthenaError("Can't review: HEAD has no commits yet.", void 0, EXIT.NOT_AVAILABLE);
|
|
167
|
+
const baseCommit = await git(root, ["rev-parse", "--verify", "-q", `${opts.base}^{commit}`], runOpts);
|
|
168
|
+
if (baseCommit.aborted) throw signal?.reason ?? new Error("Aborted");
|
|
169
|
+
if (!baseCommit.ok || !baseCommit.stdout.trim()) {
|
|
170
|
+
throw new AthenaError(`Can't review against ${opts.base}: it is not a commit in this repository (insufficient Git history).`, FETCH_HINT, EXIT.NOT_AVAILABLE);
|
|
171
|
+
}
|
|
172
|
+
const mb = await git(root, ["merge-base", baseCommit.stdout.trim(), headCommit.stdout.trim()], runOpts);
|
|
173
|
+
if (mb.aborted) throw signal?.reason ?? new Error("Aborted");
|
|
174
|
+
if (!mb.ok || !mb.stdout.trim()) {
|
|
175
|
+
throw new AthenaError(`Can't review against ${opts.base}: no common ancestor with HEAD was found (the clone may be shallow).`, FETCH_HINT, EXIT.NOT_AVAILABLE);
|
|
176
|
+
}
|
|
177
|
+
mergeBase = mb.stdout.trim();
|
|
178
|
+
before = mergeBase;
|
|
179
|
+
diffArgs = [mergeBase, "HEAD"];
|
|
180
|
+
} else {
|
|
181
|
+
before = headCommit.ok ? headCommit.stdout.trim() : null;
|
|
182
|
+
const from = before ?? (await g(["hash-object", "-t", "tree", "--stdin"], { input: "" })).trim();
|
|
183
|
+
diffArgs = mode === "staged" ? ["--cached", from] : [from];
|
|
184
|
+
}
|
|
185
|
+
let changed;
|
|
186
|
+
if (mode === "working") {
|
|
187
|
+
const w = await workingChanges(root);
|
|
188
|
+
if (!w) throw new AthenaError("Could not complete the review: `git status` failed.", "Athena did not check this change.", EXIT.NOT_AVAILABLE);
|
|
189
|
+
changed = w;
|
|
190
|
+
} else {
|
|
191
|
+
changed = parseNameStatusZ(await g(["diff", "--name-status", "-z", ...DIFF_FLAGS, ...diffArgs, "--", "."]));
|
|
192
|
+
}
|
|
193
|
+
const changedFiles = changed.filter((f) => !f.path.startsWith(".athena/"));
|
|
194
|
+
const budget = opts.maxDiffBytes ?? MAX_DIFF_BYTES;
|
|
195
|
+
const diff = await git(root, ["-c", "core.quotePath=false", "diff", ...DIFF_FLAGS, ...diffArgs, "--", "."], { ...runOpts, maxBytes: budget });
|
|
196
|
+
if (diff.aborted) throw signal?.reason ?? new Error("Aborted");
|
|
197
|
+
if (diff.overflow) {
|
|
198
|
+
throw new AthenaError(`The diff is larger than ${Math.round(budget / (1024 * 1024))} MB, so Athena can't review all of it.`, "Athena did not check this change. Review a smaller range or split the change.", EXIT.NOT_AVAILABLE);
|
|
199
|
+
}
|
|
200
|
+
if (!diff.ok) throw new GitCommandError(["diff"], diff);
|
|
201
|
+
const added = addedLines(diff.stdout);
|
|
202
|
+
const skipped = [];
|
|
203
|
+
const large = [];
|
|
204
|
+
const untrackedText = /* @__PURE__ */ new Map();
|
|
205
|
+
let untrackedAdded = 0;
|
|
206
|
+
let untrackedBytes = diff.stdout.length;
|
|
207
|
+
for (const f of changedFiles.filter((x) => x.status === "untracked")) {
|
|
208
|
+
signal?.throwIfAborted();
|
|
209
|
+
if (untrackedBytes > budget) {
|
|
210
|
+
skipped.push({ path: f.path, reason: "budget" });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const r = await readTextInsideRoot(root, f.path, LARGE_ADDED_BYTES);
|
|
214
|
+
if (!r.ok) {
|
|
215
|
+
if (r.reason === "too-large") large.push(f.path);
|
|
216
|
+
if (r.reason !== "missing") skipped.push({ path: f.path, reason: r.reason });
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
untrackedBytes += r.bytes;
|
|
220
|
+
untrackedText.set(f.path, r.text);
|
|
221
|
+
const lines = r.text.split("\n");
|
|
222
|
+
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
223
|
+
lines.forEach((text, i) => added.push({ file: f.path, line: i + 1, text }));
|
|
224
|
+
untrackedAdded += lines.filter((l) => l.length > 0).length;
|
|
225
|
+
}
|
|
226
|
+
const numstat = await g(["diff", "--numstat", ...DIFF_FLAGS, ...diffArgs, "--", "."]);
|
|
227
|
+
let addedCount = 0;
|
|
228
|
+
let removedCount = 0;
|
|
229
|
+
for (const line of numstat.split("\n")) {
|
|
230
|
+
const [a, r] = line.split(" ");
|
|
231
|
+
if (a && r && a !== "-") {
|
|
232
|
+
addedCount += Number(a) || 0;
|
|
233
|
+
removedCount += Number(r) || 0;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const readNew = async (p) => {
|
|
237
|
+
if (untrackedText.has(p)) return untrackedText.get(p);
|
|
238
|
+
if (mode === "working") {
|
|
239
|
+
const r2 = await readTextInsideRoot(root, p, LARGE_ADDED_BYTES);
|
|
240
|
+
return r2.ok ? r2.text : null;
|
|
241
|
+
}
|
|
242
|
+
const spec = `${mode === "staged" ? "" : "HEAD"}:./${p}`;
|
|
243
|
+
const r = await git(root, ["cat-file", "blob", spec], { ...runOpts, maxBytes: LARGE_ADDED_BYTES });
|
|
244
|
+
if (r.aborted) throw signal?.reason ?? new Error("Aborted");
|
|
245
|
+
if (r.overflow) return null;
|
|
246
|
+
if (!r.ok) throw new GitCommandError(["cat-file", "blob", spec], r);
|
|
247
|
+
return r.stdout;
|
|
248
|
+
};
|
|
249
|
+
const findings = [];
|
|
250
|
+
const paths = changedFiles.map((f) => f.path);
|
|
251
|
+
const add = (f) => findings.push(f);
|
|
252
|
+
const secretHits = /* @__PURE__ */ new Map();
|
|
253
|
+
for (const hit of scanAddedLines(added)) {
|
|
254
|
+
if (!secretHits.has(hit.type)) secretHits.set(hit.type, /* @__PURE__ */ new Set());
|
|
255
|
+
secretHits.get(hit.type).add(`${hit.file}:${hit.line}`);
|
|
256
|
+
}
|
|
257
|
+
for (const [type, locations] of secretHits) {
|
|
258
|
+
add({ level: "blocker", check: "secrets", message: `Possible ${type} added`, files: [...locations].slice(0, 10), hint: "Remove the value, use an environment variable, and rotate the credential if it was real." });
|
|
259
|
+
}
|
|
260
|
+
const envFiles = changedFiles.filter((f) => f.status !== "deleted" && ENV_FILE.test(f.path)).map((f) => f.path);
|
|
261
|
+
if (envFiles.length) add({ level: "blocker", check: "env-file", message: "Environment file included in the change", files: envFiles, hint: "Add it to .gitignore and commit a .env.example with names only." });
|
|
262
|
+
for (const f of changedFiles.filter((x) => x.status !== "deleted" && MANIFEST_FILE.test(x.path))) {
|
|
263
|
+
signal?.throwIfAborted();
|
|
264
|
+
const now = await readNew(f.path);
|
|
265
|
+
if (now === null) continue;
|
|
266
|
+
const nowDeps = await manifestDependencyNames(f.path, now);
|
|
267
|
+
if (!nowDeps) continue;
|
|
268
|
+
let beforeText = "";
|
|
269
|
+
const beforePath = f.status === "renamed" && f.from ? f.from : f.path;
|
|
270
|
+
if (before && f.status !== "added" && f.status !== "untracked") {
|
|
271
|
+
const spec = `${before}:./${beforePath}`;
|
|
272
|
+
const r = await git(root, ["cat-file", "blob", spec], { ...runOpts, maxBytes: 8 * 1024 * 1024 });
|
|
273
|
+
if (r.aborted) throw signal?.reason ?? new Error("Aborted");
|
|
274
|
+
if (r.ok) beforeText = r.stdout;
|
|
275
|
+
else if (f.status === "modified" || f.status === "renamed") throw new GitCommandError(["cat-file", "blob", spec], r);
|
|
276
|
+
}
|
|
277
|
+
const beforeDeps = await manifestDependencyNames(f.path, beforeText) ?? /* @__PURE__ */ new Set();
|
|
278
|
+
const newDeps = [...nowDeps].filter((d) => !beforeDeps.has(d));
|
|
279
|
+
if (newDeps.length) add({ level: "info", check: "dependencies", message: `New dependencies: ${newDeps.slice(0, 10).join(", ")}${newDeps.length > 10 ? ` +${newDeps.length - 10}` : ""}`, files: [f.path], hint: "Justify each addition; check licence, maintenance and size." });
|
|
280
|
+
}
|
|
281
|
+
const changedSource = paths.filter((p) => SOURCE_FILE.test(p) && !TEST_FILE.test(p));
|
|
282
|
+
const changedTests = paths.filter((p) => TEST_FILE.test(p));
|
|
283
|
+
if (changedSource.length && !changedTests.length) {
|
|
284
|
+
add({ level: "warning", check: "tests", message: `${changedSource.length} source file(s) changed with no test file changes`, files: changedSource.slice(0, 10), hint: "See `.athena/testing.md` for the project test commands and layout." });
|
|
285
|
+
}
|
|
286
|
+
const routeFiles = changedSource.filter((p) => API_ROUTE_PATH.test(p));
|
|
287
|
+
if (routeFiles.length) add({ level: "info", check: "api", message: "API surface changed", files: routeFiles.slice(0, 10), hint: "Check auth/authorization and error handling (`.athena/api.md`, `.athena/auth.md`)." });
|
|
288
|
+
const schemaFiles = paths.filter((p) => SCHEMA_PATH.test(p));
|
|
289
|
+
if (schemaFiles.length) add({ level: "info", check: "database", message: "Database schema or migrations changed", files: schemaFiles.slice(0, 10), hint: "Check indexes, constraints and backward compatibility (`.athena/database.md`)." });
|
|
290
|
+
const authFiles = changedSource.filter((p) => AUTH_PATH.test(p));
|
|
291
|
+
if (authFiles.length) add({ level: "warning", check: "auth", message: "Authentication/authorization code changed", files: authFiles.slice(0, 10), hint: "Review against `.athena/auth.md` and `.athena/security.md`." });
|
|
292
|
+
for (const f of changedFiles.filter((x) => x.status === "added")) {
|
|
293
|
+
signal?.throwIfAborted();
|
|
294
|
+
let size = null;
|
|
295
|
+
if (mode === "working") {
|
|
296
|
+
const st = await fs.lstat(path.join(root, f.path)).catch(() => null);
|
|
297
|
+
size = st?.isFile() ? st.size : null;
|
|
298
|
+
} else {
|
|
299
|
+
const r = await git(root, ["cat-file", "-s", `${mode === "staged" ? "" : "HEAD"}:./${f.path}`], runOpts);
|
|
300
|
+
if (r.ok) size = Number(r.stdout.trim());
|
|
301
|
+
}
|
|
302
|
+
if (size !== null && size > LARGE_ADDED_BYTES && !large.includes(f.path)) large.push(f.path);
|
|
303
|
+
}
|
|
304
|
+
if (large.length) add({ level: "warning", check: "large-files", message: "Large files added", files: large, hint: "Consider whether these belong in Git." });
|
|
305
|
+
const debugHits = added.filter((l) => /\b(TODO|FIXME|XXX|console\.log|debugger|print\(|binding\.pry|dd\()/.test(l.text) && SOURCE_FILE.test(l.file));
|
|
306
|
+
if (debugHits.length) {
|
|
307
|
+
add({ level: "info", check: "leftovers", message: `${debugHits.length} added line(s) contain TODO/FIXME or debug statements`, files: [...new Set(debugHits.map((l) => `${l.file}:${l.line}`))].slice(0, 10) });
|
|
308
|
+
}
|
|
309
|
+
const scan = await loadScan(root);
|
|
310
|
+
if (scan) {
|
|
311
|
+
const vulnerable = scan.tools.flatMap((t) => t.findings).filter((f) => ["critical", "high"].includes(f.severity));
|
|
312
|
+
if (vulnerable.length) add({ level: "warning", check: "vulnerabilities", message: `${vulnerable.length} high/critical dependency advisor${vulnerable.length === 1 ? "y" : "ies"} from the last scan (${scan.scannedAt.slice(0, 10)})`, files: [], hint: "Run `athena security` for details." });
|
|
313
|
+
}
|
|
314
|
+
let knowledgeInSync = null;
|
|
315
|
+
if (opts.checkSync) {
|
|
316
|
+
knowledgeInSync = await opts.checkSync(root).catch(() => null);
|
|
317
|
+
if (knowledgeInSync === false) add({ level: "info", check: "knowledge", message: "Athena knowledge is out of date for these changes", files: [], hint: "Run `athena sync` so agents read current context." });
|
|
318
|
+
}
|
|
319
|
+
const rulesText = await readTextIfExists(path.join(athenaDir(root), "rules.md"));
|
|
320
|
+
const rules = rulesText ? listRules(parseRules(rulesText)).filter((r) => r.enabled) : [];
|
|
321
|
+
const reviewDoc = await readTextIfExists(path.join(athenaDir(root), "code-review.md"));
|
|
322
|
+
const checklist = reviewDoc ? [...reviewDoc.matchAll(/^- \[ \] (.+)$/gm)].map((m) => m[1].trim()) : [];
|
|
323
|
+
if (skipped.length) {
|
|
324
|
+
add({ level: "warning", check: "skipped", message: `${skipped.length} changed file${skipped.length === 1 ? " was" : "s were"} not checked`, files: skipped.map((s) => `${s.path} (${SKIP_REASON[s.reason]})`).slice(0, 10), hint: "Athena could not read these files, so their contents were not checked for secrets. Check them yourself." });
|
|
325
|
+
}
|
|
326
|
+
return { base, mode, ...mergeBase ? { mergeBase } : {}, changedFiles, findings, rules, checklist, knowledgeInSync, stats: { added: addedCount + untrackedAdded, removed: removedCount, files: changedFiles.length }, incomplete: skipped.length > 0, skipped };
|
|
327
|
+
}
|
|
328
|
+
var SKIP_REASON = {
|
|
329
|
+
"outside-root": "symlink to a path outside the project",
|
|
330
|
+
"too-large": `larger than ${LARGE_ADDED_BYTES / 1024} KB`,
|
|
331
|
+
"not-a-file": "not a regular file",
|
|
332
|
+
unreadable: "could not be read",
|
|
333
|
+
budget: `over the ${MAX_DIFF_BYTES / (1024 * 1024)} MB review limit`
|
|
334
|
+
};
|
|
335
|
+
function hasBlockers(result) {
|
|
336
|
+
return result.findings.some((f) => f.level === "blocker");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export {
|
|
340
|
+
reviewChanges,
|
|
341
|
+
hasBlockers
|
|
342
|
+
};
|
|
343
|
+
//# sourceMappingURL=chunk-FVCDMQUQ.js.map
|