thumbgate 1.3.0 → 1.4.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 (146) hide show
  1. package/.claude-plugin/marketplace.json +32 -13
  2. package/.claude-plugin/plugin.json +15 -2
  3. package/.well-known/llms.txt +60 -0
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +109 -20
  6. package/adapters/README.md +1 -1
  7. package/adapters/chatgpt/openapi.yaml +168 -0
  8. package/adapters/claude/.mcp.json +2 -2
  9. package/adapters/codex/config.toml +2 -2
  10. package/adapters/mcp/server-stdio.js +84 -1
  11. package/adapters/opencode/opencode.json +1 -1
  12. package/bin/cli.js +200 -13
  13. package/bin/postinstall.js +8 -2
  14. package/config/budget.json +18 -0
  15. package/config/gates/code-edit.json +61 -0
  16. package/config/gates/db-write.json +61 -0
  17. package/config/gates/default.json +154 -3
  18. package/config/gates/deploy.json +61 -0
  19. package/config/github-about.json +2 -1
  20. package/config/merge-quality-checks.json +23 -0
  21. package/openapi/openapi.yaml +168 -0
  22. package/package.json +42 -10
  23. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  24. package/plugins/claude-codex-bridge/.mcp.json +1 -1
  25. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +1 -3
  26. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  27. package/plugins/codex-profile/.mcp.json +1 -1
  28. package/plugins/codex-profile/INSTALL.md +27 -4
  29. package/plugins/codex-profile/README.md +33 -9
  30. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  31. package/plugins/opencode-profile/INSTALL.md +1 -1
  32. package/public/blog.html +73 -0
  33. package/public/compare/mem0.html +189 -0
  34. package/public/compare/speclock.html +180 -0
  35. package/public/compare.html +10 -2
  36. package/public/guide.html +2 -2
  37. package/public/guides/claude-code-prevent-repeated-mistakes.html +161 -0
  38. package/public/guides/codex-cli-guardrails.html +158 -0
  39. package/public/guides/cursor-prevent-repeated-mistakes.html +161 -0
  40. package/public/guides/pre-action-gates.html +162 -0
  41. package/public/guides/stop-repeated-ai-agent-mistakes.html +159 -0
  42. package/public/index.html +136 -50
  43. package/public/lessons.html +33 -24
  44. package/public/llm-context.md +140 -0
  45. package/public/pro.html +24 -22
  46. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  47. package/scripts/access-anomaly-detector.js +1 -1
  48. package/scripts/adk-consolidator.js +1 -5
  49. package/scripts/agent-security-hardening.js +4 -6
  50. package/scripts/agentic-data-pipeline.js +1 -3
  51. package/scripts/async-job-runner.js +1 -5
  52. package/scripts/audit-trail.js +1 -5
  53. package/scripts/background-agent-governance.js +2 -10
  54. package/scripts/billing.js +2 -16
  55. package/scripts/budget-enforcer.js +173 -0
  56. package/scripts/build-codex-plugin.js +152 -0
  57. package/scripts/check-congruence.js +132 -14
  58. package/scripts/commercial-offer.js +5 -7
  59. package/scripts/content-engine/linkedin-content-generator.js +154 -0
  60. package/scripts/content-engine/output/linkedin-memento-validation.md +17 -0
  61. package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +175 -0
  62. package/scripts/content-engine/reddit-thread-finder.js +154 -0
  63. package/scripts/context-engine.js +21 -6
  64. package/scripts/contextfs.js +1 -21
  65. package/scripts/dashboard.js +20 -0
  66. package/scripts/decision-journal.js +341 -0
  67. package/scripts/delegation-runtime.js +1 -5
  68. package/scripts/distribution-surfaces.js +26 -0
  69. package/scripts/document-intake.js +927 -0
  70. package/scripts/ephemeral-agent-store.js +1 -8
  71. package/scripts/evolution-state.js +1 -5
  72. package/scripts/experiment-tracker.js +1 -5
  73. package/scripts/export-databricks-bundle.js +1 -5
  74. package/scripts/export-hf-dataset.js +1 -5
  75. package/scripts/export-training.js +1 -5
  76. package/scripts/feedback-attribution.js +1 -16
  77. package/scripts/feedback-history-distiller.js +1 -16
  78. package/scripts/feedback-loop.js +1 -5
  79. package/scripts/feedback-root-consolidator.js +2 -21
  80. package/scripts/feedback-session.js +49 -0
  81. package/scripts/feedback-to-rules.js +188 -28
  82. package/scripts/filesystem-search.js +1 -9
  83. package/scripts/fs-utils.js +104 -0
  84. package/scripts/gates-engine.js +149 -4
  85. package/scripts/github-about.js +32 -8
  86. package/scripts/gtm-revenue-loop.js +1 -5
  87. package/scripts/harness-selector.js +148 -0
  88. package/scripts/hosted-job-launcher.js +1 -5
  89. package/scripts/hybrid-feedback-context.js +7 -33
  90. package/scripts/intervention-policy.js +58 -1
  91. package/scripts/lesson-db.js +3 -18
  92. package/scripts/lesson-inference.js +194 -16
  93. package/scripts/lesson-retrieval.js +60 -24
  94. package/scripts/llm-client.js +59 -0
  95. package/scripts/managed-lesson-agent.js +183 -0
  96. package/scripts/marketing-experiment.js +8 -22
  97. package/scripts/meta-agent-loop.js +624 -0
  98. package/scripts/metered-billing.js +1 -1
  99. package/scripts/money-watcher.js +1 -4
  100. package/scripts/obsidian-export.js +1 -5
  101. package/scripts/operational-integrity.js +15 -3
  102. package/scripts/org-dashboard.js +6 -1
  103. package/scripts/per-step-scoring.js +2 -4
  104. package/scripts/pr-manager.js +201 -19
  105. package/scripts/pro-features.js +3 -2
  106. package/scripts/prompt-dlp.js +3 -3
  107. package/scripts/prove-adapters.js +1 -5
  108. package/scripts/prove-attribution.js +1 -5
  109. package/scripts/prove-automation.js +1 -3
  110. package/scripts/prove-cloudflare-sandbox.js +1 -3
  111. package/scripts/prove-data-pipeline.js +1 -3
  112. package/scripts/prove-intelligence.js +1 -3
  113. package/scripts/prove-lancedb.js +1 -5
  114. package/scripts/prove-local-intelligence.js +1 -3
  115. package/scripts/prove-packaged-runtime.js +75 -9
  116. package/scripts/prove-predictive-insights.js +1 -3
  117. package/scripts/prove-training-export.js +1 -3
  118. package/scripts/prove-workflow-contract.js +1 -5
  119. package/scripts/rate-limiter.js +3 -1
  120. package/scripts/reddit-dm-outreach.js +14 -4
  121. package/scripts/schedule-manager.js +3 -5
  122. package/scripts/security-scanner.js +448 -0
  123. package/scripts/self-distill-agent.js +579 -0
  124. package/scripts/semantic-dedup.js +115 -0
  125. package/scripts/skill-exporter.js +1 -3
  126. package/scripts/skill-generator.js +1 -5
  127. package/scripts/social-analytics/engagement-audit.js +1 -18
  128. package/scripts/social-analytics/pollers/linkedin.js +26 -16
  129. package/scripts/social-analytics/publishers/linkedin.js +1 -1
  130. package/scripts/social-analytics/publishers/zernio.js +51 -0
  131. package/scripts/social-pipeline.js +1 -3
  132. package/scripts/social-post-hourly.js +47 -4
  133. package/scripts/statusline-links.js +6 -5
  134. package/scripts/statusline.sh +29 -153
  135. package/scripts/sync-branch-protection.js +340 -0
  136. package/scripts/tessl-export.js +1 -3
  137. package/scripts/thumbgate-search.js +32 -1
  138. package/scripts/tool-kpi-tracker.js +1 -1
  139. package/scripts/tool-registry.js +106 -2
  140. package/scripts/vector-store.js +1 -5
  141. package/scripts/weekly-auto-post.js +1 -1
  142. package/scripts/workflow-sentinel.js +91 -0
  143. package/skills/thumbgate/SKILL.md +1 -1
  144. package/src/api/server.js +273 -4
  145. package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
  146. /package/scripts/social-analytics/db/{social-analytics.db-wal → analytics.sqlite} +0 -0
