@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.
Files changed (55) hide show
  1. package/.agent_context_pro.json +57 -0
  2. package/.env.example +3 -0
  3. package/.github/workflows/aro.yml +41 -0
  4. package/.husky/pre-commit +19 -0
  5. package/CONTRIBUTING.md +34 -0
  6. package/LICENSE +21 -0
  7. package/README.md +55 -0
  8. package/bin/aro.ts +86 -0
  9. package/dist/bin/aro.d.ts +7 -0
  10. package/dist/bin/aro.js +118 -0
  11. package/dist/src/badge.d.ts +6 -0
  12. package/dist/src/badge.js +64 -0
  13. package/dist/src/constants.d.ts +20 -0
  14. package/dist/src/constants.js +68 -0
  15. package/dist/src/core.d.ts +12 -0
  16. package/dist/src/core.js +107 -0
  17. package/dist/src/enterprise.d.ts +8 -0
  18. package/dist/src/enterprise.js +125 -0
  19. package/dist/src/init.d.ts +1 -0
  20. package/dist/src/init.js +64 -0
  21. package/dist/src/mcp.d.ts +5 -0
  22. package/dist/src/mcp.js +138 -0
  23. package/dist/src/refactor.d.ts +6 -0
  24. package/dist/src/refactor.js +91 -0
  25. package/dist/src/rules.d.ts +6 -0
  26. package/dist/src/rules.js +67 -0
  27. package/dist/src/types.d.ts +39 -0
  28. package/dist/src/types.js +6 -0
  29. package/dist/src/utils.d.ts +14 -0
  30. package/dist/src/utils.js +194 -0
  31. package/dist/tests/cli.test.d.ts +1 -0
  32. package/dist/tests/cli.test.js +56 -0
  33. package/dist/tests/core.test.d.ts +5 -0
  34. package/dist/tests/core.test.js +129 -0
  35. package/dist/tests/enterprise.test.d.ts +1 -0
  36. package/dist/tests/enterprise.test.js +74 -0
  37. package/dist/tests/mcp.test.d.ts +1 -0
  38. package/dist/tests/mcp.test.js +81 -0
  39. package/eslint.config.mjs +9 -0
  40. package/package.json +66 -0
  41. package/src/badge.ts +77 -0
  42. package/src/constants.ts +68 -0
  43. package/src/core.ts +141 -0
  44. package/src/enterprise.ts +159 -0
  45. package/src/init.ts +75 -0
  46. package/src/mcp.ts +158 -0
  47. package/src/refactor.ts +122 -0
  48. package/src/rules.ts +78 -0
  49. package/src/types.ts +43 -0
  50. package/src/utils.ts +199 -0
  51. package/tests/cli.test.ts +71 -0
  52. package/tests/core.test.ts +146 -0
  53. package/tests/enterprise.test.ts +78 -0
  54. package/tests/mcp.test.ts +89 -0
  55. 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,5 @@
1
+ /**
2
+ * @aro-context-marker
3
+ * AI READABILITY NOTE: This file is monitored for AI-Readability.
4
+ */
5
+ export {};
@@ -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
+ });
@@ -0,0 +1,9 @@
1
+ /** @aro-context-marker */
2
+ export default [
3
+ {
4
+ rules: {
5
+ "no-unused-vars": "warn",
6
+ "no-undef": "error",
7
+ },
8
+ },
9
+ ];
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
+ }