kafkacode 1.3.0 → 1.4.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [1.4.0] - 2026-06-05
6
+
7
+ ### Added
8
+ - Machine-readable output: `--format json` and `--format sarif`.
9
+ - SARIF 2.1.0 integrates with GitHub code scanning (Security tab + inline PR annotations).
10
+ - `--output <file>` writes the report to a file instead of stdout.
11
+ - `--no-fail` exits `0` even when issues are found (useful when uploading SARIF).
12
+
5
13
  ## [1.3.0] - 2026-06-05
6
14
 
7
15
  ### Added
package/README.md CHANGED
@@ -16,7 +16,7 @@ a clear **A+ → F privacy grade**, and CI-ready exit codes. Runs in seconds.
16
16
  [![node](https://img.shields.io/node/v/kafkacode.svg?color=339933&logo=node.js)](package.json)
17
17
  [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
18
18
 
19
- [Quickstart](#-quickstart) · [Features](#-features) · [Example](#-example-output) · [CI/CD](#-cicd-integration) · [How it works](#-how-it-works) · [Contributing](#-contributing)
19
+ [📖 Documentation](https://nikhil-kapu.github.io/kafkacode/) · [Quickstart](#-quickstart) · [Features](#-features) · [CI/CD](#-cicd-integration) · [Contributing](#-contributing)
20
20
 
21
21
  </div>
22
22
 
@@ -195,7 +195,7 @@ deep secret scanners rather than replacing them.
195
195
  ## 🗺️ Roadmap
196
196
 
197
197
  - [x] **Bring-your-own-key AI** — call Groq / OpenAI-compatible providers directly
198
- - [ ] `--json` and **SARIF** output (GitHub Security tab integration)
198
+ - [x] **`--json` & SARIF output** — SARIF integrates with the GitHub Security tab
199
199
  - [ ] Config file &amp; `.kafkacodeignore`
200
200
  - [ ] Baseline file to adopt on existing codebases
201
201
  - [ ] More file types (`.env`, YAML, Terraform, Dockerfiles)
@@ -1,4 +1,10 @@
1
1
  const chalk = require('chalk');
2
+ const path = require('path');
3
+
4
+ let VERSION = '0.0.0';
5
+ try {
6
+ VERSION = require('../package.json').version;
7
+ } catch (_) { /* fall back to the default */ }
2
8
 
3
9
  class ReportGenerator {
4
10
  constructor() {
@@ -66,6 +72,94 @@ class ReportGenerator {
66
72
  return { grade, url, markdown: `![Privacy Grade: ${grade}](${url})` };
67
73
  }
68
74
 
75
+ // Public: render findings as a structured JSON report.
76
+ generateJson(scanDir, findings, fileCount) {
77
+ const severityCounts = { Critical: 0, High: 0, Medium: 0, Low: 0 };
78
+ for (const f of findings) {
79
+ const s = f.severity || 'Low';
80
+ if (Object.prototype.hasOwnProperty.call(severityCounts, s)) severityCounts[s]++;
81
+ }
82
+ const cwd = process.cwd();
83
+ const rel = (p) => (p ? path.relative(cwd, p).split(path.sep).join('/') : '');
84
+ const report = {
85
+ tool: 'kafkacode',
86
+ version: VERSION,
87
+ directory: scanDir,
88
+ timestamp: this.reportTime.toISOString(),
89
+ summary: {
90
+ filesScanned: fileCount,
91
+ totalIssues: findings.length,
92
+ grade: this._calculateGrade(findings),
93
+ severityCounts
94
+ },
95
+ findings: findings.map((f) => ({
96
+ file: rel(f.file_path),
97
+ line: f.line_number || 0,
98
+ severity: f.severity || 'Low',
99
+ type: f.finding_type || 'Issue',
100
+ description: f.description || '',
101
+ suggestion: f.suggestion || '',
102
+ source: f.source === 'llm' ? 'ai' : 'pattern',
103
+ snippet: f.code_snippet || ''
104
+ }))
105
+ };
106
+ return JSON.stringify(report, null, 2);
107
+ }
108
+
109
+ // Public: render findings as SARIF 2.1.0 (for GitHub code scanning).
110
+ generateSarif(findings) {
111
+ const cwd = process.cwd();
112
+ const rel = (p) => (p ? path.relative(cwd, p).split(path.sep).join('/') : 'unknown');
113
+ const levelFor = (sev) => {
114
+ if (sev === 'Critical' || sev === 'High') return 'error';
115
+ if (sev === 'Medium') return 'warning';
116
+ return 'note';
117
+ };
118
+ const slug = (s) => ((s || 'issue').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '') || 'issue');
119
+
120
+ const rules = new Map();
121
+ for (const f of findings) {
122
+ const id = slug(f.finding_type);
123
+ if (!rules.has(id)) {
124
+ rules.set(id, {
125
+ id,
126
+ name: f.finding_type || 'Issue',
127
+ shortDescription: { text: f.finding_type || 'Issue' },
128
+ defaultConfiguration: { level: levelFor(f.severity) }
129
+ });
130
+ }
131
+ }
132
+
133
+ const results = findings.map((f) => ({
134
+ ruleId: slug(f.finding_type),
135
+ level: levelFor(f.severity),
136
+ message: { text: f.description || 'Privacy issue detected' },
137
+ locations: [{
138
+ physicalLocation: {
139
+ artifactLocation: { uri: rel(f.file_path) },
140
+ region: { startLine: Math.max(1, f.line_number || 1) }
141
+ }
142
+ }]
143
+ }));
144
+
145
+ const sarif = {
146
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
147
+ version: '2.1.0',
148
+ runs: [{
149
+ tool: {
150
+ driver: {
151
+ name: 'KafkaCode',
152
+ informationUri: 'https://github.com/nikhil-kapu/kafkacode',
153
+ version: VERSION,
154
+ rules: Array.from(rules.values())
155
+ }
156
+ },
157
+ results
158
+ }]
159
+ };
160
+ return JSON.stringify(sarif, null, 2);
161
+ }
162
+
69
163
  _groupFindingsBySeverity(findings) {
70
164
  const groups = {};
71
165
  this.severityOrder.forEach(severity => {
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ const program = new Command();
12
12
  program
13
13
  .name('kafkacode')
14
14
  .description('KafkaCode - Privacy and Compliance Scanner')
15
- .version('1.3.0');
15
+ .version('1.4.0');
16
16
 
17
17
  program
18
18
  .command('scan')
@@ -20,7 +20,10 @@ program
20
20
  .argument('<directory>', 'Path to the source code directory to scan')
21
21
  .option('-v, --verbose', 'Print verbose progress updates during the scan')
22
22
  .option('-b, --badge', 'Print a copy-paste privacy-grade badge for your README')
23
+ .option('-f, --format <format>', 'Output format: console, json, or sarif', 'console')
24
+ .option('-o, --output <file>', 'Write output to a file instead of stdout')
23
25
  .option('--no-ai', 'Disable AI-powered analysis (run pattern scan only)')
26
+ .option('--no-fail', 'Exit 0 even when issues are found')
24
27
  .action(async (directory, options) => {
25
28
  await runScan(directory, options);
26
29
  });
@@ -34,6 +37,13 @@ async function runScan(directory, options = {}) {
34
37
  process.exit(1);
35
38
  }
36
39
 
40
+ // Validate the output format before doing any work
41
+ const format = (options.format || 'console').toLowerCase();
42
+ if (!['console', 'json', 'sarif'].includes(format)) {
43
+ console.error(`Error: unknown --format '${options.format}'. Use 'console', 'json', or 'sarif'.`);
44
+ process.exit(1);
45
+ }
46
+
37
47
  if (verbose) {
38
48
  console.log('🚀 Starting KafkaCode privacy scan...');
39
49
  }
@@ -68,24 +78,39 @@ async function runScan(directory, options = {}) {
68
78
  }
69
79
  const findings = await analysisEngine.analyzeFiles(files);
70
80
 
71
- // Generate and display report
72
- const report = reportGenerator.generateReport(directory, findings, files.length);
73
- console.log(report);
81
+ // Render the findings in the requested format (validated above)
82
+ let output;
83
+ if (format === 'json') {
84
+ output = reportGenerator.generateJson(directory, findings, files.length);
85
+ } else if (format === 'sarif') {
86
+ output = reportGenerator.generateSarif(findings);
87
+ } else {
88
+ output = reportGenerator.generateReport(directory, findings, files.length);
89
+ }
74
90
 
75
- // Hint that AI analysis is available when it wasn't used.
76
- if (options.ai !== false && !analysisEngine.aiEnabled()) {
77
- console.log('💡 Tip: set KAFKACODE_API_KEY to enable AI-powered contextual analysis. See the README.\n');
91
+ // Write to a file, or print to stdout
92
+ if (options.output) {
93
+ fs.writeFileSync(options.output, output);
94
+ console.error(`✅ Wrote ${format} output to ${options.output}`);
95
+ } else {
96
+ console.log(output);
78
97
  }
79
98
 
80
- // Optionally print a copy-paste privacy-grade badge for the user's README
81
- if (options.badge) {
82
- const badge = reportGenerator.getBadge(findings);
83
- console.log('🏷️ Privacy Grade Badge paste into your README:\n');
84
- console.log(` ${badge.markdown}\n`);
99
+ // Console-only extras kept out of machine-readable output
100
+ if (format === 'console' && !options.output) {
101
+ if (options.ai !== false && !analysisEngine.aiEnabled()) {
102
+ console.log('💡 Tip: set KAFKACODE_API_KEY to enable AI-powered contextual analysis. See the README.\n');
103
+ }
104
+ if (options.badge) {
105
+ const badge = reportGenerator.getBadge(findings);
106
+ console.log('🏷️ Privacy Grade Badge — paste into your README:\n');
107
+ console.log(` ${badge.markdown}\n`);
108
+ }
85
109
  }
86
110
 
87
- // Return appropriate exit code
88
- process.exit(findings.length > 0 ? 1 : 0);
111
+ // Exit non-zero when issues are found, unless --no-fail was passed
112
+ const shouldFail = options.fail !== false && findings.length > 0;
113
+ process.exit(shouldFail ? 1 : 0);
89
114
 
90
115
  } catch (error) {
91
116
  if (error.name === 'AbortError' || error.message.includes('aborted')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kafkacode",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "AI-powered privacy and compliance scanner - find PII leaks, hardcoded secrets, and compliance violations in your source code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {