qodfy 0.1.5 → 0.2.0

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 +145 -16
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -5,10 +5,12 @@ import fs from "fs/promises";
5
5
  import path from "path";
6
6
  import { Command } from "commander";
7
7
  import pc from "picocolors";
8
- import { scanProject } from "@qodfy/core";
8
+ import {
9
+ scanProject
10
+ } from "@qodfy/core";
9
11
  var program = new Command();
10
- program.name("qodfy").description("Launch readiness scanner for AI-built apps.").version("0.1.5");
11
- 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", "50").action(async (options) => {
12
+ program.name("qodfy").description("Launch readiness scanner for AI-built apps.").version("0.2.0");
13
+ 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", "50").option("--prompts", "Show safe copy-paste fix prompts for displayed issues").action(async (options) => {
12
14
  const pathResult = await resolveProjectPath(options.path);
13
15
  if (!pathResult.ok) {
14
16
  printScanError(pathResult.reason);
@@ -18,12 +20,30 @@ program.command("scan").description("Scan a project for launch readiness issues.
18
20
  try {
19
21
  console.log(pc.cyan("Qodfy is scanning your project...\n"));
20
22
  const report = await scanProject(pathResult.projectPath);
21
- printReport(report, parseMaxIssues(options.maxIssues));
23
+ printReport(report, parseMaxIssues(options.maxIssues), Boolean(options.prompts));
22
24
  } catch (error) {
23
25
  printScanError(getErrorMessage(error));
24
26
  process.exitCode = 1;
25
27
  }
26
28
  });
29
+ var categoryOrder = [
30
+ "security",
31
+ "api",
32
+ "webhook",
33
+ "ai",
34
+ "environment",
35
+ "maintainability",
36
+ "project"
37
+ ];
38
+ var categoryLabels = {
39
+ security: "Security",
40
+ api: "API",
41
+ webhook: "Webhooks",
42
+ ai: "AI",
43
+ environment: "Environment",
44
+ maintainability: "Maintainability",
45
+ project: "Project"
46
+ };
27
47
  await program.parseAsync();
28
48
  async function resolveProjectPath(projectPath) {
29
49
  const inputPath = projectPath.trim() || process.cwd();
@@ -60,7 +80,7 @@ async function resolveProjectPath(projectPath) {
60
80
  };
61
81
  }
62
82
  }
