@ansvar/eu-regulations-mcp 0.8.0 → 1.1.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 (66) hide show
  1. package/README.md +76 -29
  2. package/data/regulations.db +0 -0
  3. package/data/seed/applicability/chips-act.json +67 -0
  4. package/data/seed/applicability/crma.json +85 -0
  5. package/data/seed/chips-act.json +714 -0
  6. package/data/seed/crma.json +877 -0
  7. package/data/seed/mappings/iso27001-chips-act.json +50 -0
  8. package/data/seed/mappings/iso27001-crma.json +50 -0
  9. package/data/seed/mappings/nist-csf-chips-act.json +56 -0
  10. package/data/seed/mappings/nist-csf-crma.json +56 -0
  11. package/dist/database/sqlite-adapter.d.ts +2 -2
  12. package/dist/database/sqlite-adapter.d.ts.map +1 -1
  13. package/dist/database/sqlite-adapter.js.map +1 -1
  14. package/dist/http-server.js +27 -5
  15. package/dist/http-server.js.map +1 -1
  16. package/dist/index.js +27 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/tools/about.d.ts +40 -0
  19. package/dist/tools/about.d.ts.map +1 -0
  20. package/dist/tools/about.js +61 -0
  21. package/dist/tools/about.js.map +1 -0
  22. package/dist/tools/list.d.ts +7 -0
  23. package/dist/tools/list.d.ts.map +1 -1
  24. package/dist/tools/list.js +73 -8
  25. package/dist/tools/list.js.map +1 -1
  26. package/dist/tools/registry.d.ts +11 -1
  27. package/dist/tools/registry.d.ts.map +1 -1
  28. package/dist/tools/registry.js +56 -4
  29. package/dist/tools/registry.js.map +1 -1
  30. package/dist/worker.d.ts.map +1 -1
  31. package/dist/worker.js +17 -5
  32. package/dist/worker.js.map +1 -1
  33. package/package.json +8 -7
  34. package/scripts/add-cross-references.sql +0 -200
  35. package/scripts/analyze-survey-responses.ts +0 -285
  36. package/scripts/build-db.ts +0 -421
  37. package/scripts/bulk-reingest-all.ts +0 -331
  38. package/scripts/check-updates.ts +0 -294
  39. package/scripts/extract-eprivacy-recitals.ts +0 -98
  40. package/scripts/ingest-eurlex-browser.ts +0 -113
  41. package/scripts/ingest-eurlex.ts +0 -346
  42. package/scripts/ingest-unece.ts +0 -382
  43. package/scripts/migrate-postgres.ts +0 -445
  44. package/scripts/migrate-to-postgres.ts +0 -353
  45. package/scripts/reingest-all-with-recitals.sh +0 -81
  46. package/scripts/sync-versions.ts +0 -206
  47. package/scripts/test-cross-refs.js +0 -26
  48. package/scripts/test-postgres-adapter.ts +0 -146
  49. package/scripts/update-dora-rts-metadata.ts +0 -112
  50. package/src/database/postgres-adapter.ts +0 -84
  51. package/src/database/sqlite-adapter.ts +0 -44
  52. package/src/database/types.ts +0 -10
  53. package/src/http-server.ts +0 -149
  54. package/src/index.ts +0 -61
  55. package/src/middleware/rate-limit.ts +0 -104
  56. package/src/tools/applicability.ts +0 -167
  57. package/src/tools/article.ts +0 -81
  58. package/src/tools/compare.ts +0 -217
  59. package/src/tools/definitions.ts +0 -49
  60. package/src/tools/evidence.ts +0 -84
  61. package/src/tools/list.ts +0 -124
  62. package/src/tools/map.ts +0 -86
  63. package/src/tools/recital.ts +0 -60
  64. package/src/tools/registry.ts +0 -311
  65. package/src/tools/search.ts +0 -297
  66. package/src/worker.ts +0 -708
