@euconform/core 1.2.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 (134) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/assets/datasets/crows-pairs-de.json +613 -0
  4. package/assets/datasets/crows-pairs-en.json +9055 -0
  5. package/dist/datasets/crows-pairs.d.ts +7 -0
  6. package/dist/datasets/crows-pairs.d.ts.map +1 -0
  7. package/dist/datasets/crows-pairs.js +41 -0
  8. package/dist/evidence/bundle.d.ts +23 -0
  9. package/dist/evidence/bundle.d.ts.map +1 -0
  10. package/dist/evidence/bundle.js +44 -0
  11. package/dist/evidence/compliance-evaluation.d.ts +12 -0
  12. package/dist/evidence/compliance-evaluation.d.ts.map +1 -0
  13. package/dist/evidence/compliance-evaluation.js +78 -0
  14. package/dist/evidence/hash.d.ts +2 -0
  15. package/dist/evidence/hash.d.ts.map +1 -0
  16. package/dist/evidence/hash.js +4 -0
  17. package/dist/evidence/index.d.ts +13 -0
  18. package/dist/evidence/index.d.ts.map +1 -0
  19. package/dist/evidence/index.js +11 -0
  20. package/dist/evidence/markdown.d.ts +9 -0
  21. package/dist/evidence/markdown.d.ts.map +1 -0
  22. package/dist/evidence/markdown.js +169 -0
  23. package/dist/evidence/output.d.ts +13 -0
  24. package/dist/evidence/output.d.ts.map +1 -0
  25. package/dist/evidence/output.js +547 -0
  26. package/dist/evidence/types.d.ts +230 -0
  27. package/dist/evidence/types.d.ts.map +1 -0
  28. package/dist/evidence/types.js +7 -0
  29. package/dist/evidence/validate.d.ts +8 -0
  30. package/dist/evidence/validate.d.ts.map +1 -0
  31. package/dist/evidence/validate.js +192 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/inference/index.d.ts +1 -1
  35. package/dist/inference/index.d.ts.map +1 -1
  36. package/dist/inference/index.js +1 -1
  37. package/dist/inference/ollama-client.d.ts +11 -3
  38. package/dist/inference/ollama-client.d.ts.map +1 -1
  39. package/dist/inference/ollama-client.js +84 -84
  40. package/dist/scanner/aggregator.d.ts +7 -0
  41. package/dist/scanner/aggregator.d.ts.map +1 -0
  42. package/dist/scanner/aggregator.js +200 -0
  43. package/dist/scanner/detectors/ai-framework-detector.d.ts +3 -0
  44. package/dist/scanner/detectors/ai-framework-detector.d.ts.map +1 -0
  45. package/dist/scanner/detectors/ai-framework-detector.js +74 -0
  46. package/dist/scanner/detectors/ai-provider-detector.d.ts +3 -0
  47. package/dist/scanner/detectors/ai-provider-detector.d.ts.map +1 -0
  48. package/dist/scanner/detectors/ai-provider-detector.js +100 -0
  49. package/dist/scanner/detectors/compliance-detector.d.ts +3 -0
  50. package/dist/scanner/detectors/compliance-detector.d.ts.map +1 -0
  51. package/dist/scanner/detectors/compliance-detector.js +201 -0
  52. package/dist/scanner/detectors/config-detector.d.ts +3 -0
  53. package/dist/scanner/detectors/config-detector.d.ts.map +1 -0
  54. package/dist/scanner/detectors/config-detector.js +248 -0
  55. package/dist/scanner/detectors/framework-detector.d.ts +3 -0
  56. package/dist/scanner/detectors/framework-detector.d.ts.map +1 -0
  57. package/dist/scanner/detectors/framework-detector.js +141 -0
  58. package/dist/scanner/detectors/index.d.ts +8 -0
  59. package/dist/scanner/detectors/index.d.ts.map +1 -0
  60. package/dist/scanner/detectors/index.js +37 -0
  61. package/dist/scanner/detectors/local-inference-detector.d.ts +3 -0
  62. package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -0
  63. package/dist/scanner/detectors/local-inference-detector.js +93 -0
  64. package/dist/scanner/detectors/markdown-detector.d.ts +3 -0
  65. package/dist/scanner/detectors/markdown-detector.d.ts.map +1 -0
  66. package/dist/scanner/detectors/markdown-detector.js +139 -0
  67. package/dist/scanner/detectors/package-json-detector.d.ts +3 -0
  68. package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -0
  69. package/dist/scanner/detectors/package-json-detector.js +166 -0
  70. package/dist/scanner/detectors/rag-detector.d.ts +3 -0
  71. package/dist/scanner/detectors/rag-detector.d.ts.map +1 -0
  72. package/dist/scanner/detectors/rag-detector.js +132 -0
  73. package/dist/scanner/detectors/shared.d.ts +18 -0
  74. package/dist/scanner/detectors/shared.d.ts.map +1 -0
  75. package/dist/scanner/detectors/shared.js +36 -0
  76. package/dist/scanner/discovery.d.ts +14 -0
  77. package/dist/scanner/discovery.d.ts.map +1 -0
  78. package/dist/scanner/discovery.js +235 -0
  79. package/dist/scanner/index.d.ts +9 -0
  80. package/dist/scanner/index.d.ts.map +1 -0
  81. package/dist/scanner/index.js +8 -0
  82. package/package.json +8 -3
  83. package/src/datasets/crows-pairs.ts +64 -0
  84. package/src/evidence/bundle.ts +65 -0
  85. package/src/evidence/compliance-evaluation.ts +114 -0
  86. package/src/evidence/hash.ts +5 -0
  87. package/src/evidence/index.ts +51 -0
  88. package/src/evidence/markdown.ts +226 -0
  89. package/src/evidence/output.ts +717 -0
  90. package/src/evidence/types.ts +329 -0
  91. package/src/evidence/validate.ts +300 -0
  92. package/src/index.ts +1 -0
  93. package/src/inference/index.ts +6 -1
  94. package/src/inference/ollama-client.ts +111 -104
  95. package/src/scanner/aggregator.ts +258 -0
  96. package/src/scanner/detectors/ai-framework-detector.ts +94 -0
  97. package/src/scanner/detectors/ai-provider-detector.ts +135 -0
  98. package/src/scanner/detectors/compliance-detector.ts +232 -0
  99. package/src/scanner/detectors/config-detector.ts +316 -0
  100. package/src/scanner/detectors/framework-detector.ts +163 -0
  101. package/src/scanner/detectors/index.ts +40 -0
  102. package/src/scanner/detectors/local-inference-detector.ts +124 -0
  103. package/src/scanner/detectors/markdown-detector.ts +176 -0
  104. package/src/scanner/detectors/package-json-detector.ts +202 -0
  105. package/src/scanner/detectors/rag-detector.ts +162 -0
  106. package/src/scanner/detectors/shared.ts +45 -0
  107. package/src/scanner/discovery.ts +290 -0
  108. package/src/scanner/index.ts +9 -0
  109. package/tests/evidence/bundle.test.ts +128 -0
  110. package/tests/evidence/hash.test.ts +25 -0
  111. package/tests/evidence/markdown.test.ts +92 -0
  112. package/tests/evidence/output.test.ts +203 -0
  113. package/tests/evidence/spec-schemas.test.ts +316 -0
  114. package/tests/fixtures/compliance-good/README.md +14 -0
  115. package/tests/fixtures/compliance-good/lib/ai-service.ts +17 -0
  116. package/tests/fixtures/compliance-good/lib/review-flow.ts +11 -0
  117. package/tests/fixtures/compliance-good/package.json +10 -0
  118. package/tests/fixtures/compliance-good/tests/bias-check.test.ts +13 -0
  119. package/tests/fixtures/nextjs-openai/README.md +5 -0
  120. package/tests/fixtures/nextjs-openai/app/page.tsx +11 -0
  121. package/tests/fixtures/nextjs-openai/package.json +9 -0
  122. package/tests/fixtures/ollama-rag/.env.example +2 -0
  123. package/tests/fixtures/ollama-rag/docker-compose.yml +9 -0
  124. package/tests/fixtures/ollama-rag/package.json +9 -0
  125. package/tests/fixtures/ollama-rag/server.ts +21 -0
  126. package/tests/fixtures/plain-webapp/README.md +3 -0
  127. package/tests/fixtures/plain-webapp/index.js +5 -0
  128. package/tests/fixtures/plain-webapp/package.json +8 -0
  129. package/tests/gpai-classifier.test.ts +4 -4
  130. package/tests/gpai-timeline.test.ts +2 -2
  131. package/tests/scanner/aggregator.test.ts +122 -0
  132. package/tests/scanner/detectors.test.ts +274 -0
  133. package/tests/scanner/discovery.test.ts +87 -0
  134. package/vitest.config.ts +1 -0
