project-athena 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/README.md +19 -11
- 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 -9795
- 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-CL6A82dQ.js → Editor-BI56-HX5.js} +1 -1
- package/dist/web/assets/{abnfDiagram-O67JEVCF-BuFfjTfx.js → abnfDiagram-O67JEVCF-y_9px2ob.js} +1 -1
- package/dist/web/assets/architecture-7GRP2DOG-DyxkwYiW.js +1 -0
- package/dist/web/assets/{architectureDiagram-NJMV4G6O-BTsv87pA.js → architectureDiagram-NJMV4G6O-lB6fcVzU.js} +1 -1
- package/dist/web/assets/{blockDiagram-BEXU5L5S-CNXqyKIx.js → blockDiagram-BEXU5L5S-DcM5pEjM.js} +1 -1
- package/dist/web/assets/{c4Diagram-YGBWAQC7-kksFXQoJ.js → c4Diagram-YGBWAQC7-CJgqxTY2.js} +1 -1
- package/dist/web/assets/channel-BvDD7AMO.js +1 -0
- package/dist/web/assets/{chunk-3FUC2YCW-BY99XYBs.js → chunk-3FUC2YCW-B8l_VuWR.js} +1 -1
- package/dist/web/assets/{chunk-5DYCD2WN-h-PRThMP.js → chunk-5DYCD2WN-CZecgNhf.js} +1 -1
- package/dist/web/assets/{chunk-742MDFTN-BIxfyBUj.js → chunk-742MDFTN-BeZ7P5jB.js} +1 -1
- package/dist/web/assets/{chunk-7INBJB4K-DZtctDdk.js → chunk-7INBJB4K-IKy0pr5m.js} +1 -1
- package/dist/web/assets/{chunk-7M6MHVWA-BX9fUUbL.js → chunk-7M6MHVWA-Dg_QqJEX.js} +1 -1
- package/dist/web/assets/{chunk-7PRAP22T-X1s3e81n.js → chunk-7PRAP22T-DqQNS8oq.js} +1 -1
- package/dist/web/assets/{chunk-GTNCS2PH-Bh8eluU-.js → chunk-GTNCS2PH-x4sM51nJ.js} +1 -1
- package/dist/web/assets/{chunk-GWA4HPMP-D1KATAi0.js → chunk-GWA4HPMP-CE5GcwVN.js} +1 -1
- package/dist/web/assets/{chunk-MBY4JIJT-CEErv5kP.js → chunk-MBY4JIJT-DA_D1hjF.js} +1 -1
- package/dist/web/assets/{chunk-NETBCI7D-BXiH4aPa.js → chunk-NETBCI7D-CeJ3XBZ2.js} +1 -1
- package/dist/web/assets/{chunk-O7XYJQB3-Dede62yi.js → chunk-O7XYJQB3-BH2gESvO.js} +1 -1
- package/dist/web/assets/{chunk-UA2S7LBM-KZ9aWRTa.js → chunk-UA2S7LBM-BWr0xCux.js} +1 -1
- package/dist/web/assets/{chunk-WEXAMYUT-xf-3L2zU.js → chunk-WEXAMYUT-B4QwUZPM.js} +1 -1
- package/dist/web/assets/{chunk-Z7XXMR3K-DLpRdZ8n.js → chunk-Z7XXMR3K-BxcA-y5B.js} +1 -1
- package/dist/web/assets/{chunk-ZIGJFQKS-BdJ0_J-U.js → chunk-ZIGJFQKS-BZYvP63G.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-NBCMYWYE-C5VzGDGq.js → classDiagram-v2-NBCMYWYE-BS3P_NXb.js} +1 -1
- package/dist/web/assets/{cynefin-OW5HDTMX-BqOgbE7U.js → cynefin-OW5HDTMX-BALD4Pve.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-VND7K2PF-CCC_feG5.js → cynefinDiagram-VND7K2PF-BM2y4W6d.js} +1 -1
- package/dist/web/assets/{dagre-6A5THRUB-SLP-94sd.js → dagre-6A5THRUB-PTfOrIdF.js} +1 -1
- package/dist/web/assets/{diagram-22UHCM2B-DpHwEBYM.js → diagram-22UHCM2B-CpXEHhUl.js} +1 -1
- package/dist/web/assets/{diagram-3UASUU5V-iGFwuMEk.js → diagram-3UASUU5V-DPHfXzr6.js} +1 -1
- package/dist/web/assets/{diagram-ATOU4E4O-Csx9hj0z.js → diagram-ATOU4E4O-DMBNXvAC.js} +1 -1
- package/dist/web/assets/{diagram-CDSNMT55-yGKrLRO5.js → diagram-CDSNMT55-CDLe1M7G.js} +1 -1
- package/dist/web/assets/{diagram-MLGK6HIB-mDpCormD.js → diagram-MLGK6HIB-D6L8oE7j.js} +1 -1
- package/dist/web/assets/{diagram-MPIPVDR6-BJHdatrF.js → diagram-MPIPVDR6-CP-S7rrQ.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-ZINNZB2B-ClJbCBBu.js → ebnfDiagram-ZINNZB2B-CkUc63TK.js} +1 -1
- package/dist/web/assets/{elk-276RUBZZ-C5tFZMYm.js → elk-276RUBZZ-C1CAJLH_.js} +1 -1
- package/dist/web/assets/{erDiagram-OPXOYQCR-u7_UFZot.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-B4hrLcLf.js → ganttDiagram-FUAMR5RP-BVm8SiGg.js} +1 -1
- package/dist/web/assets/{gitGraph-4MIJSDKK-Dxx-Bl-U.js → gitGraph-4MIJSDKK-Dm7xBrW0.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-X574FWY7-DKjDV5Kr.js → gitGraphDiagram-X574FWY7-D_PIKKhx.js} +1 -1
- package/dist/web/assets/index-CeOIBpu2.js +45 -0
- package/dist/web/assets/{info-A6RAGUB7-CgqIdgQA.js → info-A6RAGUB7-CLGrx8pn.js} +1 -1
- package/dist/web/assets/{infoDiagram-VRGFBTTK-D-qjWkwy.js → infoDiagram-VRGFBTTK-BlBjaKm7.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-CzpqV6WN.js → ishikawaDiagram-OU5B5YK6-Kxe7GGS3.js} +1 -1
- package/dist/web/assets/{journeyDiagram-ZHPQQLJL-BBnOUDzw.js → journeyDiagram-ZHPQQLJL-wCMoHK4M.js} +1 -1
- package/dist/web/assets/{kanban-definition-PNTS6WVX-Di9sTFB1.js → kanban-definition-PNTS6WVX-BxdCz9Ko.js} +1 -1
- package/dist/web/assets/{line-B8ehGiwF.js → line-Zf11FnWF.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-DWsT24e5.js → mermaid-parser.core-BvFFklev.js} +3 -3
- package/dist/web/assets/{mermaid.core-DzYpaVzI.js → mermaid.core-Cd_Ssti9.js} +4 -4
- package/dist/web/assets/{mindmap-definition-NLK3R4M7-CxagBsH3.js → mindmap-definition-NLK3R4M7-BGPQl-GY.js} +1 -1
- package/dist/web/assets/{packet-AYTQ26CC-CmxbS0qf.js → packet-AYTQ26CC-6lI3cEX_.js} +1 -1
- package/dist/web/assets/{pegDiagram-GJSIUBJH-_3jnD3q8.js → pegDiagram-GJSIUBJH-BPIjlSAj.js} +1 -1
- package/dist/web/assets/{pie-WAS4IAKB-DppCWOqP.js → pie-WAS4IAKB-D6Dzj3Ja.js} +1 -1
- package/dist/web/assets/{pieDiagram-5QR66LMP-DTVThTqz.js → pieDiagram-5QR66LMP-DtA-vZmv.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-O4NWA36T-BJ_OFnht.js → quadrantDiagram-O4NWA36T-EettyqQn.js} +1 -1
- package/dist/web/assets/{radar-RG4KPBEZ-B6quSMjs.js → radar-RG4KPBEZ-xmRc3Dl8.js} +1 -1
- package/dist/web/assets/{railroad-74A4TZTK-CErU7RG3.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-Zu0N_D-D.js → railroadDiagram-XR7U4H2S-PBw4We6b.js} +1 -1
- package/dist/web/assets/{requirementDiagram-PLB6GJNP-Bl6CN3ul.js → requirementDiagram-PLB6GJNP-DjQpaUFA.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-IPEJSGJF-DdpKzZV8.js → sankeyDiagram-IPEJSGJF-CBW-_AoF.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-PO4LG4MO-BwUqrl9F.js → sequenceDiagram-PO4LG4MO-DEoPYqY9.js} +1 -1
- package/dist/web/assets/{stateDiagram-v2-GCMORJYK-C3vZGpAi.js → stateDiagram-v2-GCMORJYK-UZq5rPud.js} +1 -1
- package/dist/web/assets/{swimlanes-2SLR337P-BjoMa2HW.js → swimlanes-2SLR337P-CACfuXJR.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-CDSrqdgx.js +8 -0
- package/dist/web/assets/{timeline-definition-EJHVYXUP-0apcf2l2.js → timeline-definition-EJHVYXUP-CYJl7mP-.js} +1 -1
- package/dist/web/assets/{treeView-Q6P3EWNA-BOPWzyg3.js → treeView-Q6P3EWNA-DtZ5nkPQ.js} +1 -1
- package/dist/web/assets/{treemap-WGGIJYW6-CC6rU6dD.js → treemap-WGGIJYW6-m49GDM-M.js} +1 -1
- package/dist/web/assets/{usecaseDiagram-POWQR4AR-BFclVNBG.js → usecaseDiagram-POWQR4AR-C2Vg20UK.js} +1 -1
- package/dist/web/assets/{vennDiagram-UO4OBE2U-D2XPv-zb.js → vennDiagram-UO4OBE2U-C07v5OGI.js} +1 -1
- package/dist/web/assets/{wardley-WFR3VGLG-Btz6ID8P.js → wardley-WFR3VGLG-C9Z7EZtz.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-VNRHLVJA-DFYPvGpS.js → wardleyDiagram-VNRHLVJA-DdA8CcSC.js} +1 -1
- package/dist/web/assets/{xychartDiagram-PMCCYNJV-DMkQD-sV.js → xychartDiagram-PMCCYNJV-C9EZw07b.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +5 -2
- package/dist/web/assets/architecture-7GRP2DOG-CGSEE8po.js +0 -1
- package/dist/web/assets/channel-Dneho6u-.js +0 -1
- package/dist/web/assets/eventmodeling-NTZA5JFV-CA-vzJb3.js +0 -1
- package/dist/web/assets/flowDiagram-KWPJA3E3-Ca_1cbbS.js +0 -1
- package/dist/web/assets/index-s6lwoj84.js +0 -45
- package/dist/web/assets/railroad-abnf-HS5TGJTU-CWsEvTRR.js +0 -1
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-Cyq9ELNn.js +0 -1
- package/dist/web/assets/railroad-peg-WCYAUIDC-BX1hI1hV.js +0 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-BGNz1HGG.js +0 -8
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readTextIfExists
|
|
4
|
+
} from "./chunk-IQQBAWSM.js";
|
|
5
|
+
|
|
6
|
+
// src/agents/common/hooks.ts
|
|
7
|
+
import { promises as fs } from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
var ATHENA_MARK = "--athena-hook";
|
|
10
|
+
function hookCommand() {
|
|
11
|
+
return process.env.ATHENA_HOOK_COMMAND?.trim() || "athena";
|
|
12
|
+
}
|
|
13
|
+
function hookArgs(agent, hook) {
|
|
14
|
+
return ["event", ATHENA_MARK, "--agent", agent, "--hook", hook];
|
|
15
|
+
}
|
|
16
|
+
async function readJsonFile(file) {
|
|
17
|
+
const text = await readTextIfExists(file);
|
|
18
|
+
if (text === null) return { data: {}, existed: false, malformed: false };
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(text);
|
|
21
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return { data: parsed, existed: true, malformed: false };
|
|
22
|
+
return { data: {}, existed: true, malformed: true };
|
|
23
|
+
} catch {
|
|
24
|
+
return { data: {}, existed: true, malformed: true };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isAthenaEntry(entry) {
|
|
28
|
+
return JSON.stringify(entry ?? "").includes(ATHENA_MARK);
|
|
29
|
+
}
|
|
30
|
+
async function planJsonChange(root, rel, mutate) {
|
|
31
|
+
const file = path.join(root, rel);
|
|
32
|
+
const { data, existed, malformed } = await readJsonFile(file);
|
|
33
|
+
if (malformed) throw new Error(`${rel} is not valid JSON \u2014 fix it before configuring hooks`);
|
|
34
|
+
const before = JSON.stringify(data);
|
|
35
|
+
mutate(data);
|
|
36
|
+
const content = `${JSON.stringify(data, null, 2)}
|
|
37
|
+
`;
|
|
38
|
+
return { path: rel, ownership: "block", action: !existed ? "create" : JSON.stringify(data) === before ? "unchanged" : "update", content };
|
|
39
|
+
}
|
|
40
|
+
async function removeJsonHooks(root, rel, strip, isEmpty) {
|
|
41
|
+
const file = path.join(root, rel);
|
|
42
|
+
const { data, existed, malformed } = await readJsonFile(file);
|
|
43
|
+
if (!existed || malformed) return false;
|
|
44
|
+
const before = JSON.stringify(data);
|
|
45
|
+
strip(data);
|
|
46
|
+
if (JSON.stringify(data) === before) return false;
|
|
47
|
+
if (isEmpty(data)) await fs.rm(file, { force: true });
|
|
48
|
+
else await fs.writeFile(file, `${JSON.stringify(data, null, 2)}
|
|
49
|
+
`, "utf8");
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
function pruneEmpty(record, key) {
|
|
53
|
+
const value = record[key];
|
|
54
|
+
if (value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0) delete record[key];
|
|
55
|
+
if (Array.isArray(value) && value.length === 0) delete record[key];
|
|
56
|
+
}
|
|
57
|
+
var SAFE_WORD = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
58
|
+
function shQuote(value) {
|
|
59
|
+
if (value.includes("\0")) throw new Error("Hook command contains a NUL byte");
|
|
60
|
+
if (value && SAFE_WORD.test(value)) return value;
|
|
61
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
62
|
+
}
|
|
63
|
+
function cmdQuote(value) {
|
|
64
|
+
if (/["\r\n\0]/.test(value)) throw new Error(`Hook command cannot be quoted safely for cmd.exe: ${JSON.stringify(value)}`);
|
|
65
|
+
const escaped = value.replace(/%/g, "%%");
|
|
66
|
+
if (value && /^[A-Za-z0-9_@+=:,./\\-]+$/.test(value)) return escaped;
|
|
67
|
+
return `"${escaped}"`;
|
|
68
|
+
}
|
|
69
|
+
function hookScript(agent, platform = process.platform) {
|
|
70
|
+
const command = hookCommand();
|
|
71
|
+
if (platform === "win32") {
|
|
72
|
+
return {
|
|
73
|
+
file: "athena-hook.cmd",
|
|
74
|
+
content: ["@echo off", "rem Generated by Athena: forwards agent hook payloads to `athena event`.", `${cmdQuote(command)} event ${ATHENA_MARK} --agent ${cmdQuote(agent)} --hook "%~1"`, "exit /b 0", ""].join("\r\n"),
|
|
75
|
+
mode: 420
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
file: "athena-hook.sh",
|
|
80
|
+
content: ["#!/bin/sh", "# Generated by Athena: forwards agent hook payloads to `athena event`.", `exec ${shQuote(command)} event ${ATHENA_MARK} --agent ${shQuote(agent)} --hook "$1"`, ""].join("\n"),
|
|
81
|
+
mode: 493
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function athenaMcpEntry() {
|
|
85
|
+
return { command: hookCommand(), args: ["mcp"] };
|
|
86
|
+
}
|
|
87
|
+
function mergeMcpServers(data) {
|
|
88
|
+
const servers = data.mcpServers && typeof data.mcpServers === "object" && !Array.isArray(data.mcpServers) ? data.mcpServers : {};
|
|
89
|
+
servers.athena = athenaMcpEntry();
|
|
90
|
+
data.mcpServers = servers;
|
|
91
|
+
}
|
|
92
|
+
function stripMcpServers(data) {
|
|
93
|
+
const servers = data.mcpServers;
|
|
94
|
+
if (!servers) return;
|
|
95
|
+
delete servers.athena;
|
|
96
|
+
pruneEmpty(data, "mcpServers");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
ATHENA_MARK,
|
|
101
|
+
hookCommand,
|
|
102
|
+
hookArgs,
|
|
103
|
+
readJsonFile,
|
|
104
|
+
isAthenaEntry,
|
|
105
|
+
planJsonChange,
|
|
106
|
+
removeJsonHooks,
|
|
107
|
+
pruneEmpty,
|
|
108
|
+
hookScript,
|
|
109
|
+
mergeMcpServers,
|
|
110
|
+
stripMcpServers
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=chunk-GHFRQAYT.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
API_SURFACE_PATH,
|
|
4
|
+
AUTH_PATH,
|
|
5
|
+
SCHEMA_RELATED_PATH,
|
|
6
|
+
TEST_FILE,
|
|
7
|
+
TEST_RUNNER_CONFIG
|
|
8
|
+
} from "./chunk-ELKFRTZT.js";
|
|
9
|
+
|
|
10
|
+
// src/core/impact/impact.ts
|
|
11
|
+
var TESTS = new RegExp(`${TEST_FILE.source}|${TEST_RUNNER_CONFIG.source}`);
|
|
12
|
+
var RULES = [
|
|
13
|
+
{ match: /(^|\/)(package\.json|pyproject\.toml|requirements[^/]*\.txt|Pipfile|go\.mod|Cargo\.toml|pom\.xml|build\.gradle(\.kts)?|composer\.json|Gemfile|pubspec\.yaml|mix\.exs|[^/]+\.csproj)$/, docs: ["project", "architecture", "security", "testing"], reason: "dependency manifest" },
|
|
14
|
+
{ match: /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|poetry\.lock|uv\.lock|Cargo\.lock|go\.sum|composer\.lock|Gemfile\.lock)$/, docs: ["project", "security"], reason: "lockfile" },
|
|
15
|
+
{ match: /(^|\/)(pnpm-workspace\.yaml|turbo\.json|nx\.json|lerna\.json|go\.work|settings\.gradle(\.kts)?)$/, docs: ["project", "architecture"], reason: "workspace configuration" },
|
|
16
|
+
{ match: SCHEMA_RELATED_PATH, docs: ["database", "performance", "code-review"], reason: "schema / migration" },
|
|
17
|
+
{ match: API_SURFACE_PATH, docs: ["api", "security", "testing"], reason: "API surface" },
|
|
18
|
+
{ match: AUTH_PATH, docs: ["auth", "security"], reason: "auth-related" },
|
|
19
|
+
{ match: /(^|\/)(Dockerfile|Containerfile)[^/]*$|(^|\/)(docker-)?compose[^/]*\.ya?ml$/i, docs: ["deployment", "architecture", "security"], reason: "container configuration" },
|
|
20
|
+
{ match: /^\.github\/workflows\/|(^|\/)(\.gitlab-ci\.yml|Jenkinsfile|azure-pipelines\.ya?ml|bitbucket-pipelines\.yml)$|^\.circleci\//, docs: ["deployment", "testing"], reason: "CI/CD" },
|
|
21
|
+
{ match: /\.tf$|(^|\/)(vercel\.json|netlify\.toml|fly\.toml|render\.ya?ml|railway\.(json|toml)|Procfile|wrangler\.(toml|jsonc?)|serverless\.ya?ml|Chart\.yaml|kustomization\.ya?ml)$|(^|\/)(k8s|kubernetes|helm|deploy|infra)\//, docs: ["deployment", "architecture"], reason: "infrastructure" },
|
|
22
|
+
{ match: /(^|\/)\.env\.(example|sample|template)$/, docs: ["deployment", "project"], reason: "environment template" },
|
|
23
|
+
{ match: TESTS, docs: ["testing"], reason: "tests" },
|
|
24
|
+
{ match: /(cache|redis|queue|worker|jobs?)\b/i, docs: ["performance"], reason: "caching / background work" },
|
|
25
|
+
{ match: /(^|\/)(Makefile|justfile|Taskfile\.ya?ml)$/, docs: ["project", "debugging"], reason: "task runner" }
|
|
26
|
+
];
|
|
27
|
+
function impactOf(paths, opts = {}) {
|
|
28
|
+
const docs = /* @__PURE__ */ new Set();
|
|
29
|
+
const reasons = {};
|
|
30
|
+
const note = (doc, why) => {
|
|
31
|
+
docs.add(doc);
|
|
32
|
+
reasons[doc] ??= [];
|
|
33
|
+
if (!reasons[doc].includes(why)) reasons[doc].push(why);
|
|
34
|
+
};
|
|
35
|
+
for (const p of paths) {
|
|
36
|
+
if (p.startsWith(".athena/")) continue;
|
|
37
|
+
for (const r of RULES) if (r.match.test(p)) for (const d of r.docs) note(d, r.reason);
|
|
38
|
+
}
|
|
39
|
+
if (opts.structural?.some((p) => !p.startsWith(".athena/"))) note("project", "files added or removed");
|
|
40
|
+
const order = ["project", "architecture", "database", "api", "auth", "security", "testing", "debugging", "performance", "code-review", "deployment", "rules"];
|
|
41
|
+
return { docs: [...docs].sort((a, b) => order.indexOf(a) - order.indexOf(b)), reasons };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
impactOf
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=chunk-GR4DL2VM.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/agents/common/hook-events.ts
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
var TEST_COMMAND = /\b(test|vitest|jest|pytest|go test|cargo test|mocha|rspec|phpunit|gradle test|mvn test|tox|playwright|cypress)\b/i;
|
|
6
|
+
var REVIEW_COMMAND = /\b(lint|eslint|ruff|clippy|tsc|typecheck|type-check|mypy|flake8|rubocop|golangci-lint|audit)\b/i;
|
|
7
|
+
var READ_TOOLS = /* @__PURE__ */ new Set(["read", "glob", "grep", "notebookread", "webfetch", "websearch", "ls", "searchreplace", "read_file", "read_many_files", "list_directory", "grep_search", "web_fetch", "google_web_search"]);
|
|
8
|
+
var EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "multiedit", "notebookedit", "applypatch", "apply_patch", "create_diff", "str_replace", "write_file", "replace"]);
|
|
9
|
+
var SEARCH_TOOLS = /* @__PURE__ */ new Set(["grep", "glob", "grep_search"]);
|
|
10
|
+
function truncate(s, n = 160) {
|
|
11
|
+
const one = s.replace(/\s+/g, " ").trim();
|
|
12
|
+
return one.length > n ? `${one.slice(0, n - 1)}\u2026` : one;
|
|
13
|
+
}
|
|
14
|
+
function asString(v) {
|
|
15
|
+
return typeof v === "string" && v.trim() ? v : null;
|
|
16
|
+
}
|
|
17
|
+
function filesFrom(input, payload) {
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const source of [input, payload]) {
|
|
20
|
+
if (!source) continue;
|
|
21
|
+
for (const key of ["file_path", "filePath", "path", "notebook_path", "target_file"]) {
|
|
22
|
+
const v = asString(source[key]);
|
|
23
|
+
if (v) out.push(v);
|
|
24
|
+
}
|
|
25
|
+
const edits = source.edits;
|
|
26
|
+
if (Array.isArray(edits)) {
|
|
27
|
+
for (const e of edits) {
|
|
28
|
+
const v = e && typeof e === "object" ? asString(e.file_path) : null;
|
|
29
|
+
if (v) out.push(v);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [...new Set(out)];
|
|
34
|
+
}
|
|
35
|
+
function patchFiles(patch) {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const m of patch.matchAll(/^\*\*\* (?:Add|Update|Delete) File: (.+)$|^\*\*\* Move to: (.+)$/gm)) {
|
|
38
|
+
const f = (m[1] ?? m[2]).trim();
|
|
39
|
+
if (f) out.push(f);
|
|
40
|
+
}
|
|
41
|
+
return [...new Set(out)];
|
|
42
|
+
}
|
|
43
|
+
function classifyTool(tool, command, files) {
|
|
44
|
+
const t = tool.toLowerCase().replace(/^mcp__/, "");
|
|
45
|
+
const where = files.length ? ` ${files.slice(0, 2).join(", ")}${files.length > 2 ? ` +${files.length - 2}` : ""}` : "";
|
|
46
|
+
if (EDIT_TOOLS.has(t)) return { kind: "edit", state: "CODING", message: `Editing${where || " files"}` };
|
|
47
|
+
if (READ_TOOLS.has(t)) return { kind: SEARCH_TOOLS.has(t) ? "search" : "read", state: "ANALYZING", message: `Reading${where || ` (${tool})`}` };
|
|
48
|
+
if (t === "bash" || t === "shell" || t === "powershell" || t === "run_shell_command" || t.includes("terminal")) {
|
|
49
|
+
if (command && TEST_COMMAND.test(command)) return { kind: "command", state: "TESTING", message: `Running tests: ${command}` };
|
|
50
|
+
if (command && REVIEW_COMMAND.test(command)) return { kind: "command", state: "REVIEWING", message: `Running checks: ${command}` };
|
|
51
|
+
return { kind: "command", state: "CODING", message: command ? `Running: ${command}` : "Running a shell command" };
|
|
52
|
+
}
|
|
53
|
+
if (t === "task" || t === "agent") return { kind: "subagent", state: "PLANNING", message: "Delegating to a subagent" };
|
|
54
|
+
if (t === "todowrite" || t === "exitplanmode" || t === "plan" || t === "update_plan" || t === "write_todos" || t === "enter_plan_mode" || t === "exit_plan_mode") return { kind: "tool", state: "PLANNING", message: "Updating its plan" };
|
|
55
|
+
return { kind: "tool", state: "CODING", message: `Using ${tool}` };
|
|
56
|
+
}
|
|
57
|
+
function normalizeHookEvent(agent, hookName, payload) {
|
|
58
|
+
const hook = hookName || asString(payload.hook_event_name) || "unknown";
|
|
59
|
+
const h = hook.toLowerCase();
|
|
60
|
+
const session = asString(payload.session_id) ?? asString(payload.conversation_id) ?? null;
|
|
61
|
+
const toolInput = payload.tool_input && typeof payload.tool_input === "object" ? payload.tool_input : null;
|
|
62
|
+
const tool = asString(payload.tool_name) ?? asString(payload.tool) ?? null;
|
|
63
|
+
const isPatch = tool?.toLowerCase() === "apply_patch";
|
|
64
|
+
const rawCommand = asString(toolInput?.command) ?? asString(payload.command);
|
|
65
|
+
const command = rawCommand && !isPatch ? truncate(rawCommand) : null;
|
|
66
|
+
const files = isPatch && rawCommand ? patchFiles(rawCommand) : filesFrom(toolInput, payload);
|
|
67
|
+
let c = null;
|
|
68
|
+
if (h === "sessionstart" || h === "workspaceopen") c = { kind: "session-start", state: "IDLE", message: "Session started" };
|
|
69
|
+
else if (h === "sessionend") c = { kind: "session-end", state: "IDLE", message: "Session ended" };
|
|
70
|
+
else if (h === "userpromptsubmit" || h === "beforesubmitprompt" || h === "beforeagent") c = { kind: "prompt", state: "PLANNING", message: "Received a new task from the developer" };
|
|
71
|
+
else if (h === "stop" || h === "afteragentresponse" || h === "afteragent") c = { kind: "stop", state: "SUCCESS", message: "Finished responding" };
|
|
72
|
+
else if (h === "subagentstart") c = { kind: "subagent", state: "PLANNING", message: "Subagent started" };
|
|
73
|
+
else if (h === "subagentstop") c = { kind: "subagent", state: "CODING", message: "Subagent finished" };
|
|
74
|
+
else if (h === "beforeshellexecution") c = classifyTool("bash", command, files);
|
|
75
|
+
else if (h === "aftershellexecution") return null;
|
|
76
|
+
else if (h === "beforereadfile" || h === "beforetabfileread") c = classifyTool("read", null, files);
|
|
77
|
+
else if (h === "afterfileedit" || h === "aftertabfileedit") c = classifyTool("edit", null, files);
|
|
78
|
+
else if (h === "pretooluse" || h === "permissionrequest" || h === "beforetool") c = tool ? classifyTool(tool, command, files) : null;
|
|
79
|
+
else if (h === "posttooluse" || h === "posttoolusefailure" || h === "aftertool") return null;
|
|
80
|
+
else if (h === "pretoolusefailure") return null;
|
|
81
|
+
else c = { kind: "other", state: "CODING", message: `Hook: ${hook}` };
|
|
82
|
+
if (!c) return null;
|
|
83
|
+
return {
|
|
84
|
+
id: crypto.randomUUID(),
|
|
85
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
86
|
+
agent,
|
|
87
|
+
session,
|
|
88
|
+
hook,
|
|
89
|
+
kind: c.kind,
|
|
90
|
+
state: c.state,
|
|
91
|
+
message: c.message,
|
|
92
|
+
files: files.slice(0, 10),
|
|
93
|
+
command,
|
|
94
|
+
tool
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "Stop", "SessionEnd"];
|
|
98
|
+
var CODEX_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "Stop", "SessionEnd"];
|
|
99
|
+
var CURSOR_HOOK_EVENTS = ["sessionStart", "beforeSubmitPrompt", "beforeShellExecution", "afterFileEdit", "stop", "sessionEnd"];
|
|
100
|
+
var COPILOT_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "Stop"];
|
|
101
|
+
var GEMINI_HOOK_EVENTS = ["SessionStart", "BeforeAgent", "BeforeTool", "AfterAgent", "SessionEnd"];
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
normalizeHookEvent,
|
|
105
|
+
CLAUDE_HOOK_EVENTS,
|
|
106
|
+
CODEX_HOOK_EVENTS,
|
|
107
|
+
CURSOR_HOOK_EVENTS,
|
|
108
|
+
COPILOT_HOOK_EVENTS,
|
|
109
|
+
GEMINI_HOOK_EVENTS
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=chunk-INT3IGMQ.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/util/fs.ts
|
|
4
|
+
import { promises as fs } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
async function writeFileAtomic(file, data) {
|
|
8
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
9
|
+
const tmp = `${file}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
10
|
+
try {
|
|
11
|
+
await fs.writeFile(tmp, data, "utf8");
|
|
12
|
+
await fs.rename(tmp, file);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
await fs.rm(tmp, { force: true }).catch(() => {
|
|
15
|
+
});
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function exists(p) {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(p);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function readTextIfExists(p) {
|
|
28
|
+
try {
|
|
29
|
+
return await fs.readFile(p, "utf8");
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (err.code === "ENOENT") return null;
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function sha256(data) {
|
|
36
|
+
return crypto.createHash("sha256").update(data).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
async function readTextInsideRoot(root, rel, maxBytes) {
|
|
39
|
+
const abs = path.resolve(root, rel);
|
|
40
|
+
let realRoot;
|
|
41
|
+
try {
|
|
42
|
+
realRoot = await fs.realpath(root);
|
|
43
|
+
} catch {
|
|
44
|
+
return { ok: false, reason: "unreadable" };
|
|
45
|
+
}
|
|
46
|
+
const inside = (p) => {
|
|
47
|
+
const r = path.relative(realRoot, p);
|
|
48
|
+
return r === "" || !r.startsWith(`..${path.sep}`) && r !== ".." && !path.isAbsolute(r);
|
|
49
|
+
};
|
|
50
|
+
let target = abs;
|
|
51
|
+
try {
|
|
52
|
+
const st = await fs.lstat(abs);
|
|
53
|
+
if (st.isSymbolicLink() || !inside(await fs.realpath(path.dirname(abs)))) {
|
|
54
|
+
target = await fs.realpath(abs);
|
|
55
|
+
if (!inside(target)) return { ok: false, reason: "outside-root" };
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return { ok: false, reason: err.code === "ENOENT" ? "missing" : "unreadable" };
|
|
59
|
+
}
|
|
60
|
+
let handle;
|
|
61
|
+
try {
|
|
62
|
+
handle = await fs.open(target, "r");
|
|
63
|
+
const st = await handle.stat();
|
|
64
|
+
if (!st.isFile()) return { ok: false, reason: "not-a-file" };
|
|
65
|
+
if (st.size > maxBytes) return { ok: false, reason: "too-large", bytes: st.size };
|
|
66
|
+
const buf = Buffer.alloc(maxBytes + 1);
|
|
67
|
+
let n = 0;
|
|
68
|
+
while (n < buf.length) {
|
|
69
|
+
const { bytesRead } = await handle.read(buf, n, buf.length - n, n);
|
|
70
|
+
if (!bytesRead) break;
|
|
71
|
+
n += bytesRead;
|
|
72
|
+
}
|
|
73
|
+
if (n > maxBytes) return { ok: false, reason: "too-large", bytes: n };
|
|
74
|
+
return { ok: true, text: buf.subarray(0, n).toString("utf8"), bytes: n };
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return { ok: false, reason: err.code === "ENOENT" ? "missing" : "unreadable" };
|
|
77
|
+
} finally {
|
|
78
|
+
await handle?.close().catch(() => {
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
writeFileAtomic,
|
|
85
|
+
exists,
|
|
86
|
+
readTextIfExists,
|
|
87
|
+
sha256,
|
|
88
|
+
readTextInsideRoot
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=chunk-IQQBAWSM.js.map
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/security/secrets.ts
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
var MAX_GENERIC_LINE = 4096;
|
|
6
|
+
var PRIVATE_KEY_WINDOW = 16 * 1024;
|
|
7
|
+
var IDENTIFIER_LOOKBACK = 64;
|
|
8
|
+
var PEM_LABEL = "(?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----";
|
|
9
|
+
var PEM_BEGIN = new RegExp(`-----BEGIN ${PEM_LABEL}`, "g");
|
|
10
|
+
var PEM_END = new RegExp(`-----END ${PEM_LABEL}`, "g");
|
|
11
|
+
var GENERIC_KEYWORD = /password|passwd|pwd|secret|token|api[_-]?key|apikey|access[_-]?key|auth[_-]?key|client[_-]?secret|private[_-]?key/i;
|
|
12
|
+
var ENV_KEYWORD = /PASSWORD|SECRET|TOKEN|API_KEY|APIKEY|ACCESS_KEY|PRIVATE_KEY|CLIENT_SECRET/;
|
|
13
|
+
var SECRET_PATTERNS = [
|
|
14
|
+
// Matched in two steps (see matchPrivateKeys): this regex only finds the BEGIN header.
|
|
15
|
+
{ id: "private-key", description: "Private key block", regex: PEM_BEGIN },
|
|
16
|
+
{ id: "aws-access-key-id", description: "AWS access key ID", regex: /\b((?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16})\b/g, group: 1 },
|
|
17
|
+
{ id: "aws-secret-access-key", description: "AWS secret access key", regex: /aws_?secret_?access_?key["']?[ \t]*[:=][ \t]*["']?([A-Za-z0-9/+=]{40})\b/gi, group: 1 },
|
|
18
|
+
{ id: "gcp-service-account", description: "GCP service account private key", regex: /"private_key"[ \t]*:[ \t]*"(-----BEGIN PRIVATE KEY-----[^"]{1,16384})"/g, group: 1 },
|
|
19
|
+
{ id: "azure-storage-key", description: "Azure storage connection string", regex: /(?<![A-Za-z0-9+/=])AccountKey=([A-Za-z0-9+/=]{60,1024})/g, group: 1 },
|
|
20
|
+
{ id: "github-token", description: "GitHub token", regex: /(?<![A-Za-z0-9_])((?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,255}|github_pat_[A-Za-z0-9_]{60,255})\b/g, group: 1 },
|
|
21
|
+
{ id: "gitlab-token", description: "GitLab token", regex: /(?<![A-Za-z0-9_-])(glpat-[A-Za-z0-9_-]{20,255})(?![A-Za-z0-9_-])/g, group: 1 },
|
|
22
|
+
{ id: "slack-token", description: "Slack token", regex: /(?<![A-Za-z0-9_-])(xox[abprs]-[A-Za-z0-9-]{10,255})(?![A-Za-z0-9_-])/g, group: 1 },
|
|
23
|
+
{ id: "slack-webhook", description: "Slack webhook URL", regex: /(https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]{20,255})/g, group: 1 },
|
|
24
|
+
{ id: "stripe-key", description: "Stripe secret key", regex: /(?<![A-Za-z0-9_])((?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,255})\b/g, group: 1 },
|
|
25
|
+
{ id: "anthropic-key", description: "Anthropic API key", regex: /(?<![A-Za-z0-9_-])(sk-ant-[A-Za-z0-9_-]{20,255})(?![A-Za-z0-9_-])/g, group: 1 },
|
|
26
|
+
{ id: "openai-key", description: "OpenAI API key", regex: /(?<![A-Za-z0-9_-])(sk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{32,255})(?![A-Za-z0-9_-])/g, group: 1 },
|
|
27
|
+
{ id: "google-api-key", description: "Google API key", regex: /(?<![A-Za-z0-9_-])(AIza[0-9A-Za-z_-]{35})(?![A-Za-z0-9_-])/g, group: 1 },
|
|
28
|
+
{ id: "sendgrid-key", description: "SendGrid API key", regex: /(?<![A-Za-z0-9_-])(SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43})(?![A-Za-z0-9_-])/g, group: 1 },
|
|
29
|
+
{ id: "npm-token", description: "npm token", regex: /(?<![A-Za-z0-9_])(npm_[A-Za-z0-9]{36})\b/g, group: 1 },
|
|
30
|
+
{ id: "jwt", description: "JSON Web Token", regex: /(?<![A-Za-z0-9_-])(eyJ[A-Za-z0-9_-]{10,4096}\.eyJ[A-Za-z0-9_-]{10,8192}\.[A-Za-z0-9_-]{10,4096})(?![A-Za-z0-9_-])/g, group: 1 },
|
|
31
|
+
{
|
|
32
|
+
id: "connection-string-credentials",
|
|
33
|
+
description: "Connection string with embedded password",
|
|
34
|
+
regex: /\b(?:postgres(?:ql)?|mysql|mariadb|mongodb(?:\+srv)?|redis|rediss|amqps?|mssql|sqlserver):\/\/[^\s:@/"'`]{1,256}:([^\s@/"'`]{3,256})@/gi,
|
|
35
|
+
group: 1
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
// Two-step, linear: find a bounded quoted value after ":" or "=", then check the
|
|
39
|
+
// identifier just before the operator for a keyword (see identifierBefore).
|
|
40
|
+
id: "generic-secret-assignment",
|
|
41
|
+
description: "Hardcoded secret-like assignment",
|
|
42
|
+
regex: /[:=][ \t]*["'`]([^"'`\s]{12,200})["'`]/g,
|
|
43
|
+
group: 1,
|
|
44
|
+
minEntropy: 3.5,
|
|
45
|
+
identifier: GENERIC_KEYWORD
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "env-secret-assignment",
|
|
49
|
+
description: "Secret-like environment value",
|
|
50
|
+
regex: /^[ \t]*(?:export[ \t]+)?[A-Z0-9_]{1,128}[ \t]*=[ \t]*["']?([^\s"'#$]{8,512})["']?[ \t\r]*$/gm,
|
|
51
|
+
group: 1,
|
|
52
|
+
minEntropy: 3,
|
|
53
|
+
identifier: ENV_KEYWORD
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
var PLACEHOLDER_VALUE = /^(?:<.*>|\$\{.*\}|\{\{.*\}\}|x{4,}|\*{4,}|changeme|change[-_]?me|your[-_].*|example.*|placeholder.*|dummy.*|test.*|fake.*|replace[-_]?me|todo|null|none|undefined|password|secret|redacted)$/i;
|
|
57
|
+
var PROCESS_KEY = crypto.randomBytes(32);
|
|
58
|
+
var ephemeralFingerprint = (value) => crypto.createHmac("sha256", PROCESS_KEY).update(value).digest("hex").slice(0, 16);
|
|
59
|
+
function shannonEntropy(s) {
|
|
60
|
+
if (!s) return 0;
|
|
61
|
+
const counts = /* @__PURE__ */ new Map();
|
|
62
|
+
for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
63
|
+
let h = 0;
|
|
64
|
+
for (const c of counts.values()) {
|
|
65
|
+
const p = c / s.length;
|
|
66
|
+
h -= p * Math.log2(p);
|
|
67
|
+
}
|
|
68
|
+
return h;
|
|
69
|
+
}
|
|
70
|
+
function matchPrivateKeys(text) {
|
|
71
|
+
if (!text.includes("PRIVATE KEY")) return [];
|
|
72
|
+
const ends = [];
|
|
73
|
+
PEM_END.lastIndex = 0;
|
|
74
|
+
for (const m of text.matchAll(PEM_END)) ends.push([m.index, m.index + m[0].length]);
|
|
75
|
+
if (!ends.length) return [];
|
|
76
|
+
const out = [];
|
|
77
|
+
let e = 0;
|
|
78
|
+
PEM_BEGIN.lastIndex = 0;
|
|
79
|
+
for (const m of text.matchAll(PEM_BEGIN)) {
|
|
80
|
+
const bodyStart = m.index + m[0].length;
|
|
81
|
+
while (e < ends.length && ends[e][0] < bodyStart) e++;
|
|
82
|
+
if (e >= ends.length) break;
|
|
83
|
+
const [endStart, endEnd] = ends[e];
|
|
84
|
+
if (endStart - bodyStart > PRIVATE_KEY_WINDOW) continue;
|
|
85
|
+
out.push({ start: m.index, end: endEnd, value: text.slice(m.index, endEnd) });
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
var IDENT_CHAR = /[A-Za-z0-9_.-]/;
|
|
90
|
+
function identifierBefore(text, opIndex) {
|
|
91
|
+
let i = opIndex - 1;
|
|
92
|
+
while (i >= 0 && (text[i] === " " || text[i] === " ")) i--;
|
|
93
|
+
if (i >= 0 && (text[i] === '"' || text[i] === "'")) i--;
|
|
94
|
+
const end = i + 1;
|
|
95
|
+
const floor = Math.max(0, end - IDENTIFIER_LOOKBACK);
|
|
96
|
+
while (i >= floor && IDENT_CHAR.test(text[i])) i--;
|
|
97
|
+
return text.slice(i + 1, end);
|
|
98
|
+
}
|
|
99
|
+
function lineLength(starts, line, textLength) {
|
|
100
|
+
const start = starts[line - 1];
|
|
101
|
+
const next = line < starts.length ? starts[line] : textLength + 1;
|
|
102
|
+
return next - 1 - start;
|
|
103
|
+
}
|
|
104
|
+
function scanText(text, opts = {}) {
|
|
105
|
+
const fingerprint = opts.fingerprint ?? ephemeralFingerprint;
|
|
106
|
+
const matches = [];
|
|
107
|
+
const taken = [];
|
|
108
|
+
const lineStarts = computeLineStarts(text);
|
|
109
|
+
if (opts.stats) {
|
|
110
|
+
for (let l = 1; l <= lineStarts.length; l++) if (lineLength(lineStarts, l, text.length) > MAX_GENERIC_LINE) opts.stats.skippedLongLines++;
|
|
111
|
+
}
|
|
112
|
+
const accept = (type, raw, line = lineAt(lineStarts, raw.start)) => {
|
|
113
|
+
if (taken.some(([s, e]) => raw.start < e && raw.end > s)) return;
|
|
114
|
+
taken.push([raw.start, raw.end]);
|
|
115
|
+
matches.push({ type, line, start: raw.start, end: raw.end, fingerprint: fingerprint(raw.value) });
|
|
116
|
+
};
|
|
117
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
118
|
+
if (pattern.id === "private-key") {
|
|
119
|
+
for (const raw of matchPrivateKeys(text)) accept(pattern.id, raw);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
pattern.regex.lastIndex = 0;
|
|
123
|
+
for (const m of text.matchAll(pattern.regex)) {
|
|
124
|
+
const group = pattern.group ?? 0;
|
|
125
|
+
const value = m[group];
|
|
126
|
+
if (!value || m.index === void 0) continue;
|
|
127
|
+
if (PLACEHOLDER_VALUE.test(value)) continue;
|
|
128
|
+
if (pattern.minEntropy !== void 0 && shannonEntropy(value) < pattern.minEntropy) continue;
|
|
129
|
+
const offsetInMatch = group === 0 ? 0 : m[0].lastIndexOf(value);
|
|
130
|
+
const start = m.index + Math.max(offsetInMatch, 0);
|
|
131
|
+
const line = lineAt(lineStarts, start);
|
|
132
|
+
if (pattern.identifier) {
|
|
133
|
+
if (lineLength(lineStarts, line, text.length) > MAX_GENERIC_LINE) continue;
|
|
134
|
+
const name = pattern.id === "env-secret-assignment" ? envName(m[0]) : identifierBefore(text, m.index);
|
|
135
|
+
if (!pattern.identifier.test(name)) continue;
|
|
136
|
+
}
|
|
137
|
+
accept(pattern.id, { start, end: start + value.length, value }, line);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return matches.sort((a, b) => a.start - b.start);
|
|
141
|
+
}
|
|
142
|
+
function envName(match) {
|
|
143
|
+
const eq = match.indexOf("=");
|
|
144
|
+
return match.slice(0, eq).replace(/^[ \t]*(?:export[ \t]+)?/, "").trim();
|
|
145
|
+
}
|
|
146
|
+
function redact(text, placeholder = "<redacted>") {
|
|
147
|
+
const matches = scanText(text);
|
|
148
|
+
if (!matches.length) return text;
|
|
149
|
+
let out = "";
|
|
150
|
+
let cursor = 0;
|
|
151
|
+
for (const m of matches) {
|
|
152
|
+
out += text.slice(cursor, m.start) + placeholder;
|
|
153
|
+
cursor = m.end;
|
|
154
|
+
}
|
|
155
|
+
return out + text.slice(cursor);
|
|
156
|
+
}
|
|
157
|
+
function computeLineStarts(text) {
|
|
158
|
+
const starts = [0];
|
|
159
|
+
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) starts.push(i + 1);
|
|
160
|
+
return starts;
|
|
161
|
+
}
|
|
162
|
+
function lineAt(starts, offset) {
|
|
163
|
+
let lo = 0;
|
|
164
|
+
let hi = starts.length - 1;
|
|
165
|
+
while (lo < hi) {
|
|
166
|
+
const mid = lo + hi + 1 >> 1;
|
|
167
|
+
if (starts[mid] <= offset) lo = mid;
|
|
168
|
+
else hi = mid - 1;
|
|
169
|
+
}
|
|
170
|
+
return lo + 1;
|
|
171
|
+
}
|
|
172
|
+
function isSecretLikeName(name) {
|
|
173
|
+
return /(PASSWORD|PASSWD|SECRET|TOKEN|API_?KEY|ACCESS_?KEY|PRIVATE_?KEY|CREDENTIAL|DATABASE_URL|DB_URL|CONNECTION_STRING|DSN|WEBHOOK)/i.test(name);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export {
|
|
177
|
+
ephemeralFingerprint,
|
|
178
|
+
scanText,
|
|
179
|
+
redact,
|
|
180
|
+
isSecretLikeName
|
|
181
|
+
};
|
|
182
|
+
//# sourceMappingURL=chunk-JCGDR4UV.js.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readRecentEvents,
|
|
4
|
+
summarizeSessions
|
|
5
|
+
} from "./chunk-65TSRDQ3.js";
|
|
6
|
+
import {
|
|
7
|
+
ADAPTERS,
|
|
8
|
+
applyChanges,
|
|
9
|
+
resolveAdapters
|
|
10
|
+
} from "./chunk-2RS6TA3D.js";
|
|
11
|
+
import {
|
|
12
|
+
athenaDir,
|
|
13
|
+
readState,
|
|
14
|
+
writeState
|
|
15
|
+
} from "./chunk-2DY4PUR4.js";
|
|
16
|
+
import {
|
|
17
|
+
AthenaError
|
|
18
|
+
} from "./chunk-AD4OZCU2.js";
|
|
19
|
+
|
|
20
|
+
// src/services/agents.ts
|
|
21
|
+
function resolve(names) {
|
|
22
|
+
try {
|
|
23
|
+
return names.length === 1 && names[0] === "all" ? ADAPTERS : resolveAdapters(names);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
throw new AthenaError(err.message);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function listAgents(root) {
|
|
29
|
+
const st = await readState(athenaDir(root));
|
|
30
|
+
const sessions = summarizeSessions(await readRecentEvents(root, 500));
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const a of ADAPTERS) {
|
|
33
|
+
const presence = await a.detectPresence(root);
|
|
34
|
+
const s = st.kind === "ok" ? st.state.agents[a.id] : void 0;
|
|
35
|
+
const checks = s?.configured ? await a.check(root) : [];
|
|
36
|
+
const hooksInstalled = a.capabilities.hooks && checks.some((c) => c.ok && /hooks installed/i.test(c.message));
|
|
37
|
+
const mine = sessions.filter((x) => x.agent === a.id);
|
|
38
|
+
out.push({
|
|
39
|
+
id: a.id,
|
|
40
|
+
name: a.displayName,
|
|
41
|
+
configured: Boolean(s?.configured),
|
|
42
|
+
configuredAt: s?.configuredAt ?? null,
|
|
43
|
+
files: s?.files ?? [],
|
|
44
|
+
detectedInProject: presence.detectedInProject,
|
|
45
|
+
evidence: presence.evidence,
|
|
46
|
+
note: a.supportNote,
|
|
47
|
+
capabilities: a.capabilities,
|
|
48
|
+
checks,
|
|
49
|
+
activityObservation: !a.capabilities.hooks ? "unsupported" : hooksInstalled ? "hooks" : "not-configured",
|
|
50
|
+
lastActivityAt: mine[0]?.lastEventAt ?? null,
|
|
51
|
+
observedEvents: mine.reduce((n, x) => n + x.events, 0)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
async function configureAgents(root, names) {
|
|
57
|
+
const st = await readState(athenaDir(root));
|
|
58
|
+
if (st.kind !== "ok") throw new AthenaError("state.json is missing or corrupted.", "Run `athena analyze` first.");
|
|
59
|
+
const adapters = resolve(names);
|
|
60
|
+
const results = [];
|
|
61
|
+
for (const a of adapters) {
|
|
62
|
+
const planned = await a.plan({ root, projectName: st.state.projectName });
|
|
63
|
+
await applyChanges(root, planned);
|
|
64
|
+
st.state.agents[a.id] = { configured: true, files: planned.map((p) => p.path), configuredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
65
|
+
results.push({ id: a.id, files: planned.map((p) => ({ path: p.path, action: p.action })) });
|
|
66
|
+
}
|
|
67
|
+
await writeState(athenaDir(root), st.state);
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
async function removeAgents(root, names) {
|
|
71
|
+
const st = await readState(athenaDir(root));
|
|
72
|
+
const removed = [];
|
|
73
|
+
const targets = resolve(names);
|
|
74
|
+
for (const a of targets) {
|
|
75
|
+
removed.push(...await a.remove(root));
|
|
76
|
+
if (st.kind === "ok") delete st.state.agents[a.id];
|
|
77
|
+
}
|
|
78
|
+
if (st.kind === "ok") {
|
|
79
|
+
for (const a of ADAPTERS) {
|
|
80
|
+
const s = st.state.agents[a.id];
|
|
81
|
+
if (!s?.configured || targets.includes(a) || !s.files.some((f) => removed.includes(f))) continue;
|
|
82
|
+
const planned = await a.plan({ root, projectName: st.state.projectName });
|
|
83
|
+
await applyChanges(root, planned.filter((p) => removed.includes(p.path)));
|
|
84
|
+
}
|
|
85
|
+
await writeState(athenaDir(root), st.state);
|
|
86
|
+
}
|
|
87
|
+
return removed;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
listAgents,
|
|
92
|
+
configureAgents,
|
|
93
|
+
removeAgents
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=chunk-JY6BFIJJ.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ATHENA_DIR
|
|
4
|
+
} from "./chunk-EUPETUWA.js";
|
|
5
|
+
|
|
6
|
+
// src/services/project.ts
|
|
7
|
+
import { promises as fs } from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
async function findProjectRoot(start) {
|
|
10
|
+
let dir = start;
|
|
11
|
+
for (; ; ) {
|
|
12
|
+
try {
|
|
13
|
+
const st = await fs.stat(path.join(dir, ATHENA_DIR));
|
|
14
|
+
if (st.isDirectory()) return dir;
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
const parent = path.dirname(dir);
|
|
18
|
+
if (parent === dir) return null;
|
|
19
|
+
dir = parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
findProjectRoot
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=chunk-KBO7XRBW.js.map
|