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.
Files changed (2) hide show
  1. package/dist/index.js +96 -23
  2. 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 = 20;
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.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 issuesToShow = displayIssues.slice(0, maxIssues);
387
- if (report.issues.length > maxIssues) {
388
- console.log(`Showing ${maxIssues} of ${report.issues.length} issues.`);
389
- console.log(`Use --max-issues <number> to show more.`);
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 --max-issues 50");
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.message);
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 (issue.suggestion) {
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(`AI fix prompt: ${getPromptCommand(issue.id, projectPath)}`));
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: ["api-route-missing-auth"],
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.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.2"
55
+ "@qodfy/core": "^0.2.4"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/node": "^25.7.0",