@euconform/core 1.0.1 → 1.3.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 (183) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/evidence/bundle.d.ts +23 -0
  4. package/dist/evidence/bundle.d.ts.map +1 -0
  5. package/dist/evidence/bundle.js +44 -0
  6. package/dist/evidence/compliance-evaluation.d.ts +12 -0
  7. package/dist/evidence/compliance-evaluation.d.ts.map +1 -0
  8. package/dist/evidence/compliance-evaluation.js +78 -0
  9. package/dist/evidence/hash.d.ts +2 -0
  10. package/dist/evidence/hash.d.ts.map +1 -0
  11. package/dist/evidence/hash.js +4 -0
  12. package/dist/evidence/index.d.ts +13 -0
  13. package/dist/evidence/index.d.ts.map +1 -0
  14. package/dist/evidence/index.js +11 -0
  15. package/dist/evidence/markdown.d.ts +9 -0
  16. package/dist/evidence/markdown.d.ts.map +1 -0
  17. package/dist/evidence/markdown.js +169 -0
  18. package/dist/evidence/output.d.ts +13 -0
  19. package/dist/evidence/output.d.ts.map +1 -0
  20. package/dist/evidence/output.js +547 -0
  21. package/dist/evidence/types.d.ts +230 -0
  22. package/dist/evidence/types.d.ts.map +1 -0
  23. package/dist/evidence/types.js +7 -0
  24. package/dist/evidence/validate.d.ts +8 -0
  25. package/dist/evidence/validate.d.ts.map +1 -0
  26. package/dist/evidence/validate.js +175 -0
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/inference/ollama-client.d.ts +3 -0
  31. package/dist/inference/ollama-client.d.ts.map +1 -1
  32. package/dist/inference/ollama-client.js +69 -71
  33. package/dist/legal-checks/annex-iv-report.d.ts +3 -0
  34. package/dist/legal-checks/annex-iv-report.d.ts.map +1 -1
  35. package/dist/legal-checks/annex-iv-report.js +1 -0
  36. package/dist/legal-checks/deadline-timeline.d.ts +57 -0
  37. package/dist/legal-checks/deadline-timeline.d.ts.map +1 -0
  38. package/dist/legal-checks/deadline-timeline.js +265 -0
  39. package/dist/legal-checks/gap-analysis.d.ts +48 -0
  40. package/dist/legal-checks/gap-analysis.d.ts.map +1 -0
  41. package/dist/legal-checks/gap-analysis.js +397 -0
  42. package/dist/legal-checks/gpai-classifier.d.ts +49 -0
  43. package/dist/legal-checks/gpai-classifier.d.ts.map +1 -0
  44. package/dist/legal-checks/gpai-classifier.js +197 -0
  45. package/dist/legal-checks/index.d.ts +6 -0
  46. package/dist/legal-checks/index.d.ts.map +1 -1
  47. package/dist/legal-checks/index.js +3 -0
  48. package/dist/scanner/aggregator.d.ts +7 -0
  49. package/dist/scanner/aggregator.d.ts.map +1 -0
  50. package/dist/scanner/aggregator.js +200 -0
  51. package/dist/scanner/detectors/ai-framework-detector.d.ts +3 -0
  52. package/dist/scanner/detectors/ai-framework-detector.d.ts.map +1 -0
  53. package/dist/scanner/detectors/ai-framework-detector.js +74 -0
  54. package/dist/scanner/detectors/ai-provider-detector.d.ts +3 -0
  55. package/dist/scanner/detectors/ai-provider-detector.d.ts.map +1 -0
  56. package/dist/scanner/detectors/ai-provider-detector.js +100 -0
  57. package/dist/scanner/detectors/compliance-detector.d.ts +3 -0
  58. package/dist/scanner/detectors/compliance-detector.d.ts.map +1 -0
  59. package/dist/scanner/detectors/compliance-detector.js +201 -0
  60. package/dist/scanner/detectors/config-detector.d.ts +3 -0
  61. package/dist/scanner/detectors/config-detector.d.ts.map +1 -0
  62. package/dist/scanner/detectors/config-detector.js +248 -0
  63. package/dist/scanner/detectors/framework-detector.d.ts +3 -0
  64. package/dist/scanner/detectors/framework-detector.d.ts.map +1 -0
  65. package/dist/scanner/detectors/framework-detector.js +141 -0
  66. package/dist/scanner/detectors/index.d.ts +8 -0
  67. package/dist/scanner/detectors/index.d.ts.map +1 -0
  68. package/dist/scanner/detectors/index.js +37 -0
  69. package/dist/scanner/detectors/local-inference-detector.d.ts +3 -0
  70. package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -0
  71. package/dist/scanner/detectors/local-inference-detector.js +93 -0
  72. package/dist/scanner/detectors/markdown-detector.d.ts +3 -0
  73. package/dist/scanner/detectors/markdown-detector.d.ts.map +1 -0
  74. package/dist/scanner/detectors/markdown-detector.js +139 -0
  75. package/dist/scanner/detectors/package-json-detector.d.ts +3 -0
  76. package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -0
  77. package/dist/scanner/detectors/package-json-detector.js +166 -0
  78. package/dist/scanner/detectors/rag-detector.d.ts +3 -0
  79. package/dist/scanner/detectors/rag-detector.d.ts.map +1 -0
  80. package/dist/scanner/detectors/rag-detector.js +132 -0
  81. package/dist/scanner/detectors/shared.d.ts +18 -0
  82. package/dist/scanner/detectors/shared.d.ts.map +1 -0
  83. package/dist/scanner/detectors/shared.js +36 -0
  84. package/dist/scanner/discovery.d.ts +14 -0
  85. package/dist/scanner/discovery.d.ts.map +1 -0
  86. package/dist/scanner/discovery.js +235 -0
  87. package/dist/scanner/index.d.ts +9 -0
  88. package/dist/scanner/index.d.ts.map +1 -0
  89. package/dist/scanner/index.js +8 -0
  90. package/package.json +6 -3
  91. package/src/evidence/bundle.ts +65 -0
  92. package/src/evidence/compliance-evaluation.ts +114 -0
  93. package/src/evidence/hash.ts +5 -0
  94. package/src/evidence/index.ts +51 -0
  95. package/src/evidence/markdown.ts +226 -0
  96. package/src/evidence/output.ts +717 -0
  97. package/src/evidence/types.ts +329 -0
  98. package/src/evidence/validate.ts +270 -0
  99. package/src/index.ts +25 -0
  100. package/src/inference/ollama-client.ts +93 -86
  101. package/src/legal-checks/annex-iv-report.ts +4 -0
  102. package/src/legal-checks/deadline-timeline.ts +313 -0
  103. package/src/legal-checks/gap-analysis.ts +454 -0
  104. package/src/legal-checks/gpai-classifier.ts +281 -0
  105. package/src/legal-checks/index.ts +33 -0
  106. package/src/scanner/aggregator.ts +258 -0
  107. package/src/scanner/detectors/ai-framework-detector.ts +94 -0
  108. package/src/scanner/detectors/ai-provider-detector.ts +135 -0
  109. package/src/scanner/detectors/compliance-detector.ts +232 -0
  110. package/src/scanner/detectors/config-detector.ts +316 -0
  111. package/src/scanner/detectors/framework-detector.ts +163 -0
  112. package/src/scanner/detectors/index.ts +40 -0
  113. package/src/scanner/detectors/local-inference-detector.ts +124 -0
  114. package/src/scanner/detectors/markdown-detector.ts +176 -0
  115. package/src/scanner/detectors/package-json-detector.ts +202 -0
  116. package/src/scanner/detectors/rag-detector.ts +162 -0
  117. package/src/scanner/detectors/shared.ts +45 -0
  118. package/src/scanner/discovery.ts +290 -0
  119. package/src/scanner/index.ts +9 -0
  120. package/tests/evidence/bundle.test.ts +128 -0
  121. package/tests/evidence/ecef-schemas.test.ts +316 -0
  122. package/tests/evidence/hash.test.ts +25 -0
  123. package/tests/evidence/markdown.test.ts +92 -0
  124. package/tests/evidence/output.test.ts +203 -0
  125. package/tests/fixtures/compliance-good/README.md +14 -0
  126. package/tests/fixtures/compliance-good/lib/ai-service.ts +17 -0
  127. package/tests/fixtures/compliance-good/lib/review-flow.ts +11 -0
  128. package/tests/fixtures/compliance-good/package.json +10 -0
  129. package/tests/fixtures/compliance-good/tests/bias-check.test.ts +13 -0
  130. package/tests/fixtures/nextjs-openai/README.md +5 -0
  131. package/tests/fixtures/nextjs-openai/app/page.tsx +11 -0
  132. package/tests/fixtures/nextjs-openai/package.json +9 -0
  133. package/tests/fixtures/ollama-rag/.env.example +2 -0
  134. package/tests/fixtures/ollama-rag/docker-compose.yml +9 -0
  135. package/tests/fixtures/ollama-rag/package.json +9 -0
  136. package/tests/fixtures/ollama-rag/server.ts +21 -0
  137. package/tests/fixtures/plain-webapp/README.md +3 -0
  138. package/tests/fixtures/plain-webapp/index.js +5 -0
  139. package/tests/fixtures/plain-webapp/package.json +8 -0
  140. package/tests/gpai-classifier.test.ts +125 -0
  141. package/tests/gpai-timeline.test.ts +55 -0
  142. package/tests/scanner/aggregator.test.ts +122 -0
  143. package/tests/scanner/detectors.test.ts +274 -0
  144. package/tests/scanner/discovery.test.ts +87 -0
  145. package/vitest.config.ts +1 -0
  146. package/.turbo/turbo-check-types.log +0 -4
  147. package/.turbo/turbo-test.log +0 -364
  148. package/coverage/base.css +0 -224
  149. package/coverage/block-navigation.js +0 -87
  150. package/coverage/clover.xml +0 -3282
  151. package/coverage/coverage-final.json +0 -20
  152. package/coverage/favicon.png +0 -0
  153. package/coverage/index.html +0 -161
  154. package/coverage/prettify.css +0 -1
  155. package/coverage/prettify.js +0 -2
  156. package/coverage/sort-arrow-sprite.png +0 -0
  157. package/coverage/sorter.js +0 -210
  158. package/coverage/src/fairness-metrics.ts.html +0 -1579
  159. package/coverage/src/index.html +0 -161
  160. package/coverage/src/index.ts.html +0 -535
  161. package/coverage/src/inference/browser-inference.ts.html +0 -859
  162. package/coverage/src/inference/capability-detection.ts.html +0 -2227
  163. package/coverage/src/inference/index.html +0 -176
  164. package/coverage/src/inference/index.ts.html +0 -553
  165. package/coverage/src/inference/inference-factory.ts.html +0 -751
  166. package/coverage/src/inference/ollama-client.ts.html +0 -1627
  167. package/coverage/src/legal-checks/annex-iv-report.ts.html +0 -556
  168. package/coverage/src/legal-checks/bias-metrics.ts.html +0 -1855
  169. package/coverage/src/legal-checks/data-governance.ts.html +0 -529
  170. package/coverage/src/legal-checks/human-oversight-logging.ts.html +0 -490
  171. package/coverage/src/legal-checks/index.html +0 -206
  172. package/coverage/src/legal-checks/index.ts.html +0 -250
  173. package/coverage/src/legal-checks/risk-classifier.ts.html +0 -940
  174. package/coverage/src/legal-checks/robustness-transparency.ts.html +0 -571
  175. package/coverage/src/metrics/crows-pairs-bias.ts.html +0 -820
  176. package/coverage/src/metrics/index.html +0 -146
  177. package/coverage/src/metrics/index.ts.html +0 -151
  178. package/coverage/src/metrics/stereotype-bias.ts.html +0 -886
  179. package/coverage/src/risk-engine.ts.html +0 -1660
  180. package/coverage/src/types.ts.html +0 -547
  181. package/dist/legal-checks/disclaimer.d.ts +0 -2
  182. package/dist/legal-checks/disclaimer.d.ts.map +0 -1
  183. package/dist/legal-checks/disclaimer.js +0 -1