@@ -0,0 +1,201 @@
1
+ import { IMPLEMENTATION_PROVENANCE, isUserFacingPath } from "../../evidence/compliance-evaluation";
2
+ import { SOURCE_EXTENSIONS } from "./shared";
3
+ const rules = [
4
+ // --- compliance-disclosure ---
5
+ {
6
+ id: "compliance-disclosure-ai-generated",
7
+ name: "AI disclosure text",
8
+ category: "compliance-disclosure",
9
+ pattern: /\b(?:generated\s+by\s+ai|ai[- ]generated|powered\s+by\s+ai)\b/i,
10
+ confidence: "medium",
11
+ },
12
+ {
13
+ id: "compliance-disclosure-ai-notice",
14
+ name: "AI transparency notice",
15
+ category: "compliance-disclosure",
16
+ pattern: /\b(?:artificial\s+intelligence|machine\s+learning\s+model|transparency\s+notice)\b/i,
17
+ confidence: "medium",
18
+ },
19
+ // --- compliance-logging ---
20
+ {
21
+ id: "compliance-logging-structured",
22
+ name: "Structured logging library",
23
+ category: "compliance-logging",
24
+ pattern: /\bfrom\s+['"](?:winston|pino|bunyan)['"]|\brequire\(\s*['"](?:winston|pino|bunyan)['"]\)/,
25
+ confidence: "medium",
26
+ },
27
+ {
28
+ id: "compliance-logging-audit",
29
+ name: "Audit trail pattern",
30
+ category: "compliance-logging",
31
+ pattern: /\b(?:audit[._-]?log|audit[._-]?trail|log[._-]?audit)\b/i,
32
+ confidence: "medium",
33
+ },
34
+ {
35
+ id: "compliance-logging-middleware",
36
+ name: "Logging middleware",
37
+ category: "compliance-logging",
38
+ pattern: /\b(?:logging[Mm]iddleware|requestLogger|morgan)\b/,
39
+ confidence: "low",
40
+ },
41
+ // --- compliance-oversight ---
42
+ {
43
+ id: "compliance-oversight-review",
44
+ name: "Human review flow",
45
+ category: "compliance-oversight",
46
+ pattern: /\b(?:human[_-]?review|manual[_-]?review|approval[_-]?flow|review[_-]?gate)\b/i,
47
+ confidence: "medium",
48
+ },
49
+ {
50
+ id: "compliance-oversight-approve-reject",
51
+ name: "Approval/rejection mechanism",
52
+ category: "compliance-oversight",
53
+ pattern: /\b(?:onApprove|onReject|handleApproval|approveAction|rejectAction)\b/,
54
+ confidence: "medium",
55
+ },
56
+ {
57
+ id: "compliance-oversight-override",
58
+ name: "Override mechanism",
59
+ category: "compliance-oversight",
60
+ pattern: /\b(?:override[_-]?ai|human[_-]?override|manual[_-]?override)\b/i,
61
+ confidence: "medium",
62
+ },
63
+ // --- compliance-bias ---
64
+ {
65
+ id: "compliance-bias-fairness",
66
+ name: "Fairness/bias testing",
67
+ category: "compliance-bias",
68
+ pattern: /\b(?:fairness[_-]?test|bias[_-]?test|disparate[_-]?impact|demographic[_-]?parity)\b/i,
69
+ confidence: "medium",
70
+ },
71
+ {
72
+ id: "compliance-bias-benchmark",
73
+ name: "Bias benchmark reference",
74
+ category: "compliance-bias",
75
+ pattern: /\bCrowS[_-]?Pairs\b/i,
76
+ confidence: "medium",
77
+ },
78
+ {
79
+ id: "compliance-bias-euconform",
80
+ name: "EuConform bias utilities",
81
+ category: "compliance-bias",
82
+ pattern: /\bfrom\s+['"]@euconform\/core.*bias/,
83
+ confidence: "medium",
84
+ },
85
+ // --- compliance-data ---
86
+ {
87
+ id: "compliance-data-quality",
88
+ name: "Data quality checks",
89
+ category: "compliance-data",
90
+ pattern: /\b(?:data[_-]?quality|data[_-]?validation|dataset[_-]?doc|data[_-]?lineage)\b/i,
91
+ confidence: "medium",
92
+ },
93
+ {
94
+ id: "compliance-data-pii",
95
+ name: "PII handling / anonymization",
96
+ category: "compliance-data",
97
+ pattern: /\b(?:pii[_-]?filter|anonymi[sz]e|pseudonymi[sz]e|redact[_-]?pii)\b/i,
98
+ confidence: "medium",
99
+ },
100
+ {
101
+ id: "compliance-data-gdpr",
102
+ name: "GDPR reference",
103
+ category: "compliance-data",
104
+ pattern: /\bGDPR\b|data[_-]?protection[_-]?regulation/i,
105
+ confidence: "low",
106
+ },
107
+ // --- compliance-reporting ---
108
+ {
109
+ id: "compliance-reporting-json",
110
+ name: "JSON export capability",
111
+ category: "compliance-reporting",
112
+ pattern: /\b(?:exportJSON|downloadJSON|writeJSONFile|saveJSONReport|exportAuditJSON|exportAnnexJSON|generateAnnexJson)\b|(?:writeFile(?:Sync)?|saveAs|download)\s*\([^)]*(?:report|audit|annex|compliance)[^)]*\.json/i,
113
+ confidence: "medium",
114
+ },
115
+ {
116
+ id: "compliance-reporting-pdf",
117
+ name: "PDF export capability",
118
+ category: "compliance-reporting",
119
+ pattern: /\b(?:jspdf|pdf[_-]?lib|pdfmake|generatePDF|exportPDF|downloadPDF|renderPDF|generateAnnexPdf)\b|puppeteer.*\.pdf/i,
120
+ confidence: "medium",
121
+ },
122
+ {
123
+ id: "compliance-reporting-report",
124
+ name: "Report generation",
125
+ category: "compliance-reporting",
126
+ pattern: /\b(?:generateReport|buildReport|exportReport|downloadReport|createReport|annex[_-]?iv[_-]?report|compliance[_-]?report|audit[_-]?report)\b/i,
127
+ confidence: "medium",
128
+ },
129
+ // --- compliance-incident ---
130
+ {
131
+ id: "compliance-incident-sentry",
132
+ name: "Sentry error reporting",
133
+ category: "compliance-incident",
134
+ pattern: /\bfrom\s+['"]@sentry\/|\bSentry\.init\b|\brequire\(\s*['"]@sentry\//,
135
+ confidence: "medium",
136
+ },
137
+ {
138
+ id: "compliance-incident-boundary",
139
+ name: "Error boundary",
140
+ category: "compliance-incident",
141
+ pattern: /\bErrorBoundary\b|componentDidCatch/,
142
+ confidence: "medium",
143
+ },
144
+ {
145
+ id: "compliance-incident-response",
146
+ name: "Incident response hook",
147
+ category: "compliance-incident",
148
+ pattern: /\b(?:incident[_-]?report|incident[_-]?response|useIncident|alerting[_-]?hook)\b/i,
149
+ confidence: "medium",
150
+ },
151
+ ];
152
+ function canEvaluateRule(rule, file) {
153
+ if (!IMPLEMENTATION_PROVENANCE.has(file.provenance))
154
+ return false;
155
+ if (rule.category === "compliance-disclosure")
156
+ return isUserFacingPath(file.relativePath);
157
+ if (rule.requiresUserFacingPath)
158
+ return isUserFacingPath(file.relativePath);
159
+ return true;
160
+ }
161
+ function gatherEvidence(lines, pattern, filePath, maxItems) {
162
+ const evidence = [];
163
+ for (let i = 0; i < lines.length; i++) {
164
+ const line = lines[i];
165
+ if (!line)
166
+ continue;
167
+ if (pattern.test(line)) {
168
+ evidence.push({ file: filePath, line: i + 1, snippet: line.trim().slice(0, 120) });
169
+ if (evidence.length >= maxItems)
170
+ break;
171
+ }
172
+ }
173
+ return evidence;
174
+ }
175
+ export function detectCompliance(ctx) {
176
+ const { file } = ctx;
177
+ if (!SOURCE_EXTENSIONS.has(file.extension))
178
+ return [];
179
+ const signalMap = new Map();
180
+ for (const rule of rules) {
181
+ if (!canEvaluateRule(rule, file))
182
+ continue;
183
+ const evidence = gatherEvidence(ctx.lines, rule.pattern, file.relativePath, 3);
184
+ if (evidence.length === 0)
185
+ continue;
186
+ const existing = signalMap.get(rule.id);
187
+ if (existing) {
188
+ existing.evidence.push(...evidence);
189
+ }
190
+ else {
191
+ signalMap.set(rule.id, {
192
+ id: rule.id,
193
+ name: rule.name,
194
+ category: rule.category,
195
+ confidence: rule.confidence,
196
+ evidence,
197
+ });
198
+ }
199
+ }
200
+ return Array.from(signalMap.values());
201
+ }
@@ -0,0 +1,3 @@
1
+ import type { DetectedSignal, DetectorContext } from "../../evidence/types";
2
+ export declare function detectConfig(ctx: DetectorContext): DetectedSignal[];
3
+ //# sourceMappingURL=config-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-detector.d.ts","sourceRoot":"","sources":["../../../src/scanner/detectors/config-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,cAAc,EACd,eAAe,EAGhB,MAAM,sBAAsB,CAAC;AAsM9B,wBAAgB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,EAAE,CAenE"}
@@ -0,0 +1,248 @@
1
+ const envKeyRules = [
2
+ {
3
+ id: "ai-provider-openai",
4
+ name: "OpenAI API key",
5
+ category: "ai-provider",
6
+ pattern: /^OPENAI_API_KEY\b/,
7
+ confidence: "high",
8
+ },
9
+ {
10
+ id: "ai-provider-anthropic",
11
+ name: "Anthropic API key",
12
+ category: "ai-provider",
13
+ pattern: /^ANTHROPIC_API_KEY\b/,
14
+ confidence: "high",
15
+ },
16
+ {
17
+ id: "ai-provider-azure-openai",
18
+ name: "Azure OpenAI key",
19
+ category: "ai-provider",
20
+ pattern: /^AZURE_OPENAI_/,
21
+ confidence: "high",
22
+ },
23
+ {
24
+ id: "ai-provider-google-ai",
25
+ name: "Google AI key",
26
+ category: "ai-provider",
27
+ pattern: /^(?:GOOGLE_AI_API_KEY|GEMINI_API_KEY)\b/,
28
+ confidence: "high",
29
+ },
30
+ {
31
+ id: "ai-provider-cohere",
32
+ name: "Cohere API key",
33
+ category: "ai-provider",
34
+ pattern: /^COHERE_API_KEY\b/,
35
+ confidence: "high",
36
+ },
37
+ {
38
+ id: "ai-provider-replicate",
39
+ name: "Replicate API token",
40
+ category: "ai-provider",
41
+ pattern: /^REPLICATE_API_TOKEN\b/,
42
+ confidence: "high",
43
+ },
44
+ {
45
+ id: "ai-provider-huggingface",
46
+ name: "HuggingFace token",
47
+ category: "ai-provider",
48
+ pattern: /^HUGGINGFACE_TOKEN\b|^HF_TOKEN\b/,
49
+ confidence: "high",
50
+ },
51
+ {
52
+ id: "local-inference-ollama",
53
+ name: "Ollama host config",
54
+ category: "local-inference",
55
+ pattern: /^OLLAMA_HOST\b|^OLLAMA_BASE_URL\b/,
56
+ confidence: "high",
57
+ },
58
+ {
59
+ id: "rag-pinecone",
60
+ name: "Pinecone API key",
61
+ category: "rag",
62
+ pattern: /^PINECONE_API_KEY\b/,
63
+ confidence: "high",
64
+ },
65
+ {
66
+ id: "rag-weaviate",
67
+ name: "Weaviate config",
68
+ category: "rag",
69
+ pattern: /^WEAVIATE_/,
70
+ confidence: "high",
71
+ },
72
+ {
73
+ id: "rag-chroma",
74
+ name: "ChromaDB config",
75
+ category: "rag",
76
+ pattern: /^CHROMA_/,
77
+ confidence: "high",
78
+ },
79
+ ];
80
+ const dockerServiceRules = [
81
+ {
82
+ id: "local-inference-ollama-docker",
83
+ name: "Ollama Docker service",
84
+ category: "local-inference",
85
+ pattern: /\bollama\/ollama\b|image:\s*ollama/,
86
+ confidence: "high",
87
+ },
88
+ {
89
+ id: "rag-chromadb-docker",
90
+ name: "ChromaDB Docker service",
91
+ category: "rag",
92
+ pattern: /\bchromadb\/chroma\b|image:\s*chroma/,
93
+ confidence: "high",
94
+ },
95
+ {
96
+ id: "rag-weaviate-docker",
97
+ name: "Weaviate Docker service",
98
+ category: "rag",
99
+ pattern: /\bsemitechnologies\/weaviate\b|image:\s*weaviate/,
100
+ confidence: "high",
101
+ },
102
+ {
103
+ id: "rag-redis-vector",
104
+ name: "Redis with vector module",
105
+ category: "rag",
106
+ pattern: /redis\/redis-stack|redisearch|redis-stack-server/,
107
+ confidence: "high",
108
+ },
109
+ {
110
+ id: "rag-qdrant-docker",
111
+ name: "Qdrant Docker service",
112
+ category: "rag",
113
+ pattern: /\bqdrant\/qdrant\b|image:\s*qdrant/,
114
+ confidence: "high",
115
+ },
116
+ ];
117
+ const dockerfileRules = [
118
+ {
119
+ id: "local-inference-pip-ai",
120
+ name: "AI pip packages in Dockerfile",
121
+ category: "local-inference",
122
+ pattern: /pip\s+install\s+.*\b(?:torch|tensorflow|transformers|langchain|llama[-_]?cpp|vllm|onnxruntime)\b/,
123
+ confidence: "high",
124
+ },
125
+ {
126
+ id: "local-inference-model-download",
127
+ name: "Model download in Dockerfile",
128
+ category: "local-inference",
129
+ pattern: /\b(?:huggingface-cli\s+download|wget\s+.*(?:\.gguf|\.safetensors|\.bin)|COPY\s+.*(?:model|weights))\b/i,
130
+ confidence: "high",
131
+ },
132
+ ];
133
+ // ---------------------------------------------------------------------------
134
+ // File matching helpers
135
+ // ---------------------------------------------------------------------------
136
+ function isEnvExample(relativePath) {
137
+ const basename = relativePath.split("/").pop() ?? "";
138
+ return /^\.env\.(?:example|sample)$/.test(basename);
139
+ }
140
+ function isDockerCompose(relativePath) {
141
+ const basename = relativePath.split("/").pop() ?? "";
142
+ return /^docker-compose/.test(basename);
143
+ }
144
+ function isDockerfile(relativePath) {
145
+ const basename = relativePath.split("/").pop() ?? "";
146
+ return /^Dockerfile/.test(basename);
147
+ }
148
+ // ---------------------------------------------------------------------------
149
+ // Detector
150
+ // ---------------------------------------------------------------------------
151
+ export function detectConfig(ctx) {
152
+ const { file } = ctx;
153
+ const signals = [];
154
+ const lines = ctx.lines;
155
+ if (isEnvExample(file.relativePath)) {
156
+ detectEnvKeys(file.relativePath, lines, signals);
157
+ }
158
+ else if (isDockerCompose(file.relativePath)) {
159
+ detectDockerServices(file.relativePath, lines, signals);
160
+ }
161
+ else if (isDockerfile(file.relativePath)) {
162
+ detectDockerfilePatterns(file.relativePath, lines, signals);
163
+ }
164
+ return signals;
165
+ }
166
+ function extractEnvVarName(rawLine) {
167
+ const line = rawLine.trim();
168
+ if (!line || line.startsWith("#"))
169
+ return null;
170
+ const eqIndex = line.indexOf("=");
171
+ return eqIndex >= 0 ? line.slice(0, eqIndex).trim() : line.trim();
172
+ }
173
+ function detectEnvKeys(relativePath, lines, signals) {
174
+ for (const rule of envKeyRules) {
175
+ const evidence = [];
176
+ for (let i = 0; i < lines.length; i++) {
177
+ const varName = extractEnvVarName(lines[i] ?? "");
178
+ if (varName && rule.pattern.test(varName)) {
179
+ evidence.push({ file: relativePath, line: i + 1, snippet: varName });
180
+ }
181
+ }
182
+ if (evidence.length > 0) {
183
+ signals.push({
184
+ id: rule.id,
185
+ name: rule.name,
186
+ category: rule.category,
187
+ confidence: rule.confidence,
188
+ evidence,
189
+ });
190
+ }
191
+ }
192
+ }
193
+ function detectDockerServices(relativePath, lines, signals) {
194
+ for (const rule of dockerServiceRules) {
195
+ const evidence = [];
196
+ for (let i = 0; i < lines.length; i++) {
197
+ const line = lines[i];
198
+ if (!line)
199
+ continue;
200
+ if (rule.pattern.test(line)) {
201
+ evidence.push({
202
+ file: relativePath,
203
+ line: i + 1,
204
+ snippet: line.trim().slice(0, 120),
205
+ });
206
+ if (evidence.length >= 3)
207
+ break;
208
+ }
209
+ }
210
+ if (evidence.length > 0) {
211
+ signals.push({
212
+ id: rule.id,
213
+ name: rule.name,
214
+ category: rule.category,
215
+ confidence: rule.confidence,
216
+ evidence,
217
+ });
218
+ }
219
+ }
220
+ }
221
+ function detectDockerfilePatterns(relativePath, lines, signals) {
222
+ for (const rule of dockerfileRules) {
223
+ const evidence = [];
224
+ for (let i = 0; i < lines.length; i++) {
225
+ const line = lines[i];
226
+ if (!line)
227
+ continue;
228
+ if (rule.pattern.test(line)) {
229
+ evidence.push({
230
+ file: relativePath,
231
+ line: i + 1,
232
+ snippet: line.trim().slice(0, 120),
233
+ });
234
+ if (evidence.length >= 3)
235
+ break;
236
+ }
237
+ }
238
+ if (evidence.length > 0) {
239
+ signals.push({
240
+ id: rule.id,
241
+ name: rule.name,
242
+ category: rule.category,
243
+ confidence: rule.confidence,
244
+ evidence,
245
+ });
246
+ }
247
+ }
248
+ }
@@ -0,0 +1,3 @@
1
+ import type { DetectedSignal, DetectorContext } from "../../evidence/types";
2
+ export declare function detectFrameworks(ctx: DetectorContext): DetectedSignal[];
3
+ //# sourceMappingURL=framework-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework-detector.d.ts","sourceRoot":"","sources":["../../../src/scanner/detectors/framework-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,cAAc,EACd,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAwH9B,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,EAAE,CAqCvE"}
@@ -0,0 +1,141 @@
1
+ import { EXTENDED_EXTENSIONS } from "./shared";
2
+ const rules = [
3
+ // Frameworks
4
+ {
5
+ name: "nextjs",
6
+ category: "framework",
7
+ configPattern: /^next\.config\./,
8
+ importPattern: /\bfrom\s+['"]next[\/'"]|\brequire\(\s*['"]next['"]\)/,
9
+ configConfidence: "high",
10
+ importConfidence: "medium",
11
+ },
12
+ {
13
+ name: "react",
14
+ category: "framework",
15
+ importPattern: /\bfrom\s+['"]react['"]|\brequire\(\s*['"]react['"]\)/,
16
+ configConfidence: "high",
17
+ importConfidence: "medium",
18
+ },
19
+ {
20
+ name: "express",
21
+ category: "framework",
22
+ importPattern: /\bfrom\s+['"]express['"]|\brequire\(\s*['"]express['"]\)/,
23
+ configConfidence: "high",
24
+ importConfidence: "medium",
25
+ },
26
+ {
27
+ name: "fastify",
28
+ category: "framework",
29
+ importPattern: /\bfrom\s+['"]fastify['"]|\brequire\(\s*['"]fastify['"]\)/,
30
+ configConfidence: "high",
31
+ importConfidence: "medium",
32
+ },
33
+ {
34
+ name: "nestjs",
35
+ category: "framework",
36
+ importPattern: /\bfrom\s+['"]@nestjs\/|\brequire\(\s*['"]@nestjs\//,
37
+ configConfidence: "high",
38
+ importConfidence: "medium",
39
+ },
40
+ {
41
+ name: "hono",
42
+ category: "framework",
43
+ importPattern: /\bfrom\s+['"]hono['"]|\brequire\(\s*['"]hono['"]\)/,
44
+ configConfidence: "high",
45
+ importConfidence: "medium",
46
+ },
47
+ {
48
+ name: "elysia",
49
+ category: "framework",
50
+ importPattern: /\bfrom\s+['"]elysia['"]|\brequire\(\s*['"]elysia['"]\)/,
51
+ configConfidence: "high",
52
+ importConfidence: "medium",
53
+ },
54
+ {
55
+ name: "vite",
56
+ category: "framework",
57
+ configPattern: /^vite\.config\./,
58
+ importPattern: /\bfrom\s+['"]vite['"]|\brequire\(\s*['"]vite['"]\)/,
59
+ configConfidence: "high",
60
+ importConfidence: "medium",
61
+ },
62
+ // Runtimes
63
+ {
64
+ name: "nodejs",
65
+ category: "runtime",
66
+ importPattern: /\bfrom\s+['"]node:(?:fs|http|path|crypto|stream)['"]|\bfrom\s+['"](?:fs|http|net|dgram)['"]|\brequire\(\s*['"](?:fs|http|path|crypto)['"]\)/,
67
+ configConfidence: "high",
68
+ importConfidence: "medium",
69
+ },
70
+ {
71
+ name: "bun",
72
+ category: "runtime",
73
+ importPattern: /\bBun\.serve\b|\bBun\.file\b|\bBun\.write\b/,
74
+ configConfidence: "high",
75
+ importConfidence: "medium",
76
+ },
77
+ {
78
+ name: "deno",
79
+ category: "runtime",
80
+ importPattern: /\bDeno\.serve\b|\bDeno\.readFile\b|\bDeno\.env\b/,
81
+ configConfidence: "high",
82
+ importConfidence: "medium",
83
+ },
84
+ ];
85
+ const CONFIG_NAME_PATTERN = /^(next|vite)\.config\./;
86
+ function shouldProcess(file) {
87
+ if (EXTENDED_EXTENSIONS.has(file.extension))
88
+ return true;
89
+ const basename = file.relativePath.split("/").pop() ?? "";
90
+ return CONFIG_NAME_PATTERN.test(basename);
91
+ }
92
+ function findImportEvidence(lines, pattern, filePath) {
93
+ const evidence = [];
94
+ for (let i = 0; i < lines.length; i++) {
95
+ const line = lines[i];
96
+ if (!line)
97
+ continue;
98
+ if (pattern.test(line)) {
99
+ evidence.push({ file: filePath, line: i + 1, snippet: line.trim().slice(0, 120) });
100
+ if (evidence.length >= 3)
101
+ break;
102
+ }
103
+ }
104
+ return evidence;
105
+ }
106
+ export function detectFrameworks(ctx) {
107
+ const { file } = ctx;
108
+ if (!shouldProcess(file))
109
+ return [];
110
+ const basename = file.relativePath.split("/").pop() ?? "";
111
+ const seen = new Set();
112
+ const signals = [];
113
+ for (const rule of rules) {
114
+ if (rule.configPattern?.test(basename) && !seen.has(rule.name)) {
115
+ seen.add(rule.name);
116
+ signals.push({
117
+ id: `${rule.category}-${rule.name}`,
118
+ name: rule.name,
119
+ category: rule.category,
120
+ confidence: rule.configConfidence,
121
+ evidence: [
122
+ { file: file.relativePath, line: 1, snippet: `Config file detected: ${basename}` },
123
+ ],
124
+ });
125
+ }
126
+ if (!rule.importPattern)
127
+ continue;
128
+ const evidence = findImportEvidence(ctx.lines, rule.importPattern, file.relativePath);
129
+ if (evidence.length > 0 && !seen.has(rule.name)) {
130
+ seen.add(rule.name);
131
+ signals.push({
132
+ id: `${rule.category}-${rule.name}`,
133
+ name: rule.name,
134
+ category: rule.category,
135
+ confidence: rule.importConfidence,
136
+ evidence,
137
+ });
138
+ }
139
+ }
140
+ return signals;
141
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Signal detector registry.
3
+ *
4
+ * Runs all registered detectors against a file and returns combined signals.
5
+ */
6
+ import type { DetectedSignal, DetectorContext } from "../../evidence/types";
7
+ export declare function runAllDetectors(ctx: DetectorContext): DetectedSignal[];
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanner/detectors/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAkB,MAAM,sBAAsB,CAAC;AAuB5F,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,EAAE,CAUtE"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Signal detector registry.
3
+ *
4
+ * Runs all registered detectors against a file and returns combined signals.
5
+ */
6
+ import { detectAiFrameworkSignals } from "./ai-framework-detector";
7
+ import { detectAiProviderSignals } from "./ai-provider-detector";
8
+ import { detectCompliance } from "./compliance-detector";
9
+ import { detectConfig } from "./config-detector";
10
+ import { detectFrameworks } from "./framework-detector";
11
+ import { detectLocalInferenceSignals } from "./local-inference-detector";
12
+ import { detectMarkdown } from "./markdown-detector";
13
+ import { detectPackageJsonSignals } from "./package-json-detector";
14
+ import { detectRagSignals } from "./rag-detector";
15
+ const ALL_DETECTORS = [
16
+ detectPackageJsonSignals,
17
+ detectAiProviderSignals,
18
+ detectAiFrameworkSignals,
19
+ detectLocalInferenceSignals,
20
+ detectRagSignals,
21
+ detectFrameworks,
22
+ detectCompliance,
23
+ detectConfig,
24
+ detectMarkdown,
25
+ ];
26
+ export function runAllDetectors(ctx) {
27
+ const signals = [];
28
+ for (const detector of ALL_DETECTORS) {
29
+ try {
30
+ signals.push(...detector(ctx));
31
+ }
32
+ catch {
33
+ // Individual detector failure should not break the scan
34
+ }
35
+ }
36
+ return signals;
37
+ }
@@ -0,0 +1,3 @@
1
+ import type { DetectedSignal, DetectorContext } from "../../evidence/types";
2
+ export declare function detectLocalInferenceSignals(ctx: DetectorContext): DetectedSignal[];
3
+ //# sourceMappingURL=local-inference-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-inference-detector.d.ts","sourceRoot":"","sources":["../../../src/scanner/detectors/local-inference-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,cAAc,EACd,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAoG9B,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,EAAE,CAkBlF"}