codeproof 1.0.1 → 1.0.3

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/.env ADDED
@@ -0,0 +1 @@
1
+ AI_API_URL = "https://api-risk-fgef.onrender.com/predict"
package/bin/codeproof.js CHANGED
@@ -4,13 +4,15 @@ import { runCli } from "../commands/run.js";
4
4
  import { runReportDashboard } from "../commands/reportDashboard.js";
5
5
  import { runMoveSecret } from "../commands/moveSecret.js";
6
6
  import { runWhoAmI } from "../commands/whoami.js";
7
+ import { runIgnore } from "../commands/ignore.js";
8
+ import { runApply } from "../commands/apply.js";
7
9
  import { logError, logInfo } from "../utils/logger.js";
8
10
 
9
11
  const [, , command, ...args] = process.argv;
10
12
 
11
13
  async function main() {
12
14
  if (!command || command === "-h" || command === "--help") {
13
- 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 whoami Show the local CodeProof client ID");
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");
14
16
  process.exit(0);
15
17
  }
16
18
 
@@ -34,6 +36,16 @@ async function main() {
34
36
  return;
35
37
  }
36
38
 
39
+ if (command === "ignore") {
40
+ await runIgnore({ args, cwd: process.cwd() });
41
+ return;
42
+ }
43
+
44
+ if (command === "apply") {
45
+ await runApply({ args, cwd: process.cwd() });
46
+ return;
47
+ }
48
+
37
49
  if (command === "whoami") {
38
50
  await runWhoAmI();
39
51
  return;
@@ -0,0 +1,32 @@
1
+ import { ensureGitRepo, getGitRoot } from "../utils/git.js";
2
+ import { logError, logInfo, logSuccess, logWarn } from "../utils/logger.js";
3
+ import { getEnforcementState, setEnforcementState } from "../core/enforcement.js";
4
+
5
+ export async function runApply({ cwd }) {
6
+ // Re-enable enforcement explicitly to restore pre-commit blocking.
7
+ ensureGitRepo(cwd);
8
+ const gitRoot = getGitRoot(cwd);
9
+
10
+ let current = "enabled";
11
+ try {
12
+ current = getEnforcementState(gitRoot);
13
+ } catch (error) {
14
+ logError(error?.message || "Unable to read codeproof.config.json.");
15
+ process.exit(1);
16
+ }
17
+
18
+ if (current === "enabled") {
19
+ logWarn("CodeProof enforcement is already enabled.");
20
+ return;
21
+ }
22
+
23
+ try {
24
+ setEnforcementState(gitRoot, "enabled");
25
+ } catch (error) {
26
+ logError(error?.message || "Unable to update codeproof.config.json.");
27
+ process.exit(1);
28
+ }
29
+
30
+ logSuccess("CodeProof enforcement re-enabled.");
31
+ logInfo("Pre-commit protection active.");
32
+ }
@@ -0,0 +1,32 @@
1
+ import { ensureGitRepo, getGitRoot } from "../utils/git.js";
2
+ import { logError, logInfo, logSuccess, logWarn } from "../utils/logger.js";
3
+ import { getEnforcementState, setEnforcementState } from "../core/enforcement.js";
4
+
5
+ export async function runIgnore({ cwd }) {
6
+ // Controlled bypass: disabling enforcement is explicit and project-scoped.
7
+ ensureGitRepo(cwd);
8
+ const gitRoot = getGitRoot(cwd);
9
+
10
+ let current = "enabled";
11
+ try {
12
+ current = getEnforcementState(gitRoot);
13
+ } catch (error) {
14
+ logError(error?.message || "Unable to read codeproof.config.json.");
15
+ process.exit(1);
16
+ }
17
+
18
+ if (current === "disabled") {
19
+ logWarn("CodeProof enforcement is already disabled.");
20
+ return;
21
+ }
22
+
23
+ try {
24
+ setEnforcementState(gitRoot, "disabled");
25
+ } catch (error) {
26
+ logError(error?.message || "Unable to update codeproof.config.json.");
27
+ process.exit(1);
28
+ }
29
+
30
+ logSuccess("CodeProof enforcement disabled.");
31
+ logInfo("Commits will not be blocked until `codeproof apply` is run.");
32
+ }
package/commands/init.js CHANGED
@@ -50,6 +50,7 @@ export async function runInit({ cwd }) {
50
50
  projectId: randomUUID(),
51
51
  projectType,
52
52
  scanMode: "staged",
53
+ enforcement: "enabled",
53
54
  features: {
54
55
  reporting: true,
55
56
  integration: false,
@@ -70,6 +71,18 @@ export async function runInit({ cwd }) {
70
71
  logSuccess("Created codeproof.config.json");
71
72
  }
72
73
 
74
+ try {
75
+ const raw = fs.readFileSync(configPath, "utf8");
76
+ const existing = JSON.parse(raw);
77
+ if (!existing.enforcement) {
78
+ existing.enforcement = "enabled";
79
+ fs.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
80
+ logSuccess("Added enforcement=enabled to codeproof.config.json");
81
+ }
82
+ } catch {
83
+ logWarn("Unable to update enforcement in codeproof.config.json.");
84
+ }
85
+
73
86
  installPreCommitHook(gitRoot);
74
87
  logSuccess("Pre-commit hook installed.");
75
88
 
@@ -8,6 +8,7 @@ import { ensureEnvFile, readEnvKeys, appendEnvEntries } from "../utils/envManage
8
8
  import { backupFileOnce, extractSecretValueFromLine, replaceSecretInFile } from "../utils/fileRewriter.js";
9
9
  import { resolveFeatureFlags, isVerbose } from "../core/featureFlags.js";
10
10
  import { reportFeatureDisabled, warnExperimentalOnce } from "../core/safetyGuards.js";
11
+ import { readLatestReport } from "../reporting/reportReader.js";
11
12
 
12
13
  const TEST_PATH_HINTS = [
13
14
  "test",
@@ -37,24 +38,6 @@ function isIgnoredPath(filePath, excludes) {
37
38
  return false;
38
39
  }
39
40
 
40
- function readLatestReport(reportPath) {
41
- if (!fs.existsSync(reportPath)) {
42
- return null;
43
- }
44
-
45
- const content = fs.readFileSync(reportPath, "utf8");
46
- const lines = content.split(/\r?\n/).filter(Boolean);
47
- if (lines.length === 0) {
48
- return null;
49
- }
50
-
51
- try {
52
- return JSON.parse(lines[lines.length - 1]);
53
- } catch {
54
- return null;
55
- }
56
- }
57
-
58
41
  function confirmProceed(message) {
59
42
  return new Promise((resolve) => {
60
43
  const rl = readline.createInterface({
@@ -73,7 +56,6 @@ export async function runMoveSecret({ cwd }) {
73
56
  // Boundary: remediation reads reports only and must not depend on analysis state.
74
57
  ensureGitRepo(cwd);
75
58
  const gitRoot = getGitRoot(cwd);
76
- const reportPath = path.join(gitRoot, "codeproof-report.log");
77
59
  const configPath = path.join(gitRoot, "codeproof.config.json");
78
60
  let config = {};
79
61
  try {
@@ -92,7 +74,7 @@ export async function runMoveSecret({ cwd }) {
92
74
  }
93
75
 
94
76
  warnExperimentalOnce("Experimental feature enabled: move-secret.", logWarn);
95
- const latestReport = readLatestReport(reportPath);
77
+ const latestReport = readLatestReport(gitRoot)?.report || null;
96
78
 
97
79
  if (!latestReport || !Array.isArray(latestReport.findings)) {
98
80
  logWarn("No reports found. Run 'codeproof run' first.");
@@ -37,8 +37,13 @@ export async function runReportDashboard({ cwd }) {
37
37
 
38
38
  if (!latestReport) {
39
39
  logWarn("No reports found. Run `codeproof run` first.");
40
- } else {
41
- const integration = config?.integration || {};
40
+ return;
41
+ }
42
+
43
+ const integration = config?.integration || {};
44
+ const integrationEnabled = features.integration && Boolean(integration.enabled);
45
+
46
+ if (integrationEnabled) {
42
47
  // Integrations are fail-open: never throw on network errors.
43
48
  withFailOpenIntegration(() => {
44
49
  sendReportToServer(latestReport, {
@@ -46,10 +51,13 @@ export async function runReportDashboard({ cwd }) {
46
51
  endpointUrl: integration.endpointUrl
47
52
  });
48
53
  });
54
+ logInfo("Report sent to server.");
55
+ } else {
56
+ reportFeatureDisabled("Integration", verbose, logInfo);
57
+ }
49
58
 
50
- if (latestReport?.projectId) {
51
- logInfo(`View dashboard: https://dashboard.codeproof.dev/project/${latestReport.projectId}`);
52
- }
59
+ if (latestReport?.projectId) {
60
+ logInfo(`View dashboard: https://dashboard.codeproof.dev/project/${latestReport.projectId}`);
53
61
  }
54
62
  } else {
55
63
  reportFeatureDisabled("Reporting", verbose, logInfo);
package/commands/run.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { ensureGitRepo, getGitRoot, getStagedFiles } from "../utils/git.js";
3
+ import { ensureGitRepo, getGitRoot, getStagedFiles, getRepoIdentifier, getProjectName } from "../utils/git.js";
4
4
  import { logError, logInfo, logSuccess, logWarn } from "../utils/logger.js";
5
5
  import { buildScanTargets } from "../utils/fileScanner.js";
6
6
  import { runRuleEngine } from "../engine/ruleEngine.js";
@@ -12,6 +12,7 @@ import { writeReport } from "../reporting/reportWriter.js";
12
12
  import { sendReportToServer } from "../utils/apiClient.js";
13
13
  import { resolveFeatureFlags, isVerbose } from "../core/featureFlags.js";
14
14
  import { getClientId } from "../core/identity.js";
15
+ import { getEnforcementState } from "../core/enforcement.js";
15
16
  import {
16
17
  reportFeatureDisabled,
17
18
  warnExperimentalOnce,
@@ -36,7 +37,7 @@ function readConfig(configPath) {
36
37
  }
37
38
  }
38
39
 
39
- export async function runCli({ cwd }) {
40
+ export async function runCli({ args = [], cwd }) {
40
41
  // Boundary: CLI orchestration only. Avoid importing this module in lower layers.
41
42
  logInfo("CodeProof run started.");
42
43
 
@@ -46,6 +47,20 @@ export async function runCli({ cwd }) {
46
47
  const config = readConfig(configPath);
47
48
  const features = resolveFeatureFlags(config);
48
49
  const verbose = isVerbose(config);
50
+ let enforcement = "enabled";
51
+ try {
52
+ enforcement = getEnforcementState(gitRoot);
53
+ } catch (error) {
54
+ logError(error?.message || "Unable to read enforcement state.");
55
+ process.exit(1);
56
+ }
57
+ const isPreCommit = args.includes("--precommit") || Boolean(process.env.CODEPROOF_PRECOMMIT);
58
+
59
+ if (isPreCommit && enforcement === "disabled") {
60
+ logWarn("CodeProof enforcement is temporarily disabled.");
61
+ logInfo("Commit allowed.");
62
+ process.exit(0);
63
+ }
49
64
 
50
65
  if (!config.scanMode) {
51
66
  logError("Config missing scanMode. Expected 'staged' or 'full'.");
@@ -95,7 +110,7 @@ export async function runCli({ cwd }) {
95
110
  }
96
111
 
97
112
  const aiDecisions = aiInputs.length > 0
98
- ? withFailOpenAiEscalation(features.aiEscalation, () => analyze(aiInputs, projectContext))
113
+ ? await withFailOpenAiEscalation(features.aiEscalation, () => analyze(aiInputs, projectContext))
99
114
  : [];
100
115
  const { blockFindings, warnFindings, aiReviewed, exitCode } = mergeDecisions({
101
116
  baselineFindings: [...findings, ...escalations],
@@ -108,9 +123,13 @@ export async function runCli({ cwd }) {
108
123
  const reportId = randomUUID();
109
124
  const projectId = config.projectId || "";
110
125
  const clientId = getClientId();
126
+ const projectName = getProjectName(gitRoot);
127
+ const repoIdentifier = getRepoIdentifier(gitRoot);
111
128
  const report = buildReport({
112
129
  projectRoot: gitRoot,
113
130
  projectId,
131
+ projectName,
132
+ repoIdentifier,
114
133
  clientId,
115
134
  reportId,
116
135
  scanMode,
@@ -0,0 +1,51 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const ENFORCEMENT_ENABLED = "enabled";
5
+ const ENFORCEMENT_DISABLED = "disabled";
6
+
7
+ function getConfigPath(gitRoot) {
8
+ return path.join(gitRoot, "codeproof.config.json");
9
+ }
10
+
11
+ function readConfig(gitRoot) {
12
+ const configPath = getConfigPath(gitRoot);
13
+ if (!fs.existsSync(configPath)) {
14
+ const error = new Error("Missing codeproof.config.json. Run codeproof init first.");
15
+ error.code = "CODEPROOF_CONFIG_MISSING";
16
+ throw error;
17
+ }
18
+
19
+ try {
20
+ const raw = fs.readFileSync(configPath, "utf8");
21
+ return JSON.parse(raw);
22
+ } catch {
23
+ const error = new Error("Invalid codeproof.config.json. Please fix the file.");
24
+ error.code = "CODEPROOF_CONFIG_INVALID";
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ function writeConfig(gitRoot, config) {
30
+ const configPath = getConfigPath(gitRoot);
31
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
32
+ }
33
+
34
+ export function getEnforcementState(gitRoot) {
35
+ const config = readConfig(gitRoot);
36
+ const enforcement = String(config.enforcement || ENFORCEMENT_ENABLED).toLowerCase();
37
+ return enforcement === ENFORCEMENT_DISABLED ? ENFORCEMENT_DISABLED : ENFORCEMENT_ENABLED;
38
+ }
39
+
40
+ export function setEnforcementState(gitRoot, nextState) {
41
+ const config = readConfig(gitRoot);
42
+ const normalized = String(nextState || "").toLowerCase();
43
+ const enforcement = normalized === ENFORCEMENT_DISABLED
44
+ ? ENFORCEMENT_DISABLED
45
+ : ENFORCEMENT_ENABLED;
46
+
47
+ // Security: explicit state keeps this a reversible bypass, not a silent disable.
48
+ const updated = { ...config, enforcement };
49
+ writeConfig(gitRoot, updated);
50
+ return enforcement;
51
+ }
@@ -37,13 +37,13 @@ export function withFailOpenIntegration(action) {
37
37
  }
38
38
  }
39
39
 
40
- export function withFailOpenAiEscalation(enabled, action) {
40
+ export async function withFailOpenAiEscalation(enabled, action) {
41
41
  if (!enabled) {
42
42
  return [];
43
43
  }
44
44
 
45
45
  try {
46
- return action();
46
+ return await action();
47
47
  } catch {
48
48
  // AI failures downgrade to warnings by returning no decisions.
49
49
  return [];
@@ -1,13 +1,23 @@
1
+ import http from "http";
2
+ import https from "https";
3
+ import { logWarn } from "../utils/logger.js";
4
+
1
5
  // AI contextual analysis layer. Only low-confidence findings reach this stage.
2
6
  // Regex-first keeps the fast baseline deterministic; AI is a cautious fallback.
3
7
 
4
- function callModel(payload) {
5
- void payload;
6
- // Stubbed: No provider hardcoded. Return null to trigger safe fallback.
7
- return null;
8
+ const DEFAULT_TIMEOUT_MS = 5000;
9
+
10
+ function getAiConfig() {
11
+ const apiUrl = process.env.AI_API_URL || "";
12
+ const timeoutMs = Number(process.env.AI_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS;
13
+ return { apiUrl, timeoutMs };
8
14
  }
9
15
 
10
- function fallbackDecision(finding) {
16
+ function fallbackDecision(finding, reason) {
17
+ if (reason) {
18
+ logWarn(`AI escalation failed: ${reason}`);
19
+ }
20
+
11
21
  return {
12
22
  findingId: finding.findingId,
13
23
  verdict: "warn",
@@ -17,33 +27,115 @@ function fallbackDecision(finding) {
17
27
  };
18
28
  }
19
29
 
20
- export function analyze(findings, projectContext) {
21
- const payload = {
22
- version: 1,
23
- projectContext,
24
- findings: findings.map((finding) => ({
25
- findingId: finding.findingId,
26
- ruleId: finding.ruleId,
27
- filePath: finding.filePath,
28
- fileType: finding.fileType,
29
- isTestLike: finding.isTestLike,
30
- snippet: finding.snippet
31
- }))
30
+ function postJsonWithTimeout({ url, payload, timeoutMs }) {
31
+ return new Promise((resolve, reject) => {
32
+ let parsedUrl;
33
+ try {
34
+ parsedUrl = new URL(url);
35
+ } catch {
36
+ reject(new Error("Invalid AI_API_URL"));
37
+ return;
38
+ }
39
+
40
+ const data = JSON.stringify(payload);
41
+ const transport = parsedUrl.protocol === "http:" ? http : https;
42
+
43
+ const request = transport.request(
44
+ {
45
+ method: "POST",
46
+ hostname: parsedUrl.hostname,
47
+ port: parsedUrl.port || (parsedUrl.protocol === "http:" ? 80 : 443),
48
+ path: `${parsedUrl.pathname}${parsedUrl.search}`,
49
+ headers: {
50
+ "Content-Type": "application/json",
51
+ "Content-Length": Buffer.byteLength(data)
52
+ },
53
+ timeout: timeoutMs
54
+ },
55
+ (res) => {
56
+ let body = "";
57
+ res.setEncoding("utf8");
58
+ res.on("data", (chunk) => {
59
+ body += chunk;
60
+ });
61
+ res.on("end", () => {
62
+ if (res.statusCode && res.statusCode >= 400) {
63
+ reject(new Error(`AI API responded with ${res.statusCode}`));
64
+ return;
65
+ }
66
+ try {
67
+ resolve(JSON.parse(body));
68
+ } catch {
69
+ reject(new Error("AI API returned invalid JSON"));
70
+ }
71
+ });
72
+ }
73
+ );
74
+
75
+ request.on("timeout", () => {
76
+ request.destroy(new Error("AI request timed out"));
77
+ });
78
+
79
+ request.on("error", (err) => {
80
+ reject(err);
81
+ });
82
+
83
+ request.write(data);
84
+ request.end();
85
+ });
86
+ }
87
+
88
+ async function callModel(finding) {
89
+ const { apiUrl, timeoutMs } = getAiConfig();
90
+
91
+ if (!apiUrl) {
92
+ throw new Error("AI_API_URL is not configured");
93
+ }
94
+
95
+ const response = await postJsonWithTimeout({
96
+ url: apiUrl,
97
+ timeoutMs,
98
+ payload: {
99
+ code: finding.snippet || ""
100
+ }
101
+ });
102
+
103
+ if (!response || typeof response.found !== "boolean") {
104
+ throw new Error("AI API returned invalid payload");
105
+ }
106
+
107
+ const verdict = response.found && response.risk === "Critical" ? "block" : "warn";
108
+ const confidence = response.found ? 0.85 : 0.35;
109
+ const explanation = response.found
110
+ ? `AI detected ${response.risk} risk: ${response.secret || "secret value"}`
111
+ : "AI did not detect risk in this code snippet.";
112
+
113
+ return {
114
+ findingId: finding.findingId,
115
+ verdict,
116
+ confidence,
117
+ explanation,
118
+ suggestedFix: response.found ? "Move secrets to environment variables." : undefined
32
119
  };
120
+ }
33
121
 
34
- const response = callModel(payload);
122
+ export async function analyze(findings, projectContext) {
123
+ void projectContext;
35
124
 
36
- if (!response || !Array.isArray(response.decisions)) {
37
- return findings.map(fallbackDecision);
125
+ if (!Array.isArray(findings) || findings.length === 0) {
126
+ return [];
38
127
  }
39
128
 
40
- return response.decisions.map((decision) => ({
41
- findingId: decision.findingId,
42
- verdict: decision.verdict || "warn",
43
- confidence: typeof decision.confidence === "number" ? decision.confidence : 0.5,
44
- explanation: decision.explanation || "AI decision provided.",
45
- suggestedFix: decision.suggestedFix
46
- }));
129
+ return Promise.all(
130
+ findings.map(async (finding) => {
131
+ try {
132
+ return await callModel(finding);
133
+ } catch (error) {
134
+ const reason = error instanceof Error ? error.message : "AI call failed";
135
+ return fallbackDecision(finding, reason);
136
+ }
137
+ })
138
+ );
47
139
  }
48
140
 
49
141
 
@@ -12,7 +12,20 @@ function getHookBlock() {
12
12
  return [
13
13
  "",
14
14
  HOOK_MARKER,
15
- "codeproof run",
15
+ "GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)",
16
+ "if [ -n \"$GIT_ROOT\" ]; then",
17
+ " cd \"$GIT_ROOT\" || exit 1",
18
+ "fi",
19
+ "CONFIG_PATH=\"$GIT_ROOT/codeproof.config.json\"",
20
+ "if [ -f \"$CONFIG_PATH\" ]; then",
21
+ " ENFORCEMENT=$(node -e \"const fs=require('fs');const path=process.argv[1];try{const c=JSON.parse(fs.readFileSync(path,'utf8'));console.log((c.enforcement||'enabled').toLowerCase());}catch(e){console.log('enabled');}\" \"$CONFIG_PATH\")",
22
+ " if [ \"$ENFORCEMENT\" = \"disabled\" ]; then",
23
+ " echo \"CodeProof enforcement is temporarily disabled.\"",
24
+ " echo \"Commit allowed.\"",
25
+ " exit 0",
26
+ " fi",
27
+ "fi",
28
+ "CODEPROOF_PRECOMMIT=1 codeproof run --precommit",
16
29
  "RESULT=$?",
17
30
  "if [ $RESULT -ne 0 ]; then",
18
31
  " echo \"CodeProof checks failed. Commit blocked.\"",
@@ -36,7 +49,20 @@ export function installPreCommitHook(gitRoot) {
36
49
  "#!/bin/sh",
37
50
  "",
38
51
  "# Auto-generated by CodeProof",
39
- "codeproof run",
52
+ "GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)",
53
+ "if [ -n \"$GIT_ROOT\" ]; then",
54
+ " cd \"$GIT_ROOT\" || exit 1",
55
+ "fi",
56
+ "CONFIG_PATH=\"$GIT_ROOT/codeproof.config.json\"",
57
+ "if [ -f \"$CONFIG_PATH\" ]; then",
58
+ " ENFORCEMENT=$(node -e \"const fs=require('fs');const path=process.argv[1];try{const c=JSON.parse(fs.readFileSync(path,'utf8'));console.log((c.enforcement||'enabled').toLowerCase());}catch(e){console.log('enabled');}\" \"$CONFIG_PATH\")",
59
+ " if [ \"$ENFORCEMENT\" = \"disabled\" ]; then",
60
+ " echo \"CodeProof enforcement is temporarily disabled.\"",
61
+ " echo \"Commit allowed.\"",
62
+ " exit 0",
63
+ " fi",
64
+ "fi",
65
+ "CODEPROOF_PRECOMMIT=1 codeproof run --precommit",
40
66
  "RESULT=$?",
41
67
  "if [ $RESULT -ne 0 ]; then",
42
68
  " echo \"CodeProof checks failed. Commit blocked.\"",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeproof",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "CodeProof CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,6 +46,8 @@ function normalizeSeverity(value) {
46
46
  export function buildReport({
47
47
  projectRoot,
48
48
  projectId,
49
+ projectName,
50
+ repoIdentifier,
49
51
  clientId,
50
52
  reportId,
51
53
  scanMode,
@@ -85,17 +87,24 @@ export function buildReport({
85
87
  ? "allowed_with_warnings"
86
88
  : "allowed";
87
89
 
90
+ // Server-compatible format
88
91
  return {
89
- reportId,
90
- timestamp,
91
92
  projectId,
92
93
  clientId,
93
- scanMode,
94
- summary: {
95
- totalFilesScanned: filesScannedCount,
96
- totalFindings: findings.length,
97
- blocksCount,
98
- warningsCount
94
+ project: {
95
+ name: projectName || "Unknown Project",
96
+ repoIdentifier: repoIdentifier || projectRoot
97
+ },
98
+ report: {
99
+ timestamp,
100
+ scanMode,
101
+ summary: {
102
+ filesScanned: filesScannedCount,
103
+ findings: findings.length,
104
+ blocks: blocksCount,
105
+ warnings: warningsCount,
106
+ finalVerdict
107
+ }
99
108
  },
100
109
  findings,
101
110
  finalVerdict
@@ -44,6 +44,22 @@ export function writeReport({ projectRoot, report }) {
44
44
  const reportDir = getReportDir(projectRoot);
45
45
  ensureReportDir(reportDir);
46
46
 
47
+ // Cleanup old temp files before creating new ones
48
+ try {
49
+ const files = fs.readdirSync(reportDir);
50
+ for (const file of files) {
51
+ if (file.startsWith('.tmp-')) {
52
+ try {
53
+ fs.unlinkSync(path.join(reportDir, file));
54
+ } catch {
55
+ // Ignore errors on cleanup
56
+ }
57
+ }
58
+ }
59
+ } catch {
60
+ // Ignore cleanup errors
61
+ }
62
+
47
63
  // Per-run JSON keeps every audit entry immutable and easy to archive.
48
64
  let reportNumber = getNextReportNumber(reportDir);
49
65
  let reportPath = path.join(reportDir, `${REPORT_PREFIX}${reportNumber}${REPORT_SUFFIX}`);
@@ -55,8 +71,21 @@ export function writeReport({ projectRoot, report }) {
55
71
  // Use numeric sequencing over timestamps to avoid collisions in fast CI runs.
56
72
  const tempPath = path.join(reportDir, `.tmp-${process.pid}-${Date.now()}.json`);
57
73
  const payload = JSON.stringify(report, null, 2) + "\n";
58
- fs.writeFileSync(tempPath, payload, "utf8");
59
- fs.renameSync(tempPath, reportPath);
74
+
75
+ try {
76
+ fs.writeFileSync(tempPath, payload, "utf8");
77
+ fs.renameSync(tempPath, reportPath);
78
+ } catch (err) {
79
+ // Cleanup temp file on error
80
+ try {
81
+ if (fs.existsSync(tempPath)) {
82
+ fs.unlinkSync(tempPath);
83
+ }
84
+ } catch {
85
+ // Ignore cleanup errors
86
+ }
87
+ throw err;
88
+ }
60
89
 
61
90
  return reportPath;
62
91
  }
package/utils/git.js CHANGED
@@ -44,3 +44,20 @@ export function getStagedFiles(cwd) {
44
44
  .map((line) => line.trim())
45
45
  .filter(Boolean);
46
46
  }
47
+
48
+ export function getRepoIdentifier(gitRoot) {
49
+ try {
50
+ const result = runGit(["config", "--get", "remote.origin.url"], gitRoot);
51
+ if (result.status === 0) {
52
+ return String(result.stdout).trim() || gitRoot;
53
+ }
54
+ } catch {
55
+ // Fallback to directory name
56
+ }
57
+ return gitRoot;
58
+ }
59
+
60
+ export function getProjectName(gitRoot) {
61
+ const parts = gitRoot.split(/[\\/]/);
62
+ return parts[parts.length - 1] || "Unknown";
63
+ }