@@ -0,0 +1,448 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Security Scanner — OWASP-aware static analysis for PreToolUse gates.
6
+ *
7
+ * Scans code being written/edited by AI agents for common vulnerability
8
+ * patterns (injection, XSS, path traversal, etc.) and suspicious dependency
9
+ * changes. Designed to run in the hot path of PreToolUse hooks with <50ms
10
+ * latency for pattern-match tier; deeper analysis is opt-in.
11
+ *
12
+ * Tier 1 (always): regex pattern matching — fast, zero external deps
13
+ * Tier 2 (high-risk): AST-level checks for dependency mutations
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { recordAuditEvent, auditToFeedback } = require('./audit-trail');
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Vulnerability pattern definitions (OWASP Top 10 + supply chain)
22
+ // ---------------------------------------------------------------------------
23
+
24
+ const VULN_PATTERNS = [
25
+ // Injection
26
+ {
27
+ id: 'cmd-injection',
28
+ category: 'injection',
29
+ severity: 'critical',
30
+ label: 'Command injection via unsanitized input',
31
+ regex: /\bexec(?:Sync)?\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+\s*(?:req\.|input|args|params|query|body|user))/g,
32
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
33
+ },
34
+ {
35
+ id: 'shell-interpolation',
36
+ category: 'injection',
37
+ severity: 'critical',
38
+ label: 'Shell command with string interpolation',
39
+ regex: /\bexec(?:Sync)?\s*\(\s*`[^`]*\$\{[^}]*(?:req\.|input|args|params|query|body|user|process\.env)/g,
40
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
41
+ },
42
+ {
43
+ id: 'sql-injection',
44
+ category: 'injection',
45
+ severity: 'critical',
46
+ label: 'Potential SQL injection via string concatenation',
47
+ regex: /(?:query|execute|run|all|get)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+\s*(?:req\.|input|args|params|query|body|user))/g,
48
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs', '.py'],
49
+ },
50
+ {
51
+ id: 'eval-usage',
52
+ category: 'injection',
53
+ severity: 'high',
54
+ label: 'Dynamic code execution (eval/Function constructor)',
55
+ regex: /\b(?:eval|new\s+Function)\s*\([^)]*(?:req\.|input|args|params|query|body|user)/g,
56
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
57
+ },
58
+
59
+ // XSS
60
+ {
61
+ id: 'xss-innerhtml',
62
+ category: 'xss',
63
+ severity: 'high',
64
+ label: 'Potential XSS via innerHTML assignment',
65
+ regex: /\.innerHTML\s*=\s*(?!['"]<(?:div|span|p|br|hr)\s*\/?>['"])/g,
66
+ fileTypes: ['.js', '.ts', '.jsx', '.tsx', '.mjs'],
67
+ },
68
+ {
69
+ id: 'xss-dangerously-set',
70
+ category: 'xss',
71
+ severity: 'high',
72
+ label: 'React dangerouslySetInnerHTML with dynamic content',
73
+ regex: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?!['"])/g,
74
+ fileTypes: ['.jsx', '.tsx', '.js', '.ts'],
75
+ },
76
+
77
+ // Path traversal
78
+ {
79
+ id: 'path-traversal',
80
+ category: 'path-traversal',
81
+ severity: 'critical',
82
+ label: 'Path traversal via unsanitized user input',
83
+ regex: /path\.(?:join|resolve)\s*\([^)]*(?:req\.|input|args|params|query|body|user)/g,
84
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
85
+ },
86
+ {
87
+ id: 'path-traversal-direct',
88
+ category: 'path-traversal',
89
+ severity: 'high',
90
+ label: 'Direct file read with user-controlled path',
91
+ regex: /fs\.(?:readFile(?:Sync)?|createReadStream)\s*\(\s*(?:req\.|input|args|params|query|body|user)/g,
92
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
93
+ },
94
+
95
+ // Prototype pollution
96
+ {
97
+ id: 'prototype-pollution',
98
+ category: 'prototype-pollution',
99
+ severity: 'high',
100
+ label: 'Potential prototype pollution via recursive merge',
101
+ regex: /(?:__proto__|constructor\s*\[\s*['"]prototype['"]\s*\]|Object\.assign\s*\(\s*\{\s*\})/g,
102
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
103
+ },
104
+
105
+ // Insecure crypto
106
+ {
107
+ id: 'weak-hash',
108
+ category: 'crypto',
109
+ severity: 'medium',
110
+ label: 'Weak hash algorithm (MD5/SHA1) for security use',
111
+ regex: /createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/gi,
112
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
113
+ },
114
+ {
115
+ id: 'hardcoded-secret',
116
+ category: 'crypto',
117
+ severity: 'high',
118
+ label: 'Hardcoded secret/password in source code',
119
+ regex: /(?:password|secret|apiKey|api_key|token)\s*[:=]\s*['"][A-Za-z0-9+/=_-]{12,}['"]/g,
120
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs', '.py', '.go', '.java'],
121
+ },
122
+
123
+ // SSRF
124
+ {
125
+ id: 'ssrf-dynamic-url',
126
+ category: 'ssrf',
127
+ severity: 'high',
128
+ label: 'Potential SSRF via user-controlled URL',
129
+ regex: /(?:fetch|axios|got|request|https?\.(?:get|request))\s*\(\s*(?:`[^`]*\$\{|(?:req\.|input|args|params|query|body|user))/g,
130
+ fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
131
+ },
132
+
133
+ // Insecure deserialization
134
+ {
135
+ id: 'unsafe-deserialize',
136
+ category: 'deserialization',
137
+ severity: 'critical',
138
+ label: 'Unsafe deserialization of untrusted data',
139
+ regex: /(?:unserialize|yaml\.load\s*\((?!.*Loader\s*=\s*yaml\.SafeLoader)|pickle\.loads?|Marshal\.load)/g,
140
+ fileTypes: ['.js', '.ts', '.py', '.rb'],
141
+ },
142
+ ];
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Supply chain patterns (dependency mutations)
146
+ // ---------------------------------------------------------------------------
147
+
148
+ const SUPPLY_CHAIN_PATTERNS = [
149
+ {
150
+ id: 'typosquat-suspect',
151
+ category: 'supply-chain',
152
+ severity: 'high',
153
+ label: 'Potentially typosquatted package name',
154
+ // Common typosquat indicators: single-char substitutions of popular packages
155
+ knownSafe: new Set([
156
+ 'express', 'lodash', 'axios', 'react', 'vue', 'angular', 'moment',
157
+ 'chalk', 'commander', 'inquirer', 'jest', 'mocha', 'webpack',
158
+ 'typescript', 'eslint', 'prettier', 'nodemon', 'dotenv', 'cors',
159
+ 'uuid', 'debug', 'semver', 'glob', 'minimatch', 'yargs',
160
+ ]),
161
+ },
162
+ {
163
+ id: 'install-script-abuse',
164
+ category: 'supply-chain',
165
+ severity: 'critical',
166
+ label: 'Suspicious install script in package.json',
167
+ regex: /["'](?:pre|post)?install["']\s*:\s*["'](?:.*(?:curl|wget|nc\s|bash\s|sh\s|eval|exec|child_process))/g,
168
+ },
169
+ {
170
+ id: 'dep-version-wildcard',
171
+ category: 'supply-chain',
172
+ severity: 'medium',
173
+ label: 'Wildcard or latest version in dependency',
174
+ regex: /["'](?:dependencies|devDependencies|peerDependencies)["'][\s\S]{0,500}?["'][^"']+["']\s*:\s*["'](?:\*|latest|>=)/g,
175
+ },
176
+ ];
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Core scanning functions
180
+ // ---------------------------------------------------------------------------
181
+
182
+ /**
183
+ * Scan code content for vulnerability patterns.
184
+ * @param {string} content - The code content to scan
185
+ * @param {string} filePath - The file path (for file-type filtering)
186
+ * @returns {{ detected: boolean, findings: Array<Object> }}
187
+ */
188
+ function scanCode(content, filePath = '') {
189
+ if (!content || typeof content !== 'string') {
190
+ return { detected: false, findings: [] };
191
+ }
192
+
193
+ const ext = path.extname(filePath).toLowerCase();
194
+ const findings = [];
195
+
196
+ for (const pattern of VULN_PATTERNS) {
197
+ // Skip patterns that don't apply to this file type
198
+ if (pattern.fileTypes && pattern.fileTypes.length > 0 && ext && !pattern.fileTypes.includes(ext)) {
199
+ continue;
200
+ }
201
+
202
+ // Reset regex lastIndex for global patterns
203
+ pattern.regex.lastIndex = 0;
204
+ let match;
205
+ while ((match = pattern.regex.exec(content)) !== null) {
206
+ const lineNumber = content.substring(0, match.index).split('\n').length;
207
+ findings.push({
208
+ id: pattern.id,
209
+ category: pattern.category,
210
+ severity: pattern.severity,
211
+ label: pattern.label,
212
+ line: lineNumber,
213
+ match: match[0].slice(0, 120),
214
+ path: filePath,
215
+ });
216
+ // Only report first match per pattern per file to avoid noise
217
+ break;
218
+ }
219
+ }
220
+
221
+ return {
222
+ detected: findings.length > 0,
223
+ findings,
224
+ };
225
+ }
226
+
227
+ /**
228
+ * Scan dependency changes in package.json mutations.
229
+ * @param {string} oldContent - Previous package.json content (empty string if new file)
230
+ * @param {string} newContent - New package.json content
231
+ * @returns {{ detected: boolean, findings: Array<Object> }}
232
+ */
233
+ function scanDependencyChange(oldContent, newContent) {
234
+ const findings = [];
235
+
236
+ if (!newContent) return { detected: false, findings: [] };
237
+
238
+ let newPkg;
239
+ try {
240
+ newPkg = JSON.parse(newContent);
241
+ } catch {
242
+ return { detected: false, findings: [] };
243
+ }
244
+
245
+ let oldPkg = {};
246
+ if (oldContent) {
247
+ try { oldPkg = JSON.parse(oldContent); } catch { /* treat as empty */ }
248
+ }
249
+
250
+ const depSections = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
251
+
252
+ // Check for new dependencies added
253
+ for (const section of depSections) {
254
+ const oldDeps = (oldPkg[section] || {});
255
+ const newDeps = (newPkg[section] || {});
256
+
257
+ for (const [pkg, version] of Object.entries(newDeps)) {
258
+ if (!oldDeps[pkg]) {
259
+ // New dependency added — check for red flags
260
+ if (version === '*' || version === 'latest' || version.startsWith('>=')) {
261
+ findings.push({
262
+ id: 'dep-version-wildcard',
263
+ category: 'supply-chain',
264
+ severity: 'medium',
265
+ label: `Wildcard version for new dependency: ${pkg}@${version}`,
266
+ path: 'package.json',
267
+ });
268
+ }
269
+
270
+ // Check for packages with suspicious names (very short, similar to popular ones)
271
+ if (pkg.length <= 2 && !['fs', 'os', 'vm'].includes(pkg)) {
272
+ findings.push({
273
+ id: 'suspicious-pkg-name',
274
+ category: 'supply-chain',
275
+ severity: 'high',
276
+ label: `Suspiciously short package name: "${pkg}"`,
277
+ path: 'package.json',
278
+ });
279
+ }
280
+ }
281
+ }
282
+ }
283
+
284
+ // Check for suspicious install scripts
285
+ const scripts = newPkg.scripts || {};
286
+ const dangerousScriptPatterns = /curl|wget|nc\s|bash\s-c|sh\s-c|eval\s|child_process|\.exec\(/i;
287
+ for (const [name, cmd] of Object.entries(scripts)) {
288
+ if (/^(?:pre|post)?install$/.test(name) && dangerousScriptPatterns.test(cmd)) {
289
+ findings.push({
290
+ id: 'install-script-abuse',
291
+ category: 'supply-chain',
292
+ severity: 'critical',
293
+ label: `Suspicious install script: ${name} → ${cmd.slice(0, 80)}`,
294
+ path: 'package.json',
295
+ });
296
+ }
297
+ }
298
+
299
+ return {
300
+ detected: findings.length > 0,
301
+ findings,
302
+ };
303
+ }
304
+
305
+ // ---------------------------------------------------------------------------
306
+ // PreToolUse integration — called from gates-engine
307
+ // ---------------------------------------------------------------------------
308
+
309
+ /**
310
+ * Evaluate security scan for a PreToolUse hook input.
311
+ * Returns a gate result if vulnerabilities are found, null otherwise.
312
+ *
313
+ * @param {Object} input - Hook input { tool_name, tool_input }
314
+ * @returns {Object|null} Gate result or null if clean
315
+ */
316
+ function evaluateSecurityScan(input = {}) {
317
+ const toolName = input.tool_name || input.toolName || '';
318
+ const toolInput = input.tool_input || {};
319
+
320
+ // Only scan write-type operations
321
+ const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
322
+ if (!WRITE_TOOLS.has(toolName)) {
323
+ return null;
324
+ }
325
+
326
+ const filePath = toolInput.file_path || toolInput.path || '';
327
+ const content = toolInput.content || toolInput.new_string || '';
328
+
329
+ if (!content) return null;
330
+
331
+ // Tier 1: Code vulnerability scan
332
+ const codeResult = scanCode(content, filePath);
333
+
334
+ // Tier 2: Supply chain scan for package.json changes
335
+ let supplyChainResult = { detected: false, findings: [] };
336
+ if (filePath && path.basename(filePath) === 'package.json') {
337
+ let oldContent = '';
338
+ try {
339
+ const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
340
+ if (fs.existsSync(absPath)) {
341
+ oldContent = fs.readFileSync(absPath, 'utf8');
342
+ }
343
+ } catch { /* new file */ }
344
+ supplyChainResult = scanDependencyChange(oldContent, content);
345
+ }
346
+
347
+ const allFindings = [...codeResult.findings, ...supplyChainResult.findings];
348
+ if (allFindings.length === 0) return null;
349
+
350
+ // Determine overall severity
351
+ const hasCritical = allFindings.some(f => f.severity === 'critical');
352
+ const hasHigh = allFindings.some(f => f.severity === 'high');
353
+ const overallSeverity = hasCritical ? 'critical' : hasHigh ? 'high' : 'medium';
354
+
355
+ // Critical findings block; high/medium warn
356
+ const decision = hasCritical ? 'deny' : 'warn';
357
+ const gateId = 'security-vuln-scan';
358
+ const summary = allFindings.map(f =>
359
+ `[${f.severity.toUpperCase()}] ${f.label}${f.line ? ` (line ${f.line})` : ''}`
360
+ ).join('; ');
361
+
362
+ const message = `Security scan detected ${allFindings.length} issue(s) in ${filePath || 'code'}: ${summary}`;
363
+
364
+ const reasoning = [
365
+ `Scanned ${content.length} bytes of content being written to ${filePath || 'unknown file'}`,
366
+ ...allFindings.map(f => `${f.category}/${f.id}: ${f.label}${f.match ? ` — matched: ${f.match.slice(0, 60)}` : ''}`),
367
+ ];
368
+
369
+ recordAuditEvent({
370
+ toolName,
371
+ toolInput: { file_path: filePath, content_length: content.length },
372
+ decision,
373
+ gateId,
374
+ message,
375
+ severity: overallSeverity,
376
+ source: 'security-scanner',
377
+ });
378
+
379
+ return {
380
+ decision,
381
+ gate: gateId,
382
+ message,
383
+ severity: overallSeverity,
384
+ reasoning,
385
+ securityScan: {
386
+ findings: allFindings,
387
+ scannedBytes: content.length,
388
+ filePath,
389
+ },
390
+ };
391
+ }
392
+
393
+ // ---------------------------------------------------------------------------
394
+ // Self-heal integration — scan recent commits for vulnerabilities
395
+ // ---------------------------------------------------------------------------
396
+
397
+ /**
398
+ * Scan git diff content for vulnerabilities introduced in recent changes.
399
+ * Intended for self-heal pipeline and post-commit auditing.
400
+ *
401
+ * @param {string} diffContent - Output of `git diff` or `git show`
402
+ * @returns {{ clean: boolean, findings: Array<Object> }}
403
+ */
404
+ function scanGitDiff(diffContent) {
405
+ if (!diffContent) return { clean: true, findings: [] };
406
+
407
+ const allFindings = [];
408
+ let currentFile = '';
409
+
410
+ for (const line of diffContent.split('\n')) {
411
+ // Track current file from diff headers
412
+ const fileMatch = line.match(/^\+\+\+ b\/(.+)$/);
413
+ if (fileMatch) {
414
+ currentFile = fileMatch[1];
415
+ continue;
416
+ }
417
+
418
+ // Only scan added lines
419
+ if (!line.startsWith('+') || line.startsWith('+++')) continue;
420
+
421
+ const addedContent = line.slice(1);
422
+ const result = scanCode(addedContent, currentFile);
423
+ if (result.detected) {
424
+ for (const finding of result.findings) {
425
+ finding.path = currentFile;
426
+ allFindings.push(finding);
427
+ }
428
+ }
429
+ }
430
+
431
+ return {
432
+ clean: allFindings.length === 0,
433
+ findings: allFindings,
434
+ };
435
+ }
436
+
437
+ // ---------------------------------------------------------------------------
438
+ // Exports
439
+ // ---------------------------------------------------------------------------
440
+
441
+ module.exports = {
442
+ VULN_PATTERNS,
443
+ SUPPLY_CHAIN_PATTERNS,
444
+ scanCode,
445
+ scanDependencyChange,
446
+ evaluateSecurityScan,
447
+ scanGitDiff,
448
+ };