@@ -1,285 +0,0 @@
1
- #!/usr/bin/env tsx
2
-
3
- /**
4
- * Survey Response Analysis Helper
5
- *
6
- * Helps analyze delegated acts survey responses from GitHub Discussions.
7
- * Can be run manually or integrated into workflow.
8
- *
9
- * Usage:
10
- * npx tsx scripts/analyze-survey-responses.ts [discussion-number]
11
- *
12
- * Requires:
13
- * GITHUB_TOKEN environment variable (for API access)
14
- */
15
-
16
- interface SurveyResponse {
17
- id: string;
18
- author: string;
19
- createdAt: string;
20
- urgency: string;
21
- regulations: string[];
22
- useCase: string;
23
- specificStandards: string;
24
- currentWorkaround: string;
25
- willingToHelp: string[];
26
- additionalContext: string;
27
- }
28
-
29
- interface AnalysisResults {
30
- totalResponses: number;
31
- urgencyBreakdown: Record<string, number>;
32
- topRegulations: Record<string, number>;
33
- betaTesters: number;
34
- potentialSponsors: number;
35
- qualityScore: number;
36
- recommendation: 'proceed' | 'phased' | 'defer';
37
- reasoning: string[];
38
- }
39
-
40
- const URGENCY_LEVELS = {
41
- blocking: '🔴 Blocking current work',
42
- threeMonth: '🟡 Needed within 3 months',
43
- sixMonth: '🟢 Needed within 6 months',
44
- niceToHave: '🔵 Nice to have eventually',
45
- notNeeded: '⚪ Not needed',
46
- };
47
-
48
- const DECISION_THRESHOLDS = {
49
- proceed: {
50
- minResponses: 20,
51
- minHighUrgency: 0.5,
52
- minBetaTesters: 3,
53
- },
54
- phased: {
55
- minResponses: 10,
56
- minMediumUrgency: 0.3,
57
- minBetaTesters: 1,
58
- },
59
- };
60
-
61
- /**
62
- * Parse a survey response from GitHub Discussion comment body
63
- */
64
- function parseResponse(commentBody: string, author: string, createdAt: string): SurveyResponse | null {
65
- try {
66
- // This is a simplified parser - actual implementation would parse the structured form data
67
- // from GitHub Discussion form responses
68
-
69
- const urgencyMatch = commentBody.match(/urgency[:\s]+(.+)/i);
70
- const regulationsMatch = commentBody.match(/regulations[:\s]+(.+)/i);
71
- const useCaseMatch = commentBody.match(/use case[:\s]+(.+)/i);
72
-
73
- return {
74
- id: `response-${Date.now()}`,
75
- author,
76
- createdAt,
77
- urgency: urgencyMatch?.[1] || 'unknown',
78
- regulations: regulationsMatch?.[1]?.split(',').map(r => r.trim()) || [],
79
- useCase: useCaseMatch?.[1] || '',
80
- specificStandards: '',
81
- currentWorkaround: '',
82
- willingToHelp: [],
83
- additionalContext: '',
84
- };
85
- } catch (error) {
86
- console.error('Failed to parse response:', error);
87
- return null;
88
- }
89
- }
90
-
91
- /**
92
- * Analyze collected survey responses
93
- */
94
- function analyzeResponses(responses: SurveyResponse[]): AnalysisResults {
95
- const urgencyBreakdown: Record<string, number> = {};
96
- const topRegulations: Record<string, number> = {};
97
- let betaTesters = 0;
98
- let potentialSponsors = 0;
99
- let qualityScore = 0;
100
-
101
- for (const response of responses) {
102
- // Count urgency levels
103
- urgencyBreakdown[response.urgency] = (urgencyBreakdown[response.urgency] || 0) + 1;
104
-
105
- // Count regulation requests
106
- for (const reg of response.regulations) {
107
- topRegulations[reg] = (topRegulations[reg] || 0) + 1;
108
- }
109
-
110
- // Count beta testers
111
- if (response.willingToHelp.includes('beta test')) {
112
- betaTesters++;
113
- }
114
-
115
- // Count potential sponsors
116
- if (response.willingToHelp.includes('sponsoring')) {
117
- potentialSponsors++;
118
- }
119
-
120
- // Calculate quality score (0-100)
121
- let responseQuality = 0;
122
- if (response.useCase.length > 100) responseQuality += 30;
123
- if (response.specificStandards.length > 0) responseQuality += 30;
124
- if (response.regulations.length > 0) responseQuality += 20;
125
- if (response.additionalContext.length > 50) responseQuality += 20;
126
- qualityScore += responseQuality;
127
- }
128
-
129
- qualityScore = responses.length > 0 ? qualityScore / responses.length : 0;
130
-
131
- // Determine recommendation
132
- const highUrgency = (urgencyBreakdown['🔴 Blocking current work'] || 0) +
133
- (urgencyBreakdown['🟡 Needed within 3 months'] || 0);
134
- const highUrgencyPercent = responses.length > 0 ? highUrgency / responses.length : 0;
135
-
136
- let recommendation: 'proceed' | 'phased' | 'defer' = 'defer';
137
- const reasoning: string[] = [];
138
-
139
- if (responses.length >= DECISION_THRESHOLDS.proceed.minResponses &&
140
- highUrgencyPercent >= DECISION_THRESHOLDS.proceed.minHighUrgency &&
141
- betaTesters >= DECISION_THRESHOLDS.proceed.minBetaTesters) {
142
- recommendation = 'proceed';
143
- reasoning.push(`Strong signal: ${responses.length} responses, ${Math.round(highUrgencyPercent * 100)}% high urgency`);
144
- reasoning.push(`${betaTesters} beta testers willing to help validate`);
145
- reasoning.push(`Quality score: ${Math.round(qualityScore)}/100`);
146
- } else if (responses.length >= DECISION_THRESHOLDS.phased.minResponses &&
147
- betaTesters >= DECISION_THRESHOLDS.phased.minBetaTesters) {
148
- recommendation = 'phased';
149
- reasoning.push(`Moderate signal: ${responses.length} responses`);
150
- reasoning.push('Consider phased approach starting with top-requested regulation');
151
-
152
- const topReg = Object.entries(topRegulations).sort((a, b) => b[1] - a[1])[0];
153
- if (topReg) {
154
- reasoning.push(`Start with ${topReg[0]} (${topReg[1]} requests)`);
155
- }
156
- } else {
157
- recommendation = 'defer';
158
- reasoning.push(`Insufficient signal: ${responses.length} responses (need ${DECISION_THRESHOLDS.phased.minResponses}+)`);
159
- reasoning.push(`High urgency: ${Math.round(highUrgencyPercent * 100)}%`);
160
- reasoning.push(`Beta testers: ${betaTesters}`);
161
- reasoning.push('Consider focusing on other features');
162
- }
163
-
164
- return {
165
- totalResponses: responses.length,
166
- urgencyBreakdown,
167
- topRegulations,
168
- betaTesters,
169
- potentialSponsors,
170
- qualityScore,
171
- recommendation,
172
- reasoning,
173
- };
174
- }
175
-
176
- /**
177
- * Format analysis results for display
178
- */
179
- function formatResults(results: AnalysisResults): string {
180
- const sections: string[] = [];
181
-
182
- sections.push('# Survey Analysis Results\n');
183
- sections.push(`**Total Responses:** ${results.totalResponses}`);
184
- sections.push(`**Quality Score:** ${Math.round(results.qualityScore)}/100`);
185
- sections.push(`**Recommendation:** ${results.recommendation.toUpperCase()}\n`);
186
-
187
- sections.push('## Urgency Breakdown\n');
188
- for (const [level, count] of Object.entries(results.urgencyBreakdown)) {
189
- const percent = Math.round((count / results.totalResponses) * 100);
190
- sections.push(`- ${level}: ${count} (${percent}%)`);
191
- }
192
-
193
- sections.push('\n## Top Requested Regulations\n');
194
- const sortedRegs = Object.entries(results.topRegulations)
195
- .sort((a, b) => b[1] - a[1])
196
- .slice(0, 5);
197
- for (const [reg, count] of sortedRegs) {
198
- sections.push(`- ${reg}: ${count} requests`);
199
- }
200
-
201
- sections.push('\n## Engagement\n');
202
- sections.push(`- Willing to beta test: ${results.betaTesters}`);
203
- sections.push(`- Potential sponsors: ${results.potentialSponsors}`);
204
-
205
- sections.push('\n## Recommendation Reasoning\n');
206
- for (const reason of results.reasoning) {
207
- sections.push(`- ${reason}`);
208
- }
209
-
210
- return sections.join('\n');
211
- }
212
-
213
- /**
214
- * Fetch survey responses from GitHub Discussion (placeholder)
215
- */
216
- async function fetchResponses(discussionNumber: number): Promise<SurveyResponse[]> {
217
- // This is a placeholder - actual implementation would use GitHub API
218
- console.log(`Fetching responses from discussion #${discussionNumber}...`);
219
-
220
- // In production, this would use:
221
- // - GitHub GraphQL API to fetch discussion comments
222
- // - Parse structured form responses
223
- // - Return array of SurveyResponse objects
224
-
225
- return [];
226
- }
227
-
228
- /**
229
- * Main function
230
- */
231
- async function main() {
232
- const discussionNumber = parseInt(process.argv[2] || '0', 10);
233
-
234
- if (discussionNumber === 0) {
235
- console.log('Usage: npx tsx scripts/analyze-survey-responses.ts [discussion-number]');
236
- console.log('\nExample: npx tsx scripts/analyze-survey-responses.ts 42');
237
- console.log('\nYou can find the discussion number in the URL:');
238
- console.log('https://github.com/owner/repo/discussions/42');
239
- console.log(' ^^');
240
- process.exit(1);
241
- }
242
-
243
- console.log('EU Regulations MCP - Survey Response Analyzer');
244
- console.log('='.repeat(50));
245
- console.log();
246
-
247
- // Fetch responses
248
- const responses = await fetchResponses(discussionNumber);
249
-
250
- if (responses.length === 0) {
251
- console.log('⚠️ No responses found yet.');
252
- console.log();
253
- console.log('This could mean:');
254
- console.log('- Survey just launched');
255
- console.log('- Wrong discussion number');
256
- console.log('- API token issues');
257
- console.log();
258
- console.log('Manual tracking in: docs/demand-validation-2026-q1.md');
259
- process.exit(0);
260
- }
261
-
262
- // Analyze responses
263
- const results = analyzeResponses(responses);
264
-
265
- // Display results
266
- console.log(formatResults(results));
267
- console.log();
268
- console.log('='.repeat(50));
269
- console.log(`Analysis complete. ${results.totalResponses} responses analyzed.`);
270
- console.log();
271
- console.log('Next steps:');
272
- console.log('1. Update docs/demand-validation-2026-q1.md with these results');
273
- console.log('2. Conduct user interviews with high-urgency respondents');
274
- console.log('3. Review decision criteria after survey closes');
275
- }
276
-
277
- // Run if called directly
278
- if (import.meta.url === `file://${process.argv[1]}`) {
279
- main().catch((error) => {
280
- console.error('Error:', error);
281
- process.exit(1);
282
- });
283
- }
284
-
285
- export { analyzeResponses, parseResponse, type SurveyResponse, type AnalysisResults };
@@ -1,421 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
-
3
- /**
4
- * Build the regulations.db SQLite database from seed JSON files.
5
- * Run with: npm run build:db
6
- */
7
-
8
- import Database from 'better-sqlite3';
9
- import { readFileSync, existsSync, mkdirSync, unlinkSync, readdirSync } from 'fs';
10
- import { join, dirname } from 'path';
11
- import { fileURLToPath } from 'url';
12
-
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = dirname(__filename);
15
-
16
- const DATA_DIR = join(__dirname, '..', 'data');
17
- const SEED_DIR = join(DATA_DIR, 'seed');
18
- const DB_PATH = join(DATA_DIR, 'regulations.db');
19
-
20
- const SCHEMA = `
21
- -- Core regulation metadata
22
- CREATE TABLE IF NOT EXISTS regulations (
23
- id TEXT PRIMARY KEY,
24
- full_name TEXT NOT NULL,
25
- celex_id TEXT NOT NULL,
26
- effective_date TEXT,
27
- last_amended TEXT,
28
- eur_lex_url TEXT
29
- );
30
-
31
- -- Articles table
32
- CREATE TABLE IF NOT EXISTS articles (
33
- rowid INTEGER PRIMARY KEY,
34
- regulation TEXT NOT NULL REFERENCES regulations(id),
35
- article_number TEXT NOT NULL,
36
- title TEXT,
37
- text TEXT NOT NULL,
38
- chapter TEXT,
39
- recitals TEXT,
40
- cross_references TEXT,
41
- UNIQUE(regulation, article_number)
42
- );
43
-
44
- -- FTS5 virtual table for full-text search
45
- CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts USING fts5(
46
- regulation,
47
- article_number,
48
- title,
49
- text,
50
- content='articles',
51
- content_rowid='rowid'
52
- );
53
-
54
- -- FTS5 triggers
55
- CREATE TRIGGER IF NOT EXISTS articles_ai AFTER INSERT ON articles BEGIN
56
- INSERT INTO articles_fts(rowid, regulation, article_number, title, text)
57
- VALUES (new.rowid, new.regulation, new.article_number, new.title, new.text);
58
- END;
59
-
60
- CREATE TRIGGER IF NOT EXISTS articles_ad AFTER DELETE ON articles BEGIN
61
- INSERT INTO articles_fts(articles_fts, rowid, regulation, article_number, title, text)
62
- VALUES('delete', old.rowid, old.regulation, old.article_number, old.title, old.text);
63
- END;
64
-
65
- CREATE TRIGGER IF NOT EXISTS articles_au AFTER UPDATE ON articles BEGIN
66
- INSERT INTO articles_fts(articles_fts, rowid, regulation, article_number, title, text)
67
- VALUES('delete', old.rowid, old.regulation, old.article_number, old.title, old.text);
68
- INSERT INTO articles_fts(rowid, regulation, article_number, title, text)
69
- VALUES (new.rowid, new.regulation, new.article_number, new.title, new.text);
70
- END;
71
-
72
- -- Definitions
73
- CREATE TABLE IF NOT EXISTS definitions (
74
- id INTEGER PRIMARY KEY,
75
- regulation TEXT NOT NULL REFERENCES regulations(id),
76
- term TEXT NOT NULL,
77
- definition TEXT NOT NULL,
78
- article TEXT NOT NULL,
79
- UNIQUE(regulation, term)
80
- );
81
-
82
- -- Control mappings
83
- CREATE TABLE IF NOT EXISTS control_mappings (
84
- id INTEGER PRIMARY KEY,
85
- framework TEXT NOT NULL DEFAULT 'ISO27001',
86
- control_id TEXT NOT NULL,
87
- control_name TEXT NOT NULL,
88
- regulation TEXT NOT NULL REFERENCES regulations(id),
89
- articles TEXT NOT NULL,
90
- coverage TEXT CHECK(coverage IN ('full', 'partial', 'related')),
91
- notes TEXT
92
- );
93
-
94
- -- Applicability rules
95
- CREATE TABLE IF NOT EXISTS applicability_rules (
96
- id INTEGER PRIMARY KEY,
97
- regulation TEXT NOT NULL REFERENCES regulations(id),
98
- sector TEXT NOT NULL,
99
- subsector TEXT,
100
- applies INTEGER NOT NULL,
101
- confidence TEXT CHECK(confidence IN ('definite', 'likely', 'possible')),
102
- basis_article TEXT,
103
- notes TEXT
104
- );
105
-
106
- -- Source registry for tracking data quality
107
- CREATE TABLE IF NOT EXISTS source_registry (
108
- regulation TEXT PRIMARY KEY REFERENCES regulations(id),
109
- celex_id TEXT NOT NULL,
110
- eur_lex_version TEXT,
111
- last_fetched TEXT,
112
- articles_expected INTEGER,
113
- articles_parsed INTEGER,
114
- quality_status TEXT CHECK(quality_status IN ('complete', 'review', 'incomplete')),
115
- notes TEXT
116
- );
117
-
118
- -- Recitals table
119
- CREATE TABLE IF NOT EXISTS recitals (
120
- id INTEGER PRIMARY KEY,
121
- regulation TEXT NOT NULL REFERENCES regulations(id),
122
- recital_number INTEGER NOT NULL,
123
- text TEXT NOT NULL,
124
- related_articles TEXT,
125
- UNIQUE(regulation, recital_number)
126
- );
127
-
128
- -- FTS5 virtual table for recital search
129
- CREATE VIRTUAL TABLE IF NOT EXISTS recitals_fts USING fts5(
130
- regulation,
131
- recital_number,
132
- text,
133
- content='recitals',
134
- content_rowid='id'
135
- );
136
-
137
- -- FTS5 triggers for recitals
138
- CREATE TRIGGER IF NOT EXISTS recitals_ai AFTER INSERT ON recitals BEGIN
139
- INSERT INTO recitals_fts(rowid, regulation, recital_number, text)
140
- VALUES (new.id, new.regulation, new.recital_number, new.text);
141
- END;
142
-
143
- CREATE TRIGGER IF NOT EXISTS recitals_ad AFTER DELETE ON recitals BEGIN
144
- INSERT INTO recitals_fts(recitals_fts, rowid, regulation, recital_number, text)
145
- VALUES('delete', old.id, old.regulation, old.recital_number, old.text);
146
- END;
147
-
148
- CREATE TRIGGER IF NOT EXISTS recitals_au AFTER UPDATE ON recitals BEGIN
149
- INSERT INTO recitals_fts(recitals_fts, rowid, regulation, recital_number, text)
150
- VALUES('delete', old.id, old.regulation, old.recital_number, old.text);
151
- INSERT INTO recitals_fts(rowid, regulation, recital_number, text)
152
- VALUES (new.id, new.regulation, new.recital_number, new.text);
153
- END;
154
-
155
- -- Evidence requirements table
156
- CREATE TABLE IF NOT EXISTS evidence_requirements (
157
- id INTEGER PRIMARY KEY,
158
- regulation TEXT NOT NULL REFERENCES regulations(id),
159
- article TEXT NOT NULL,
160
- requirement_summary TEXT NOT NULL,
161
- evidence_type TEXT NOT NULL CHECK(evidence_type IN ('document', 'log', 'test_result', 'certification', 'policy', 'procedure')),
162
- artifact_name TEXT NOT NULL,
163
- artifact_example TEXT,
164
- description TEXT,
165
- retention_period TEXT,
166
- auditor_questions TEXT,
167
- maturity_levels TEXT,
168
- cross_references TEXT
169
- );
170
- `;
171
-
172
- interface RegulationSeed {
173
- id: string;
174
- full_name: string;
175
- celex_id: string;
176
- effective_date?: string;
177
- eur_lex_url?: string;
178
- articles: Array<{
179
- number: string;
180
- title?: string;
181
- text: string;
182
- chapter?: string;
183
- recitals?: string[];
184
- cross_references?: string[];
185
- }>;
186
- definitions?: Array<{
187
- term: string;
188
- definition: string;
189
- article: string;
190
- }>;
191
- recitals?: Array<{
192
- recital_number: number;
193
- text: string;
194
- related_articles?: string;
195
- }>;
196
- }
197
-
198
- function buildDatabase() {
199
- console.log('Building regulations database...');
200
-
201
- // Ensure data directory exists
202
- if (!existsSync(DATA_DIR)) {
203
- mkdirSync(DATA_DIR, { recursive: true });
204
- }
205
-
206
- // Delete existing database
207
- if (existsSync(DB_PATH)) {
208
- console.log('Removing existing database...');
209
- unlinkSync(DB_PATH);
210
- }
211
-
212
- // Create new database
213
- const db = new Database(DB_PATH);
214
- db.pragma('foreign_keys = ON');
215
-
216
- // Create schema
217
- console.log('Creating schema...');
218
- db.exec(SCHEMA);
219
-
220
- // Load and insert seed files
221
- if (existsSync(SEED_DIR)) {
222
- const seedFiles = readdirSync(SEED_DIR).filter((f: string) => f.endsWith('.json'));
223
-
224
- for (const file of seedFiles) {
225
- if (file.startsWith('mappings')) continue;
226
-
227
- console.log(`Loading ${file}...`);
228
- const content = readFileSync(join(SEED_DIR, file), 'utf-8');
229
- const regulation: RegulationSeed = JSON.parse(content);
230
-
231
- // Insert regulation
232
- db.prepare(`
233
- INSERT INTO regulations (id, full_name, celex_id, effective_date, eur_lex_url)
234
- VALUES (?, ?, ?, ?, ?)
235
- `).run(
236
- regulation.id,
237
- regulation.full_name,
238
- regulation.celex_id,
239
- regulation.effective_date || null,
240
- regulation.eur_lex_url || null
241
- );
242
-
243
- // Insert articles
244
- const insertArticle = db.prepare(`
245
- INSERT INTO articles (regulation, article_number, title, text, chapter, recitals, cross_references)
246
- VALUES (?, ?, ?, ?, ?, ?, ?)
247
- `);
248
-
249
- for (const article of regulation.articles) {
250
- insertArticle.run(
251
- regulation.id,
252
- article.number,
253
- article.title || null,
254
- article.text,
255
- article.chapter || null,
256
- article.recitals ? JSON.stringify(article.recitals) : null,
257
- article.cross_references ? JSON.stringify(article.cross_references) : null
258
- );
259
- }
260
-
261
- // Insert definitions
262
- if (regulation.definitions) {
263
- const insertDefinition = db.prepare(`
264
- INSERT OR IGNORE INTO definitions (regulation, term, definition, article)
265
- VALUES (?, ?, ?, ?)
266
- `);
267
-
268
- for (const def of regulation.definitions) {
269
- insertDefinition.run(regulation.id, def.term, def.definition, def.article);
270
- }
271
- }
272
-
273
- // Insert recitals
274
- if (regulation.recitals) {
275
- const insertRecital = db.prepare(`
276
- INSERT OR IGNORE INTO recitals (regulation, recital_number, text, related_articles)
277
- VALUES (?, ?, ?, ?)
278
- `);
279
-
280
- for (const recital of regulation.recitals) {
281
- insertRecital.run(
282
- regulation.id,
283
- recital.recital_number,
284
- recital.text,
285
- recital.related_articles ? JSON.stringify(recital.related_articles) : null
286
- );
287
- }
288
- }
289
-
290
- // Update source registry with timestamps
291
- const now = new Date().toISOString();
292
- const eurLexVersion = regulation.effective_date || now.split('T')[0];
293
- db.prepare(`
294
- INSERT INTO source_registry (regulation, celex_id, eur_lex_version, last_fetched, articles_expected, articles_parsed, quality_status)
295
- VALUES (?, ?, ?, ?, ?, ?, 'complete')
296
- `).run(regulation.id, regulation.celex_id, eurLexVersion, now, regulation.articles.length, regulation.articles.length);
297
-
298
- console.log(` Loaded ${regulation.articles.length} articles, ${regulation.definitions?.length || 0} definitions`);
299
- if (regulation.recitals && regulation.recitals.length > 0) {
300
- console.log(` Loaded ${regulation.recitals.length} recitals`);
301
- }
302
- }
303
-
304
- // Load mappings
305
- const mappingsDir = join(SEED_DIR, 'mappings');
306
- if (existsSync(mappingsDir)) {
307
- const mappingFiles = readdirSync(mappingsDir).filter((f: string) => f.endsWith('.json'));
308
-
309
- for (const file of mappingFiles) {
310
- console.log(`Loading mappings from ${file}...`);
311
- const content = readFileSync(join(mappingsDir, file), 'utf-8');
312
- const mappings = JSON.parse(content);
313
-
314
- // Detect framework from filename
315
- let framework = 'ISO27001';
316
- if (file.startsWith('nist-csf-')) {
317
- framework = 'NIST_CSF';
318
- } else if (file.startsWith('iso27001-')) {
319
- framework = 'ISO27001';
320
- }
321
-
322
- const insertMapping = db.prepare(`
323
- INSERT INTO control_mappings (framework, control_id, control_name, regulation, articles, coverage, notes)
324
- VALUES (?, ?, ?, ?, ?, ?, ?)
325
- `);
326
-
327
- for (const mapping of mappings) {
328
- insertMapping.run(
329
- framework,
330
- mapping.control_id,
331
- mapping.control_name,
332
- mapping.regulation,
333
- JSON.stringify(mapping.articles),
334
- mapping.coverage,
335
- mapping.notes || null
336
- );
337
- }
338
-
339
- console.log(` Loaded ${mappings.length} ${framework} control mappings`);
340
- }
341
- }
342
-
343
- // Load applicability rules
344
- const applicabilityDir = join(SEED_DIR, 'applicability');
345
- if (existsSync(applicabilityDir)) {
346
- const applicabilityFiles = readdirSync(applicabilityDir).filter((f: string) => f.endsWith('.json'));
347
-
348
- const insertApplicability = db.prepare(`
349
- INSERT INTO applicability_rules (regulation, sector, subsector, applies, confidence, basis_article, notes)
350
- VALUES (?, ?, ?, ?, ?, ?, ?)
351
- `);
352
-
353
- for (const file of applicabilityFiles) {
354
- console.log(`Loading applicability rules from ${file}...`);
355
- const content = readFileSync(join(applicabilityDir, file), 'utf-8');
356
- const rules = JSON.parse(content);
357
-
358
- for (const rule of rules) {
359
- insertApplicability.run(
360
- rule.regulation,
361
- rule.sector,
362
- rule.subsector || null,
363
- rule.applies ? 1 : 0,
364
- rule.confidence,
365
- rule.basis_article || null,
366
- rule.notes || null
367
- );
368
- }
369
-
370
- console.log(` Loaded ${rules.length} applicability rules`);
371
- }
372
- }
373
-
374
- // Load evidence requirements
375
- const evidenceDir = join(SEED_DIR, 'evidence');
376
- if (existsSync(evidenceDir)) {
377
- const evidenceFiles = readdirSync(evidenceDir).filter((f: string) => f.endsWith('.json'));
378
-
379
- const insertEvidence = db.prepare(`
380
- INSERT INTO evidence_requirements (
381
- regulation, article, requirement_summary, evidence_type,
382
- artifact_name, artifact_example, description, retention_period,
383
- auditor_questions, maturity_levels, cross_references
384
- )
385
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
386
- `);
387
-
388
- for (const file of evidenceFiles) {
389
- console.log(`Loading evidence requirements from ${file}...`);
390
- const content = readFileSync(join(evidenceDir, file), 'utf-8');
391
- const requirements = JSON.parse(content);
392
-
393
- for (const req of requirements) {
394
- insertEvidence.run(
395
- req.regulation,
396
- req.article,
397
- req.requirement_summary,
398
- req.evidence_type,
399
- req.artifact_name,
400
- req.artifact_example || null,
401
- req.description || null,
402
- req.retention_period || null,
403
- req.auditor_questions ? JSON.stringify(req.auditor_questions) : null,
404
- req.maturity_levels ? JSON.stringify(req.maturity_levels) : null,
405
- req.cross_references ? JSON.stringify(req.cross_references) : null
406
- );
407
- }
408
-
409
- console.log(` Loaded ${requirements.length} evidence requirements`);
410
- }
411
- }
412
- } else {
413
- console.log('No seed directory found. Database created with empty tables.');
414
- console.log(`Create seed files in: ${SEED_DIR}`);
415
- }
416
-
417
- db.close();
418
- console.log(`\nDatabase created at: ${DB_PATH}`);
419
- }
420
-
421
- buildDatabase();