@aiready/cli 0.9.2 → 0.9.6
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/.turbo/turbo-build.log +8 -14
- package/.turbo/turbo-test.log +7 -24
- package/dist/__tests__/cli.test.d.ts +2 -0
- package/dist/__tests__/cli.test.d.ts.map +1 -0
- package/dist/__tests__/cli.test.js +52 -0
- package/dist/__tests__/cli.test.js.map +1 -0
- package/dist/chunk-5GZDRZ3T.mjs +126 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +281 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +282 -1
- package/dist/index.d.ts +10 -11
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/package.json +7 -6
- package/src/cli.ts +318 -1
- package/tsconfig.tsbuildinfo +1 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/cli@0.9.
|
|
4
|
-
> tsup src/index.ts src/cli.ts --format cjs,esm
|
|
3
|
+
> @aiready/cli@0.9.6 build /Users/pengcao/projects/aiready/packages/cli
|
|
4
|
+
> tsup src/index.ts src/cli.ts --format cjs,esm
|
|
5
5
|
|
|
6
6
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
7
7
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
@@ -9,16 +9,10 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mCJS[39m [1mdist/index.js [22m[32m4.93 KB[39m
|
|
13
|
-
[32mCJS[39m [1mdist/cli.js [22m[32m44.24 KB[39m
|
|
14
|
-
[32mCJS[39m ⚡️ Build success in 22ms
|
|
15
|
-
[32mESM[39m [1mdist/chunk-3SG2GLFJ.mjs [22m[32m3.80 KB[39m
|
|
16
12
|
[32mESM[39m [1mdist/index.mjs [22m[32m138.00 B[39m
|
|
17
|
-
[32mESM[39m [1mdist/cli.mjs [22m[
|
|
18
|
-
[32mESM[39m
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
DTS dist/cli.d.mts 20.00 B
|
|
24
|
-
DTS dist/index.d.mts 1.22 KB
|
|
13
|
+
[32mESM[39m [1mdist/cli.mjs [22m[32m50.92 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/chunk-3SG2GLFJ.mjs [22m[32m3.80 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 32ms
|
|
16
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m58.06 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.js [22m[32m4.93 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 32ms
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,34 +1,17 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/cli@0.9.
|
|
3
|
+
> @aiready/cli@0.9.6 test /Users/pengcao/projects/aiready/packages/cli
|
|
4
4
|
> vitest run
|
|
5
5
|
|
|
6
6
|
[?25l
|
|
7
7
|
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/Users/pengcao/projects/aiready/packages/cli[39m
|
|
8
8
|
|
|
9
|
-
[
|
|
10
|
-
[
|
|
9
|
+
[32m✓[39m src/__tests__/cli.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
10
|
+
[32m✓[39m dist/__tests__/cli.test.js [2m([22m[2m3 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
11
11
|
|
|
12
|
-
[2m Test Files [22m[1m[
|
|
13
|
-
[2m Tests [22m[1m[
|
|
14
|
-
[2m Start at [
|
|
15
|
-
[2m Duration [
|
|
16
|
-
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
17
|
-
[1m[33m ❯ [39m[22msrc/__tests__/cli.test.ts[2m 0/3[22m
|
|
18
|
-
|
|
19
|
-
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
20
|
-
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (3)[39m
|
|
21
|
-
[2m Start at [22m20:59:54
|
|
22
|
-
[2m Duration [22m521ms
|
|
23
|
-
[?2026l[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K [32m✓[39m src/__tests__/cli.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
24
|
-
[32m✓[39m CLI Unified Analysis [2m(3)[22m
|
|
25
|
-
[32m✓[39m should run unified analysis with both tools[32m 1[2mms[22m[39m
|
|
26
|
-
[32m✓[39m should run analysis with only patterns tool[32m 0[2mms[22m[39m
|
|
27
|
-
[32m✓[39m should run analysis with only context tool[32m 0[2mms[22m[39m
|
|
28
|
-
|
|
29
|
-
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
30
|
-
[2m Tests [22m [1m[32m3 passed[39m[22m[90m (3)[39m
|
|
31
|
-
[2m Start at [22m 20:59:54
|
|
32
|
-
[2m Duration [22m 581ms[2m (transform 114ms, setup 0ms, import 378ms, tests 2ms, environment 0ms)[22m
|
|
12
|
+
[2m Test Files [22m [1m[32m2 passed[39m[22m[90m (2)[39m
|
|
13
|
+
[2m Tests [22m [1m[32m6 passed[39m[22m[90m (6)[39m
|
|
14
|
+
[2m Start at [22m 22:40:01
|
|
15
|
+
[2m Duration [22m 1.23s[2m (transform 446ms, setup 0ms, import 1.97s, tests 4ms, environment 0ms)[22m
|
|
33
16
|
|
|
34
17
|
[?25h
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cli.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { analyzeUnified } from '../index';
|
|
3
|
+
// Mock the individual tools
|
|
4
|
+
vi.mock('@aiready/pattern-detect', () => ({
|
|
5
|
+
analyzePatterns: vi.fn().mockResolvedValue({
|
|
6
|
+
results: [],
|
|
7
|
+
duplicates: [],
|
|
8
|
+
files: [],
|
|
9
|
+
}),
|
|
10
|
+
generateSummary: vi.fn().mockReturnValue({
|
|
11
|
+
totalDuplicateLines: 0,
|
|
12
|
+
potentialSavings: 0,
|
|
13
|
+
}),
|
|
14
|
+
}));
|
|
15
|
+
vi.mock('@aiready/context-analyzer', () => ({
|
|
16
|
+
analyzeContext: vi.fn().mockResolvedValue([]),
|
|
17
|
+
generateSummary: vi.fn().mockReturnValue({
|
|
18
|
+
totalFiles: 0,
|
|
19
|
+
averageCohesion: 0,
|
|
20
|
+
averageFragmentation: 0,
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
describe('CLI Unified Analysis', () => {
|
|
24
|
+
it('should run unified analysis with both tools', async () => {
|
|
25
|
+
const results = await analyzeUnified({
|
|
26
|
+
rootDir: '/test',
|
|
27
|
+
tools: ['patterns', 'context'],
|
|
28
|
+
});
|
|
29
|
+
expect(results).toHaveProperty('patterns');
|
|
30
|
+
expect(results).toHaveProperty('context');
|
|
31
|
+
expect(results).toHaveProperty('summary');
|
|
32
|
+
});
|
|
33
|
+
it('should run analysis with only patterns tool', async () => {
|
|
34
|
+
const results = await analyzeUnified({
|
|
35
|
+
rootDir: '/test',
|
|
36
|
+
tools: ['patterns'],
|
|
37
|
+
});
|
|
38
|
+
expect(results).toHaveProperty('patterns');
|
|
39
|
+
expect(results).not.toHaveProperty('context');
|
|
40
|
+
expect(results).toHaveProperty('summary');
|
|
41
|
+
});
|
|
42
|
+
it('should run analysis with only context tool', async () => {
|
|
43
|
+
const results = await analyzeUnified({
|
|
44
|
+
rootDir: '/test',
|
|
45
|
+
tools: ['context'],
|
|
46
|
+
});
|
|
47
|
+
expect(results).not.toHaveProperty('patterns');
|
|
48
|
+
expect(results).toHaveProperty('context');
|
|
49
|
+
expect(results).toHaveProperty('summary');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=cli.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.test.js","sourceRoot":"","sources":["../../src/__tests__/cli.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1C,4BAA4B;AAC5B,EAAE,CAAC,IAAI,CAAC,yBAAyB,EAAE,GAAG,EAAE,CAAC,CAAC;IACxC,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC;QACzC,OAAO,EAAE,EAAE;QACX,UAAU,EAAE,EAAE;QACd,KAAK,EAAE,EAAE;KACV,CAAC;IACF,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC;QACvC,mBAAmB,EAAE,CAAC;QACtB,gBAAgB,EAAE,CAAC;KACpB,CAAC;CACH,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,2BAA2B,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1C,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC;IAC7C,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC;QACvC,UAAU,EAAE,CAAC;QACb,eAAe,EAAE,CAAC;QAClB,oBAAoB,EAAE,CAAC;KACxB,CAAC;CACH,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC;YACnC,OAAO,EAAE,OAAO;YAChB,KAAK,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC;SAC/B,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC;YACnC,OAAO,EAAE,OAAO;YAChB,KAAK,EAAE,CAAC,UAAU,CAAC;SACpB,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QAC9C,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC;YACnC,OAAO,EAAE,OAAO;YAChB,KAAK,EAAE,CAAC,SAAS,CAAC;SACnB,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
import { analyzePatterns } from "@aiready/pattern-detect";
|
|
10
|
+
import { analyzeContext } from "@aiready/context-analyzer";
|
|
11
|
+
import { analyzeConsistency } from "@aiready/consistency";
|
|
12
|
+
var severityOrder = {
|
|
13
|
+
critical: 4,
|
|
14
|
+
major: 3,
|
|
15
|
+
minor: 2,
|
|
16
|
+
info: 1
|
|
17
|
+
};
|
|
18
|
+
function sortBySeverity(results) {
|
|
19
|
+
return results.map((file) => {
|
|
20
|
+
const sortedIssues = [...file.issues].sort((a, b) => {
|
|
21
|
+
const severityDiff = (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
|
|
22
|
+
if (severityDiff !== 0) return severityDiff;
|
|
23
|
+
return (a.location?.line || 0) - (b.location?.line || 0);
|
|
24
|
+
});
|
|
25
|
+
return { ...file, issues: sortedIssues };
|
|
26
|
+
}).sort((a, b) => {
|
|
27
|
+
const aMaxSeverity = Math.max(...a.issues.map((i) => severityOrder[i.severity] || 0), 0);
|
|
28
|
+
const bMaxSeverity = Math.max(...b.issues.map((i) => severityOrder[i.severity] || 0), 0);
|
|
29
|
+
if (aMaxSeverity !== bMaxSeverity) {
|
|
30
|
+
return bMaxSeverity - aMaxSeverity;
|
|
31
|
+
}
|
|
32
|
+
if (a.issues.length !== b.issues.length) {
|
|
33
|
+
return b.issues.length - a.issues.length;
|
|
34
|
+
}
|
|
35
|
+
return a.fileName.localeCompare(b.fileName);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function analyzeUnified(options) {
|
|
39
|
+
const startTime = Date.now();
|
|
40
|
+
const tools = options.tools || ["patterns", "context", "consistency"];
|
|
41
|
+
const result = {
|
|
42
|
+
summary: {
|
|
43
|
+
totalIssues: 0,
|
|
44
|
+
toolsRun: tools,
|
|
45
|
+
executionTime: 0
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
if (tools.includes("patterns")) {
|
|
49
|
+
const patternResult = await analyzePatterns(options);
|
|
50
|
+
if (options.progressCallback) {
|
|
51
|
+
options.progressCallback({ tool: "patterns", data: patternResult });
|
|
52
|
+
}
|
|
53
|
+
result.patterns = sortBySeverity(patternResult.results);
|
|
54
|
+
result.duplicates = patternResult.duplicates;
|
|
55
|
+
result.summary.totalIssues += patternResult.results.reduce(
|
|
56
|
+
(sum, file) => sum + file.issues.length,
|
|
57
|
+
0
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (tools.includes("context")) {
|
|
61
|
+
const contextResults = await analyzeContext(options);
|
|
62
|
+
if (options.progressCallback) {
|
|
63
|
+
options.progressCallback({ tool: "context", data: contextResults });
|
|
64
|
+
}
|
|
65
|
+
result.context = contextResults.sort((a, b) => {
|
|
66
|
+
const severityDiff = (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
|
|
67
|
+
if (severityDiff !== 0) return severityDiff;
|
|
68
|
+
if (a.tokenCost !== b.tokenCost) return b.tokenCost - a.tokenCost;
|
|
69
|
+
return b.fragmentationScore - a.fragmentationScore;
|
|
70
|
+
});
|
|
71
|
+
result.summary.totalIssues += result.context?.length || 0;
|
|
72
|
+
}
|
|
73
|
+
if (tools.includes("consistency")) {
|
|
74
|
+
const consistencyOptions = {
|
|
75
|
+
rootDir: options.rootDir,
|
|
76
|
+
include: options.include,
|
|
77
|
+
exclude: options.exclude,
|
|
78
|
+
...options.consistency || {}
|
|
79
|
+
};
|
|
80
|
+
const report = await analyzeConsistency(consistencyOptions);
|
|
81
|
+
if (options.progressCallback) {
|
|
82
|
+
options.progressCallback({ tool: "consistency", data: report });
|
|
83
|
+
}
|
|
84
|
+
if (report.results) {
|
|
85
|
+
report.results = sortBySeverity(report.results);
|
|
86
|
+
}
|
|
87
|
+
result.consistency = report;
|
|
88
|
+
result.summary.totalIssues += report.summary.totalIssues;
|
|
89
|
+
}
|
|
90
|
+
result.summary.executionTime = Date.now() - startTime;
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
function generateUnifiedSummary(result) {
|
|
94
|
+
const { summary } = result;
|
|
95
|
+
let output = `\u{1F680} AIReady Analysis Complete
|
|
96
|
+
|
|
97
|
+
`;
|
|
98
|
+
output += `\u{1F4CA} Summary:
|
|
99
|
+
`;
|
|
100
|
+
output += ` Tools run: ${summary.toolsRun.join(", ")}
|
|
101
|
+
`;
|
|
102
|
+
output += ` Total issues found: ${summary.totalIssues}
|
|
103
|
+
`;
|
|
104
|
+
output += ` Execution time: ${(summary.executionTime / 1e3).toFixed(2)}s
|
|
105
|
+
|
|
106
|
+
`;
|
|
107
|
+
if (result.patterns) {
|
|
108
|
+
output += `\u{1F50D} Pattern Analysis: ${result.patterns.length} issues
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
if (result.context) {
|
|
112
|
+
output += `\u{1F9E0} Context Analysis: ${result.context.length} issues
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
if (result.consistency) {
|
|
116
|
+
output += `\u{1F3F7}\uFE0F Consistency Analysis: ${result.consistency.summary.totalIssues} issues
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
return output;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
__require,
|
|
124
|
+
analyzeUnified,
|
|
125
|
+
generateUnifiedSummary
|
|
126
|
+
};
|
package/dist/cli.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
CHANGED
|
@@ -118,6 +118,7 @@ var import_fs = require("fs");
|
|
|
118
118
|
var import_path = require("path");
|
|
119
119
|
var import_core = require("@aiready/core");
|
|
120
120
|
var import_fs2 = require("fs");
|
|
121
|
+
var import_path2 = require("path");
|
|
121
122
|
var packageJson = JSON.parse((0, import_fs2.readFileSync)((0, import_path.join)(__dirname, "../package.json"), "utf8"));
|
|
122
123
|
var program = new import_commander.Command();
|
|
123
124
|
program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
|
|
@@ -811,4 +812,284 @@ function generateMarkdownReport(report, elapsedTime) {
|
|
|
811
812
|
}
|
|
812
813
|
return markdown;
|
|
813
814
|
}
|
|
815
|
+
function generateHTML(graph) {
|
|
816
|
+
const payload = JSON.stringify(graph, null, 2);
|
|
817
|
+
return `<!doctype html>
|
|
818
|
+
<html>
|
|
819
|
+
<head>
|
|
820
|
+
<meta charset="utf-8" />
|
|
821
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
822
|
+
<title>AIReady Visualization</title>
|
|
823
|
+
<style>
|
|
824
|
+
html,body { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0 }
|
|
825
|
+
#container { display:flex; height:100vh }
|
|
826
|
+
#panel { width: 320px; padding: 16px; background: #071130; box-shadow: -2px 0 8px rgba(0,0,0,0.3); overflow:auto }
|
|
827
|
+
#canvasWrap { flex:1; display:flex; align-items:center; justify-content:center }
|
|
828
|
+
canvas { background: #0b1220; border-radius:8px }
|
|
829
|
+
.stat { margin-bottom:12px }
|
|
830
|
+
</style>
|
|
831
|
+
</head>
|
|
832
|
+
<body>
|
|
833
|
+
<div id="container">
|
|
834
|
+
<div id="canvasWrap"><canvas id="canvas" width="1200" height="800"></canvas></div>
|
|
835
|
+
<div id="panel">
|
|
836
|
+
<h2>AIReady Visualization</h2>
|
|
837
|
+
<div class="stat"><strong>Files:</strong> <span id="stat-files"></span></div>
|
|
838
|
+
<div class="stat"><strong>Dependencies:</strong> <span id="stat-deps"></span></div>
|
|
839
|
+
<div class="stat"><strong>Legend</strong></div>
|
|
840
|
+
<div style="font-size:13px;line-height:1.3;color:#cbd5e1;margin-top:8px">
|
|
841
|
+
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff4d4f;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Critical</strong>: highest severity issues.</div>
|
|
842
|
+
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff9900;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Major</strong>: important issues.</div>
|
|
843
|
+
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ffd666;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Minor</strong>: low priority issues.</div>
|
|
844
|
+
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#91d5ff;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Info</strong>: informational notes.</div>
|
|
845
|
+
<div style="margin-top:10px;color:#94a3b8"><strong>Node size</strong>: larger = higher token cost, more issues or dependency weight.</div>
|
|
846
|
+
<div style="margin-top:6px;color:#94a3b8"><strong>Proximity</strong>: nodes that are spatially close are more contextually related; relatedness is represented by distance and size rather than explicit edges.</div>
|
|
847
|
+
<div style="margin-top:6px;color:#94a3b8"><strong>Edge colors</strong>: <span style="color:#fb7e81">Similarity</span>, <span style="color:#84c1ff">Dependency</span>, <span style="color:#ffa500">Reference</span>, default <span style="color:#334155">Other</span>.</div>
|
|
848
|
+
</div>
|
|
849
|
+
</div>
|
|
850
|
+
</div>
|
|
851
|
+
|
|
852
|
+
<script>
|
|
853
|
+
const graphData = ${payload};
|
|
854
|
+
document.getElementById('stat-files').textContent = graphData.metadata.totalFiles;
|
|
855
|
+
document.getElementById('stat-deps').textContent = graphData.metadata.totalDependencies;
|
|
856
|
+
|
|
857
|
+
const canvas = document.getElementById('canvas');
|
|
858
|
+
const ctx = canvas.getContext('2d');
|
|
859
|
+
|
|
860
|
+
const nodes = graphData.nodes.map((n, i) => ({
|
|
861
|
+
...n,
|
|
862
|
+
x: canvas.width / 2 + Math.cos(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
|
|
863
|
+
y: canvas.height / 2 + Math.sin(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
|
|
864
|
+
}));
|
|
865
|
+
|
|
866
|
+
function draw() {
|
|
867
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
868
|
+
|
|
869
|
+
graphData.edges.forEach(edge => {
|
|
870
|
+
const s = nodes.find(n => n.id === edge.source);
|
|
871
|
+
const t = nodes.find(n => n.id === edge.target);
|
|
872
|
+
if (!s || !t) return;
|
|
873
|
+
if (edge.type === 'related') return;
|
|
874
|
+
if (edge.type === 'similarity') {
|
|
875
|
+
ctx.strokeStyle = '#fb7e81';
|
|
876
|
+
ctx.lineWidth = 1.2;
|
|
877
|
+
} else if (edge.type === 'dependency') {
|
|
878
|
+
ctx.strokeStyle = '#84c1ff';
|
|
879
|
+
ctx.lineWidth = 1.0;
|
|
880
|
+
} else if (edge.type === 'reference') {
|
|
881
|
+
ctx.strokeStyle = '#ffa500';
|
|
882
|
+
ctx.lineWidth = 0.9;
|
|
883
|
+
} else {
|
|
884
|
+
ctx.strokeStyle = '#334155';
|
|
885
|
+
ctx.lineWidth = 0.8;
|
|
886
|
+
}
|
|
887
|
+
ctx.beginPath();
|
|
888
|
+
ctx.moveTo(s.x, s.y);
|
|
889
|
+
ctx.lineTo(t.x, t.y);
|
|
890
|
+
ctx.stroke();
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
const groups = {};
|
|
894
|
+
nodes.forEach(n => {
|
|
895
|
+
const g = n.group || '__default';
|
|
896
|
+
if (!groups[g]) groups[g] = { minX: n.x, minY: n.y, maxX: n.x, maxY: n.y };
|
|
897
|
+
groups[g].minX = Math.min(groups[g].minX, n.x);
|
|
898
|
+
groups[g].minY = Math.min(groups[g].minY, n.y);
|
|
899
|
+
groups[g].maxX = Math.max(groups[g].maxX, n.x);
|
|
900
|
+
groups[g].maxY = Math.max(groups[g].maxY, n.y);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
const groupRelations = {};
|
|
904
|
+
graphData.edges.forEach(edge => {
|
|
905
|
+
const sNode = nodes.find(n => n.id === edge.source);
|
|
906
|
+
const tNode = nodes.find(n => n.id === edge.target);
|
|
907
|
+
if (!sNode || !tNode) return;
|
|
908
|
+
const g1 = sNode.group || '__default';
|
|
909
|
+
const g2 = tNode.group || '__default';
|
|
910
|
+
if (g1 === g2) return;
|
|
911
|
+
const key = g1 < g2 ? g1 + '::' + g2 : g2 + '::' + g1;
|
|
912
|
+
groupRelations[key] = (groupRelations[key] || 0) + 1;
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
Object.keys(groupRelations).forEach(k => {
|
|
916
|
+
const count = groupRelations[k];
|
|
917
|
+
const [ga, gb] = k.split('::');
|
|
918
|
+
if (!groups[ga] || !groups[gb]) return;
|
|
919
|
+
const ax = (groups[ga].minX + groups[ga].maxX) / 2;
|
|
920
|
+
const ay = (groups[ga].minY + groups[ga].maxY) / 2;
|
|
921
|
+
const bx = (groups[gb].minX + groups[gb].maxX) / 2;
|
|
922
|
+
const by = (groups[gb].minY + groups[gb].maxY) / 2;
|
|
923
|
+
ctx.beginPath();
|
|
924
|
+
ctx.strokeStyle = 'rgba(148,163,184,0.25)';
|
|
925
|
+
ctx.lineWidth = Math.min(6, 0.6 + Math.sqrt(count));
|
|
926
|
+
ctx.moveTo(ax, ay);
|
|
927
|
+
ctx.lineTo(bx, by);
|
|
928
|
+
ctx.stroke();
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
Object.keys(groups).forEach(g => {
|
|
932
|
+
if (g === '__default') return;
|
|
933
|
+
const box = groups[g];
|
|
934
|
+
const pad = 16;
|
|
935
|
+
const x = box.minX - pad;
|
|
936
|
+
const y = box.minY - pad;
|
|
937
|
+
const w = (box.maxX - box.minX) + pad * 2;
|
|
938
|
+
const h = (box.maxY - box.minY) + pad * 2;
|
|
939
|
+
ctx.save();
|
|
940
|
+
ctx.fillStyle = 'rgba(30,64,175,0.04)';
|
|
941
|
+
ctx.strokeStyle = 'rgba(30,64,175,0.12)';
|
|
942
|
+
ctx.lineWidth = 1.2;
|
|
943
|
+
const r = 8;
|
|
944
|
+
ctx.beginPath();
|
|
945
|
+
ctx.moveTo(x + r, y);
|
|
946
|
+
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
947
|
+
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
948
|
+
ctx.arcTo(x, y + h, x, y, r);
|
|
949
|
+
ctx.arcTo(x, y, x + w, y, r);
|
|
950
|
+
ctx.closePath();
|
|
951
|
+
ctx.fill();
|
|
952
|
+
ctx.stroke();
|
|
953
|
+
ctx.restore();
|
|
954
|
+
ctx.fillStyle = '#94a3b8';
|
|
955
|
+
ctx.font = '11px sans-serif';
|
|
956
|
+
ctx.fillText(g, x + 8, y + 14);
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
nodes.forEach(n => {
|
|
960
|
+
const sizeVal = (n.size || n.value || 1);
|
|
961
|
+
const r = 6 + (sizeVal / 2);
|
|
962
|
+
ctx.beginPath();
|
|
963
|
+
ctx.fillStyle = n.color || '#60a5fa';
|
|
964
|
+
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
|
|
965
|
+
ctx.fill();
|
|
966
|
+
|
|
967
|
+
ctx.fillStyle = '#e2e8f0';
|
|
968
|
+
ctx.font = '11px sans-serif';
|
|
969
|
+
ctx.textAlign = 'center';
|
|
970
|
+
ctx.fillText(n.label || n.id.split('/').slice(-1)[0], n.x, n.y + r + 12);
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
draw();
|
|
975
|
+
</script>
|
|
976
|
+
</body>
|
|
977
|
+
</html>`;
|
|
978
|
+
}
|
|
979
|
+
async function handleVisualize(directory, options) {
|
|
980
|
+
try {
|
|
981
|
+
const dirPath = (0, import_path2.resolve)(process.cwd(), directory || ".");
|
|
982
|
+
const reportPath = (0, import_path2.resolve)(dirPath, options.report || "aiready-improvement-report.json");
|
|
983
|
+
if (!(0, import_fs2.existsSync)(reportPath)) {
|
|
984
|
+
console.error("Report not found at", reportPath);
|
|
985
|
+
console.log("Run `aiready scan` to generate the report, or pass --report");
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const raw = (0, import_fs2.readFileSync)(reportPath, "utf8");
|
|
989
|
+
const report = JSON.parse(raw);
|
|
990
|
+
console.log("Building graph from report...");
|
|
991
|
+
const { GraphBuilder } = await import("@aiready/visualizer/graph");
|
|
992
|
+
const graph = GraphBuilder.buildFromReport(report, dirPath);
|
|
993
|
+
if (options.dev) {
|
|
994
|
+
try {
|
|
995
|
+
const { spawn } = await import("child_process");
|
|
996
|
+
const webDir = (0, import_path2.resolve)(dirPath, "packages/visualizer");
|
|
997
|
+
const watcher = spawn("node", ["scripts/watch-report.js", reportPath], { cwd: webDir, stdio: "inherit" });
|
|
998
|
+
const vite = spawn("pnpm", ["run", "dev:web"], { cwd: webDir, stdio: "inherit", shell: true });
|
|
999
|
+
const onExit = () => {
|
|
1000
|
+
try {
|
|
1001
|
+
watcher.kill();
|
|
1002
|
+
} catch (e) {
|
|
1003
|
+
}
|
|
1004
|
+
try {
|
|
1005
|
+
vite.kill();
|
|
1006
|
+
} catch (e) {
|
|
1007
|
+
}
|
|
1008
|
+
process.exit(0);
|
|
1009
|
+
};
|
|
1010
|
+
process.on("SIGINT", onExit);
|
|
1011
|
+
process.on("SIGTERM", onExit);
|
|
1012
|
+
return;
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
console.error("Failed to start dev server:", err);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
console.log("Generating HTML...");
|
|
1018
|
+
const html = generateHTML(graph);
|
|
1019
|
+
const outPath = (0, import_path2.resolve)(dirPath, options.output || "packages/visualizer/visualization.html");
|
|
1020
|
+
(0, import_fs.writeFileSync)(outPath, html, "utf8");
|
|
1021
|
+
console.log("Visualization written to:", outPath);
|
|
1022
|
+
if (options.open) {
|
|
1023
|
+
const { exec } = await import("child_process");
|
|
1024
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1025
|
+
exec(`${opener} "${outPath}"`);
|
|
1026
|
+
}
|
|
1027
|
+
if (options.serve) {
|
|
1028
|
+
try {
|
|
1029
|
+
const port = Number(options.serve) || 5173;
|
|
1030
|
+
const http = await import("http");
|
|
1031
|
+
const fsp = await import("fs/promises");
|
|
1032
|
+
const { exec } = await import("child_process");
|
|
1033
|
+
const server = http.createServer(async (req, res) => {
|
|
1034
|
+
try {
|
|
1035
|
+
const urlPath = req.url || "/";
|
|
1036
|
+
if (urlPath === "/" || urlPath === "/index.html") {
|
|
1037
|
+
const content = await fsp.readFile(outPath, "utf8");
|
|
1038
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1039
|
+
res.end(content);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1043
|
+
res.end("Not found");
|
|
1044
|
+
} catch (e) {
|
|
1045
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
1046
|
+
res.end("Server error");
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
server.listen(port, () => {
|
|
1050
|
+
const addr = `http://localhost:${port}/`;
|
|
1051
|
+
console.log(`Local visualization server running at ${addr}`);
|
|
1052
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1053
|
+
exec(`${opener} "${addr}"`);
|
|
1054
|
+
});
|
|
1055
|
+
process.on("SIGINT", () => {
|
|
1056
|
+
server.close();
|
|
1057
|
+
process.exit(0);
|
|
1058
|
+
});
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
console.error("Failed to start local server:", err);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
(0, import_core.handleCLIError)(err, "Visualization");
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (relative to directory)", "aiready-improvement-report.json").option("-o, --output <path>", "Output HTML path (relative to directory)", "packages/visualizer/visualization.html").option("--open", "Open generated HTML in default browser").option("--serve [port]", "Start a local static server to serve the visualization (optional port number)", false).option("--dev", "Start Vite dev server (live reload) for interactive development", false).addHelpText("after", `
|
|
1068
|
+
EXAMPLES:
|
|
1069
|
+
$ aiready visualise . --report aiready-improvement-report.json
|
|
1070
|
+
$ aiready visualise . --report report.json --dev
|
|
1071
|
+
$ aiready visualise . --report report.json --serve 8080
|
|
1072
|
+
|
|
1073
|
+
NOTES:
|
|
1074
|
+
- Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
|
|
1075
|
+
`).action(async (directory, options) => await handleVisualize(directory, options));
|
|
1076
|
+
program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (relative to directory)", "aiready-improvement-report.json").option("-o, --output <path>", "Output HTML path (relative to directory)", "packages/visualizer/visualization.html").option("--open", "Open generated HTML in default browser").option("--serve [port]", "Start a local static server to serve the visualization (optional port number)", false).option("--dev", "Start Vite dev server (live reload) for interactive development", false).addHelpText("after", `
|
|
1077
|
+
EXAMPLES:
|
|
1078
|
+
$ aiready visualize . --report aiready-improvement-report.json
|
|
1079
|
+
$ aiready visualize . --report report.json -o out/visualization.html --open
|
|
1080
|
+
$ aiready visualize . --report report.json --serve
|
|
1081
|
+
$ aiready visualize . --report report.json --serve 8080
|
|
1082
|
+
$ aiready visualize . --report report.json --dev
|
|
1083
|
+
|
|
1084
|
+
NOTES:
|
|
1085
|
+
- The value passed to --report is interpreted relative to the directory argument (first positional).
|
|
1086
|
+
If the report is not found, the CLI will suggest running 'aiready scan' to generate it.
|
|
1087
|
+
- Default output path: packages/visualizer/visualization.html (relative to the directory argument).
|
|
1088
|
+
- --serve starts a tiny single-file HTTP server (default port: 5173) and opens your browser.
|
|
1089
|
+
It serves only the generated HTML (no additional asset folders).
|
|
1090
|
+
- Relatedness is represented by node proximity and size; explicit 'related' edges are not drawn to
|
|
1091
|
+
reduce clutter and improve interactivity on large graphs.
|
|
1092
|
+
- For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
|
|
1093
|
+
allow the browser a moment to stabilize after load.
|
|
1094
|
+
`).action(async (directory, options) => await handleVisualize(directory, options));
|
|
814
1095
|
program.parse();
|