@@ -4,3 +4,6 @@ export { runDataGovernanceChecklist } from "./data-governance";
4
4
  export { defaultTextPerturbations, runTextPerturbationRobustnessTest, } from "./robustness-transparency";
5
5
  export { getHumanOversightAndLoggingTemplate } from "./human-oversight-logging";
6
6
  export { buildAnnexIVReportV1 } from "./annex-iv-report";
7
+ export { EU_AI_ACT_TIMELINE, DIGITAL_OMNIBUS_PROPOSALS, getDeadlineStatus, getDaysUntilDeadline, getAudiencesForRiskLevel, getNextDeadline, getTimelineForRiskLevel, getTimelineForAudiences, } from "./deadline-timeline";
8
+ export { GPAI_QUESTIONS, classifyGPAICompliance, getGPAIQuestions, isSystemicRiskModel, } from "./gpai-classifier";
9
+ export { generateAnnexIIIGapAnalysis, generateGPAIGapAnalysis } from "./gap-analysis";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Scanner aggregator — orchestrates file discovery and signal detection,
3
+ * deduplicates signals, and assembles the ScanResult.
4
+ */
5
+ import type { ScanOptions, ScanResult } from "../evidence/types";
6
+ export declare function scanRepository(options: ScanOptions): Promise<ScanResult>;
7
+ //# sourceMappingURL=aggregator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregator.d.ts","sourceRoot":"","sources":["../../src/scanner/aggregator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAQV,WAAW,EACX,UAAU,EAEX,MAAM,mBAAmB,CAAC;AA6M3B,wBAAsB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAkC9E"}
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Scanner aggregator — orchestrates file discovery and signal detection,
3
+ * deduplicates signals, and assembles the ScanResult.
4
+ */
5
+ import { basename } from "node:path";
6
+ import { hasImplementationSignals } from "../evidence/compliance-evaluation";
7
+ import { runAllDetectors } from "./detectors";
8
+ import { CONFIDENCE_RANK } from "./detectors/shared";
9
+ import { discoverFiles } from "./discovery";
10
+ const TOOL_VERSION = "0.1.0";
11
+ function parseRootPackageJson(files) {
12
+ const pkgFile = files.find((f) => f.relativePath === "package.json");
13
+ if (!pkgFile)
14
+ return undefined;
15
+ try {
16
+ return JSON.parse(pkgFile.content);
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ function buildRepoProfile(targetPath, pkg, files) {
23
+ const hasTs = files.some((f) => f.extension === ".ts" || f.extension === ".tsx");
24
+ const hasJs = files.some((f) => f.extension === ".js" || f.extension === ".jsx");
25
+ const languages = [];
26
+ if (hasTs)
27
+ languages.push("typescript");
28
+ if (hasJs)
29
+ languages.push("javascript");
30
+ let packageManager;
31
+ if (files.some((f) => f.relativePath === "pnpm-lock.yaml"))
32
+ packageManager = "pnpm";
33
+ else if (files.some((f) => f.relativePath === "yarn.lock"))
34
+ packageManager = "yarn";
35
+ else if (files.some((f) => f.relativePath === "package-lock.json"))
36
+ packageManager = "npm";
37
+ const isMonorepo = Boolean(pkg?.workspaces || files.some((f) => f.relativePath === "pnpm-workspace.yaml"));
38
+ return {
39
+ name: pkg?.name ?? basename(targetPath),
40
+ version: pkg?.version,
41
+ description: pkg?.description,
42
+ license: pkg?.license,
43
+ isMonorepo,
44
+ packageManager,
45
+ languages,
46
+ };
47
+ }
48
+ function inferAppTypes(signals) {
49
+ const types = new Set();
50
+ const has = (id) => signals.some((s) => s.id === id);
51
+ const hasCategory = (cat) => signals.some((s) => s.category === cat);
52
+ if (has("framework-nextjs") || has("framework-react"))
53
+ types.add("web-app");
54
+ if (has("framework-express") ||
55
+ has("framework-fastify") ||
56
+ has("framework-nestjs") ||
57
+ has("framework-hono") ||
58
+ has("framework-elysia"))
59
+ types.add("api-server");
60
+ if (hasCategory("local-inference") || hasCategory("ai-provider"))
61
+ if (signals.some((s) => s.name.toLowerCase().includes("chat")))
62
+ types.add("chatbot");
63
+ if (hasCategory("training"))
64
+ types.add("ml-pipeline");
65
+ if (types.size === 0)
66
+ types.add("unknown");
67
+ return [...types];
68
+ }
69
+ /** Merge signals with the same ID: keep highest confidence, combine evidence */
70
+ function deduplicateSignals(signals) {
71
+ const map = new Map();
72
+ for (const signal of signals) {
73
+ const existing = map.get(signal.id);
74
+ if (!existing) {
75
+ map.set(signal.id, { ...signal, evidence: [...signal.evidence] });
76
+ continue;
77
+ }
78
+ // Merge evidence (use Set for O(1) dedup instead of O(n) .some())
79
+ const seen = new Set(existing.evidence.map((e) => `${e.file}:${e.line ?? ""}`));
80
+ for (const ev of signal.evidence) {
81
+ const key = `${ev.file}:${ev.line ?? ""}`;
82
+ if (!seen.has(key)) {
83
+ seen.add(key);
84
+ existing.evidence.push(ev);
85
+ }
86
+ }
87
+ // Upgrade confidence
88
+ if (CONFIDENCE_RANK[signal.confidence] > CONFIDENCE_RANK[existing.confidence]) {
89
+ existing.confidence = signal.confidence;
90
+ }
91
+ }
92
+ return [...map.values()];
93
+ }
94
+ function annotateEvidenceProvenance(signals, files) {
95
+ const provenanceByPath = new Map();
96
+ for (const file of files) {
97
+ provenanceByPath.set(file.relativePath, file.provenance);
98
+ }
99
+ return signals.map((signal) => ({
100
+ ...signal,
101
+ evidence: signal.evidence.map((evidence) => ({
102
+ ...evidence,
103
+ provenance: evidence.provenance ?? provenanceByPath.get(evidence.file) ?? "runtime",
104
+ })),
105
+ }));
106
+ }
107
+ function generateOpenQuestions(signals) {
108
+ const questions = [];
109
+ const hasAI = signals.some((s) => s.category === "ai-provider" ||
110
+ s.category === "ai-framework" ||
111
+ s.category === "local-inference");
112
+ if (hasAI) {
113
+ questions.push({
114
+ id: "oq-market-placement",
115
+ question: "Is this AI system placed on the EU market or used within the EU?",
116
+ context: "The EU AI Act applies to AI systems placed on or put into service in the EU market.",
117
+ relatedSignalIds: signals.filter((s) => s.category.startsWith("ai-")).map((s) => s.id),
118
+ suggestedAction: "Determine your system's geographic scope and market placement.",
119
+ });
120
+ questions.push({
121
+ id: "oq-intended-purpose",
122
+ question: "What is the intended purpose of this AI system?",
123
+ context: "Risk classification under the EU AI Act depends on the system's intended purpose and deployment context, which cannot be determined from code alone.",
124
+ relatedSignalIds: [],
125
+ suggestedAction: "Use the EuConform wizard to complete your risk assessment with human-provided context.",
126
+ });
127
+ questions.push({
128
+ id: "oq-provider-role",
129
+ question: "Are you a provider, deployer, or importer of this AI system?",
130
+ context: "Different roles under Art. 3 carry different obligations. A scanner cannot determine your organizational role.",
131
+ relatedSignalIds: [],
132
+ suggestedAction: "Clarify your role in the AI value chain to determine applicable obligations.",
133
+ });
134
+ }
135
+ const hasGpaiSignals = signals.some((s) => s.category === "ai-provider" || s.id.includes("langchain") || s.id.includes("llamaindex"));
136
+ if (hasGpaiSignals) {
137
+ questions.push({
138
+ id: "oq-gpai-downstream",
139
+ question: "Are you using a general-purpose AI model? Do you provide downstream access to others?",
140
+ context: "GPAI obligations (Art. 53-55) apply to providers of general-purpose AI models. Using an API does not automatically make you a GPAI provider.",
141
+ relatedSignalIds: signals.filter((s) => s.category === "ai-provider").map((s) => s.id),
142
+ suggestedAction: "Determine whether you are a GPAI provider or deployer.",
143
+ });
144
+ }
145
+ const hasNoDisclosure = !hasImplementationSignals(signals, ["compliance-disclosure"]);
146
+ if (hasAI && hasNoDisclosure) {
147
+ questions.push({
148
+ id: "oq-disclosure-missing",
149
+ question: "Does your system inform users that they are interacting with AI?",
150
+ context: "No AI disclosure signals were found in the codebase. Art. 50 requires transparency for AI systems interacting with natural persons.",
151
+ relatedSignalIds: [],
152
+ suggestedAction: "Add explicit AI disclosure in user-facing entry points.",
153
+ });
154
+ }
155
+ return questions;
156
+ }
157
+ function buildSignalSummary(signals) {
158
+ const byCategory = {};
159
+ const byConfidence = {
160
+ high: 0,
161
+ medium: 0,
162
+ low: 0,
163
+ };
164
+ for (const s of signals) {
165
+ byCategory[s.category] = (byCategory[s.category] ?? 0) + 1;
166
+ byConfidence[s.confidence]++;
167
+ }
168
+ return { total: signals.length, byCategory, byConfidence };
169
+ }
170
+ export async function scanRepository(options) {
171
+ const startTime = Date.now();
172
+ const scope = options.scope ?? "production";
173
+ const discovery = await discoverFiles(options);
174
+ const allFiles = discovery.files;
175
+ const rootPkg = parseRootPackageJson(allFiles);
176
+ // Run detectors on all files
177
+ const rawSignals = [];
178
+ for (const file of allFiles) {
179
+ const ctx = { file, lines: file.content.split("\n"), allFiles, rootPackageJson: rootPkg };
180
+ rawSignals.push(...runAllDetectors(ctx));
181
+ }
182
+ const signals = annotateEvidenceProvenance(deduplicateSignals(rawSignals), allFiles);
183
+ const openQuestions = generateOpenQuestions(signals);
184
+ return {
185
+ meta: {
186
+ scannedAt: new Date().toISOString(),
187
+ scanDurationMs: Date.now() - startTime,
188
+ toolVersion: TOOL_VERSION,
189
+ targetPath: options.targetPath,
190
+ scanScope: scope,
191
+ filesScanned: allFiles.length,
192
+ filesSkipped: discovery.skippedCount,
193
+ },
194
+ repo: buildRepoProfile(options.targetPath, rootPkg, allFiles),
195
+ appTypes: inferAppTypes(signals),
196
+ signals,
197
+ openQuestions,
198
+ signalSummary: buildSignalSummary(signals),
199
+ };
200
+ }
@@ -0,0 +1,3 @@
1
+ import type { DetectedSignal, DetectorContext } from "../../evidence/types";
2
+ export declare function detectAiFrameworkSignals(ctx: DetectorContext): DetectedSignal[];
3
+ //# sourceMappingURL=ai-framework-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-framework-detector.d.ts","sourceRoot":"","sources":["../../../src/scanner/detectors/ai-framework-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAwD5E,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,EAAE,CAqC/E"}
@@ -0,0 +1,74 @@
1
+ import { SOURCE_EXTENSIONS } from "./shared";
2
+ const FRAMEWORK_IMPORTS = [
3
+ {
4
+ id: "ai-framework-langchain",
5
+ name: "LangChain",
6
+ importPattern: /(?:from\s+['"]langchain[/'"]|require\s*\(\s*['"]langchain[/'"])/,
7
+ },
8
+ {
9
+ id: "ai-framework-langchain-core",
10
+ name: "LangChain Core",
11
+ importPattern: /(?:from\s+['"]@langchain\/[^'"]+['"]|require\s*\(\s*['"]@langchain\/[^'"]+['"])/,
12
+ },
13
+ {
14
+ id: "ai-framework-llamaindex",
15
+ name: "LlamaIndex",
16
+ importPattern: /(?:from\s+['"]llamaindex[/'"]|require\s*\(\s*['"]llamaindex[/'"])/,
17
+ },
18
+ {
19
+ id: "ai-framework-transformers",
20
+ name: "Transformers.js",
21
+ importPattern: /(?:from\s+['"]@xenova\/transformers['"]|require\s*\(\s*['"]@xenova\/transformers['"])/,
22
+ },
23
+ {
24
+ id: "ai-framework-vercel-ai",
25
+ name: "Vercel AI SDK",
26
+ // Only match Vercel AI SDK subpath imports (ai/react, ai/rsc) to avoid false positives
27
+ // on generic "ai" package name. Bare "ai" import matched only with package.json cross-check.
28
+ importPattern: /(?:from\s+['"]ai\/(?:react|rsc|svelte|vue|solid|core)['"']|require\s*\(\s*['"]ai\/(?:react|rsc|svelte|vue|solid|core)['"])/,
29
+ },
30
+ {
31
+ id: "ai-framework-huggingface",
32
+ name: "HuggingFace",
33
+ importPattern: /(?:from\s+['"]@huggingface\/[^'"]+['"]|require\s*\(\s*['"]@huggingface\/[^'"]+['"])/,
34
+ },
35
+ ];
36
+ // ---------------------------------------------------------------------------
37
+ // Detector
38
+ // ---------------------------------------------------------------------------
39
+ export function detectAiFrameworkSignals(ctx) {
40
+ const { file } = ctx;
41
+ if (!SOURCE_EXTENSIONS.has(file.extension))
42
+ return [];
43
+ const lines = ctx.lines;
44
+ const signalMap = new Map();
45
+ for (let i = 0; i < lines.length; i++) {
46
+ const line = lines[i];
47
+ if (!line)
48
+ continue;
49
+ const lineNum = i + 1;
50
+ for (const framework of FRAMEWORK_IMPORTS) {
51
+ if (framework.importPattern.test(line)) {
52
+ const existing = signalMap.get(framework.id);
53
+ const evidence = {
54
+ file: file.relativePath,
55
+ line: lineNum,
56
+ snippet: line.trim().slice(0, 200),
57
+ };
58
+ if (existing) {
59
+ existing.evidence.push(evidence);
60
+ }
61
+ else {
62
+ signalMap.set(framework.id, {
63
+ id: framework.id,
64
+ name: framework.name,
65
+ category: "ai-framework",
66
+ confidence: "medium",
67
+ evidence: [evidence],
68
+ });
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return Array.from(signalMap.values());
74
+ }
@@ -0,0 +1,3 @@
1
+ import type { DetectedSignal, DetectorContext } from "../../evidence/types";
2
+ export declare function detectAiProviderSignals(ctx: DetectorContext): DetectedSignal[];
3
+ //# sourceMappingURL=ai-provider-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-provider-detector.d.ts","sourceRoot":"","sources":["../../../src/scanner/detectors/ai-provider-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAyE5E,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,EAAE,CAoC9E"}
@@ -0,0 +1,100 @@
1
+ import { SOURCE_EXTENSIONS } from "./shared";
2
+ const PROVIDER_IMPORTS = [
3
+ {
4
+ id: "ai-provider-openai",
5
+ name: "OpenAI SDK",
6
+ importPattern: /(?:from\s+['"]openai['"]|require\s*\(\s*['"]openai['"])/,
7
+ },
8
+ {
9
+ id: "ai-provider-anthropic",
10
+ name: "Anthropic SDK",
11
+ importPattern: /(?:from\s+['"]@anthropic-ai\/sdk['"]|require\s*\(\s*['"]@anthropic-ai\/sdk['"])/,
12
+ },
13
+ {
14
+ id: "ai-provider-cohere",
15
+ name: "Cohere SDK",
16
+ importPattern: /(?:from\s+['"]cohere-ai['"]|require\s*\(\s*['"]cohere-ai['"])/,
17
+ },
18
+ {
19
+ id: "ai-provider-google-genai",
20
+ name: "Google Generative AI",
21
+ importPattern: /(?:from\s+['"]@google\/generative-ai['"]|require\s*\(\s*['"]@google\/generative-ai['"])/,
22
+ },
23
+ {
24
+ id: "ai-provider-mistral",
25
+ name: "Mistral AI SDK",
26
+ importPattern: /(?:from\s+['"]@mistralai\/mistralai['"]|require\s*\(\s*['"]@mistralai\/mistralai['"])/,
27
+ },
28
+ ];
29
+ const API_ENDPOINTS = [
30
+ {
31
+ id: "ai-provider-openai-api",
32
+ name: "OpenAI API endpoint",
33
+ pattern: /api\.openai\.com/,
34
+ },
35
+ {
36
+ id: "ai-provider-anthropic-api",
37
+ name: "Anthropic API endpoint",
38
+ pattern: /api\.anthropic\.com/,
39
+ },
40
+ {
41
+ id: "ai-provider-cohere-api",
42
+ name: "Cohere API endpoint",
43
+ pattern: /api\.cohere\.ai/,
44
+ },
45
+ ];
46
+ // ---------------------------------------------------------------------------
47
+ // Detector
48
+ // ---------------------------------------------------------------------------
49
+ export function detectAiProviderSignals(ctx) {
50
+ const { file } = ctx;
51
+ if (!SOURCE_EXTENSIONS.has(file.extension))
52
+ return [];
53
+ const lines = ctx.lines;
54
+ const signalMap = new Map();
55
+ for (let i = 0; i < lines.length; i++) {
56
+ const line = lines[i];
57
+ if (!line)
58
+ continue;
59
+ const lineNum = i + 1;
60
+ // Check import/require patterns
61
+ for (const provider of PROVIDER_IMPORTS) {
62
+ if (provider.importPattern.test(line)) {
63
+ addEvidence(signalMap, provider.id, provider.name, "medium", {
64
+ file: file.relativePath,
65
+ line: lineNum,
66
+ snippet: line.trim().slice(0, 200),
67
+ });
68
+ }
69
+ }
70
+ // Check API endpoint URLs
71
+ for (const endpoint of API_ENDPOINTS) {
72
+ if (endpoint.pattern.test(line)) {
73
+ addEvidence(signalMap, endpoint.id, endpoint.name, "medium", {
74
+ file: file.relativePath,
75
+ line: lineNum,
76
+ snippet: line.trim().slice(0, 200),
77
+ });
78
+ }
79
+ }
80
+ }
81
+ return Array.from(signalMap.values());
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // Helpers
85
+ // ---------------------------------------------------------------------------
86
+ function addEvidence(map, id, name, confidence, evidence) {
87
+ const existing = map.get(id);
88
+ if (existing) {
89
+ existing.evidence.push(evidence);
90
+ }
91
+ else {
92
+ map.set(id, {
93
+ id,
94
+ name,
95
+ category: "ai-provider",
96
+ confidence,
97
+ evidence: [evidence],
98
+ });
99
+ }
100
+ }
@@ -0,0 +1,3 @@
1
+ import type { DetectedSignal, DetectorContext } from "../../evidence/types";
2
+ export declare function detectCompliance(ctx: DetectorContext): DetectedSignal[];
3
+ //# sourceMappingURL=compliance-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compliance-detector.d.ts","sourceRoot":"","sources":["../../../src/scanner/detectors/compliance-detector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,cAAc,EACd,eAAe,EAGhB,MAAM,sBAAsB,CAAC;AAqM9B,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,EAAE,CA2BvE"}
@@ -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"}