codeproof 1.0.5 → 1.1.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/bin/codeproof.js CHANGED
@@ -6,14 +6,16 @@ import { runMoveSecret } from "../commands/moveSecret.js";
6
6
  import { runWhoAmI } from "../commands/whoami.js";
7
7
  import { runIgnore } from "../commands/ignore.js";
8
8
  import { runApply } from "../commands/apply.js";
9
- import { logError, logInfo } from "../utils/logger.js";
9
+ import { runHelp } from "../commands/help.js";
10
+ import { logError } from "../utils/logger.js";
10
11
 
11
12
  const [, , command, ...args] = process.argv;
12
13
 
13
14
  async function main() {
14
- if (!command || command === "-h" || command === "--help") {
15
- logInfo("Usage: codeproof <command>\n\nCommands:\n init Initialize CodeProof in a Git repository\n run Run CodeProof checks (stub)\n report@dashboard Send latest report and show dashboard link\n move-secret Move high-confidence secrets to .env\n ignore Temporarily disable commit enforcement\n apply Re-enable commit enforcement\n whoami Show the local CodeProof client ID");
16
- process.exit(0);
15
+ // Show help for no command or explicit help flags
16
+ if (!command || command === "-h" || command === "--help" || command === "help") {
17
+ await runHelp();
18
+ return;
17
19
  }
18
20
 
19
21
  if (command === "init") {
@@ -0,0 +1,34 @@
1
+ // Help command implementation
2
+ // Displays banner and available commands
3
+
4
+ import { displayBanner } from "../ui/banner.js";
5
+ import { formatBlock } from "../ui/formatting.js";
6
+
7
+ const HELP_TEXT = `Usage: codeproof <command>
8
+
9
+ Commands:
10
+ init Initialize CodeProof in a Git repository
11
+ run Run CodeProof checks
12
+ report@dashboard Send latest report and show dashboard link
13
+ move-secret Move high-risk secrets to .env
14
+ ignore Temporarily disable commit enforcement
15
+ apply Re-enable commit enforcement
16
+ whoami Show the local CodeProof client ID
17
+ help Show this help menu
18
+ -h, --help Show this help menu
19
+
20
+ Examples:
21
+ codeproof init
22
+ codeproof run
23
+ codeproof move-secret --yes
24
+ codeproof help
25
+ `;
26
+
27
+ /**
28
+ * Display help information with banner
29
+ */
30
+ export async function runHelp() {
31
+ displayBanner();
32
+ formatBlock(HELP_TEXT);
33
+ process.exit(0);
34
+ }
package/commands/init.js CHANGED
@@ -5,12 +5,16 @@ import { logInfo, logSuccess, logWarn } from "../utils/logger.js";
5
5
  import { detectProjectType } from "../utils/projectType.js";
6
6
  import { installPreCommitHook } from "../hooks/preCommit.js";
7
7
  import { showWelcomeScreen } from "../ui/welcomeScreen.js";
8
+ import { displayBanner } from "../ui/banner.js";
8
9
  import { getClientId } from "../core/identity.js";
9
10
  import { randomUUID } from "crypto";
10
11
 
11
12
 
12
13
 
13
14
  export async function runInit({ cwd }) {
15
+ // Display banner at the start of initialization
16
+ displayBanner();
17
+
14
18
  logInfo("Initializing CodeProof...");
15
19
 
16
20
  getClientId();
@@ -52,7 +52,9 @@ function confirmProceed(message) {
52
52
  });
53
53
  }
54
54
 
55
- export async function runMoveSecret({ cwd }) {
55
+ export async function runMoveSecret({ args, cwd }) {
56
+ // Check for --yes flag to skip confirmation (for testing/CI)
57
+ const autoConfirm = args?.includes("--yes") || args?.includes("-y");
56
58
  // Boundary: remediation reads reports only and must not depend on analysis state.
57
59
  ensureGitRepo(cwd);
58
60
  const gitRoot = getGitRoot(cwd);
@@ -74,27 +76,49 @@ export async function runMoveSecret({ cwd }) {
74
76
  }
75
77
 
76
78
  warnExperimentalOnce("Experimental feature enabled: move-secret.", logWarn);
77
- const latestReport = readLatestReport(gitRoot)?.report || null;
78
-
79
- if (!latestReport || !Array.isArray(latestReport.findings)) {
79
+
80
+ // Safety: Read latest report with validation
81
+ const reportData = readLatestReport(gitRoot);
82
+ if (!reportData) {
80
83
  logWarn("No reports found. Run 'codeproof run' first.");
81
84
  process.exit(0);
82
85
  }
83
86
 
87
+ // The reportReader returns { report: parsedJSON, reportPath }
88
+ // The parsedJSON has structure: { projectId, clientId, project, report: {...}, ...}
89
+ // So reportData.report.report contains the actual report with findings
90
+ const fullReport = reportData.report;
91
+ const latestReport = fullReport.report || {};
92
+
93
+ if (!Array.isArray(latestReport.findings)) {
94
+ logWarn("Report has no findings array. Invalid report format.");
95
+ process.exit(1);
96
+ }
97
+
84
98
  const excludes = getDefaultExcludes();
85
99
 
86
100
  const eligible = latestReport.findings.filter((finding) => {
87
- if (finding.ruleId?.startsWith("secret.") !== true) {
101
+ // Debug logging
102
+ const isSecret = finding.ruleId?.startsWith("secret.");
103
+ if (!isSecret) {
104
+ if (process.env.DEBUG_MOVE_SECRET) logWarn(`Filtered (not secret): ${finding.ruleId}`);
88
105
  return false;
89
106
  }
90
- if (finding.severity !== "block" || finding.confidence !== "high") {
107
+
108
+ // Process findings where severity is "block" OR "high"
109
+ const isHighRisk = finding.severity === "block" || finding.severity === "high";
110
+ if (!isHighRisk) {
111
+ if (process.env.DEBUG_MOVE_SECRET) logWarn(`Filtered (low severity): ${finding.ruleId} severity=${finding.severity}`);
91
112
  return false;
92
113
  }
114
+
93
115
  if (!finding.filePath || !finding.lineNumber) {
116
+ if (process.env.DEBUG_MOVE_SECRET) logWarn(`Filtered (no path/line): ${finding.ruleId}`);
94
117
  return false;
95
118
  }
96
119
 
97
120
  if (!finding.codeSnippet) {
121
+ if (process.env.DEBUG_MOVE_SECRET) logWarn(`Filtered (no snippet): ${finding.filePath}:${finding.lineNumber}`);
98
122
  return false;
99
123
  }
100
124
 
@@ -103,10 +127,12 @@ export async function runMoveSecret({ cwd }) {
103
127
  : path.join(gitRoot, finding.filePath);
104
128
 
105
129
  if (isTestLike(absolutePath)) {
130
+ if (process.env.DEBUG_MOVE_SECRET) logWarn(`Filtered (test-like): ${absolutePath}`);
106
131
  return false;
107
132
  }
108
133
 
109
134
  if (isIgnoredPath(absolutePath, excludes)) {
135
+ if (process.env.DEBUG_MOVE_SECRET) logWarn(`Filtered (ignored path): ${absolutePath}`);
110
136
  return false;
111
137
  }
112
138
 
@@ -126,7 +152,7 @@ export async function runMoveSecret({ cwd }) {
126
152
  }
127
153
  logInfo(`Secrets to move: ${eligible.length}`);
128
154
 
129
- const confirmed = await confirmProceed("Proceed with moving these secrets? (y/N): ");
155
+ const confirmed = autoConfirm || await confirmProceed("Proceed with moving these secrets? (y/N): ");
130
156
  if (!confirmed) {
131
157
  logInfo("No changes made.");
132
158
  process.exit(0);
@@ -139,6 +165,7 @@ export async function runMoveSecret({ cwd }) {
139
165
  let secretIndex = 1;
140
166
  let secretsMoved = 0;
141
167
  const modifiedFiles = new Set();
168
+ const errors = [];
142
169
 
143
170
  for (const finding of eligible) {
144
171
  const absolutePath = path.isAbsolute(finding.filePath)
@@ -150,17 +177,18 @@ export async function runMoveSecret({ cwd }) {
150
177
  const content = fs.readFileSync(absolutePath, "utf8");
151
178
  const lines = content.split(/\r?\n/);
152
179
  lineContent = lines[finding.lineNumber - 1] || "";
153
- } catch {
154
- logWarn(`Skipped ${finding.filePath}:${finding.lineNumber} (unable to read file).`);
180
+ } catch (err) {
181
+ errors.push(`${finding.filePath}:${finding.lineNumber} - unable to read file: ${err.message}`);
155
182
  continue;
156
183
  }
157
184
 
158
185
  const expectedSecretValue = extractSecretValueFromLine(lineContent);
159
186
  if (!expectedSecretValue) {
160
- logWarn(`Skipped ${finding.filePath}:${finding.lineNumber} (unable to validate secret value).`);
187
+ errors.push(`${finding.filePath}:${finding.lineNumber} - unable to extract secret value from line`);
161
188
  continue;
162
189
  }
163
190
 
191
+ // Avoid key collisions
164
192
  while (existingKeys.has(`CODEPROOF_SECRET_${secretIndex}`)) {
165
193
  secretIndex += 1;
166
194
  }
@@ -168,7 +196,12 @@ export async function runMoveSecret({ cwd }) {
168
196
  const envKey = `CODEPROOF_SECRET_${secretIndex}`;
169
197
 
170
198
  // Safety: keep an original copy before any rewrite.
171
- backupFileOnce(gitRoot, absolutePath, backedUp);
199
+ try {
200
+ backupFileOnce(gitRoot, absolutePath, backedUp);
201
+ } catch (err) {
202
+ errors.push(`${finding.filePath} - backup failed: ${err.message}`);
203
+ continue;
204
+ }
172
205
 
173
206
  const result = replaceSecretInFile({
174
207
  filePath: absolutePath,
@@ -179,7 +212,7 @@ export async function runMoveSecret({ cwd }) {
179
212
  });
180
213
 
181
214
  if (!result.updated) {
182
- logWarn(`Skipped ${finding.filePath}:${finding.lineNumber} (${result.reason}).`);
215
+ errors.push(`${finding.filePath}:${finding.lineNumber} - ${result.reason}`);
183
216
  continue;
184
217
  }
185
218
 
@@ -190,13 +223,32 @@ export async function runMoveSecret({ cwd }) {
190
223
  modifiedFiles.add(absolutePath);
191
224
 
192
225
  const relative = path.relative(gitRoot, absolutePath) || absolutePath;
193
- logInfo(`Updated ${relative}:${finding.lineNumber} → process.env.${envKey}`);
226
+ logInfo(`✓ Updated ${relative}:${finding.lineNumber} → process.env.${envKey}`);
194
227
  }
195
228
 
196
- appendEnvEntries(envPath, newEntries);
229
+ // Append env entries atomically
230
+ try {
231
+ appendEnvEntries(envPath, newEntries);
232
+ } catch (err) {
233
+ logWarn(`Failed to write .env entries: ${err.message}`);
234
+ errors.push(`env-write: ${err.message}`);
235
+ }
197
236
 
198
- logInfo("Secret move summary:");
237
+ // Output summary
238
+ logInfo("");
239
+ logInfo("═══════════════════════════════════════════");
240
+ logInfo("Secret Move Summary");
241
+ logInfo("═══════════════════════════════════════════");
242
+ logInfo(`Secrets processed: ${eligible.length}`);
199
243
  logInfo(`Secrets moved: ${secretsMoved}`);
200
244
  logInfo(`Files modified: ${modifiedFiles.size}`);
201
245
  logInfo(`Backup location: ${path.join(gitRoot, ".codeproof-backup")}`);
246
+
247
+ if (errors.length > 0) {
248
+ logInfo("");
249
+ logWarn(`Errors/Skipped (${errors.length}):`);
250
+ errors.forEach((err) => logWarn(` - ${err}`));
251
+ }
252
+
253
+ logInfo("═══════════════════════════════════════════");
202
254
  }
@@ -13,7 +13,7 @@ export async function runWhoAmI() {
13
13
  logInfo(clientId);
14
14
  logInfo("");
15
15
  logInfo("Use this ID to log in at:");
16
- logInfo("https://your-dashboard-url.com/login");
16
+ logInfo("http://localhost:3000/login");
17
17
  logInfo("");
18
18
  logInfo(`Config: ${getGlobalConfigPath()}`);
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeproof",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "CodeProof CLI",
5
5
  "type": "module",
6
6
  "bin": {
package/ui/banner.js ADDED
@@ -0,0 +1,36 @@
1
+ // ASCII banner for CodeProof CLI
2
+ // Professional-grade banner displayed on init and help commands
3
+
4
+ const BANNER_TEXT = `
5
+ ██████╗ ██████╗ ██████╗ ███████╗██████╗ ██████╗ ██████╗ ██████╗ ███████╗
6
+ ██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔═══██╗██╔════╝ ██╔════╝
7
+ ██║ ██║ ██║██████╔╝█████╗ ██████╔╝██████╔╝██║ ██║██║ ███╗█████╗
8
+ ██║ ██║ ██║██╔═══╝ ██╔══╝ ██╔═══╝ ██╔══██╗██║ ██║██║ ██║██╔══╝
9
+ ╚██████╗╚██████╔╝██║ ███████╗██║ ██║ ██║╚██████╔╝╚██████╔╝███████╗
10
+ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝
11
+ `;
12
+
13
+ const TAGLINE = "AI-Powered Pre-Commit Security Enforcement";
14
+
15
+ /**
16
+ * Display the CodeProof banner with tagline.
17
+ * Used on init and help commands.
18
+ */
19
+ export function displayBanner() {
20
+ console.log(BANNER_TEXT);
21
+ console.log(` ${TAGLINE}\n`);
22
+ }
23
+
24
+ /**
25
+ * Get the banner text for programmatic use.
26
+ */
27
+ export function getBannerText() {
28
+ return BANNER_TEXT;
29
+ }
30
+
31
+ /**
32
+ * Get the tagline for programmatic use.
33
+ */
34
+ export function getTagline() {
35
+ return TAGLINE;
36
+ }
@@ -0,0 +1,76 @@
1
+ // CLI output formatting helpers
2
+ // Provides consistent, professional-grade output formatting
3
+
4
+ const DIVIDER = "─".repeat(50);
5
+
6
+ /**
7
+ * Log informational message
8
+ */
9
+ function logInfo(message) {
10
+ console.log(`[codeproof] ${message}`);
11
+ }
12
+
13
+ /**
14
+ * Log success message (for completed actions)
15
+ */
16
+ function logSuccess(message) {
17
+ console.log(`[codeproof] ✓ ${message}`);
18
+ }
19
+
20
+ /**
21
+ * Log warning message
22
+ */
23
+ function logWarning(message) {
24
+ console.warn(`[codeproof] ⚠ ${message}`);
25
+ }
26
+
27
+ /**
28
+ * Log error message
29
+ */
30
+ function logError(message) {
31
+ console.error(`[codeproof] ✗ ${message}`);
32
+ }
33
+
34
+ /**
35
+ * Print a section header with dividers
36
+ * Usage: sectionHeader("Initializing CodeProof")
37
+ */
38
+ function sectionHeader(title) {
39
+ console.log(DIVIDER);
40
+ console.log(title.padStart(title.length + Math.floor((DIVIDER.length - title.length) / 2)));
41
+ console.log(DIVIDER);
42
+ }
43
+
44
+ /**
45
+ * Print structured output block (like a code block)
46
+ */
47
+ function formatBlock(content) {
48
+ const lines = content.split("\n");
49
+ lines.forEach((line) => {
50
+ if (line.trim()) {
51
+ console.log(` ${line}`);
52
+ } else {
53
+ console.log("");
54
+ }
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Print a key-value pair with alignment
60
+ */
61
+ function logKeyValue(key, value, indent = 2) {
62
+ const spaces = " ".repeat(indent);
63
+ console.log(`${spaces}${key.padEnd(20)} ${value}`);
64
+ }
65
+
66
+ /**
67
+ * Print an unordered list
68
+ */
69
+ function logList(items, indent = 2) {
70
+ const spaces = " ".repeat(indent);
71
+ items.forEach((item) => {
72
+ console.log(`${spaces}• ${item}`);
73
+ });
74
+ }
75
+
76
+ export { logInfo, logSuccess, logWarning, logError, sectionHeader, formatBlock, logKeyValue, logList, DIVIDER };