thuban 0.3.2 → 0.3.4

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 (59) hide show
  1. package/README.md +137 -102
  2. package/dist/LICENSE +21 -0
  3. package/dist/README.md +185 -0
  4. package/dist/cli.js +2 -0
  5. package/dist/packages/scanner/ai-confidence-scorer.js +1 -0
  6. package/dist/packages/scanner/alert-manager.js +1 -0
  7. package/dist/packages/scanner/baseline-manager.js +1 -0
  8. package/dist/packages/scanner/code-scanner.js +1 -0
  9. package/dist/packages/scanner/codebase-passport.js +1 -0
  10. package/dist/packages/scanner/copy-paste-detector.js +1 -0
  11. package/dist/packages/scanner/dependency-graph.js +1 -0
  12. package/dist/packages/scanner/drift-detector.js +1 -0
  13. package/dist/packages/scanner/executive-report.js +1 -0
  14. package/dist/packages/scanner/file-collector.js +1 -0
  15. package/dist/packages/scanner/file-watcher.js +1 -0
  16. package/dist/packages/scanner/ghost-code-detector.js +1 -0
  17. package/dist/packages/scanner/hallucination-detector.js +1 -0
  18. package/dist/packages/scanner/health-checker.js +1 -0
  19. package/dist/packages/scanner/html-report.js +1 -0
  20. package/dist/packages/scanner/index.js +1 -0
  21. package/dist/packages/scanner/license-manager.js +1 -0
  22. package/dist/packages/scanner/master-health-checker.js +1 -0
  23. package/dist/packages/scanner/monitor-notifier.js +1 -0
  24. package/dist/packages/scanner/monitor-service.js +1 -0
  25. package/dist/packages/scanner/monitor-store.js +1 -0
  26. package/dist/packages/scanner/pre-commit-gate.js +1 -0
  27. package/dist/packages/scanner/scan-diff.js +1 -0
  28. package/dist/packages/scanner/scan-runner.js +1 -0
  29. package/dist/packages/scanner/secret-scanner.js +1 -0
  30. package/dist/packages/scanner/sentinel-core.js +1 -0
  31. package/dist/packages/scanner/sentinel-knowledge.js +1 -0
  32. package/dist/packages/scanner/tech-debt-analyzer.js +1 -0
  33. package/dist/packages/scanner/tech-debt-cost.js +1 -0
  34. package/dist/packages/scanner/widget-generator.js +1 -0
  35. package/package.json +16 -8
  36. package/cli.js +0 -2412
  37. package/packages/scanner/ai-confidence-scorer.js +0 -260
  38. package/packages/scanner/alert-manager.js +0 -398
  39. package/packages/scanner/baseline-manager.js +0 -109
  40. package/packages/scanner/code-scanner.js +0 -713
  41. package/packages/scanner/codebase-passport.js +0 -299
  42. package/packages/scanner/copy-paste-detector.js +0 -250
  43. package/packages/scanner/dependency-graph.js +0 -536
  44. package/packages/scanner/drift-detector.js +0 -423
  45. package/packages/scanner/executive-report.js +0 -774
  46. package/packages/scanner/file-watcher.js +0 -323
  47. package/packages/scanner/ghost-code-detector.js +0 -226
  48. package/packages/scanner/hallucination-detector.js +0 -652
  49. package/packages/scanner/health-checker.js +0 -586
  50. package/packages/scanner/html-report.js +0 -634
  51. package/packages/scanner/index.js +0 -92
  52. package/packages/scanner/license-manager.js +0 -318
  53. package/packages/scanner/master-health-checker.js +0 -513
  54. package/packages/scanner/pre-commit-gate.js +0 -216
  55. package/packages/scanner/sentinel-core.js +0 -608
  56. package/packages/scanner/sentinel-knowledge.js +0 -322
  57. package/packages/scanner/tech-debt-analyzer.js +0 -565
  58. package/packages/scanner/tech-debt-cost.js +0 -194
  59. package/packages/scanner/widget-generator.js +0 -415
