@hasankemaldemirci/aro 1.0.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/.agent_context_pro.json +57 -0
- package/.env.example +3 -0
- package/.github/workflows/aro.yml +41 -0
- package/.husky/pre-commit +19 -0
- package/CONTRIBUTING.md +34 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/bin/aro.ts +86 -0
- package/dist/bin/aro.d.ts +7 -0
- package/dist/bin/aro.js +118 -0
- package/dist/src/badge.d.ts +6 -0
- package/dist/src/badge.js +64 -0
- package/dist/src/constants.d.ts +20 -0
- package/dist/src/constants.js +68 -0
- package/dist/src/core.d.ts +12 -0
- package/dist/src/core.js +107 -0
- package/dist/src/enterprise.d.ts +8 -0
- package/dist/src/enterprise.js +125 -0
- package/dist/src/init.d.ts +1 -0
- package/dist/src/init.js +64 -0
- package/dist/src/mcp.d.ts +5 -0
- package/dist/src/mcp.js +138 -0
- package/dist/src/refactor.d.ts +6 -0
- package/dist/src/refactor.js +91 -0
- package/dist/src/rules.d.ts +6 -0
- package/dist/src/rules.js +67 -0
- package/dist/src/types.d.ts +39 -0
- package/dist/src/types.js +6 -0
- package/dist/src/utils.d.ts +14 -0
- package/dist/src/utils.js +194 -0
- package/dist/tests/cli.test.d.ts +1 -0
- package/dist/tests/cli.test.js +56 -0
- package/dist/tests/core.test.d.ts +5 -0
- package/dist/tests/core.test.js +129 -0
- package/dist/tests/enterprise.test.d.ts +1 -0
- package/dist/tests/enterprise.test.js +74 -0
- package/dist/tests/mcp.test.d.ts +1 -0
- package/dist/tests/mcp.test.js +81 -0
- package/eslint.config.mjs +9 -0
- package/package.json +66 -0
- package/src/badge.ts +77 -0
- package/src/constants.ts +68 -0
- package/src/core.ts +141 -0
- package/src/enterprise.ts +159 -0
- package/src/init.ts +75 -0
- package/src/mcp.ts +158 -0
- package/src/refactor.ts +122 -0
- package/src/rules.ts +78 -0
- package/src/types.ts +43 -0
- package/src/utils.ts +199 -0
- package/tests/cli.test.ts +71 -0
- package/tests/core.test.ts +146 -0
- package/tests/enterprise.test.ts +78 -0
- package/tests/mcp.test.ts +89 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AROMetrics } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* @aro-context-marker
|
|
4
|
+
* AI READABILITY NOTE: Performance Optimized Utilities.
|
|
5
|
+
* Using fast-glob for high-speed file discovery.
|
|
6
|
+
*/
|
|
7
|
+
export * from "./types";
|
|
8
|
+
export declare function loadIgnoreList(projectPath: string): string[];
|
|
9
|
+
export declare function detectFramework(pkg: any): string;
|
|
10
|
+
export declare function detectTechStack(pkg: any): string[];
|
|
11
|
+
export declare function findEntryPoints(projectPath: string): string[];
|
|
12
|
+
export declare function analyzeMetrics(projectPath: string, ignoreList: string[]): AROMetrics;
|
|
13
|
+
export declare function calculateScore(metrics: AROMetrics): number;
|
|
14
|
+
export declare function getRepoStructure(dir: string, depth: number, ignoreList: string[]): any;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.loadIgnoreList = loadIgnoreList;
|
|
21
|
+
exports.detectFramework = detectFramework;
|
|
22
|
+
exports.detectTechStack = detectTechStack;
|
|
23
|
+
exports.findEntryPoints = findEntryPoints;
|
|
24
|
+
exports.analyzeMetrics = analyzeMetrics;
|
|
25
|
+
exports.calculateScore = calculateScore;
|
|
26
|
+
exports.getRepoStructure = getRepoStructure;
|
|
27
|
+
const fs_1 = __importDefault(require("fs"));
|
|
28
|
+
const path_1 = __importDefault(require("path"));
|
|
29
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
30
|
+
const constants_1 = require("./constants");
|
|
31
|
+
/**
|
|
32
|
+
* @aro-context-marker
|
|
33
|
+
* AI READABILITY NOTE: Performance Optimized Utilities.
|
|
34
|
+
* Using fast-glob for high-speed file discovery.
|
|
35
|
+
*/
|
|
36
|
+
__exportStar(require("./types"), exports);
|
|
37
|
+
function loadIgnoreList(projectPath) {
|
|
38
|
+
const ignorePath = path_1.default.join(projectPath, ".aroignore");
|
|
39
|
+
if (fs_1.default.existsSync(ignorePath)) {
|
|
40
|
+
const content = fs_1.default.readFileSync(ignorePath, "utf8");
|
|
41
|
+
const userIgnores = content
|
|
42
|
+
.split("\n")
|
|
43
|
+
.map((l) => l.trim())
|
|
44
|
+
.filter((l) => l && !l.startsWith("#"));
|
|
45
|
+
return [...constants_1.DEFAULT_IGNORES, ...userIgnores];
|
|
46
|
+
}
|
|
47
|
+
return constants_1.DEFAULT_IGNORES;
|
|
48
|
+
}
|
|
49
|
+
function detectFramework(pkg) {
|
|
50
|
+
const allDeps = {
|
|
51
|
+
...(pkg.dependencies || {}),
|
|
52
|
+
...(pkg.devDependencies || {}),
|
|
53
|
+
};
|
|
54
|
+
if (allDeps["next"])
|
|
55
|
+
return "Next.js";
|
|
56
|
+
if (allDeps["nuxt"])
|
|
57
|
+
return "Nuxt.js";
|
|
58
|
+
if (allDeps["vue"])
|
|
59
|
+
return "Vue.js";
|
|
60
|
+
if (allDeps["react"])
|
|
61
|
+
return "React";
|
|
62
|
+
if (allDeps["express"])
|
|
63
|
+
return "Node.js (Express)";
|
|
64
|
+
return "Vanilla Node.js";
|
|
65
|
+
}
|
|
66
|
+
function detectTechStack(pkg) {
|
|
67
|
+
const stack = [];
|
|
68
|
+
const allDeps = {
|
|
69
|
+
...(pkg.dependencies || {}),
|
|
70
|
+
...(pkg.devDependencies || {}),
|
|
71
|
+
};
|
|
72
|
+
Object.keys(allDeps).forEach((dep) => {
|
|
73
|
+
if (constants_1.TECH_KEYWORDS.some((k) => dep.toLowerCase().includes(k))) {
|
|
74
|
+
stack.push(dep);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return stack;
|
|
78
|
+
}
|
|
79
|
+
function findEntryPoints(projectPath) {
|
|
80
|
+
const common = [
|
|
81
|
+
"src/index.ts",
|
|
82
|
+
"src/main.ts",
|
|
83
|
+
"src/app.ts",
|
|
84
|
+
"bin/aro.ts",
|
|
85
|
+
"src/index.js",
|
|
86
|
+
"server.js",
|
|
87
|
+
"app.js",
|
|
88
|
+
"index.js",
|
|
89
|
+
];
|
|
90
|
+
return common.filter((file) => fs_1.default.existsSync(path_1.default.join(projectPath, file)));
|
|
91
|
+
}
|
|
92
|
+
function analyzeMetrics(projectPath, ignoreList) {
|
|
93
|
+
const metrics = {
|
|
94
|
+
hasReadme: false,
|
|
95
|
+
readmeSize: 0,
|
|
96
|
+
hasSrc: false,
|
|
97
|
+
hasConfig: 0,
|
|
98
|
+
largeFiles: 0,
|
|
99
|
+
securityIssues: 0,
|
|
100
|
+
blindSpots: [],
|
|
101
|
+
};
|
|
102
|
+
const readmePath = path_1.default.join(projectPath, "README.md");
|
|
103
|
+
if (fs_1.default.existsSync(readmePath)) {
|
|
104
|
+
metrics.hasReadme = true;
|
|
105
|
+
metrics.readmeSize = fs_1.default.statSync(readmePath).size;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
metrics.blindSpots.push("Missing README.md - AI Agents lack project high-level context.");
|
|
109
|
+
}
|
|
110
|
+
metrics.hasSrc =
|
|
111
|
+
fs_1.default.existsSync(path_1.default.join(projectPath, "src")) ||
|
|
112
|
+
fs_1.default.existsSync(path_1.default.join(projectPath, "app"));
|
|
113
|
+
if (!metrics.hasSrc)
|
|
114
|
+
metrics.blindSpots.push("Flat directory structure - Harder for AI to navigate.");
|
|
115
|
+
constants_1.CONFIG_FILES.forEach((file) => {
|
|
116
|
+
if (fs_1.default.existsSync(path_1.default.join(projectPath, file)))
|
|
117
|
+
metrics.hasConfig++;
|
|
118
|
+
});
|
|
119
|
+
// PERFORMANCE: Use fast-glob for sub-directory scanning (parallelized)
|
|
120
|
+
const files = fast_glob_1.default.sync(["**/*.{js,ts,tsx,jsx}"], {
|
|
121
|
+
cwd: projectPath,
|
|
122
|
+
ignore: ignoreList.map((i) => `**/${i}/**`),
|
|
123
|
+
absolute: true,
|
|
124
|
+
deep: 3,
|
|
125
|
+
});
|
|
126
|
+
files.forEach((file) => {
|
|
127
|
+
const content = fs_1.default.readFileSync(file, "utf8");
|
|
128
|
+
const lines = content.split("\n");
|
|
129
|
+
// Size check
|
|
130
|
+
if (lines.length > 300) {
|
|
131
|
+
metrics.largeFiles++;
|
|
132
|
+
}
|
|
133
|
+
// Security check (Basic detection for AI-gen risks)
|
|
134
|
+
constants_1.SECURITY_KEYWORDS.forEach((key) => {
|
|
135
|
+
if (content.includes(key + "=") || content.includes(key + ":")) {
|
|
136
|
+
metrics.securityIssues++;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
constants_1.DANGEROUS_FUNCS.forEach((func) => {
|
|
140
|
+
if (content.includes(func)) {
|
|
141
|
+
metrics.securityIssues++;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
if (metrics.largeFiles > 0) {
|
|
146
|
+
metrics.blindSpots.push(`${metrics.largeFiles} large files detected - Causes 'Context Truncation'.`);
|
|
147
|
+
}
|
|
148
|
+
if (metrics.securityIssues > 0) {
|
|
149
|
+
metrics.blindSpots.push(`${metrics.securityIssues} potential security/hallucination risks (hardcoded keys or dangerous functions) detected.`);
|
|
150
|
+
}
|
|
151
|
+
return metrics;
|
|
152
|
+
}
|
|
153
|
+
function calculateScore(metrics) {
|
|
154
|
+
let score = 0;
|
|
155
|
+
if (metrics.hasReadme) {
|
|
156
|
+
score += 15;
|
|
157
|
+
if (metrics.readmeSize >= 500)
|
|
158
|
+
score += 15;
|
|
159
|
+
}
|
|
160
|
+
if (metrics.hasSrc)
|
|
161
|
+
score += 20;
|
|
162
|
+
score += Math.min(metrics.hasConfig * 10, 20);
|
|
163
|
+
score += Math.max(30 - metrics.largeFiles * 5, 0);
|
|
164
|
+
// Security Penalty: -5 per issue, caps at 20
|
|
165
|
+
score -= Math.min(metrics.securityIssues * 5, 20);
|
|
166
|
+
return Math.max(0, Math.min(score, 100));
|
|
167
|
+
}
|
|
168
|
+
function getRepoStructure(dir, depth, ignoreList) {
|
|
169
|
+
if (depth > 2)
|
|
170
|
+
return "...";
|
|
171
|
+
const res = {};
|
|
172
|
+
try {
|
|
173
|
+
fs_1.default.readdirSync(dir).forEach((file) => {
|
|
174
|
+
if (!ignoreList.some((ignore) => file === ignore)) {
|
|
175
|
+
const full = path_1.default.join(dir, file);
|
|
176
|
+
let stat;
|
|
177
|
+
try {
|
|
178
|
+
stat = fs_1.default.statSync(full);
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (stat.isDirectory()) {
|
|
184
|
+
res[file] = getRepoStructure(full, depth + 1, ignoreList);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
res[file] = "file";
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch (e) { }
|
|
193
|
+
return res;
|
|
194
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
describe("ARO CLI E2E", () => {
|
|
10
|
+
const testDir = path_1.default.join(__dirname, "e2e-dummy-project");
|
|
11
|
+
const aroBin = path_1.default.join(__dirname, "../dist/bin/aro.js");
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
// Setup dummy project
|
|
14
|
+
if (!fs_1.default.existsSync(testDir))
|
|
15
|
+
fs_1.default.mkdirSync(testDir);
|
|
16
|
+
fs_1.default.writeFileSync(path_1.default.join(testDir, "package.json"), JSON.stringify({
|
|
17
|
+
name: "e2e-test-project",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
dependencies: { express: "^4.17.1" },
|
|
20
|
+
}));
|
|
21
|
+
fs_1.default.writeFileSync(path_1.default.join(testDir, "README.md"), "# Test Project\nThis is a test project for ARO E2E. It needs to be long enough to pass some size checks if necessary.".repeat(10));
|
|
22
|
+
const srcDir = path_1.default.join(testDir, "src");
|
|
23
|
+
if (!fs_1.default.existsSync(srcDir))
|
|
24
|
+
fs_1.default.mkdirSync(srcDir);
|
|
25
|
+
fs_1.default.writeFileSync(path_1.default.join(srcDir, "app.js"), "console.log('hello world');");
|
|
26
|
+
});
|
|
27
|
+
afterAll(() => {
|
|
28
|
+
// Cleanup
|
|
29
|
+
if (fs_1.default.existsSync(testDir)) {
|
|
30
|
+
fs_1.default.rmSync(testDir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
test('should run "aro audit" and generate reports', () => {
|
|
34
|
+
// Ensure build exists
|
|
35
|
+
if (!fs_1.default.existsSync(aroBin)) {
|
|
36
|
+
(0, child_process_1.execSync)("npm run build", { cwd: path_1.default.join(__dirname, "..") });
|
|
37
|
+
}
|
|
38
|
+
// Execution
|
|
39
|
+
const output = (0, child_process_1.execSync)(`node ${aroBin} audit`, {
|
|
40
|
+
cwd: testDir,
|
|
41
|
+
}).toString();
|
|
42
|
+
// Verifications (Updated strings to match new Branding and modular output)
|
|
43
|
+
expect(output).toContain("ARO");
|
|
44
|
+
expect(output).toContain("Framework");
|
|
45
|
+
expect(output).toContain("Node.js (Express)");
|
|
46
|
+
expect(output).toContain("Quality Score");
|
|
47
|
+
expect(output).toContain("Financial Analyzer");
|
|
48
|
+
expect(output).toContain("ANNUAL DEBT"); // Updated from "TOTAL ANNUAL AI DEBT"
|
|
49
|
+
// Check if artifact was created
|
|
50
|
+
const contextFilePath = path_1.default.join(testDir, ".agent_context_pro.json");
|
|
51
|
+
expect(fs_1.default.existsSync(contextFilePath)).toBe(true);
|
|
52
|
+
const contextData = JSON.parse(fs_1.default.readFileSync(contextFilePath, "utf8"));
|
|
53
|
+
expect(contextData.projectName).toBe("e2e-test-project");
|
|
54
|
+
expect(contextData.framework).toBe("Node.js (Express)");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @aro-context-marker
|
|
4
|
+
* AI READABILITY NOTE: This file is monitored for AI-Readability.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
/**
|
|
8
|
+
* @aro-context-marker
|
|
9
|
+
* AI READABILITY NOTE: This file is monitored for AI-Readability.
|
|
10
|
+
*/
|
|
11
|
+
const utils_1 = require("../src/utils");
|
|
12
|
+
describe("ARO Core Engine", () => {
|
|
13
|
+
describe("detectFramework - Precision & Edge Cases", () => {
|
|
14
|
+
test("should detect Next.js (Priority Case)", () => {
|
|
15
|
+
const pkg = { dependencies: { next: "latest", react: "latest" } };
|
|
16
|
+
expect((0, utils_1.detectFramework)(pkg)).toBe("Next.js");
|
|
17
|
+
});
|
|
18
|
+
test("should detect React", () => {
|
|
19
|
+
const pkg = { dependencies: { react: "latest" } };
|
|
20
|
+
expect((0, utils_1.detectFramework)(pkg)).toBe("React");
|
|
21
|
+
});
|
|
22
|
+
test("should detect Vue.js from devDependencies", () => {
|
|
23
|
+
const pkg = { devDependencies: { vue: "latest" } };
|
|
24
|
+
expect((0, utils_1.detectFramework)(pkg)).toBe("Vue.js");
|
|
25
|
+
});
|
|
26
|
+
test("should detect Nuxt.js", () => {
|
|
27
|
+
const pkg = { dependencies: { nuxt: "latest" } };
|
|
28
|
+
expect((0, utils_1.detectFramework)(pkg)).toBe("Nuxt.js");
|
|
29
|
+
});
|
|
30
|
+
test("should detect Node.js (Express)", () => {
|
|
31
|
+
const pkg = { dependencies: { express: "latest" } };
|
|
32
|
+
expect((0, utils_1.detectFramework)(pkg)).toBe("Node.js (Express)");
|
|
33
|
+
});
|
|
34
|
+
test("should default to Vanilla Node.js for unknown or empty projects", () => {
|
|
35
|
+
expect((0, utils_1.detectFramework)({})).toBe("Vanilla Node.js");
|
|
36
|
+
expect((0, utils_1.detectFramework)({ dependencies: { lodash: "1.0.0" } })).toBe("Vanilla Node.js");
|
|
37
|
+
});
|
|
38
|
+
test("should handle null/undefined fields gracefully", () => {
|
|
39
|
+
expect((0, utils_1.detectFramework)({ dependencies: undefined })).toBe("Vanilla Node.js");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe("calculateScore - Logic Boundaries", () => {
|
|
43
|
+
test("should give 100 for a perfect, well-documented project", () => {
|
|
44
|
+
const metrics = {
|
|
45
|
+
hasReadme: true,
|
|
46
|
+
readmeSize: 1000,
|
|
47
|
+
hasSrc: true,
|
|
48
|
+
hasConfig: 4,
|
|
49
|
+
largeFiles: 0,
|
|
50
|
+
securityIssues: 0,
|
|
51
|
+
blindSpots: [],
|
|
52
|
+
};
|
|
53
|
+
expect((0, utils_1.calculateScore)(metrics)).toBe(100);
|
|
54
|
+
});
|
|
55
|
+
test("should penalize for missing README", () => {
|
|
56
|
+
const metrics = {
|
|
57
|
+
hasReadme: false,
|
|
58
|
+
readmeSize: 0,
|
|
59
|
+
hasSrc: true,
|
|
60
|
+
hasConfig: 4,
|
|
61
|
+
largeFiles: 0,
|
|
62
|
+
securityIssues: 0,
|
|
63
|
+
blindSpots: [],
|
|
64
|
+
};
|
|
65
|
+
expect((0, utils_1.calculateScore)(metrics)).toBe(70);
|
|
66
|
+
});
|
|
67
|
+
test("should penalize for small README (<500 chars)", () => {
|
|
68
|
+
const metrics = {
|
|
69
|
+
hasReadme: true,
|
|
70
|
+
readmeSize: 100,
|
|
71
|
+
hasSrc: true,
|
|
72
|
+
hasConfig: 4,
|
|
73
|
+
largeFiles: 0,
|
|
74
|
+
securityIssues: 0,
|
|
75
|
+
blindSpots: [],
|
|
76
|
+
};
|
|
77
|
+
expect((0, utils_1.calculateScore)(metrics)).toBe(85);
|
|
78
|
+
});
|
|
79
|
+
test("should penalize heavily for flat directory structure", () => {
|
|
80
|
+
const metrics = {
|
|
81
|
+
hasReadme: true,
|
|
82
|
+
readmeSize: 1000,
|
|
83
|
+
hasSrc: false,
|
|
84
|
+
hasConfig: 4,
|
|
85
|
+
largeFiles: 0,
|
|
86
|
+
securityIssues: 0,
|
|
87
|
+
blindSpots: [],
|
|
88
|
+
};
|
|
89
|
+
expect((0, utils_1.calculateScore)(metrics)).toBe(80);
|
|
90
|
+
});
|
|
91
|
+
test("should penalize for large files (truncation debt)", () => {
|
|
92
|
+
const metrics = {
|
|
93
|
+
hasReadme: true,
|
|
94
|
+
readmeSize: 1000,
|
|
95
|
+
hasSrc: true,
|
|
96
|
+
hasConfig: 4,
|
|
97
|
+
largeFiles: 5,
|
|
98
|
+
securityIssues: 0,
|
|
99
|
+
blindSpots: [],
|
|
100
|
+
};
|
|
101
|
+
const score = (0, utils_1.calculateScore)(metrics);
|
|
102
|
+
expect(score).toBe(75);
|
|
103
|
+
});
|
|
104
|
+
test("should penalize for security issues", () => {
|
|
105
|
+
const metrics = {
|
|
106
|
+
hasReadme: true,
|
|
107
|
+
readmeSize: 1000,
|
|
108
|
+
hasSrc: true,
|
|
109
|
+
hasConfig: 4,
|
|
110
|
+
largeFiles: 0,
|
|
111
|
+
securityIssues: 2,
|
|
112
|
+
blindSpots: [],
|
|
113
|
+
};
|
|
114
|
+
expect((0, utils_1.calculateScore)(metrics)).toBe(90); // 100 - (2 * 5)
|
|
115
|
+
});
|
|
116
|
+
test("should not go below 0", () => {
|
|
117
|
+
const metrics = {
|
|
118
|
+
hasReadme: false,
|
|
119
|
+
readmeSize: 0,
|
|
120
|
+
hasSrc: false,
|
|
121
|
+
hasConfig: 0,
|
|
122
|
+
largeFiles: 20,
|
|
123
|
+
securityIssues: 10,
|
|
124
|
+
blindSpots: [],
|
|
125
|
+
};
|
|
126
|
+
expect((0, utils_1.calculateScore)(metrics)).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const enterprise_1 = require("../src/enterprise");
|
|
4
|
+
describe("ARO Enterprise Analytics", () => {
|
|
5
|
+
test("should calculate $0 debt for a perfect project", () => {
|
|
6
|
+
const metrics = {
|
|
7
|
+
hasReadme: true,
|
|
8
|
+
readmeSize: 1000,
|
|
9
|
+
hasSrc: true,
|
|
10
|
+
hasConfig: 4,
|
|
11
|
+
largeFiles: 0,
|
|
12
|
+
securityIssues: 0,
|
|
13
|
+
blindSpots: [],
|
|
14
|
+
};
|
|
15
|
+
const debt = (0, enterprise_1.calculateDebt)(metrics);
|
|
16
|
+
expect(debt.totalDebt).toBe(0);
|
|
17
|
+
expect(debt.docDebt).toBe(0);
|
|
18
|
+
expect(debt.truncationDebt).toBe(0);
|
|
19
|
+
expect(debt.structuralDebt).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
test("should calculate maximum debt for a missing documentation", () => {
|
|
22
|
+
const metrics = {
|
|
23
|
+
hasReadme: false,
|
|
24
|
+
readmeSize: 0,
|
|
25
|
+
hasSrc: true,
|
|
26
|
+
hasConfig: 4,
|
|
27
|
+
largeFiles: 0,
|
|
28
|
+
securityIssues: 0,
|
|
29
|
+
blindSpots: [],
|
|
30
|
+
};
|
|
31
|
+
const debt = (0, enterprise_1.calculateDebt)(metrics);
|
|
32
|
+
expect(debt.docDebt).toBe(15000);
|
|
33
|
+
expect(debt.totalDebt).toBe(15000);
|
|
34
|
+
});
|
|
35
|
+
test("should scale truncation debt with large files", () => {
|
|
36
|
+
const metrics = {
|
|
37
|
+
hasReadme: true,
|
|
38
|
+
readmeSize: 1000,
|
|
39
|
+
hasSrc: true,
|
|
40
|
+
hasConfig: 4,
|
|
41
|
+
largeFiles: 3,
|
|
42
|
+
securityIssues: 0,
|
|
43
|
+
blindSpots: [],
|
|
44
|
+
};
|
|
45
|
+
const debt = (0, enterprise_1.calculateDebt)(metrics);
|
|
46
|
+
expect(debt.truncationDebt).toBe(15000); // 3 * 5000
|
|
47
|
+
});
|
|
48
|
+
test("should calculate structural debt for flat projects", () => {
|
|
49
|
+
const metrics = {
|
|
50
|
+
hasReadme: true,
|
|
51
|
+
readmeSize: 1000,
|
|
52
|
+
hasSrc: false,
|
|
53
|
+
hasConfig: 0,
|
|
54
|
+
largeFiles: 0,
|
|
55
|
+
securityIssues: 0,
|
|
56
|
+
blindSpots: [],
|
|
57
|
+
};
|
|
58
|
+
const debt = (0, enterprise_1.calculateDebt)(metrics);
|
|
59
|
+
expect(debt.structuralDebt).toBe(20000); // 10000 (no src) + (2 * 5000) (missing 2 configs)
|
|
60
|
+
});
|
|
61
|
+
test("should handle partial configurations", () => {
|
|
62
|
+
const metrics = {
|
|
63
|
+
hasReadme: true,
|
|
64
|
+
readmeSize: 1000,
|
|
65
|
+
hasSrc: true,
|
|
66
|
+
hasConfig: 2,
|
|
67
|
+
largeFiles: 0,
|
|
68
|
+
securityIssues: 0,
|
|
69
|
+
blindSpots: [],
|
|
70
|
+
};
|
|
71
|
+
const debt = (0, enterprise_1.calculateDebt)(metrics);
|
|
72
|
+
expect(debt.structuralDebt).toBe(0); // Target met with 2 configs
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
describe("ARO MCP Server Integration", () => {
|
|
9
|
+
const mcpPath = path_1.default.join(__dirname, "../dist/src/mcp.js");
|
|
10
|
+
test("should start and respond to ListTools request over stdio", (done) => {
|
|
11
|
+
// We run the compiled version
|
|
12
|
+
const mcp = (0, child_process_1.spawn)("node", [mcpPath]);
|
|
13
|
+
let output = "";
|
|
14
|
+
mcp.stdout.on("data", (data) => {
|
|
15
|
+
output += data.toString();
|
|
16
|
+
try {
|
|
17
|
+
// MCP is JSON-RPC. We are looking for the tools definition.
|
|
18
|
+
if (output.includes("analyze_readability") &&
|
|
19
|
+
output.includes("optimize_readability")) {
|
|
20
|
+
mcp.kill();
|
|
21
|
+
done();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
// Wait for more data
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
mcp.stderr.on("data", (data) => {
|
|
29
|
+
const msg = data.toString();
|
|
30
|
+
// The server logs "ARO MCP Server running on stdio" to stderr
|
|
31
|
+
if (msg.includes("ARO MCP Server running on stdio")) {
|
|
32
|
+
// Send dummy ListTools request
|
|
33
|
+
const listToolsRequest = {
|
|
34
|
+
jsonrpc: "2.0",
|
|
35
|
+
id: 1,
|
|
36
|
+
method: "tools/list",
|
|
37
|
+
params: {},
|
|
38
|
+
};
|
|
39
|
+
mcp.stdin.write(JSON.stringify(listToolsRequest) + "\n");
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
mcp.on("error", (err) => {
|
|
43
|
+
done(err);
|
|
44
|
+
});
|
|
45
|
+
// Timeout safety
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
if (mcp.killed)
|
|
48
|
+
return;
|
|
49
|
+
mcp.kill();
|
|
50
|
+
done(new Error("MCP Server response timeout or tools not found in output: " + output));
|
|
51
|
+
}, 5000);
|
|
52
|
+
});
|
|
53
|
+
test("should expose current-metrics resource", (done) => {
|
|
54
|
+
const mcp = (0, child_process_1.spawn)("node", [mcpPath]);
|
|
55
|
+
let output = "";
|
|
56
|
+
mcp.stdout.on("data", (data) => {
|
|
57
|
+
output += data.toString();
|
|
58
|
+
if (output.includes("aro://current-metrics")) {
|
|
59
|
+
mcp.kill();
|
|
60
|
+
done();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
mcp.stderr.on("data", (data) => {
|
|
64
|
+
if (data.toString().includes("ARO MCP Server running on stdio")) {
|
|
65
|
+
const listResourcesRequest = {
|
|
66
|
+
jsonrpc: "2.0",
|
|
67
|
+
id: 2,
|
|
68
|
+
method: "resources/list",
|
|
69
|
+
params: {},
|
|
70
|
+
};
|
|
71
|
+
mcp.stdin.write(JSON.stringify(listResourcesRequest) + "\n");
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
if (mcp.killed)
|
|
76
|
+
return;
|
|
77
|
+
mcp.kill();
|
|
78
|
+
done(new Error("MCP Server resource discovery failed"));
|
|
79
|
+
}, 5000);
|
|
80
|
+
});
|
|
81
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasankemaldemirci/aro",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Agent Readability Optimizer - Professional SEO for your code.",
|
|
5
|
+
"main": "dist/src/core.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/hasankemaldemirci/aro.git"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"aro": "dist/bin/aro.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "NODE_ENV=development ts-node bin/aro.ts",
|
|
19
|
+
"analyze": "ts-node src/enterprise.ts",
|
|
20
|
+
"mcp": "ts-node src/mcp.ts",
|
|
21
|
+
"test": "jest",
|
|
22
|
+
"prepare": "husky"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"ai",
|
|
26
|
+
"agentic",
|
|
27
|
+
"debt",
|
|
28
|
+
"seo",
|
|
29
|
+
"readability",
|
|
30
|
+
"optimization",
|
|
31
|
+
"mcp"
|
|
32
|
+
],
|
|
33
|
+
"author": "Hasan Kemal Demirci",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.1.0",
|
|
37
|
+
"chalk": "^4.1.2",
|
|
38
|
+
"fast-glob": "^3.2.11"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/chalk": "^2.2.0",
|
|
42
|
+
"@types/jest": "^29.5.12",
|
|
43
|
+
"@types/node": "^22.13.4",
|
|
44
|
+
"husky": "^9.1.7",
|
|
45
|
+
"jest": "^29.7.0",
|
|
46
|
+
"ts-jest": "^29.1.2",
|
|
47
|
+
"ts-node": "^10.9.2",
|
|
48
|
+
"typescript": "^5.3.3"
|
|
49
|
+
},
|
|
50
|
+
"jest": {
|
|
51
|
+
"preset": "ts-jest",
|
|
52
|
+
"testEnvironment": "node",
|
|
53
|
+
"testPathIgnorePatterns": [
|
|
54
|
+
"/node_modules/",
|
|
55
|
+
"/temp_repos/",
|
|
56
|
+
"/dist/"
|
|
57
|
+
],
|
|
58
|
+
"modulePathIgnorePatterns": [
|
|
59
|
+
"/temp_repos/",
|
|
60
|
+
"/dist/"
|
|
61
|
+
],
|
|
62
|
+
"moduleNameMapper": {
|
|
63
|
+
"^@src/(.*)$": "<rootDir>/src/$1"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|