@aiready/cli 0.9.15 → 0.9.17
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 +5 -5
- package/.turbo/turbo-test.log +5 -5
- package/dist/cli.js +112 -37
- package/dist/cli.mjs +113 -38
- package/package.json +6 -6
- package/src/cli.ts +136 -42
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/cli@0.9.
|
|
3
|
+
> @aiready/cli@0.9.17 build /Users/pengcao/projects/aiready/packages/cli
|
|
4
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
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mCJS[39m [1mdist/cli.js [22m[32m58.90 KB[39m
|
|
13
12
|
[32mCJS[39m [1mdist/index.js [22m[32m4.93 KB[39m
|
|
14
|
-
[32mCJS[39m
|
|
13
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m62.23 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 17ms
|
|
15
|
+
[32mESM[39m [1mdist/cli.mjs [22m[32m54.76 KB[39m
|
|
15
16
|
[32mESM[39m [1mdist/index.mjs [22m[32m138.00 B[39m
|
|
16
17
|
[32mESM[39m [1mdist/chunk-5GZDRZ3T.mjs [22m[32m4.17 KB[39m
|
|
17
|
-
[32mESM[39m
|
|
18
|
-
[32mESM[39m ⚡️ Build success in 18ms
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 17ms
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/cli@0.9.
|
|
3
|
+
> @aiready/cli@0.9.17 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
|
-
[32m✓[39m dist/__tests__/cli.test.js [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
10
|
-
[32m✓[39m src/__tests__/cli.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
9
|
+
[32m✓[39m dist/__tests__/cli.test.js [2m([22m[2m3 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
10
|
+
[32m✓[39m src/__tests__/cli.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
11
11
|
|
|
12
12
|
[2m Test Files [22m [1m[32m2 passed[39m[22m[90m (2)[39m
|
|
13
13
|
[2m Tests [22m [1m[32m6 passed[39m[22m[90m (6)[39m
|
|
14
|
-
[2m Start at [22m
|
|
15
|
-
[2m Duration [22m
|
|
14
|
+
[2m Start at [22m 12:09:08
|
|
15
|
+
[2m Duration [22m 2.39s[2m (transform 599ms, setup 0ms, import 3.19s, tests 10ms, environment 0ms)[22m
|
|
16
16
|
|
|
17
17
|
[?25h
|
package/dist/cli.js
CHANGED
|
@@ -166,6 +166,7 @@ EXAMPLES:
|
|
|
166
166
|
`).action(async (directory, options) => {
|
|
167
167
|
console.log(import_chalk.default.blue("\u{1F680} Starting AIReady unified analysis...\n"));
|
|
168
168
|
const startTime = Date.now();
|
|
169
|
+
const resolvedDir = (0, import_path2.resolve)(process.cwd(), directory || ".");
|
|
169
170
|
try {
|
|
170
171
|
const defaults = {
|
|
171
172
|
tools: ["patterns", "context", "consistency"],
|
|
@@ -176,7 +177,7 @@ EXAMPLES:
|
|
|
176
177
|
file: void 0
|
|
177
178
|
}
|
|
178
179
|
};
|
|
179
|
-
const baseOptions = await (0, import_core.loadMergedConfig)(
|
|
180
|
+
const baseOptions = await (0, import_core.loadMergedConfig)(resolvedDir, defaults, {
|
|
180
181
|
tools: options.tools ? options.tools.split(",").map((t) => t.trim()) : void 0,
|
|
181
182
|
include: options.include?.split(","),
|
|
182
183
|
exclude: options.exclude?.split(",")
|
|
@@ -184,7 +185,7 @@ EXAMPLES:
|
|
|
184
185
|
let finalOptions = { ...baseOptions };
|
|
185
186
|
if (baseOptions.tools.includes("patterns")) {
|
|
186
187
|
const { getSmartDefaults } = await import("@aiready/pattern-detect");
|
|
187
|
-
const patternSmartDefaults = await getSmartDefaults(
|
|
188
|
+
const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
188
189
|
finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
|
|
189
190
|
}
|
|
190
191
|
console.log(import_chalk.default.cyan("\n=== AIReady Run Preview ==="));
|
|
@@ -381,17 +382,17 @@ EXAMPLES:
|
|
|
381
382
|
console.log();
|
|
382
383
|
}
|
|
383
384
|
}
|
|
384
|
-
const outputFormat = options.output || finalOptions.output?.format || "console";
|
|
385
|
-
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
386
|
-
if (outputFormat === "json") {
|
|
387
|
-
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
388
|
-
const defaultFilename = `aiready-scan-${dateStr}.json`;
|
|
389
|
-
const outputPath = (0, import_core.resolveOutputPath)(userOutputFile, defaultFilename, directory);
|
|
390
|
-
const outputData = { ...results, scoring: scoringResult };
|
|
391
|
-
(0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Summary saved to ${outputPath}`);
|
|
392
|
-
}
|
|
393
385
|
}
|
|
394
386
|
}
|
|
387
|
+
const outputFormat = options.output || finalOptions.output?.format || "console";
|
|
388
|
+
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
389
|
+
if (outputFormat === "json") {
|
|
390
|
+
const timestamp = getReportTimestamp();
|
|
391
|
+
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
392
|
+
const outputPath = (0, import_core.resolveOutputPath)(userOutputFile, defaultFilename, resolvedDir);
|
|
393
|
+
const outputData = { ...results, scoring: scoringResult };
|
|
394
|
+
(0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Report saved to ${outputPath}`);
|
|
395
|
+
}
|
|
395
396
|
} catch (error) {
|
|
396
397
|
(0, import_core.handleCLIError)(error, "Analysis");
|
|
397
398
|
}
|
|
@@ -404,6 +405,7 @@ EXAMPLES:
|
|
|
404
405
|
`).action(async (directory, options) => {
|
|
405
406
|
console.log(import_chalk.default.blue("\u{1F50D} Analyzing patterns...\n"));
|
|
406
407
|
const startTime = Date.now();
|
|
408
|
+
const resolvedDir = (0, import_path2.resolve)(process.cwd(), directory || ".");
|
|
407
409
|
try {
|
|
408
410
|
const useSmartDefaults = !options.fullScan;
|
|
409
411
|
const defaults = {
|
|
@@ -432,7 +434,7 @@ EXAMPLES:
|
|
|
432
434
|
if (options.minSharedTokens) {
|
|
433
435
|
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
434
436
|
}
|
|
435
|
-
const finalOptions = await (0, import_core.loadMergedConfig)(
|
|
437
|
+
const finalOptions = await (0, import_core.loadMergedConfig)(resolvedDir, defaults, cliOptions);
|
|
436
438
|
const { analyzePatterns: analyzePatterns2, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
|
|
437
439
|
const { results, duplicates } = await analyzePatterns2(finalOptions);
|
|
438
440
|
const elapsedTime = (0, import_core.getElapsedTime)(startTime);
|
|
@@ -451,8 +453,8 @@ EXAMPLES:
|
|
|
451
453
|
};
|
|
452
454
|
const outputPath = (0, import_core.resolveOutputPath)(
|
|
453
455
|
userOutputFile,
|
|
454
|
-
`
|
|
455
|
-
|
|
456
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
457
|
+
resolvedDir
|
|
456
458
|
);
|
|
457
459
|
(0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
458
460
|
} else {
|
|
@@ -508,6 +510,7 @@ EXAMPLES:
|
|
|
508
510
|
program.command("context").description("Analyze context window costs and dependency fragmentation").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option("--max-context <number>", "Maximum acceptable context budget (tokens)", "10000").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for context (0-100)").action(async (directory, options) => {
|
|
509
511
|
console.log(import_chalk.default.blue("\u{1F9E0} Analyzing context costs...\n"));
|
|
510
512
|
const startTime = Date.now();
|
|
513
|
+
const resolvedDir = (0, import_path2.resolve)(process.cwd(), directory || ".");
|
|
511
514
|
try {
|
|
512
515
|
const defaults = {
|
|
513
516
|
maxDepth: 5,
|
|
@@ -519,7 +522,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
519
522
|
file: void 0
|
|
520
523
|
}
|
|
521
524
|
};
|
|
522
|
-
let baseOptions = await (0, import_core.loadMergedConfig)(
|
|
525
|
+
let baseOptions = await (0, import_core.loadMergedConfig)(resolvedDir, defaults, {
|
|
523
526
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
524
527
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
525
528
|
include: options.include?.split(","),
|
|
@@ -527,7 +530,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
527
530
|
});
|
|
528
531
|
let finalOptions = { ...baseOptions };
|
|
529
532
|
const { getSmartDefaults } = await import("@aiready/context-analyzer");
|
|
530
|
-
const contextSmartDefaults = await getSmartDefaults(
|
|
533
|
+
const contextSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
531
534
|
finalOptions = { ...contextSmartDefaults, ...finalOptions };
|
|
532
535
|
console.log("\u{1F4CB} Configuration:");
|
|
533
536
|
console.log(` Max depth: ${finalOptions.maxDepth}`);
|
|
@@ -554,8 +557,8 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
554
557
|
};
|
|
555
558
|
const outputPath = (0, import_core.resolveOutputPath)(
|
|
556
559
|
userOutputFile,
|
|
557
|
-
`
|
|
558
|
-
|
|
560
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
561
|
+
resolvedDir
|
|
559
562
|
);
|
|
560
563
|
(0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
561
564
|
} else {
|
|
@@ -645,6 +648,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
645
648
|
program.command("consistency").description("Check naming conventions and architectural consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option("--min-severity <level>", "Minimum severity: info|minor|major|critical", "info").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json, markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").option("--score", "Calculate and display AI Readiness Score for consistency (0-100)").action(async (directory, options) => {
|
|
646
649
|
console.log(import_chalk.default.blue("\u{1F50D} Analyzing consistency...\n"));
|
|
647
650
|
const startTime = Date.now();
|
|
651
|
+
const resolvedDir = (0, import_path2.resolve)(process.cwd(), directory || ".");
|
|
648
652
|
try {
|
|
649
653
|
const defaults = {
|
|
650
654
|
checkNaming: true,
|
|
@@ -657,7 +661,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
657
661
|
file: void 0
|
|
658
662
|
}
|
|
659
663
|
};
|
|
660
|
-
const finalOptions = await (0, import_core.loadMergedConfig)(
|
|
664
|
+
const finalOptions = await (0, import_core.loadMergedConfig)(resolvedDir, defaults, {
|
|
661
665
|
checkNaming: options.naming !== false,
|
|
662
666
|
checkPatterns: options.patterns !== false,
|
|
663
667
|
minSeverity: options.minSeverity,
|
|
@@ -685,16 +689,16 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
685
689
|
};
|
|
686
690
|
const outputPath = (0, import_core.resolveOutputPath)(
|
|
687
691
|
userOutputFile,
|
|
688
|
-
`
|
|
689
|
-
|
|
692
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
693
|
+
resolvedDir
|
|
690
694
|
);
|
|
691
695
|
(0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
692
696
|
} else if (outputFormat === "markdown") {
|
|
693
697
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
694
698
|
const outputPath = (0, import_core.resolveOutputPath)(
|
|
695
699
|
userOutputFile,
|
|
696
|
-
`
|
|
697
|
-
|
|
700
|
+
`aiready-report-${getReportTimestamp()}.md`,
|
|
701
|
+
resolvedDir
|
|
698
702
|
);
|
|
699
703
|
(0, import_fs.writeFileSync)(outputPath, markdown);
|
|
700
704
|
console.log(import_chalk.default.green(`\u2705 Report saved to ${outputPath}`));
|
|
@@ -976,13 +980,21 @@ function generateHTML(graph) {
|
|
|
976
980
|
</body>
|
|
977
981
|
</html>`;
|
|
978
982
|
}
|
|
983
|
+
function getReportTimestamp() {
|
|
984
|
+
const now = /* @__PURE__ */ new Date();
|
|
985
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
986
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
987
|
+
}
|
|
979
988
|
function findLatestScanReport(dirPath) {
|
|
980
989
|
const aireadyDir = (0, import_path2.resolve)(dirPath, ".aiready");
|
|
981
990
|
if (!(0, import_fs2.existsSync)(aireadyDir)) {
|
|
982
991
|
return null;
|
|
983
992
|
}
|
|
984
993
|
const { readdirSync, statSync } = require("fs");
|
|
985
|
-
|
|
994
|
+
let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
|
|
995
|
+
if (files.length === 0) {
|
|
996
|
+
files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
|
|
997
|
+
}
|
|
986
998
|
if (files.length === 0) {
|
|
987
999
|
return null;
|
|
988
1000
|
}
|
|
@@ -992,15 +1004,20 @@ function findLatestScanReport(dirPath) {
|
|
|
992
1004
|
async function handleVisualize(directory, options) {
|
|
993
1005
|
try {
|
|
994
1006
|
const dirPath = (0, import_path2.resolve)(process.cwd(), directory || ".");
|
|
995
|
-
let reportPath = (0, import_path2.resolve)(dirPath, options.report
|
|
996
|
-
if (!(0, import_fs2.existsSync)(reportPath)) {
|
|
1007
|
+
let reportPath = options.report ? (0, import_path2.resolve)(dirPath, options.report) : null;
|
|
1008
|
+
if (!reportPath || !(0, import_fs2.existsSync)(reportPath)) {
|
|
997
1009
|
const latestScan = findLatestScanReport(dirPath);
|
|
998
1010
|
if (latestScan) {
|
|
999
|
-
console.log(`Using latest scan report: ${latestScan}`);
|
|
1000
1011
|
reportPath = latestScan;
|
|
1012
|
+
console.log(import_chalk.default.dim(`Found latest report: ${latestScan.split("/").pop()}`));
|
|
1001
1013
|
} else {
|
|
1002
|
-
console.error("
|
|
1003
|
-
console.log(
|
|
1014
|
+
console.error(import_chalk.default.red("\u274C No AI readiness report found"));
|
|
1015
|
+
console.log(import_chalk.default.dim(`
|
|
1016
|
+
Generate a report with:
|
|
1017
|
+
aiready scan --output json
|
|
1018
|
+
|
|
1019
|
+
Or specify a custom report:
|
|
1020
|
+
aiready visualise --report <path-to-report.json>`));
|
|
1004
1021
|
return;
|
|
1005
1022
|
}
|
|
1006
1023
|
}
|
|
@@ -1012,12 +1029,68 @@ async function handleVisualize(directory, options) {
|
|
|
1012
1029
|
if (options.dev) {
|
|
1013
1030
|
try {
|
|
1014
1031
|
const { spawn } = await import("child_process");
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1032
|
+
const monorepoWebDir = (0, import_path2.resolve)(dirPath, "packages/visualizer");
|
|
1033
|
+
let webDir = "";
|
|
1034
|
+
let visualizerAvailable = false;
|
|
1035
|
+
if ((0, import_fs2.existsSync)(monorepoWebDir)) {
|
|
1036
|
+
webDir = monorepoWebDir;
|
|
1037
|
+
visualizerAvailable = true;
|
|
1038
|
+
} else {
|
|
1039
|
+
const nodemodulesLocations = [
|
|
1040
|
+
(0, import_path2.resolve)(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
1041
|
+
(0, import_path2.resolve)(process.cwd(), "node_modules", "@aiready", "visualizer")
|
|
1042
|
+
];
|
|
1043
|
+
let currentDir = dirPath;
|
|
1044
|
+
while (currentDir !== "/" && currentDir !== ".") {
|
|
1045
|
+
nodemodulesLocations.push((0, import_path2.resolve)(currentDir, "node_modules", "@aiready", "visualizer"));
|
|
1046
|
+
const parent = (0, import_path2.resolve)(currentDir, "..");
|
|
1047
|
+
if (parent === currentDir) break;
|
|
1048
|
+
currentDir = parent;
|
|
1049
|
+
}
|
|
1050
|
+
for (const location of nodemodulesLocations) {
|
|
1051
|
+
if ((0, import_fs2.existsSync)(location) && (0, import_fs2.existsSync)((0, import_path2.resolve)(location, "package.json"))) {
|
|
1052
|
+
webDir = location;
|
|
1053
|
+
visualizerAvailable = true;
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
if (!visualizerAvailable) {
|
|
1058
|
+
try {
|
|
1059
|
+
const vizPkgPath = require.resolve("@aiready/visualizer/package.json");
|
|
1060
|
+
webDir = (0, import_path2.resolve)(vizPkgPath, "..");
|
|
1061
|
+
visualizerAvailable = true;
|
|
1062
|
+
} catch (e) {
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
const spawnCwd = webDir || process.cwd();
|
|
1067
|
+
const nodeBinCandidate = process.execPath;
|
|
1068
|
+
const nodeBin = (0, import_fs2.existsSync)(nodeBinCandidate) ? nodeBinCandidate : "node";
|
|
1069
|
+
if (!visualizerAvailable) {
|
|
1070
|
+
console.error(import_chalk.default.red("\u274C Cannot start dev server: @aiready/visualizer not available."));
|
|
1071
|
+
console.log(import_chalk.default.dim("Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer"));
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
const { watch } = await import("fs");
|
|
1075
|
+
const copyReportToViz = () => {
|
|
1076
|
+
try {
|
|
1077
|
+
const destPath = (0, import_path2.resolve)(spawnCwd, "web", "report-data.json");
|
|
1078
|
+
(0, import_fs2.copyFileSync)(reportPath, destPath);
|
|
1079
|
+
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
console.error("Failed to sync report:", e);
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
copyReportToViz();
|
|
1085
|
+
let watchTimeout = null;
|
|
1086
|
+
const reportWatcher = watch(reportPath, () => {
|
|
1087
|
+
if (watchTimeout) clearTimeout(watchTimeout);
|
|
1088
|
+
watchTimeout = setTimeout(copyReportToViz, 100);
|
|
1089
|
+
});
|
|
1090
|
+
const vite = spawn("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true });
|
|
1018
1091
|
const onExit = () => {
|
|
1019
1092
|
try {
|
|
1020
|
-
|
|
1093
|
+
reportWatcher.close();
|
|
1021
1094
|
} catch (e) {
|
|
1022
1095
|
}
|
|
1023
1096
|
try {
|
|
@@ -1083,18 +1156,20 @@ async function handleVisualize(directory, options) {
|
|
|
1083
1156
|
(0, import_core.handleCLIError)(err, "Visualization");
|
|
1084
1157
|
}
|
|
1085
1158
|
}
|
|
1086
|
-
program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (
|
|
1159
|
+
program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").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", true).addHelpText("after", `
|
|
1087
1160
|
EXAMPLES:
|
|
1088
|
-
$ aiready visualise .
|
|
1161
|
+
$ aiready visualise . # Auto-detects latest report
|
|
1162
|
+
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1089
1163
|
$ aiready visualise . --report report.json --dev
|
|
1090
1164
|
$ aiready visualise . --report report.json --serve 8080
|
|
1091
1165
|
|
|
1092
1166
|
NOTES:
|
|
1093
1167
|
- Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
|
|
1094
1168
|
`).action(async (directory, options) => await handleVisualize(directory, options));
|
|
1095
|
-
program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (
|
|
1169
|
+
program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").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", `
|
|
1096
1170
|
EXAMPLES:
|
|
1097
|
-
$ aiready visualize .
|
|
1171
|
+
$ aiready visualize . # Auto-detects latest report
|
|
1172
|
+
$ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
|
|
1098
1173
|
$ aiready visualize . --report report.json -o out/visualization.html --open
|
|
1099
1174
|
$ aiready visualize . --report report.json --serve
|
|
1100
1175
|
$ aiready visualize . --report report.json --serve 8080
|
package/dist/cli.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
getRatingDisplay,
|
|
23
23
|
parseWeightString
|
|
24
24
|
} from "@aiready/core";
|
|
25
|
-
import { readFileSync, existsSync } from "fs";
|
|
25
|
+
import { readFileSync, existsSync, copyFileSync } from "fs";
|
|
26
26
|
import { resolve as resolvePath } from "path";
|
|
27
27
|
var packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
|
|
28
28
|
var program = new Command();
|
|
@@ -71,6 +71,7 @@ EXAMPLES:
|
|
|
71
71
|
`).action(async (directory, options) => {
|
|
72
72
|
console.log(chalk.blue("\u{1F680} Starting AIReady unified analysis...\n"));
|
|
73
73
|
const startTime = Date.now();
|
|
74
|
+
const resolvedDir = resolvePath(process.cwd(), directory || ".");
|
|
74
75
|
try {
|
|
75
76
|
const defaults = {
|
|
76
77
|
tools: ["patterns", "context", "consistency"],
|
|
@@ -81,7 +82,7 @@ EXAMPLES:
|
|
|
81
82
|
file: void 0
|
|
82
83
|
}
|
|
83
84
|
};
|
|
84
|
-
const baseOptions = await loadMergedConfig(
|
|
85
|
+
const baseOptions = await loadMergedConfig(resolvedDir, defaults, {
|
|
85
86
|
tools: options.tools ? options.tools.split(",").map((t) => t.trim()) : void 0,
|
|
86
87
|
include: options.include?.split(","),
|
|
87
88
|
exclude: options.exclude?.split(",")
|
|
@@ -89,7 +90,7 @@ EXAMPLES:
|
|
|
89
90
|
let finalOptions = { ...baseOptions };
|
|
90
91
|
if (baseOptions.tools.includes("patterns")) {
|
|
91
92
|
const { getSmartDefaults } = await import("@aiready/pattern-detect");
|
|
92
|
-
const patternSmartDefaults = await getSmartDefaults(
|
|
93
|
+
const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
93
94
|
finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
|
|
94
95
|
}
|
|
95
96
|
console.log(chalk.cyan("\n=== AIReady Run Preview ==="));
|
|
@@ -286,17 +287,17 @@ EXAMPLES:
|
|
|
286
287
|
console.log();
|
|
287
288
|
}
|
|
288
289
|
}
|
|
289
|
-
const outputFormat = options.output || finalOptions.output?.format || "console";
|
|
290
|
-
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
291
|
-
if (outputFormat === "json") {
|
|
292
|
-
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
293
|
-
const defaultFilename = `aiready-scan-${dateStr}.json`;
|
|
294
|
-
const outputPath = resolveOutputPath(userOutputFile, defaultFilename, directory);
|
|
295
|
-
const outputData = { ...results, scoring: scoringResult };
|
|
296
|
-
handleJSONOutput(outputData, outputPath, `\u2705 Summary saved to ${outputPath}`);
|
|
297
|
-
}
|
|
298
290
|
}
|
|
299
291
|
}
|
|
292
|
+
const outputFormat = options.output || finalOptions.output?.format || "console";
|
|
293
|
+
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
294
|
+
if (outputFormat === "json") {
|
|
295
|
+
const timestamp = getReportTimestamp();
|
|
296
|
+
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
297
|
+
const outputPath = resolveOutputPath(userOutputFile, defaultFilename, resolvedDir);
|
|
298
|
+
const outputData = { ...results, scoring: scoringResult };
|
|
299
|
+
handleJSONOutput(outputData, outputPath, `\u2705 Report saved to ${outputPath}`);
|
|
300
|
+
}
|
|
300
301
|
} catch (error) {
|
|
301
302
|
handleCLIError(error, "Analysis");
|
|
302
303
|
}
|
|
@@ -309,6 +310,7 @@ EXAMPLES:
|
|
|
309
310
|
`).action(async (directory, options) => {
|
|
310
311
|
console.log(chalk.blue("\u{1F50D} Analyzing patterns...\n"));
|
|
311
312
|
const startTime = Date.now();
|
|
313
|
+
const resolvedDir = resolvePath(process.cwd(), directory || ".");
|
|
312
314
|
try {
|
|
313
315
|
const useSmartDefaults = !options.fullScan;
|
|
314
316
|
const defaults = {
|
|
@@ -337,7 +339,7 @@ EXAMPLES:
|
|
|
337
339
|
if (options.minSharedTokens) {
|
|
338
340
|
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
339
341
|
}
|
|
340
|
-
const finalOptions = await loadMergedConfig(
|
|
342
|
+
const finalOptions = await loadMergedConfig(resolvedDir, defaults, cliOptions);
|
|
341
343
|
const { analyzePatterns, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
|
|
342
344
|
const { results, duplicates } = await analyzePatterns(finalOptions);
|
|
343
345
|
const elapsedTime = getElapsedTime(startTime);
|
|
@@ -356,8 +358,8 @@ EXAMPLES:
|
|
|
356
358
|
};
|
|
357
359
|
const outputPath = resolveOutputPath(
|
|
358
360
|
userOutputFile,
|
|
359
|
-
`
|
|
360
|
-
|
|
361
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
362
|
+
resolvedDir
|
|
361
363
|
);
|
|
362
364
|
handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
363
365
|
} else {
|
|
@@ -413,6 +415,7 @@ EXAMPLES:
|
|
|
413
415
|
program.command("context").description("Analyze context window costs and dependency fragmentation").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option("--max-context <number>", "Maximum acceptable context budget (tokens)", "10000").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for context (0-100)").action(async (directory, options) => {
|
|
414
416
|
console.log(chalk.blue("\u{1F9E0} Analyzing context costs...\n"));
|
|
415
417
|
const startTime = Date.now();
|
|
418
|
+
const resolvedDir = resolvePath(process.cwd(), directory || ".");
|
|
416
419
|
try {
|
|
417
420
|
const defaults = {
|
|
418
421
|
maxDepth: 5,
|
|
@@ -424,7 +427,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
424
427
|
file: void 0
|
|
425
428
|
}
|
|
426
429
|
};
|
|
427
|
-
let baseOptions = await loadMergedConfig(
|
|
430
|
+
let baseOptions = await loadMergedConfig(resolvedDir, defaults, {
|
|
428
431
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
429
432
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
430
433
|
include: options.include?.split(","),
|
|
@@ -432,7 +435,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
432
435
|
});
|
|
433
436
|
let finalOptions = { ...baseOptions };
|
|
434
437
|
const { getSmartDefaults } = await import("@aiready/context-analyzer");
|
|
435
|
-
const contextSmartDefaults = await getSmartDefaults(
|
|
438
|
+
const contextSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
436
439
|
finalOptions = { ...contextSmartDefaults, ...finalOptions };
|
|
437
440
|
console.log("\u{1F4CB} Configuration:");
|
|
438
441
|
console.log(` Max depth: ${finalOptions.maxDepth}`);
|
|
@@ -459,8 +462,8 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
459
462
|
};
|
|
460
463
|
const outputPath = resolveOutputPath(
|
|
461
464
|
userOutputFile,
|
|
462
|
-
`
|
|
463
|
-
|
|
465
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
466
|
+
resolvedDir
|
|
464
467
|
);
|
|
465
468
|
handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
466
469
|
} else {
|
|
@@ -550,6 +553,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
550
553
|
program.command("consistency").description("Check naming conventions and architectural consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option("--min-severity <level>", "Minimum severity: info|minor|major|critical", "info").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json, markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").option("--score", "Calculate and display AI Readiness Score for consistency (0-100)").action(async (directory, options) => {
|
|
551
554
|
console.log(chalk.blue("\u{1F50D} Analyzing consistency...\n"));
|
|
552
555
|
const startTime = Date.now();
|
|
556
|
+
const resolvedDir = resolvePath(process.cwd(), directory || ".");
|
|
553
557
|
try {
|
|
554
558
|
const defaults = {
|
|
555
559
|
checkNaming: true,
|
|
@@ -562,7 +566,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
562
566
|
file: void 0
|
|
563
567
|
}
|
|
564
568
|
};
|
|
565
|
-
const finalOptions = await loadMergedConfig(
|
|
569
|
+
const finalOptions = await loadMergedConfig(resolvedDir, defaults, {
|
|
566
570
|
checkNaming: options.naming !== false,
|
|
567
571
|
checkPatterns: options.patterns !== false,
|
|
568
572
|
minSeverity: options.minSeverity,
|
|
@@ -590,16 +594,16 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
590
594
|
};
|
|
591
595
|
const outputPath = resolveOutputPath(
|
|
592
596
|
userOutputFile,
|
|
593
|
-
`
|
|
594
|
-
|
|
597
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
598
|
+
resolvedDir
|
|
595
599
|
);
|
|
596
600
|
handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
597
601
|
} else if (outputFormat === "markdown") {
|
|
598
602
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
599
603
|
const outputPath = resolveOutputPath(
|
|
600
604
|
userOutputFile,
|
|
601
|
-
`
|
|
602
|
-
|
|
605
|
+
`aiready-report-${getReportTimestamp()}.md`,
|
|
606
|
+
resolvedDir
|
|
603
607
|
);
|
|
604
608
|
writeFileSync(outputPath, markdown);
|
|
605
609
|
console.log(chalk.green(`\u2705 Report saved to ${outputPath}`));
|
|
@@ -881,13 +885,21 @@ function generateHTML(graph) {
|
|
|
881
885
|
</body>
|
|
882
886
|
</html>`;
|
|
883
887
|
}
|
|
888
|
+
function getReportTimestamp() {
|
|
889
|
+
const now = /* @__PURE__ */ new Date();
|
|
890
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
891
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
892
|
+
}
|
|
884
893
|
function findLatestScanReport(dirPath) {
|
|
885
894
|
const aireadyDir = resolvePath(dirPath, ".aiready");
|
|
886
895
|
if (!existsSync(aireadyDir)) {
|
|
887
896
|
return null;
|
|
888
897
|
}
|
|
889
898
|
const { readdirSync, statSync } = __require("fs");
|
|
890
|
-
|
|
899
|
+
let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
|
|
900
|
+
if (files.length === 0) {
|
|
901
|
+
files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
|
|
902
|
+
}
|
|
891
903
|
if (files.length === 0) {
|
|
892
904
|
return null;
|
|
893
905
|
}
|
|
@@ -897,15 +909,20 @@ function findLatestScanReport(dirPath) {
|
|
|
897
909
|
async function handleVisualize(directory, options) {
|
|
898
910
|
try {
|
|
899
911
|
const dirPath = resolvePath(process.cwd(), directory || ".");
|
|
900
|
-
let reportPath = resolvePath(dirPath, options.report
|
|
901
|
-
if (!existsSync(reportPath)) {
|
|
912
|
+
let reportPath = options.report ? resolvePath(dirPath, options.report) : null;
|
|
913
|
+
if (!reportPath || !existsSync(reportPath)) {
|
|
902
914
|
const latestScan = findLatestScanReport(dirPath);
|
|
903
915
|
if (latestScan) {
|
|
904
|
-
console.log(`Using latest scan report: ${latestScan}`);
|
|
905
916
|
reportPath = latestScan;
|
|
917
|
+
console.log(chalk.dim(`Found latest report: ${latestScan.split("/").pop()}`));
|
|
906
918
|
} else {
|
|
907
|
-
console.error("
|
|
908
|
-
console.log(
|
|
919
|
+
console.error(chalk.red("\u274C No AI readiness report found"));
|
|
920
|
+
console.log(chalk.dim(`
|
|
921
|
+
Generate a report with:
|
|
922
|
+
aiready scan --output json
|
|
923
|
+
|
|
924
|
+
Or specify a custom report:
|
|
925
|
+
aiready visualise --report <path-to-report.json>`));
|
|
909
926
|
return;
|
|
910
927
|
}
|
|
911
928
|
}
|
|
@@ -917,12 +934,68 @@ async function handleVisualize(directory, options) {
|
|
|
917
934
|
if (options.dev) {
|
|
918
935
|
try {
|
|
919
936
|
const { spawn } = await import("child_process");
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
937
|
+
const monorepoWebDir = resolvePath(dirPath, "packages/visualizer");
|
|
938
|
+
let webDir = "";
|
|
939
|
+
let visualizerAvailable = false;
|
|
940
|
+
if (existsSync(monorepoWebDir)) {
|
|
941
|
+
webDir = monorepoWebDir;
|
|
942
|
+
visualizerAvailable = true;
|
|
943
|
+
} else {
|
|
944
|
+
const nodemodulesLocations = [
|
|
945
|
+
resolvePath(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
946
|
+
resolvePath(process.cwd(), "node_modules", "@aiready", "visualizer")
|
|
947
|
+
];
|
|
948
|
+
let currentDir = dirPath;
|
|
949
|
+
while (currentDir !== "/" && currentDir !== ".") {
|
|
950
|
+
nodemodulesLocations.push(resolvePath(currentDir, "node_modules", "@aiready", "visualizer"));
|
|
951
|
+
const parent = resolvePath(currentDir, "..");
|
|
952
|
+
if (parent === currentDir) break;
|
|
953
|
+
currentDir = parent;
|
|
954
|
+
}
|
|
955
|
+
for (const location of nodemodulesLocations) {
|
|
956
|
+
if (existsSync(location) && existsSync(resolvePath(location, "package.json"))) {
|
|
957
|
+
webDir = location;
|
|
958
|
+
visualizerAvailable = true;
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (!visualizerAvailable) {
|
|
963
|
+
try {
|
|
964
|
+
const vizPkgPath = __require.resolve("@aiready/visualizer/package.json");
|
|
965
|
+
webDir = resolvePath(vizPkgPath, "..");
|
|
966
|
+
visualizerAvailable = true;
|
|
967
|
+
} catch (e) {
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
const spawnCwd = webDir || process.cwd();
|
|
972
|
+
const nodeBinCandidate = process.execPath;
|
|
973
|
+
const nodeBin = existsSync(nodeBinCandidate) ? nodeBinCandidate : "node";
|
|
974
|
+
if (!visualizerAvailable) {
|
|
975
|
+
console.error(chalk.red("\u274C Cannot start dev server: @aiready/visualizer not available."));
|
|
976
|
+
console.log(chalk.dim("Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer"));
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const { watch } = await import("fs");
|
|
980
|
+
const copyReportToViz = () => {
|
|
981
|
+
try {
|
|
982
|
+
const destPath = resolvePath(spawnCwd, "web", "report-data.json");
|
|
983
|
+
copyFileSync(reportPath, destPath);
|
|
984
|
+
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
985
|
+
} catch (e) {
|
|
986
|
+
console.error("Failed to sync report:", e);
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
copyReportToViz();
|
|
990
|
+
let watchTimeout = null;
|
|
991
|
+
const reportWatcher = watch(reportPath, () => {
|
|
992
|
+
if (watchTimeout) clearTimeout(watchTimeout);
|
|
993
|
+
watchTimeout = setTimeout(copyReportToViz, 100);
|
|
994
|
+
});
|
|
995
|
+
const vite = spawn("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true });
|
|
923
996
|
const onExit = () => {
|
|
924
997
|
try {
|
|
925
|
-
|
|
998
|
+
reportWatcher.close();
|
|
926
999
|
} catch (e) {
|
|
927
1000
|
}
|
|
928
1001
|
try {
|
|
@@ -988,18 +1061,20 @@ async function handleVisualize(directory, options) {
|
|
|
988
1061
|
handleCLIError(err, "Visualization");
|
|
989
1062
|
}
|
|
990
1063
|
}
|
|
991
|
-
program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (
|
|
1064
|
+
program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").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", true).addHelpText("after", `
|
|
992
1065
|
EXAMPLES:
|
|
993
|
-
$ aiready visualise .
|
|
1066
|
+
$ aiready visualise . # Auto-detects latest report
|
|
1067
|
+
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
994
1068
|
$ aiready visualise . --report report.json --dev
|
|
995
1069
|
$ aiready visualise . --report report.json --serve 8080
|
|
996
1070
|
|
|
997
1071
|
NOTES:
|
|
998
1072
|
- Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
|
|
999
1073
|
`).action(async (directory, options) => await handleVisualize(directory, options));
|
|
1000
|
-
program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (
|
|
1074
|
+
program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").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", `
|
|
1001
1075
|
EXAMPLES:
|
|
1002
|
-
$ aiready visualize .
|
|
1076
|
+
$ aiready visualize . # Auto-detects latest report
|
|
1077
|
+
$ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
|
|
1003
1078
|
$ aiready visualize . --report report.json -o out/visualization.html --open
|
|
1004
1079
|
$ aiready visualize . --report report.json --serve
|
|
1005
1080
|
$ aiready visualize . --report report.json --serve 8080
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.17",
|
|
4
4
|
"description": "Unified CLI for AIReady analysis tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"commander": "^14.0.0",
|
|
13
13
|
"chalk": "^5.3.0",
|
|
14
|
-
"@aiready/visualizer": "0.1.
|
|
15
|
-
"@aiready/core": "0.9.
|
|
16
|
-
"@aiready/consistency": "0.8.
|
|
17
|
-
"@aiready/
|
|
18
|
-
"@aiready/
|
|
14
|
+
"@aiready/visualizer": "0.1.18",
|
|
15
|
+
"@aiready/core": "0.9.16",
|
|
16
|
+
"@aiready/consistency": "0.8.16",
|
|
17
|
+
"@aiready/context-analyzer": "0.9.16",
|
|
18
|
+
"@aiready/pattern-detect": "0.11.16"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"tsup": "^8.3.5",
|
package/src/cli.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
type AIReadyConfig,
|
|
21
21
|
type ToolScoringOutput,
|
|
22
22
|
} from '@aiready/core';
|
|
23
|
-
import { readFileSync, existsSync } from 'fs';
|
|
23
|
+
import { readFileSync, existsSync, copyFileSync } from 'fs';
|
|
24
24
|
import { resolve as resolvePath } from 'path';
|
|
25
25
|
import { GraphBuilder } from '@aiready/visualizer/graph';
|
|
26
26
|
import type { GraphData } from '@aiready/visualizer';
|
|
@@ -93,6 +93,8 @@ EXAMPLES:
|
|
|
93
93
|
console.log(chalk.blue('🚀 Starting AIReady unified analysis...\n'));
|
|
94
94
|
|
|
95
95
|
const startTime = Date.now();
|
|
96
|
+
// Resolve directory to absolute path to ensure .aiready/ is created in the right location
|
|
97
|
+
const resolvedDir = resolvePath(process.cwd(), directory || '.');
|
|
96
98
|
|
|
97
99
|
try {
|
|
98
100
|
// Define defaults
|
|
@@ -107,7 +109,7 @@ EXAMPLES:
|
|
|
107
109
|
};
|
|
108
110
|
|
|
109
111
|
// Load and merge config with CLI options
|
|
110
|
-
const baseOptions = await loadMergedConfig(
|
|
112
|
+
const baseOptions = await loadMergedConfig(resolvedDir, defaults, {
|
|
111
113
|
tools: options.tools ? options.tools.split(',').map((t: string) => t.trim()) as ('patterns' | 'context' | 'consistency')[] : undefined,
|
|
112
114
|
include: options.include?.split(','),
|
|
113
115
|
exclude: options.exclude?.split(','),
|
|
@@ -118,7 +120,7 @@ EXAMPLES:
|
|
|
118
120
|
let finalOptions = { ...baseOptions };
|
|
119
121
|
if (baseOptions.tools.includes('patterns')) {
|
|
120
122
|
const { getSmartDefaults } = await import('@aiready/pattern-detect');
|
|
121
|
-
const patternSmartDefaults = await getSmartDefaults(
|
|
123
|
+
const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
122
124
|
// Merge deeply to preserve nested config
|
|
123
125
|
finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
|
|
124
126
|
}
|
|
@@ -367,19 +369,19 @@ EXAMPLES:
|
|
|
367
369
|
console.log();
|
|
368
370
|
}
|
|
369
371
|
}
|
|
370
|
-
|
|
371
|
-
// Persist JSON summary by default when output is json or when config directory present
|
|
372
|
-
const outputFormat = options.output || finalOptions.output?.format || 'console';
|
|
373
|
-
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
374
|
-
if (outputFormat === 'json') {
|
|
375
|
-
const dateStr = new Date().toISOString().split('T')[0];
|
|
376
|
-
const defaultFilename = `aiready-scan-${dateStr}.json`;
|
|
377
|
-
const outputPath = resolveOutputPath(userOutputFile, defaultFilename, directory);
|
|
378
|
-
const outputData = { ...results, scoring: scoringResult };
|
|
379
|
-
handleJSONOutput(outputData, outputPath, `✅ Summary saved to ${outputPath}`);
|
|
380
|
-
}
|
|
381
372
|
}
|
|
382
373
|
}
|
|
374
|
+
|
|
375
|
+
// Persist JSON summary when output format is json
|
|
376
|
+
const outputFormat = options.output || finalOptions.output?.format || 'console';
|
|
377
|
+
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
378
|
+
if (outputFormat === 'json') {
|
|
379
|
+
const timestamp = getReportTimestamp();
|
|
380
|
+
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
381
|
+
const outputPath = resolveOutputPath(userOutputFile, defaultFilename, resolvedDir);
|
|
382
|
+
const outputData = { ...results, scoring: scoringResult };
|
|
383
|
+
handleJSONOutput(outputData, outputPath, `✅ Report saved to ${outputPath}`);
|
|
384
|
+
}
|
|
383
385
|
} catch (error) {
|
|
384
386
|
handleCLIError(error, 'Analysis');
|
|
385
387
|
}
|
|
@@ -410,6 +412,7 @@ EXAMPLES:
|
|
|
410
412
|
console.log(chalk.blue('🔍 Analyzing patterns...\n'));
|
|
411
413
|
|
|
412
414
|
const startTime = Date.now();
|
|
415
|
+
const resolvedDir = resolvePath(process.cwd(), directory || '.');
|
|
413
416
|
|
|
414
417
|
try {
|
|
415
418
|
// Determine if smart defaults should be used
|
|
@@ -449,7 +452,7 @@ EXAMPLES:
|
|
|
449
452
|
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
450
453
|
}
|
|
451
454
|
|
|
452
|
-
const finalOptions = await loadMergedConfig(
|
|
455
|
+
const finalOptions = await loadMergedConfig(resolvedDir, defaults, cliOptions);
|
|
453
456
|
|
|
454
457
|
const { analyzePatterns, generateSummary, calculatePatternScore } = await import('@aiready/pattern-detect');
|
|
455
458
|
|
|
@@ -476,8 +479,8 @@ EXAMPLES:
|
|
|
476
479
|
|
|
477
480
|
const outputPath = resolveOutputPath(
|
|
478
481
|
userOutputFile,
|
|
479
|
-
`
|
|
480
|
-
|
|
482
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
483
|
+
resolvedDir
|
|
481
484
|
);
|
|
482
485
|
|
|
483
486
|
handleJSONOutput(outputData, outputPath, `✅ Results saved to ${outputPath}`);
|
|
@@ -563,6 +566,7 @@ program
|
|
|
563
566
|
console.log(chalk.blue('🧠 Analyzing context costs...\n'));
|
|
564
567
|
|
|
565
568
|
const startTime = Date.now();
|
|
569
|
+
const resolvedDir = resolvePath(process.cwd(), directory || '.');
|
|
566
570
|
|
|
567
571
|
try {
|
|
568
572
|
// Define defaults
|
|
@@ -578,7 +582,7 @@ program
|
|
|
578
582
|
};
|
|
579
583
|
|
|
580
584
|
// Load and merge config with CLI options
|
|
581
|
-
let baseOptions = await loadMergedConfig(
|
|
585
|
+
let baseOptions = await loadMergedConfig(resolvedDir, defaults, {
|
|
582
586
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : undefined,
|
|
583
587
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : undefined,
|
|
584
588
|
include: options.include?.split(','),
|
|
@@ -588,7 +592,7 @@ program
|
|
|
588
592
|
// Apply smart defaults for context analysis (always for individual context command)
|
|
589
593
|
let finalOptions: any = { ...baseOptions };
|
|
590
594
|
const { getSmartDefaults } = await import('@aiready/context-analyzer');
|
|
591
|
-
const contextSmartDefaults = await getSmartDefaults(
|
|
595
|
+
const contextSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
592
596
|
finalOptions = { ...contextSmartDefaults, ...finalOptions };
|
|
593
597
|
|
|
594
598
|
// Display configuration
|
|
@@ -625,8 +629,8 @@ program
|
|
|
625
629
|
|
|
626
630
|
const outputPath = resolveOutputPath(
|
|
627
631
|
userOutputFile,
|
|
628
|
-
`
|
|
629
|
-
|
|
632
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
633
|
+
resolvedDir
|
|
630
634
|
);
|
|
631
635
|
|
|
632
636
|
handleJSONOutput(outputData, outputPath, `✅ Results saved to ${outputPath}`);
|
|
@@ -742,6 +746,7 @@ program
|
|
|
742
746
|
console.log(chalk.blue('🔍 Analyzing consistency...\n'));
|
|
743
747
|
|
|
744
748
|
const startTime = Date.now();
|
|
749
|
+
const resolvedDir = resolvePath(process.cwd(), directory || '.');
|
|
745
750
|
|
|
746
751
|
try {
|
|
747
752
|
// Define defaults
|
|
@@ -758,7 +763,7 @@ program
|
|
|
758
763
|
};
|
|
759
764
|
|
|
760
765
|
// Load and merge config with CLI options
|
|
761
|
-
const finalOptions = await loadMergedConfig(
|
|
766
|
+
const finalOptions = await loadMergedConfig(resolvedDir, defaults, {
|
|
762
767
|
checkNaming: options.naming !== false,
|
|
763
768
|
checkPatterns: options.patterns !== false,
|
|
764
769
|
minSeverity: options.minSeverity,
|
|
@@ -794,8 +799,8 @@ program
|
|
|
794
799
|
|
|
795
800
|
const outputPath = resolveOutputPath(
|
|
796
801
|
userOutputFile,
|
|
797
|
-
`
|
|
798
|
-
|
|
802
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
803
|
+
resolvedDir
|
|
799
804
|
);
|
|
800
805
|
|
|
801
806
|
handleJSONOutput(outputData, outputPath, `✅ Results saved to ${outputPath}`);
|
|
@@ -804,8 +809,8 @@ program
|
|
|
804
809
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
805
810
|
const outputPath = resolveOutputPath(
|
|
806
811
|
userOutputFile,
|
|
807
|
-
`
|
|
808
|
-
|
|
812
|
+
`aiready-report-${getReportTimestamp()}.md`,
|
|
813
|
+
resolvedDir
|
|
809
814
|
);
|
|
810
815
|
writeFileSync(outputPath, markdown);
|
|
811
816
|
console.log(chalk.green(`✅ Report saved to ${outputPath}`));
|
|
@@ -1088,7 +1093,18 @@ program
|
|
|
1088
1093
|
}
|
|
1089
1094
|
|
|
1090
1095
|
/**
|
|
1091
|
-
*
|
|
1096
|
+
* Generate timestamp for report filenames (YYYYMMDD-HHMMSS)
|
|
1097
|
+
* Provides better granularity than date-only filenames
|
|
1098
|
+
*/
|
|
1099
|
+
function getReportTimestamp(): string {
|
|
1100
|
+
const now = new Date();
|
|
1101
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
1102
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Find the latest aiready report in the .aiready directory
|
|
1107
|
+
* Searches for both new format (aiready-report-*) and legacy format (aiready-scan-*)
|
|
1092
1108
|
*/
|
|
1093
1109
|
function findLatestScanReport(dirPath: string): string | null {
|
|
1094
1110
|
const aireadyDir = resolvePath(dirPath, '.aiready');
|
|
@@ -1097,7 +1113,11 @@ program
|
|
|
1097
1113
|
}
|
|
1098
1114
|
|
|
1099
1115
|
const { readdirSync, statSync } = require('fs');
|
|
1100
|
-
|
|
1116
|
+
// Search for new format first, then legacy format
|
|
1117
|
+
let files = readdirSync(aireadyDir).filter(f => f.startsWith('aiready-report-') && f.endsWith('.json'));
|
|
1118
|
+
if (files.length === 0) {
|
|
1119
|
+
files = readdirSync(aireadyDir).filter(f => f.startsWith('aiready-scan-') && f.endsWith('.json'));
|
|
1120
|
+
}
|
|
1101
1121
|
|
|
1102
1122
|
if (files.length === 0) {
|
|
1103
1123
|
return null;
|
|
@@ -1114,17 +1134,17 @@ program
|
|
|
1114
1134
|
async function handleVisualize(directory: string | undefined, options: any) {
|
|
1115
1135
|
try {
|
|
1116
1136
|
const dirPath = resolvePath(process.cwd(), directory || '.');
|
|
1117
|
-
let reportPath = resolvePath(dirPath, options.report
|
|
1137
|
+
let reportPath = options.report ? resolvePath(dirPath, options.report) : null;
|
|
1118
1138
|
|
|
1119
|
-
// If report not found, try to find latest scan report
|
|
1120
|
-
if (!existsSync(reportPath)) {
|
|
1139
|
+
// If report not provided or not found, try to find latest scan report
|
|
1140
|
+
if (!reportPath || !existsSync(reportPath)) {
|
|
1121
1141
|
const latestScan = findLatestScanReport(dirPath);
|
|
1122
1142
|
if (latestScan) {
|
|
1123
|
-
console.log(`Using latest scan report: ${latestScan}`);
|
|
1124
1143
|
reportPath = latestScan;
|
|
1144
|
+
console.log(chalk.dim(`Found latest report: ${latestScan.split('/').pop()}`));
|
|
1125
1145
|
} else {
|
|
1126
|
-
console.error('
|
|
1127
|
-
console.log(
|
|
1146
|
+
console.error(chalk.red('❌ No AI readiness report found'));
|
|
1147
|
+
console.log(chalk.dim(`\nGenerate a report with:\n aiready scan --output json\n\nOr specify a custom report:\n aiready visualise --report <path-to-report.json>`));
|
|
1128
1148
|
return;
|
|
1129
1149
|
}
|
|
1130
1150
|
}
|
|
@@ -1139,12 +1159,84 @@ program
|
|
|
1139
1159
|
if (options.dev) {
|
|
1140
1160
|
try {
|
|
1141
1161
|
const { spawn } = await import("child_process");
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
|
|
1162
|
+
const monorepoWebDir = resolvePath(dirPath, 'packages/visualizer');
|
|
1163
|
+
let webDir = '';
|
|
1164
|
+
let visualizerAvailable = false;
|
|
1165
|
+
if (existsSync(monorepoWebDir)) {
|
|
1166
|
+
webDir = monorepoWebDir;
|
|
1167
|
+
visualizerAvailable = true;
|
|
1168
|
+
} else {
|
|
1169
|
+
// Try to resolve installed @aiready/visualizer package from node_modules
|
|
1170
|
+
// Check multiple locations to support pnpm, npm, yarn, etc.
|
|
1171
|
+
const nodemodulesLocations: string[] = [
|
|
1172
|
+
resolvePath(dirPath, 'node_modules', '@aiready', 'visualizer'),
|
|
1173
|
+
resolvePath(process.cwd(), 'node_modules', '@aiready', 'visualizer'),
|
|
1174
|
+
];
|
|
1175
|
+
|
|
1176
|
+
// Walk up directory tree to find node_modules in parent directories
|
|
1177
|
+
let currentDir = dirPath;
|
|
1178
|
+
while (currentDir !== '/' && currentDir !== '.') {
|
|
1179
|
+
nodemodulesLocations.push(resolvePath(currentDir, 'node_modules', '@aiready', 'visualizer'));
|
|
1180
|
+
const parent = resolvePath(currentDir, '..');
|
|
1181
|
+
if (parent === currentDir) break; // Reached filesystem root
|
|
1182
|
+
currentDir = parent;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
for (const location of nodemodulesLocations) {
|
|
1186
|
+
if (existsSync(location) && existsSync(resolvePath(location, 'package.json'))) {
|
|
1187
|
+
webDir = location;
|
|
1188
|
+
visualizerAvailable = true;
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Fallback: try require.resolve
|
|
1194
|
+
if (!visualizerAvailable) {
|
|
1195
|
+
try {
|
|
1196
|
+
const vizPkgPath = require.resolve('@aiready/visualizer/package.json');
|
|
1197
|
+
webDir = resolvePath(vizPkgPath, '..');
|
|
1198
|
+
visualizerAvailable = true;
|
|
1199
|
+
} catch (e) {
|
|
1200
|
+
// Visualizer not found
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
const spawnCwd = webDir || process.cwd();
|
|
1205
|
+
const nodeBinCandidate = process.execPath;
|
|
1206
|
+
const nodeBin = existsSync(nodeBinCandidate) ? nodeBinCandidate : 'node';
|
|
1207
|
+
if (!visualizerAvailable) {
|
|
1208
|
+
console.error(chalk.red('❌ Cannot start dev server: @aiready/visualizer not available.'));
|
|
1209
|
+
console.log(chalk.dim('Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer'));
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Inline report watcher: copy report to web/report-data.json and watch for changes
|
|
1214
|
+
const { watch } = await import('fs');
|
|
1215
|
+
const copyReportToViz = () => {
|
|
1216
|
+
try {
|
|
1217
|
+
const destPath = resolvePath(spawnCwd, 'web', 'report-data.json');
|
|
1218
|
+
copyFileSync(reportPath, destPath);
|
|
1219
|
+
console.log(`📋 Report synced to ${destPath}`);
|
|
1220
|
+
} catch (e) {
|
|
1221
|
+
console.error('Failed to sync report:', e);
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
// Initial copy
|
|
1226
|
+
copyReportToViz();
|
|
1227
|
+
|
|
1228
|
+
// Watch source report for changes
|
|
1229
|
+
let watchTimeout: NodeJS.Timeout | null = null;
|
|
1230
|
+
const reportWatcher = watch(reportPath, () => {
|
|
1231
|
+
// Debounce to avoid multiple copies during file write
|
|
1232
|
+
if (watchTimeout) clearTimeout(watchTimeout);
|
|
1233
|
+
watchTimeout = setTimeout(copyReportToViz, 100);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
const vite = spawn("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true });
|
|
1145
1237
|
const onExit = () => {
|
|
1146
1238
|
try {
|
|
1147
|
-
|
|
1239
|
+
reportWatcher.close();
|
|
1148
1240
|
} catch (e) {}
|
|
1149
1241
|
try {
|
|
1150
1242
|
vite.kill();
|
|
@@ -1222,14 +1314,15 @@ program
|
|
|
1222
1314
|
.command('visualise')
|
|
1223
1315
|
.description('Alias for visualize (British spelling)')
|
|
1224
1316
|
.argument('[directory]', 'Directory to analyze', '.')
|
|
1225
|
-
.option('--report <path>', 'Report path (
|
|
1317
|
+
.option('--report <path>', 'Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)')
|
|
1226
1318
|
.option('-o, --output <path>', 'Output HTML path (relative to directory)', 'packages/visualizer/visualization.html')
|
|
1227
1319
|
.option('--open', 'Open generated HTML in default browser')
|
|
1228
1320
|
.option('--serve [port]', 'Start a local static server to serve the visualization (optional port number)', false)
|
|
1229
1321
|
.option('--dev', 'Start Vite dev server (live reload) for interactive development', true)
|
|
1230
1322
|
.addHelpText('after', `
|
|
1231
1323
|
EXAMPLES:
|
|
1232
|
-
$ aiready visualise .
|
|
1324
|
+
$ aiready visualise . # Auto-detects latest report
|
|
1325
|
+
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1233
1326
|
$ aiready visualise . --report report.json --dev
|
|
1234
1327
|
$ aiready visualise . --report report.json --serve 8080
|
|
1235
1328
|
|
|
@@ -1242,14 +1335,15 @@ program
|
|
|
1242
1335
|
.command('visualize')
|
|
1243
1336
|
.description('Generate interactive visualization from an AIReady report')
|
|
1244
1337
|
.argument('[directory]', 'Directory to analyze', '.')
|
|
1245
|
-
.option('--report <path>', 'Report path (
|
|
1338
|
+
.option('--report <path>', 'Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)')
|
|
1246
1339
|
.option('-o, --output <path>', 'Output HTML path (relative to directory)', 'packages/visualizer/visualization.html')
|
|
1247
1340
|
.option('--open', 'Open generated HTML in default browser')
|
|
1248
1341
|
.option('--serve [port]', 'Start a local static server to serve the visualization (optional port number)', false)
|
|
1249
1342
|
.option('--dev', 'Start Vite dev server (live reload) for interactive development', false)
|
|
1250
1343
|
.addHelpText('after', `
|
|
1251
1344
|
EXAMPLES:
|
|
1252
|
-
$ aiready visualize .
|
|
1345
|
+
$ aiready visualize . # Auto-detects latest report
|
|
1346
|
+
$ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
|
|
1253
1347
|
$ aiready visualize . --report report.json -o out/visualization.html --open
|
|
1254
1348
|
$ aiready visualize . --report report.json --serve
|
|
1255
1349
|
$ aiready visualize . --report report.json --serve 8080
|