63
- function printReport(report, maxIssues) {
83
+ function printReport(report, maxIssues, showPrompts) {
64
84
  console.log(pc.bold("Qodfy Report"));
65
85
  console.log("");
66
86
  const scoreColor = report.score >= 80 ? pc.green : report.score >= 60 ? pc.yellow : pc.red;
@@ -73,6 +93,7 @@ function printReport(report, maxIssues) {
73
93
  console.log(`Large files: ${report.stats.largeFiles}`);
74
94
  console.log(`Scan duration: ${formatDuration(report.stats.durationMs)}`);
75
95
  console.log("");
96
+ printSummary(report.issues);
76
97
  if (report.issues.length === 0) {
77
98
  console.log(pc.green("No issues found. Your project looks clean."));
78
99
  return;
@@ -83,21 +104,129 @@ function printReport(report, maxIssues) {
83
104
  console.log(`Showing ${maxIssues} of ${report.issues.length} issues.`);
84
105
  console.log(`Use --max-issues <number> to show more.`);
85
106
  }
86
- for (const issue of issuesToShow) {
87
- const label = issue.severity === "critical" ? pc.red("CRITICAL") : issue.severity === "warning" ? pc.yellow("WARNING") : pc.blue("INFO");
88
- console.log(`
89
- ${label} ${pc.bold(issue.title)}`);
90
- console.log(issue.message);
91
- if (issue.file) {
92
- console.log(pc.dim(`File: ${issue.file}`));
107
+ printGroupedIssues(issuesToShow, showPrompts);
108
+ console.log("");
109
+ console.log(pc.bold("Recommended next step:"));
110
+ console.log("Fix critical issues first, then warnings, then cleanup items.");
111
+ }
112
+ function printSummary(issues) {
113
+ const criticalCount = countIssuesBySeverity(issues, "critical");
114
+ const warningCount = countIssuesBySeverity(issues, "warning");
115
+ const infoCount = countIssuesBySeverity(issues, "info");
116
+ console.log(pc.bold("Summary"));
117
+ console.log(`Critical: ${criticalCount}`);
118
+ console.log(`Warnings: ${warningCount}`);
119
+ console.log(`Info: ${infoCount}`);
120
+ console.log("");
121
+ if (issues.length === 0) {
122
+ return;
123
+ }
124
+ console.log(pc.bold("Categories"));
125
+ for (const category of categoryOrder) {
126
+ const count = issues.filter((issue) => issue.category === category).length;
127
+ if (count > 0) {
128
+ console.log(`${categoryLabels[category]}: ${count}`);
93
129
  }
94
- if (issue.suggestion) {
95
- console.log(pc.dim(`Suggestion: ${issue.suggestion}`));
130
+ }
131
+ const priorities = getTopPriorities(issues);
132
+ if (priorities.length > 0) {
133
+ console.log("");
134
+ console.log(pc.bold("Top priorities"));
135
+ for (const [index, priority] of priorities.entries()) {
136
+ console.log(`${index + 1}. ${priority}`);
96
137
  }
97
138
  }
98
139
  console.log("");
99
- console.log(pc.bold("Recommended next step:"));
100
- console.log("Fix critical issues first, then warnings, then cleanup items.");
140
+ }
141
+ function printGroupedIssues(issues, showPrompts) {
142
+ for (const category of categoryOrder) {
143
+ const categoryIssues = issues.filter((issue) => issue.category === category);
144
+ if (categoryIssues.length === 0) {
145
+ continue;
146
+ }
147
+ console.log("");
148
+ console.log(pc.bold(categoryLabels[category]));
149
+ for (const issue of categoryIssues) {
150
+ printIssue(issue, showPrompts);
151
+ }
152
+ }
153
+ }
154
+ function printIssue(issue, showPrompts) {
155
+ console.log("");
156
+ console.log(`${pc.dim(`[${issue.id}]`)} ${getSeverityLabel(issue.severity)} ${pc.bold(issue.title)}`);
157
+ console.log(issue.message);
158
+ if (issue.file) {
159
+ console.log(pc.dim(`File: ${issue.file}`));
160
+ }
161
+ if (issue.suggestion) {
162
+ console.log(pc.dim(`Suggestion: ${issue.suggestion}`));
163
+ }
164
+ if (showPrompts && issue.fixPrompt) {
165
+ console.log("");
166
+ console.log(pc.bold("Fix Prompt:"));
167
+ console.log(issue.fixPrompt);
168
+ }
169
+ }
170
+ function getSeverityLabel(severity) {
171
+ if (severity === "critical") {
172
+ return pc.red("CRITICAL");
173
+ }
174
+ if (severity === "warning") {
175
+ return pc.yellow("WARNING");
176
+ }
177
+ return pc.blue("INFO");
178
+ }
179
+ function countIssuesBySeverity(issues, severity) {
180
+ return issues.filter((issue) => issue.severity === severity).length;
181
+ }
182
+ function getTopPriorities(issues) {
183
+ const priorities = [
184
+ {
185
+ ruleIds: ["security-hardcoded-secret"],
186
+ message: "Remove possible hardcoded secrets and rotate any real exposed values."
187
+ },
188
+ {
189
+ ruleIds: ["webhook-missing-signature-verification"],
190
+ message: "Verify webhook signatures before handling external events."
191
+ },
192
+ {
193
+ ruleIds: ["ai-route-missing-rate-limit"],
194
+ message: "Add cost and abuse protection to AI-related API routes."
195
+ },
196
+ {
197
+ ruleIds: ["security-client-side-secret"],
198
+ message: "Move possible server-only secrets out of client-side code."
199
+ },
200
+ {
201
+ ruleIds: ["api-route-missing-auth"],
202
+ message: "Review API routes that may be missing authentication."
203
+ },
204
+ {
205
+ ruleIds: [
206
+ "environment-missing-env-example",
207
+ "environment-variable-missing-from-example"
208
+ ],
209
+ message: "Add missing environment variables to .env.example."
210
+ },
211
+ {
212
+ ruleIds: [
213
+ "maintainability-large-file",
214
+ "maintainability-large-file-skipped"
215
+ ],
216
+ message: "Review large files for maintainability before launch."
217
+ },
218
+ {
219
+ ruleIds: [
220
+ "project-missing-package-json",
221
+ "project-invalid-package-json",
222
+ "project-next-not-detected"
223
+ ],
224
+ message: "Confirm Qodfy is scanning the correct project root."
225
+ }
226
+ ];
227
+ return priorities.filter(
228
+ (priority) => issues.some((issue) => priority.ruleIds.includes(issue.ruleId))
229
+ ).slice(0, 3).map((priority) => priority.message);
101
230
  }
102
231
  function printScanError(reason) {
103
232
  console.error(pc.red("Qodfy could not scan this project."));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qodfy",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "Open-source launch readiness scanner for AI-built apps.",
5
5
  "keywords": [
6
6
  "qodfy",
@@ -51,7 +51,7 @@
51
51
  "dependencies": {
52
52
  "commander": "^14.0.3",
53
53
  "picocolors": "^1.1.1",
54
- "@qodfy/core": "^0.1.5"
54
+ "@qodfy/core": "^0.2.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^25.7.0",