itworksbut 0.1.1 → 0.3.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.
- package/README.md +60 -61
- package/bin/itworksbut.js +65 -56
- package/package.json +1 -1
- package/src/checks/node/express-json-limit-missing.js +2 -2
- package/src/cli/output.js +12 -3
- package/src/cli/parseArgs.js +9 -11
- package/src/cli/terminal.js +10 -27
- package/src/core/packageInfo.js +11 -0
- package/src/core/scanner.js +2 -1
- package/src/reporters/consoleReporter.js +91 -86
- package/src/reporters/consoleStyle.js +226 -121
- package/src/reporters/todoReporter.js +133 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { SEVERITIES } from "../core/config.js";
|
|
4
|
+
import { countBySeverity, getExitCode } from "../core/findings.js";
|
|
5
|
+
import { getConsoleFindingTitle, getFixPrompt } from "./consoleStyle.js";
|
|
6
|
+
|
|
7
|
+
export async function writeTodoReport(result, options = {}) {
|
|
8
|
+
const filePath = options.filePath || path.join(result.meta.rootPath, "todo.md");
|
|
9
|
+
await fs.writeFile(filePath, reportTodo(result), "utf8");
|
|
10
|
+
return filePath;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function reportTodo(result) {
|
|
14
|
+
const counts = countBySeverity(result.findings);
|
|
15
|
+
const exitCode = getExitCode(result.findings, result.config.failOn);
|
|
16
|
+
const findingsBySeverity = new Map(
|
|
17
|
+
SEVERITIES.map((severity) => [severity, result.findings.filter((finding) => finding.severity === severity)])
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return `${[
|
|
21
|
+
"# AI Fix Todo",
|
|
22
|
+
"",
|
|
23
|
+
"This file was generated by ItWorksBut. It is optimized for coding agents: work top to bottom, keep changes focused, and mark tasks complete only after verification.",
|
|
24
|
+
"",
|
|
25
|
+
"## Agent Rules",
|
|
26
|
+
"",
|
|
27
|
+
"- Fix critical and high findings before lower-priority cleanup.",
|
|
28
|
+
"- Inspect the referenced code before editing, especially for heuristic findings.",
|
|
29
|
+
"- Preserve existing behavior unless the finding explicitly requires changing it.",
|
|
30
|
+
"- Add or update focused tests when the change has behavioral risk.",
|
|
31
|
+
"- Do not silence ItWorksBut checks unless the underlying issue is actually fixed.",
|
|
32
|
+
"- Never print, log, or preserve raw secret values. Use placeholders only.",
|
|
33
|
+
"",
|
|
34
|
+
"## Scan Summary",
|
|
35
|
+
"",
|
|
36
|
+
`- Tool: ${result.meta.tool || "ItWorksBut"} ${result.meta.version || ""}`.trim(),
|
|
37
|
+
`- Project: ${result.meta.rootPath || "unknown"}`,
|
|
38
|
+
`- Completed: ${result.meta.completedAt || "unknown"}`,
|
|
39
|
+
`- Files scanned: ${result.meta.filesScanned ?? "unknown"}`,
|
|
40
|
+
`- Text files scanned: ${result.meta.textFilesScanned ?? "unknown"}`,
|
|
41
|
+
`- Fail-on: ${result.config.failOn}`,
|
|
42
|
+
`- Exit decision: ${exitCode}`,
|
|
43
|
+
`- Total findings: ${result.findings.length}`,
|
|
44
|
+
...SEVERITIES.map((severity) => `- ${capitalize(severity)}: ${counts[severity]}`),
|
|
45
|
+
"",
|
|
46
|
+
renderFindingSections(findingsBySeverity),
|
|
47
|
+
renderWarnings(result.warnings),
|
|
48
|
+
"## Final Verification",
|
|
49
|
+
"",
|
|
50
|
+
"- [ ] Run the relevant test suite.",
|
|
51
|
+
"- [ ] Run `itworksbut scan` again.",
|
|
52
|
+
"- [ ] Confirm there are no remaining findings at or above the configured fail-on severity.",
|
|
53
|
+
"",
|
|
54
|
+
].join("\n")}\n`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function renderFindingSections(findingsBySeverity) {
|
|
58
|
+
const sections = [];
|
|
59
|
+
|
|
60
|
+
for (const severity of SEVERITIES) {
|
|
61
|
+
const findings = findingsBySeverity.get(severity) || [];
|
|
62
|
+
if (findings.length === 0) continue;
|
|
63
|
+
|
|
64
|
+
sections.push(`## ${capitalize(severity)} Findings`);
|
|
65
|
+
sections.push("");
|
|
66
|
+
|
|
67
|
+
findings.forEach((finding, index) => {
|
|
68
|
+
sections.push(renderFinding(finding, index + 1));
|
|
69
|
+
sections.push("");
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (sections.length === 0) {
|
|
74
|
+
return [
|
|
75
|
+
"## Findings",
|
|
76
|
+
"",
|
|
77
|
+
"No findings were detected. Keep this file as a record or delete it when no longer useful.",
|
|
78
|
+
"",
|
|
79
|
+
].join("\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return sections.join("\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function renderFinding(finding, number) {
|
|
86
|
+
const location = finding.file
|
|
87
|
+
? `${finding.file}${finding.line ? `:${finding.line}` : ""}${finding.column ? `:${finding.column}` : ""}`
|
|
88
|
+
: "project-wide";
|
|
89
|
+
const heuristic = finding.heuristic ? "yes" : "no";
|
|
90
|
+
const tags = finding.tags?.length ? finding.tags.join(", ") : "none";
|
|
91
|
+
|
|
92
|
+
return [
|
|
93
|
+
`### ${number}. ${getConsoleFindingTitle(finding)}`,
|
|
94
|
+
"",
|
|
95
|
+
"- [ ] Fix this finding.",
|
|
96
|
+
`- Check ID: \`${finding.checkId}\``,
|
|
97
|
+
`- Severity: \`${finding.severity}\``,
|
|
98
|
+
`- Category: \`${finding.category || "unknown"}\``,
|
|
99
|
+
`- Location: \`${location}\``,
|
|
100
|
+
`- Heuristic: \`${heuristic}\``,
|
|
101
|
+
`- Tags: ${tags}`,
|
|
102
|
+
`- Problem: ${finding.message}`,
|
|
103
|
+
`- Recommendation: ${finding.recommendation || "Fix the underlying issue without suppressing the scanner."}`,
|
|
104
|
+
"",
|
|
105
|
+
"#### Agent Prompt",
|
|
106
|
+
"",
|
|
107
|
+
"```text",
|
|
108
|
+
getFixPrompt(finding),
|
|
109
|
+
"```",
|
|
110
|
+
"",
|
|
111
|
+
"#### Acceptance Criteria",
|
|
112
|
+
"",
|
|
113
|
+
`- [ ] The issue behind \`${finding.checkId}\` is fixed at the source.`,
|
|
114
|
+
"- [ ] Existing behavior is preserved or intentional behavior changes are covered by tests.",
|
|
115
|
+
"- [ ] No raw secrets, tokens, credentials, or private values were added to code, logs, tests, or this todo.",
|
|
116
|
+
"- [ ] The relevant scanner finding no longer appears after rerunning ItWorksBut.",
|
|
117
|
+
].join("\n");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function renderWarnings(warnings = []) {
|
|
121
|
+
if (!warnings.length) return "";
|
|
122
|
+
|
|
123
|
+
return [
|
|
124
|
+
"## Scanner Warnings",
|
|
125
|
+
"",
|
|
126
|
+
...warnings.map((warning) => `- \`${warning.checkId}\`: ${warning.message}`),
|
|
127
|
+
"",
|
|
128
|
+
].join("\n");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function capitalize(value) {
|
|
132
|
+
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
133
|
+
}
|