qodfy 0.2.2 → 0.2.4
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 +96 -23
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
scanProject,
|
|
12
12
|
validScanChecks
|
|
13
13
|
} from "@qodfy/core";
|
|
14
|
-
var DEFAULT_MAX_ISSUES =
|
|
14
|
+
var DEFAULT_MAX_ISSUES = 5;
|
|
15
15
|
var program = new Command();
|
|
16
|
-
program.name("qodfy").description("Launch readiness scanner for AI-built apps.").version("0.2.
|
|
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) => {
|
|
16
|
+
program.name("qodfy").description("Launch readiness scanner for AI-built apps.").version("0.2.4");
|
|
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("--prompt <issue-id>", "Show the safe AI fix prompt for one issue").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) {
|
|
20
20
|
printScanError(pathResult.reason);
|
|
@@ -35,13 +35,20 @@ program.command("scan").description("Scan a project for launch readiness issues.
|
|
|
35
35
|
console.log(pc.cyan("Qodfy is scanning your project...\n"));
|
|
36
36
|
const report = await scanProject({
|
|
37
37
|
projectPath: pathResult.projectPath,
|
|
38
|
-
checks: scanModeResult.checks
|
|
38
|
+
checks: scanModeResult.checks,
|
|
39
|
+
includeLowConfidence: Boolean(scanModeResult.includeLowConfidence)
|
|
39
40
|
});
|
|
41
|
+
if (options.prompt) {
|
|
42
|
+
printPromptFromReport(report, options.prompt);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
40
45
|
printReport(
|
|
41
46
|
report,
|
|
42
47
|
parseMaxIssues(options.maxIssues),
|
|
43
48
|
Boolean(options.prompts),
|
|
44
|
-
scanModeResult.label
|
|
49
|
+
scanModeResult.label,
|
|
50
|
+
Boolean(options.all),
|
|
51
|
+
Boolean(options.all)
|
|
45
52
|
);
|
|
46
53
|
} catch (error) {
|
|
47
54
|
if (isPromptCancelError(error)) {
|
|
@@ -67,7 +74,8 @@ program.command("prompt <issue-id>").description("Print the safe AI fix prompt f
|
|
|
67
74
|
}
|
|
68
75
|
const report = await scanProject({
|
|
69
76
|
projectPath: pathResult.projectPath,
|
|
70
|
-
checks: checksResult.checks
|
|
77
|
+
checks: checksResult.checks,
|
|
78
|
+
includeLowConfidence: true
|
|
71
79
|
});
|
|
72
80
|
const issue = report.issues.find((scanIssue) => scanIssue.id === issueId);
|
|
73
81
|
if (!issue) {
|
|
@@ -120,7 +128,16 @@ async function resolveScanMode(options) {
|
|
|
120
128
|
return {
|
|
121
129
|
ok: true,
|
|
122
130
|
checks: [...validScanChecks],
|
|
123
|
-
label: "All checks"
|
|
131
|
+
label: "All checks",
|
|
132
|
+
includeLowConfidence: true
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (options.prompt) {
|
|
136
|
+
return {
|
|
137
|
+
ok: true,
|
|
138
|
+
checks: [...validScanChecks],
|
|
139
|
+
label: "All checks",
|
|
140
|
+
includeLowConfidence: true
|
|
124
141
|
};
|
|
125
142
|
}
|
|
126
143
|
if (options.interactive === false) {
|
|
@@ -362,7 +379,7 @@ async function resolveProjectPath(projectPath) {
|
|
|
362
379
|
};
|
|
363
380
|
}
|
|
364
381
|
}
|
|
365
|
-
function printReport(report, maxIssues, showPrompts, scanModeLabel) {
|
|
382
|
+
function printReport(report, maxIssues, showPrompts, scanModeLabel, showDetails, showAllIssues) {
|
|
366
383
|
console.log(pc.bold("Qodfy Report"));
|
|
367
384
|
console.log("");
|
|
368
385
|
const scoreColor = report.score >= 80 ? pc.green : report.score >= 60 ? pc.yellow : pc.red;
|
|
@@ -381,14 +398,15 @@ function printReport(report, maxIssues, showPrompts, scanModeLabel) {
|
|
|
381
398
|
console.log(pc.green("No issues found. Your project looks clean."));
|
|
382
399
|
return;
|
|
383
400
|
}
|
|
384
|
-
console.log(pc.bold("Issues"));
|
|
401
|
+
console.log(pc.bold(showAllIssues ? "Issues" : "Top issues"));
|
|
385
402
|
const displayIssues = getSortedDisplayIssues(report.issues);
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
console.log(`
|
|
403
|
+
const issueLimit = showAllIssues ? displayIssues.length : maxIssues;
|
|
404
|
+
const issuesToShow = displayIssues.slice(0, issueLimit);
|
|
405
|
+
if (report.issues.length > issueLimit) {
|
|
406
|
+
console.log(`Showing ${issueLimit} of ${report.issues.length} issues.`);
|
|
407
|
+
console.log(`Use --max-issues <number> to show more, or --all for full details.`);
|
|
390
408
|
}
|
|
391
|
-
printGroupedIssues(issuesToShow, showPrompts, report.projectPath);
|
|
409
|
+
printGroupedIssues(issuesToShow, showPrompts, showDetails, report.projectPath);
|
|
392
410
|
console.log("");
|
|
393
411
|
console.log(pc.bold("Recommended next step:"));
|
|
394
412
|
console.log("Fix critical issues first, then warnings, then cleanup items.");
|
|
@@ -399,7 +417,7 @@ function printReport(report, maxIssues, showPrompts, scanModeLabel) {
|
|
|
399
417
|
console.log(getPromptCommand(firstPromptIssue.id, report.projectPath));
|
|
400
418
|
}
|
|
401
419
|
console.log("qodfy scan --checks api,environment");
|
|
402
|
-
console.log("qodfy scan --
|
|
420
|
+
console.log("qodfy scan --all");
|
|
403
421
|
}
|
|
404
422
|
function printSummary(issues) {
|
|
405
423
|
const criticalCount = countIssuesBySeverity(issues, "critical");
|
|
@@ -430,7 +448,7 @@ function printSummary(issues) {
|
|
|
430
448
|
}
|
|
431
449
|
console.log("");
|
|
432
450
|
}
|
|
433
|
-
function printGroupedIssues(issues, showPrompts, projectPath) {
|
|
451
|
+
function printGroupedIssues(issues, showPrompts, showDetails, projectPath) {
|
|
434
452
|
for (const category of categoryOrder) {
|
|
435
453
|
const categoryIssues = issues.filter((issue) => issue.category === category);
|
|
436
454
|
if (categoryIssues.length === 0) {
|
|
@@ -439,18 +457,24 @@ function printGroupedIssues(issues, showPrompts, projectPath) {
|
|
|
439
457
|
console.log("");
|
|
440
458
|
console.log(pc.bold(categoryLabels[category]));
|
|
441
459
|
for (const issue of categoryIssues) {
|
|
442
|
-
printIssue(issue, showPrompts, projectPath);
|
|
460
|
+
printIssue(issue, showPrompts, showDetails, projectPath);
|
|
443
461
|
}
|
|
444
462
|
}
|
|
445
463
|
}
|
|
446
|
-
function printIssue(issue, showPrompts, projectPath) {
|
|
464
|
+
function printIssue(issue, showPrompts, showDetails, projectPath) {
|
|
447
465
|
console.log("");
|
|
448
466
|
console.log(`${pc.dim(`[${issue.id}]`)} ${getSeverityLabel(issue.severity)} ${pc.bold(issue.title)}`);
|
|
449
|
-
console.log(issue.
|
|
467
|
+
console.log(pc.dim(`Confidence: ${issue.confidence}`));
|
|
450
468
|
if (issue.file) {
|
|
451
469
|
console.log(pc.dim(`File: ${issue.file}`));
|
|
452
470
|
}
|
|
453
|
-
if (
|
|
471
|
+
if (showDetails || showPrompts) {
|
|
472
|
+
console.log(issue.message);
|
|
473
|
+
}
|
|
474
|
+
if ((showDetails || showPrompts) && issue.evidence && issue.evidence.length > 0) {
|
|
475
|
+
printEvidence(issue.evidence);
|
|
476
|
+
}
|
|
477
|
+
if ((showDetails || showPrompts) && issue.suggestion) {
|
|
454
478
|
console.log(pc.dim(`Suggestion: ${issue.suggestion}`));
|
|
455
479
|
}
|
|
456
480
|
if (showPrompts && issue.fixPrompt) {
|
|
@@ -458,19 +482,48 @@ function printIssue(issue, showPrompts, projectPath) {
|
|
|
458
482
|
console.log(pc.bold("Fix Prompt:"));
|
|
459
483
|
console.log(issue.fixPrompt);
|
|
460
484
|
} else if (issue.fixPrompt) {
|
|
461
|
-
console.log(pc.dim(`
|
|
485
|
+
console.log(pc.dim(`Fix: ${getPromptCommand(issue.id, projectPath)}`));
|
|
462
486
|
}
|
|
463
487
|
}
|
|
464
488
|
function printFixPrompt(issue) {
|
|
465
489
|
console.log(pc.bold("Qodfy Fix Prompt"));
|
|
466
490
|
console.log("");
|
|
467
491
|
console.log(`${pc.dim(`[${issue.id}]`)} ${getSeverityLabel(issue.severity)} ${pc.bold(issue.title)}`);
|
|
492
|
+
console.log(pc.dim(`Confidence: ${issue.confidence}`));
|
|
468
493
|
if (issue.file) {
|
|
469
494
|
console.log(pc.dim(`File: ${issue.file}`));
|
|
470
495
|
}
|
|
496
|
+
if (issue.evidence && issue.evidence.length > 0) {
|
|
497
|
+
printEvidence(issue.evidence);
|
|
498
|
+
}
|
|
471
499
|
console.log("");
|
|
472
500
|
console.log(issue.fixPrompt);
|
|
473
501
|
}
|
|
502
|
+
function printEvidence(evidence) {
|
|
503
|
+
console.log("");
|
|
504
|
+
console.log(pc.bold("Evidence:"));
|
|
505
|
+
for (const item of evidence) {
|
|
506
|
+
const detail = item.detail ? ` ${item.detail}` : "";
|
|
507
|
+
console.log(pc.dim(`- ${item.label}${detail}`));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function printPromptFromReport(report, issueId) {
|
|
511
|
+
const issue = report.issues.find((scanIssue) => scanIssue.id === issueId);
|
|
512
|
+
if (!issue) {
|
|
513
|
+
printPromptError(
|
|
514
|
+
`Issue "${issueId}" was not found in this project scan.
|
|
515
|
+
Run qodfy scan --all to see the current issue IDs.`
|
|
516
|
+
);
|
|
517
|
+
process.exitCode = 1;
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (!issue.fixPrompt) {
|
|
521
|
+
printPromptError(`Issue "${issueId}" does not have an AI fix prompt yet.`);
|
|
522
|
+
process.exitCode = 1;
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
printFixPrompt(issue);
|
|
526
|
+
}
|
|
474
527
|
function getSeverityLabel(severity) {
|
|
475
528
|
if (severity === "critical") {
|
|
476
529
|
return pc.red("CRITICAL");
|
|
@@ -485,7 +538,7 @@ function countIssuesBySeverity(issues, severity) {
|
|
|
485
538
|
}
|
|
486
539
|
function getSortedDisplayIssues(issues) {
|
|
487
540
|
return [...issues].sort((leftIssue, rightIssue) => {
|
|
488
|
-
return getSeverityRank(leftIssue.severity) - getSeverityRank(rightIssue.severity) || categoryOrder.indexOf(leftIssue.category) - categoryOrder.indexOf(rightIssue.category) || leftIssue.ruleId.localeCompare(rightIssue.ruleId) || getIssueNumber(leftIssue.id) - getIssueNumber(rightIssue.id) || (leftIssue.file ?? "").localeCompare(rightIssue.file ?? "") || leftIssue.id.localeCompare(rightIssue.id);
|
|
541
|
+
return getSeverityRank(leftIssue.severity) - getSeverityRank(rightIssue.severity) || getConfidenceRank(leftIssue.confidence) - getConfidenceRank(rightIssue.confidence) || categoryOrder.indexOf(leftIssue.category) - categoryOrder.indexOf(rightIssue.category) || leftIssue.ruleId.localeCompare(rightIssue.ruleId) || getIssueNumber(leftIssue.id) - getIssueNumber(rightIssue.id) || (leftIssue.file ?? "").localeCompare(rightIssue.file ?? "") || leftIssue.id.localeCompare(rightIssue.id);
|
|
489
542
|
});
|
|
490
543
|
}
|
|
491
544
|
function getIssueNumber(issueId) {
|
|
@@ -501,6 +554,15 @@ function getSeverityRank(severity) {
|
|
|
501
554
|
}
|
|
502
555
|
return 2;
|
|
503
556
|
}
|
|
557
|
+
function getConfidenceRank(confidence) {
|
|
558
|
+
if (confidence === "high") {
|
|
559
|
+
return 0;
|
|
560
|
+
}
|
|
561
|
+
if (confidence === "medium") {
|
|
562
|
+
return 1;
|
|
563
|
+
}
|
|
564
|
+
return 2;
|
|
565
|
+
}
|
|
504
566
|
function getTopPriorities(issues) {
|
|
505
567
|
const priorities = [
|
|
506
568
|
{
|
|
@@ -520,9 +582,20 @@ function getTopPriorities(issues) {
|
|
|
520
582
|
message: "Move possible server-only secrets out of client-side code."
|
|
521
583
|
},
|
|
522
584
|
{
|
|
523
|
-
ruleIds: [
|
|
585
|
+
ruleIds: [
|
|
586
|
+
"sensitive-api-route-missing-auth",
|
|
587
|
+
"api-mutation-route-review-auth"
|
|
588
|
+
],
|
|
524
589
|
message: "Review API routes that may be missing authentication."
|
|
525
590
|
},
|
|
591
|
+
{
|
|
592
|
+
ruleIds: ["internal-route-missing-protection"],
|
|
593
|
+
message: "Protect internal or operational API routes before launch."
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
ruleIds: ["public-form-missing-abuse-protection"],
|
|
597
|
+
message: "Add abuse protection to public form routes."
|
|
598
|
+
},
|
|
526
599
|
{
|
|
527
600
|
ruleIds: [
|
|
528
601
|
"environment-missing-env-example",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qodfy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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.4"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/node": "^25.7.0",
|