qodfy 0.2.1 → 0.2.2
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/dist/index.js +103 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "@qodfy/core";
|
|
14
14
|
var DEFAULT_MAX_ISSUES = 20;
|
|
15
15
|
var program = new Command();
|
|
16
|
-
program.name("qodfy").description("Launch readiness scanner for AI-built apps.").version("0.2.
|
|
16
|
+
program.name("qodfy").description("Launch readiness scanner for AI-built apps.").version("0.2.2");
|
|
17
17
|
program.command("scan").description("Scan a project for launch readiness issues.").option("-p, --path <path>", "Project path to scan", process.cwd()).option("--max-issues <number>", "Maximum number of issues to display", String(DEFAULT_MAX_ISSUES)).option("--prompts", "Show safe copy-paste fix prompts for displayed issues").option("--checks <checks>", "Comma-separated checks to run").option("--all", "Run all checks without prompting").option("--no-interactive", "Skip interactive prompts and run the recommended scan").action(async (options) => {
|
|
18
18
|
const pathResult = await resolveProjectPath(options.path);
|
|
19
19
|
if (!pathResult.ok) {
|
|
@@ -52,6 +52,39 @@ program.command("scan").description("Scan a project for launch readiness issues.
|
|
|
52
52
|
process.exitCode = 1;
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
|
+
program.command("prompt <issue-id>").description("Print the safe AI fix prompt for a specific issue.").option("-p, --path <path>", "Project path to scan", process.cwd()).option("--checks <checks>", "Comma-separated checks to search").option("--all", "Search all checks", true).action(async (issueId, options) => {
|
|
56
|
+
const pathResult = await resolveProjectPath(options.path);
|
|
57
|
+
if (!pathResult.ok) {
|
|
58
|
+
printScanError(pathResult.reason);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const checksResult = resolvePromptChecks(options);
|
|
63
|
+
if (!checksResult.ok) {
|
|
64
|
+
printPromptError(checksResult.reason);
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const report = await scanProject({
|
|
69
|
+
projectPath: pathResult.projectPath,
|
|
70
|
+
checks: checksResult.checks
|
|
71
|
+
});
|
|
72
|
+
const issue = report.issues.find((scanIssue) => scanIssue.id === issueId);
|
|
73
|
+
if (!issue) {
|
|
74
|
+
printPromptError(
|
|
75
|
+
`Issue "${issueId}" was not found in this project scan.
|
|
76
|
+
Run qodfy scan --all to see the current issue IDs.`
|
|
77
|
+
);
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!issue.fixPrompt) {
|
|
82
|
+
printPromptError(`Issue "${issueId}" does not have an AI fix prompt yet.`);
|
|
83
|
+
process.exitCode = 1;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
printFixPrompt(issue);
|
|
87
|
+
});
|
|
55
88
|
var categoryOrder = [
|
|
56
89
|
"security",
|
|
57
90
|
"webhook",
|
|
@@ -276,6 +309,24 @@ function hasSameChecks(leftChecks, rightChecks) {
|
|
|
276
309
|
function isNonInteractiveTerminal() {
|
|
277
310
|
return Boolean(process.env.CI) || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
278
311
|
}
|
|
312
|
+
function resolvePromptChecks(options) {
|
|
313
|
+
if (options.checks) {
|
|
314
|
+
const parsedChecks = parseChecks(options.checks);
|
|
315
|
+
if (!parsedChecks.ok) {
|
|
316
|
+
return parsedChecks;
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
ok: true,
|
|
320
|
+
checks: parsedChecks.checks,
|
|
321
|
+
label: getScanModeLabel(parsedChecks.checks)
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
ok: true,
|
|
326
|
+
checks: [...validScanChecks],
|
|
327
|
+
label: "All checks"
|
|
328
|
+
};
|
|
329
|
+
}
|
|
279
330
|
async function resolveProjectPath(projectPath) {
|
|
280
331
|
const inputPath = projectPath.trim() || process.cwd();
|
|
281
332
|
const resolvedPath = path.resolve(inputPath);
|
|
@@ -337,14 +388,18 @@ function printReport(report, maxIssues, showPrompts, scanModeLabel) {
|
|
|
337
388
|
console.log(`Showing ${maxIssues} of ${report.issues.length} issues.`);
|
|
338
389
|
console.log(`Use --max-issues <number> to show more.`);
|
|
339
390
|
}
|
|
340
|
-
printGroupedIssues(issuesToShow, showPrompts);
|
|
391
|
+
printGroupedIssues(issuesToShow, showPrompts, report.projectPath);
|
|
341
392
|
console.log("");
|
|
342
393
|
console.log(pc.bold("Recommended next step:"));
|
|
343
394
|
console.log("Fix critical issues first, then warnings, then cleanup items.");
|
|
344
395
|
console.log("");
|
|
345
396
|
console.log(pc.bold("Next commands:"));
|
|
397
|
+
const firstPromptIssue = issuesToShow.find((issue) => issue.fixPrompt);
|
|
398
|
+
if (firstPromptIssue) {
|
|
399
|
+
console.log(getPromptCommand(firstPromptIssue.id, report.projectPath));
|
|
400
|
+
}
|
|
346
401
|
console.log("qodfy scan --checks api,environment");
|
|
347
|
-
console.log("qodfy scan --
|
|
402
|
+
console.log("qodfy scan --max-issues 50");
|
|
348
403
|
}
|
|
349
404
|
function printSummary(issues) {
|
|
350
405
|
const criticalCount = countIssuesBySeverity(issues, "critical");
|
|
@@ -375,7 +430,7 @@ function printSummary(issues) {
|
|
|
375
430
|
}
|
|
376
431
|
console.log("");
|
|
377
432
|
}
|
|
378
|
-
function printGroupedIssues(issues, showPrompts) {
|
|
433
|
+
function printGroupedIssues(issues, showPrompts, projectPath) {
|
|
379
434
|
for (const category of categoryOrder) {
|
|
380
435
|
const categoryIssues = issues.filter((issue) => issue.category === category);
|
|
381
436
|
if (categoryIssues.length === 0) {
|
|
@@ -384,11 +439,11 @@ function printGroupedIssues(issues, showPrompts) {
|
|
|
384
439
|
console.log("");
|
|
385
440
|
console.log(pc.bold(categoryLabels[category]));
|
|
386
441
|
for (const issue of categoryIssues) {
|
|
387
|
-
printIssue(issue, showPrompts);
|
|
442
|
+
printIssue(issue, showPrompts, projectPath);
|
|
388
443
|
}
|
|
389
444
|
}
|
|
390
445
|
}
|
|
391
|
-
function printIssue(issue, showPrompts) {
|
|
446
|
+
function printIssue(issue, showPrompts, projectPath) {
|
|
392
447
|
console.log("");
|
|
393
448
|
console.log(`${pc.dim(`[${issue.id}]`)} ${getSeverityLabel(issue.severity)} ${pc.bold(issue.title)}`);
|
|
394
449
|
console.log(issue.message);
|
|
@@ -402,7 +457,19 @@ function printIssue(issue, showPrompts) {
|
|
|
402
457
|
console.log("");
|
|
403
458
|
console.log(pc.bold("Fix Prompt:"));
|
|
404
459
|
console.log(issue.fixPrompt);
|
|
460
|
+
} else if (issue.fixPrompt) {
|
|
461
|
+
console.log(pc.dim(`AI fix prompt: ${getPromptCommand(issue.id, projectPath)}`));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function printFixPrompt(issue) {
|
|
465
|
+
console.log(pc.bold("Qodfy Fix Prompt"));
|
|
466
|
+
console.log("");
|
|
467
|
+
console.log(`${pc.dim(`[${issue.id}]`)} ${getSeverityLabel(issue.severity)} ${pc.bold(issue.title)}`);
|
|
468
|
+
if (issue.file) {
|
|
469
|
+
console.log(pc.dim(`File: ${issue.file}`));
|
|
405
470
|
}
|
|
471
|
+
console.log("");
|
|
472
|
+
console.log(issue.fixPrompt);
|
|
406
473
|
}
|
|
407
474
|
function getSeverityLabel(severity) {
|
|
408
475
|
if (severity === "critical") {
|
|
@@ -483,6 +550,27 @@ function getTopPriorities(issues) {
|
|
|
483
550
|
(priority) => issues.some((issue) => priority.ruleIds.includes(issue.ruleId))
|
|
484
551
|
).slice(0, 3).map((priority) => priority.message);
|
|
485
552
|
}
|
|
553
|
+
function getPromptCommand(issueId, projectPath) {
|
|
554
|
+
const relativeProjectPath = path.relative(process.cwd(), projectPath);
|
|
555
|
+
const promptPath = getPromptPath(projectPath, relativeProjectPath);
|
|
556
|
+
const pathOption = promptPath ? ` --path ${shellQuote(promptPath)}` : "";
|
|
557
|
+
return `qodfy prompt ${issueId}${pathOption}`;
|
|
558
|
+
}
|
|
559
|
+
function getPromptPath(projectPath, relativeProjectPath) {
|
|
560
|
+
if (!relativeProjectPath) {
|
|
561
|
+
return "";
|
|
562
|
+
}
|
|
563
|
+
if (!relativeProjectPath.startsWith("..") && !path.isAbsolute(relativeProjectPath)) {
|
|
564
|
+
return relativeProjectPath;
|
|
565
|
+
}
|
|
566
|
+
return projectPath;
|
|
567
|
+
}
|
|
568
|
+
function shellQuote(value) {
|
|
569
|
+
if (/^[A-Za-z0-9_./:-]+$/.test(value)) {
|
|
570
|
+
return value;
|
|
571
|
+
}
|
|
572
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
573
|
+
}
|
|
486
574
|
function printScanError(reason) {
|
|
487
575
|
console.error(pc.red("Qodfy could not scan this project."));
|
|
488
576
|
console.error("");
|
|
@@ -492,6 +580,15 @@ function printScanError(reason) {
|
|
|
492
580
|
console.error(pc.bold("Try:"));
|
|
493
581
|
console.error("qodfy scan --path ./my-next-app");
|
|
494
582
|
}
|
|
583
|
+
function printPromptError(reason) {
|
|
584
|
+
console.error(pc.red("Qodfy could not create this fix prompt."));
|
|
585
|
+
console.error("");
|
|
586
|
+
console.error(pc.bold("Reason:"));
|
|
587
|
+
console.error(reason);
|
|
588
|
+
console.error("");
|
|
589
|
+
console.error(pc.bold("Try:"));
|
|
590
|
+
console.error("qodfy scan --all");
|
|
591
|
+
}
|
|
495
592
|
function getErrorMessage(error) {
|
|
496
593
|
if (error instanceof Error && error.message) {
|
|
497
594
|
return error.message;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qodfy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Open-source launch readiness scanner for AI-built apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"qodfy",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@inquirer/prompts": "^8.4.3",
|
|
53
53
|
"commander": "^14.0.3",
|
|
54
54
|
"picocolors": "^1.1.1",
|
|
55
|
-
"@qodfy/core": "^0.2.
|
|
55
|
+
"@qodfy/core": "^0.2.2"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/node": "^25.7.0",
|