@@ -1,216 +0,0 @@
1
- /**
2
- * Thuban Pre-Commit Gate
3
- *
4
- * @purpose 3-second critical scan on staged files — blocks commits with hallucinations
5
- * @module thuban
6
- * @layer scanner
7
- * @exports-to cli
8
- * @critical-for Revenue retention — stickiest feature, devs never remove it
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const { execSync } = require('child_process');
14
-
15
- class PreCommitGate {
16
- constructor(opts = {}) {
17
- this.rootPath = opts.rootPath || process.cwd();
18
- this.strict = opts.strict !== false; // Block commit on critical issues
19
- this.timeout = opts.timeout || 5000; // 5 second max
20
- }
21
-
22
- /**
23
- * Get staged files from git
24
- */
25
- getStagedFiles() {
26
- try {
27
- const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
28
- cwd: this.rootPath,
29
- encoding: 'utf-8',
30
- timeout: 3000,
31
- });
32
- const extensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.mjs', '.cjs'];
33
- return output
34
- .split('\n')
35
- .filter(f => f.trim() && extensions.some(ext => f.endsWith(ext)))
36
- .map(f => path.resolve(this.rootPath, f.trim()));
37
- } catch (e) {
38
- return [];
39
- }
40
- }
41
-
42
- /**
43
- * Quick hallucination check on a single file
44
- */
45
- checkFile(filePath) {
46
- const issues = [];
47
- let content;
48
- try {
49
- const stat = fs.statSync(filePath);
50
- if (stat.size > 512 * 1024) return issues; // Skip files > 512KB
51
- content = fs.readFileSync(filePath, 'utf-8');
52
- } catch (e) {
53
- return issues;
54
- }
55
-
56
- const lines = content.split('\n');
57
- const relPath = path.relative(this.rootPath, filePath);
58
-
59
- // Phantom API patterns — APIs that don't exist but AI commonly generates
60
- const phantomAPIs = [
61
- { pattern: /\bJSON\.tryParse\s*\(/g, name: 'JSON.tryParse()', fix: 'Use JSON.parse() with try/catch' },
62
- { pattern: /\bArray\.flatten\s*\(/g, name: 'Array.flatten()', fix: 'Use Array.flat()' },
63
- { pattern: /\bconsole\.success\s*\(/g, name: 'console.success()', fix: 'Use console.log()' },
64
- { pattern: /\bconsole\.fail\s*\(/g, name: 'console.fail()', fix: 'Use console.error()' },
65
- { pattern: /\bfs\.readFileAsync\s*\(/g, name: 'fs.readFileAsync()', fix: 'Use fs.promises.readFile()' },
66
- { pattern: /\bfs\.writeFileAsync\s*\(/g, name: 'fs.writeFileAsync()', fix: 'Use fs.promises.writeFile()' },
67
- { pattern: /\bfs\.existsAsync\s*\(/g, name: 'fs.existsAsync()', fix: 'Use fs.promises.access()' },
68
- { pattern: /\bObject\.deepClone\s*\(/g, name: 'Object.deepClone()', fix: 'Use structuredClone() or JSON.parse(JSON.stringify())' },
69
- { pattern: /\bString\.prototype\.contains\s*\(/g, name: 'String.contains()', fix: 'Use String.includes()' },
70
- { pattern: /\bPromise\.delay\s*\(/g, name: 'Promise.delay()', fix: 'Use new Promise(r => setTimeout(r, ms))' },
71
- { pattern: /\bprocess\.exit\s*\(\s*["'].*["']\s*\)/g, name: 'process.exit(string)', fix: 'process.exit() takes a number, not a string' },
72
- { pattern: /\brequire\s*\(\s*['"]node:(?:fetch|axios)['"]s*\)/g, name: 'require("node:fetch")', fix: 'fetch is global in Node 18+, or use node-fetch package' },
73
- { pattern: /\.validateSync\s*\(/g, name: '.validateSync()', fix: 'Check if this method exists on the object — AI commonly invents Sync variants' },
74
- { pattern: /\bBuffer\.from\s*\(.*,\s*['"]base64url['"]\)/g, name: 'Buffer.from(x, "base64url")', fix: 'Use "base64" encoding, not "base64url"' },
75
- { pattern: /\bresponse\.json\s*\(\s*{/g, name: 'response.json({...})', fix: 'res.json() takes data, but check you\'re not confusing fetch Response.json() (no args) with Express res.json()' },
76
- ];
77
-
78
- // Skip if this looks like a pattern definition file
79
- const patternDefLines = lines.filter(l => /pattern:\s*\//.test(l)).length;
80
- if (patternDefLines > 5) return issues;
81
-
82
- for (const api of phantomAPIs) {
83
- for (let i = 0; i < lines.length; i++) {
84
- const line = lines[i];
85
- // Skip comments and pattern definitions
86
- if (/^\s*(\/\/|\/\*|\*|#|pattern:|name:|suggestion:|message:)/.test(line)) continue;
87
- if (/thuban-ignore/.test(line)) continue;
88
-
89
- if (api.pattern.test(line)) {
90
- issues.push({
91
- file: relPath,
92
- line: i + 1,
93
- severity: 'critical',
94
- code: line.trim(),
95
- message: `${api.name} — this API does not exist`,
96
- fix: api.fix,
97
- });
98
- }
99
- // Reset regex lastIndex
100
- api.pattern.lastIndex = 0;
101
- }
102
- }
103
-
104
- return issues;
105
- }
106
-
107
- /**
108
- * Run the gate check on all staged files
109
- */
110
- run() {
111
- const startTime = Date.now();
112
- const stagedFiles = this.getStagedFiles();
113
-
114
- if (stagedFiles.length === 0) {
115
- return {
116
- passed: true,
117
- files: 0,
118
- issues: [],
119
- elapsed: Date.now() - startTime,
120
- message: 'No staged files to check',
121
- };
122
- }
123
-
124
- const allIssues = [];
125
- for (const file of stagedFiles) {
126
- if (Date.now() - startTime > this.timeout) break; // Respect timeout
127
- const fileIssues = this.checkFile(file);
128
- allIssues.push(...fileIssues);
129
- }
130
-
131
- const criticals = allIssues.filter(i => i.severity === 'critical');
132
- const passed = this.strict ? criticals.length === 0 : true;
133
-
134
- return {
135
- passed,
136
- files: stagedFiles.length,
137
- issues: allIssues,
138
- criticals: criticals.length,
139
- elapsed: Date.now() - startTime,
140
- message: passed
141
- ? `${stagedFiles.length} files checked. All clear.`
142
- : `BLOCKED: ${criticals.length} critical issue${criticals.length > 1 ? 's' : ''} found in staged files.`,
143
- };
144
- }
145
-
146
- /**
147
- * Install the git pre-commit hook
148
- */
149
- static install(rootPath = process.cwd()) {
150
- const gitDir = path.join(rootPath, '.git');
151
- if (!fs.existsSync(gitDir)) {
152
- return { success: false, error: 'Not a git repository' };
153
- }
154
-
155
- const hooksDir = path.join(gitDir, 'hooks');
156
- if (!fs.existsSync(hooksDir)) {
157
- fs.mkdirSync(hooksDir, { recursive: true });
158
- }
159
-
160
- const hookPath = path.join(hooksDir, 'pre-commit');
161
- const hookContent = `#!/bin/sh
162
- # Thuban Pre-Commit Gate — blocks commits with hallucinated APIs
163
- # Remove this file to disable, or run: thuban gate --uninstall
164
-
165
- npx --yes thuban gate --ci 2>&1
166
- exit $?
167
- `;
168
-
169
- // Check if hook already exists
170
- if (fs.existsSync(hookPath)) {
171
- const existing = fs.readFileSync(hookPath, 'utf-8');
172
- if (existing.includes('thuban gate')) {
173
- return { success: true, message: 'Thuban gate already installed' };
174
- }
175
- // Append to existing hook
176
- fs.appendFileSync(hookPath, '\n' + hookContent);
177
- } else {
178
- fs.writeFileSync(hookPath, hookContent);
179
- }
180
-
181
- // Make executable on Unix
182
- try { fs.chmodSync(hookPath, '755'); } catch (e) { /* Windows */ }
183
-
184
- return { success: true, message: 'Pre-commit hook installed' };
185
- }
186
-
187
- /**
188
- * Uninstall the git pre-commit hook
189
- */
190
- static uninstall(rootPath = process.cwd()) {
191
- const hookPath = path.join(rootPath, '.git', 'hooks', 'pre-commit');
192
- if (!fs.existsSync(hookPath)) {
193
- return { success: true, message: 'No pre-commit hook found' };
194
- }
195
-
196
- const content = fs.readFileSync(hookPath, 'utf-8');
197
- if (content.includes('thuban gate')) {
198
- // Remove thuban lines
199
- const cleaned = content
200
- .split('\n')
201
- .filter(l => !l.includes('thuban') && !l.includes('Thuban'))
202
- .join('\n')
203
- .trim();
204
-
205
- if (cleaned.length < 10) {
206
- fs.unlinkSync(hookPath);
207
- } else {
208
- fs.writeFileSync(hookPath, cleaned + '\n');
209
- }
210
- }
211
-
212
- return { success: true, message: 'Thuban gate uninstalled' };
213
- }
214
- }
215
-
216
- module.exports = PreCommitGate;