@ansvar/eu-regulations-mcp 1.0.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 (58) hide show
  1. package/README.md +60 -22
  2. package/data/regulations.db +0 -0
  3. package/dist/database/sqlite-adapter.d.ts +2 -2
  4. package/dist/database/sqlite-adapter.d.ts.map +1 -1
  5. package/dist/database/sqlite-adapter.js.map +1 -1
  6. package/dist/http-server.js +27 -5
  7. package/dist/http-server.js.map +1 -1
  8. package/dist/index.js +27 -4
  9. package/dist/index.js.map +1 -1
  10. package/dist/tools/about.d.ts +40 -0
  11. package/dist/tools/about.d.ts.map +1 -0
  12. package/dist/tools/about.js +61 -0
  13. package/dist/tools/about.js.map +1 -0
  14. package/dist/tools/list.d.ts +7 -0
  15. package/dist/tools/list.d.ts.map +1 -1
  16. package/dist/tools/list.js +73 -8
  17. package/dist/tools/list.js.map +1 -1
  18. package/dist/tools/registry.d.ts +11 -1
  19. package/dist/tools/registry.d.ts.map +1 -1
  20. package/dist/tools/registry.js +56 -4
  21. package/dist/tools/registry.js.map +1 -1
  22. package/dist/worker.d.ts.map +1 -1
  23. package/dist/worker.js +17 -5
  24. package/dist/worker.js.map +1 -1
  25. package/package.json +6 -5
  26. package/scripts/add-cross-references.sql +0 -200
  27. package/scripts/analyze-survey-responses.ts +0 -285
  28. package/scripts/build-db.ts +0 -421
  29. package/scripts/bulk-reingest-all.ts +0 -331
  30. package/scripts/check-updates.ts +0 -294
  31. package/scripts/extract-eprivacy-recitals.ts +0 -98
  32. package/scripts/ingest-eurlex-browser.ts +0 -113
  33. package/scripts/ingest-eurlex.ts +0 -349
  34. package/scripts/ingest-unece.ts +0 -382
  35. package/scripts/migrate-postgres.ts +0 -445
  36. package/scripts/migrate-to-postgres.ts +0 -353
  37. package/scripts/reingest-all-with-recitals.sh +0 -81
  38. package/scripts/sync-versions.ts +0 -206
  39. package/scripts/test-cross-refs.js +0 -26
  40. package/scripts/test-postgres-adapter.ts +0 -146
  41. package/scripts/update-dora-rts-metadata.ts +0 -112
  42. package/src/database/postgres-adapter.ts +0 -84
  43. package/src/database/sqlite-adapter.ts +0 -44
  44. package/src/database/types.ts +0 -10
  45. package/src/http-server.ts +0 -149
  46. package/src/index.ts +0 -61
  47. package/src/middleware/rate-limit.ts +0 -104
  48. package/src/tools/applicability.ts +0 -167
  49. package/src/tools/article.ts +0 -81
  50. package/src/tools/compare.ts +0 -217
  51. package/src/tools/definitions.ts +0 -49
  52. package/src/tools/evidence.ts +0 -84
  53. package/src/tools/list.ts +0 -124
  54. package/src/tools/map.ts +0 -86
  55. package/src/tools/recital.ts +0 -60
  56. package/src/tools/registry.ts +0 -311
  57. package/src/tools/search.ts +0 -297
  58. package/src/worker.ts +0 -708
@@ -1,445 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
-
3
- /**
4
- * Migrate SQLite database to PostgreSQL
5
- *
6
- * Usage:
7
- * DATABASE_URL=postgresql://... npx tsx scripts/migrate-postgres.ts
8
- *
9
- * Options:
10
- * --drop Drop existing tables before creating (DESTRUCTIVE)
11
- * --verify Verify migration without making changes
12
- */
13
-
14
- import Database from 'better-sqlite3';
15
- import pg from 'pg';
16
- import { fileURLToPath } from 'url';
17
- import { dirname, join } from 'path';
18
-
19
- const __filename = fileURLToPath(import.meta.url);
20
- const __dirname = dirname(__filename);
21
- const SQLITE_PATH = join(__dirname, '../data/regulations.db');
22
-
23
- // Parse command line args
24
- const args = process.argv.slice(2);
25
- const DROP_TABLES = args.includes('--drop');
26
- const VERIFY_ONLY = args.includes('--verify');
27
-
28
- interface MigrationStats {
29
- regulations: number;
30
- articles: number;
31
- recitals: number;
32
- definitions: number;
33
- control_mappings: number;
34
- applicability_rules: number;
35
- source_registry: number;
36
- }
37
-
38
- async function main() {
39
- console.log('🚀 PostgreSQL Migration Tool\n');
40
-
41
- // Get connection string
42
- const connectionString = process.env.DATABASE_URL;
43
- if (!connectionString) {
44
- console.error('❌ DATABASE_URL environment variable is required');
45
- process.exit(1);
46
- }
47
-
48
- console.log(`📊 SQLite Source: ${SQLITE_PATH}`);
49
- console.log(`🐘 PostgreSQL Target: ${connectionString.replace(/:[^:@]+@/, ':***@')}\n`);
50
-
51
- if (VERIFY_ONLY) {
52
- console.log('🔍 Verification mode - no changes will be made\n');
53
- }
54
-
55
- if (DROP_TABLES) {
56
- console.log('⚠️ DROP MODE - All existing data will be deleted!\n');
57
- }
58
-
59
- // Connect to databases
60
- console.log('Connecting to databases...');
61
- const sqlite = new Database(SQLITE_PATH, { readonly: true });
62
- const pgPool = new pg.Pool({ connectionString });
63
-
64
- try {
65
- // Test PostgreSQL connection
66
- await pgPool.query('SELECT 1');
67
- console.log('✅ Connected to PostgreSQL\n');
68
-
69
- if (VERIFY_ONLY) {
70
- await verifyMigration(sqlite, pgPool);
71
- } else {
72
- await migrate(sqlite, pgPool, DROP_TABLES);
73
- }
74
- } catch (error) {
75
- console.error('❌ Migration failed:', error);
76
- process.exit(1);
77
- } finally {
78
- sqlite.close();
79
- await pgPool.end();
80
- }
81
- }
82
-
83
- async function migrate(sqlite: Database.Database, pgPool: pg.Pool, dropTables: boolean) {
84
- console.log('='.repeat(60));
85
- console.log('STARTING MIGRATION');
86
- console.log('='.repeat(60));
87
- console.log();
88
-
89
- const client = await pgPool.connect();
90
- try {
91
- await client.query('BEGIN');
92
-
93
- // Step 1: Drop tables if requested
94
- if (dropTables) {
95
- console.log('🗑️ Dropping existing tables...');
96
- await dropAllTables(client);
97
- console.log('✅ Tables dropped\n');
98
- }
99
-
100
- // Step 2: Create schema
101
- console.log('📐 Creating PostgreSQL schema...');
102
- await createSchema(client);
103
- console.log('✅ Schema created\n');
104
-
105
- // Step 3: Copy data
106
- console.log('📦 Copying data from SQLite...');
107
- const stats = await copyData(sqlite, client);
108
- console.log('✅ Data copied\n');
109
-
110
- // Step 4: Create indexes
111
- console.log('🔍 Creating indexes...');
112
- await createIndexes(client);
113
- console.log('✅ Indexes created\n');
114
-
115
- // Step 5: Create FTS
116
- console.log('🔎 Creating full-text search indexes...');
117
- await createFullTextSearch(client);
118
- console.log('✅ Full-text search ready\n');
119
-
120
- await client.query('COMMIT');
121
-
122
- // Step 6: Print summary
123
- console.log('='.repeat(60));
124
- console.log('MIGRATION COMPLETE');
125
- console.log('='.repeat(60));
126
- console.log('\nMigrated records:');
127
- console.log(` Regulations: ${stats.regulations}`);
128
- console.log(` Articles: ${stats.articles}`);
129
- console.log(` Recitals: ${stats.recitals}`);
130
- console.log(` Definitions: ${stats.definitions}`);
131
- console.log(` Control Mappings: ${stats.control_mappings}`);
132
- console.log(` Applicability Rules:${stats.applicability_rules}`);
133
- console.log(` Source Registry: ${stats.source_registry}`);
134
- console.log(`\n✅ Total: ${Object.values(stats).reduce((a, b) => a + b, 0)} records\n`);
135
- } catch (error) {
136
- await client.query('ROLLBACK');
137
- throw error;
138
- } finally {
139
- client.release();
140
- }
141
- }
142
-
143
- async function dropAllTables(client: pg.PoolClient) {
144
- const tables = [
145
- 'applicability_rules',
146
- 'control_mappings',
147
- 'definitions',
148
- 'recitals',
149
- 'articles',
150
- 'source_registry',
151
- 'regulations'
152
- ];
153
-
154
- for (const table of tables) {
155
- await client.query(`DROP TABLE IF EXISTS ${table} CASCADE`);
156
- }
157
- }
158
-
159
- async function createSchema(client: pg.PoolClient) {
160
- // Regulations table
161
- await client.query(`
162
- CREATE TABLE IF NOT EXISTS regulations (
163
- id TEXT PRIMARY KEY,
164
- full_name TEXT NOT NULL,
165
- celex_id TEXT NOT NULL,
166
- effective_date TEXT,
167
- last_amended TEXT,
168
- eur_lex_url TEXT
169
- )
170
- `);
171
-
172
- // Source registry table
173
- await client.query(`
174
- CREATE TABLE IF NOT EXISTS source_registry (
175
- regulation TEXT PRIMARY KEY REFERENCES regulations(id),
176
- celex_id TEXT NOT NULL,
177
- eur_lex_version TEXT,
178
- last_fetched TEXT,
179
- articles_expected INTEGER,
180
- articles_parsed INTEGER,
181
- quality_status TEXT CHECK(quality_status IN ('complete', 'review', 'incomplete')),
182
- notes TEXT
183
- )
184
- `);
185
-
186
- // Articles table
187
- await client.query(`
188
- CREATE TABLE IF NOT EXISTS articles (
189
- id SERIAL PRIMARY KEY,
190
- regulation TEXT NOT NULL REFERENCES regulations(id),
191
- article_number TEXT NOT NULL,
192
- title TEXT,
193
- text TEXT NOT NULL,
194
- chapter TEXT,
195
- recitals TEXT,
196
- cross_references TEXT,
197
- search_vector tsvector,
198
- UNIQUE(regulation, article_number)
199
- )
200
- `);
201
-
202
- // Recitals table
203
- await client.query(`
204
- CREATE TABLE IF NOT EXISTS recitals (
205
- id SERIAL PRIMARY KEY,
206
- regulation TEXT NOT NULL REFERENCES regulations(id),
207
- recital_number INTEGER NOT NULL,
208
- text TEXT NOT NULL,
209
- related_articles TEXT,
210
- search_vector tsvector,
211
- UNIQUE(regulation, recital_number)
212
- )
213
- `);
214
-
215
- // Definitions table
216
- await client.query(`
217
- CREATE TABLE IF NOT EXISTS definitions (
218
- id SERIAL PRIMARY KEY,
219
- regulation TEXT NOT NULL REFERENCES regulations(id),
220
- term TEXT NOT NULL,
221
- definition TEXT NOT NULL,
222
- article TEXT NOT NULL,
223
- UNIQUE(regulation, term)
224
- )
225
- `);
226
-
227
- // Control mappings table
228
- await client.query(`
229
- CREATE TABLE IF NOT EXISTS control_mappings (
230
- id SERIAL PRIMARY KEY,
231
- framework TEXT NOT NULL DEFAULT 'ISO27001',
232
- control_id TEXT NOT NULL,
233
- control_name TEXT NOT NULL,
234
- regulation TEXT NOT NULL REFERENCES regulations(id),
235
- articles TEXT NOT NULL,
236
- coverage TEXT CHECK(coverage IN ('full', 'partial', 'related')),
237
- notes TEXT
238
- )
239
- `);
240
-
241
- // Applicability rules table
242
- await client.query(`
243
- CREATE TABLE IF NOT EXISTS applicability_rules (
244
- id SERIAL PRIMARY KEY,
245
- regulation TEXT NOT NULL REFERENCES regulations(id),
246
- sector TEXT NOT NULL,
247
- subsector TEXT,
248
- applies INTEGER NOT NULL,
249
- confidence TEXT CHECK(confidence IN ('definite', 'likely', 'possible')),
250
- basis_article TEXT,
251
- notes TEXT
252
- )
253
- `);
254
- }
255
-
256
- async function copyData(sqlite: Database.Database, client: pg.PoolClient): Promise<MigrationStats> {
257
- const stats: MigrationStats = {
258
- regulations: 0,
259
- articles: 0,
260
- recitals: 0,
261
- definitions: 0,
262
- control_mappings: 0,
263
- applicability_rules: 0,
264
- source_registry: 0
265
- };
266
-
267
- // Copy regulations
268
- const regulations = sqlite.prepare('SELECT * FROM regulations').all();
269
- for (const reg of regulations) {
270
- await client.query(
271
- 'INSERT INTO regulations (id, full_name, celex_id, effective_date, last_amended, eur_lex_url) VALUES ($1, $2, $3, $4, $5, $6)',
272
- [reg.id, reg.full_name, reg.celex_id, reg.effective_date, reg.last_amended, reg.eur_lex_url]
273
- );
274
- stats.regulations++;
275
- }
276
- console.log(` ✓ Copied ${stats.regulations} regulations`);
277
-
278
- // Copy source registry
279
- const sourceRegistry = sqlite.prepare('SELECT * FROM source_registry').all();
280
- for (const sr of sourceRegistry) {
281
- await client.query(
282
- 'INSERT INTO source_registry (regulation, celex_id, eur_lex_version, last_fetched, articles_expected, articles_parsed, quality_status, notes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
283
- [sr.regulation, sr.celex_id, sr.eur_lex_version, sr.last_fetched, sr.articles_expected, sr.articles_parsed, sr.quality_status, sr.notes]
284
- );
285
- stats.source_registry++;
286
- }
287
- console.log(` ✓ Copied ${stats.source_registry} source registry entries`);
288
-
289
- // Copy articles
290
- const articles = sqlite.prepare('SELECT * FROM articles').all();
291
- for (const art of articles) {
292
- await client.query(
293
- 'INSERT INTO articles (regulation, article_number, title, text, chapter, recitals, cross_references) VALUES ($1, $2, $3, $4, $5, $6, $7)',
294
- [art.regulation, art.article_number, art.title, art.text, art.chapter, art.recitals, art.cross_references]
295
- );
296
- stats.articles++;
297
- }
298
- console.log(` ✓ Copied ${stats.articles} articles`);
299
-
300
- // Copy recitals
301
- const recitals = sqlite.prepare('SELECT * FROM recitals').all();
302
- for (const rec of recitals) {
303
- await client.query(
304
- 'INSERT INTO recitals (regulation, recital_number, text, related_articles) VALUES ($1, $2, $3, $4)',
305
- [rec.regulation, rec.recital_number, rec.text, rec.related_articles]
306
- );
307
- stats.recitals++;
308
- }
309
- console.log(` ✓ Copied ${stats.recitals} recitals`);
310
-
311
- // Copy definitions
312
- const definitions = sqlite.prepare('SELECT * FROM definitions').all();
313
- for (const def of definitions) {
314
- await client.query(
315
- 'INSERT INTO definitions (regulation, term, definition, article) VALUES ($1, $2, $3, $4)',
316
- [def.regulation, def.term, def.definition, def.article]
317
- );
318
- stats.definitions++;
319
- }
320
- console.log(` ✓ Copied ${stats.definitions} definitions`);
321
-
322
- // Copy control mappings
323
- const mappings = sqlite.prepare('SELECT * FROM control_mappings').all();
324
- for (const map of mappings) {
325
- await client.query(
326
- 'INSERT INTO control_mappings (framework, control_id, control_name, regulation, articles, coverage, notes) VALUES ($1, $2, $3, $4, $5, $6, $7)',
327
- [map.framework, map.control_id, map.control_name, map.regulation, map.articles, map.coverage, map.notes]
328
- );
329
- stats.control_mappings++;
330
- }
331
- console.log(` ✓ Copied ${stats.control_mappings} control mappings`);
332
-
333
- // Copy applicability rules
334
- const rules = sqlite.prepare('SELECT * FROM applicability_rules').all();
335
- for (const rule of rules) {
336
- await client.query(
337
- 'INSERT INTO applicability_rules (regulation, sector, subsector, applies, confidence, basis_article, notes) VALUES ($1, $2, $3, $4, $5, $6, $7)',
338
- [rule.regulation, rule.sector, rule.subsector, rule.applies, rule.confidence, rule.basis_article, rule.notes]
339
- );
340
- stats.applicability_rules++;
341
- }
342
- console.log(` ✓ Copied ${stats.applicability_rules} applicability rules`);
343
-
344
- return stats;
345
- }
346
-
347
- async function createIndexes(client: pg.PoolClient) {
348
- await client.query('CREATE INDEX IF NOT EXISTS idx_articles_regulation ON articles(regulation)');
349
- await client.query('CREATE INDEX IF NOT EXISTS idx_recitals_regulation ON recitals(regulation)');
350
- await client.query('CREATE INDEX IF NOT EXISTS idx_definitions_regulation ON definitions(regulation)');
351
- await client.query('CREATE INDEX IF NOT EXISTS idx_definitions_term ON definitions(term)');
352
- await client.query('CREATE INDEX IF NOT EXISTS idx_control_mappings_framework ON control_mappings(framework, regulation)');
353
- await client.query('CREATE INDEX IF NOT EXISTS idx_applicability_sector ON applicability_rules(sector, regulation)');
354
- }
355
-
356
- async function createFullTextSearch(client: pg.PoolClient) {
357
- // Update search_vector for articles
358
- await client.query(`
359
- UPDATE articles
360
- SET search_vector = to_tsvector('english', coalesce(title, '') || ' ' || text)
361
- `);
362
-
363
- // Create GIN index for articles
364
- await client.query('CREATE INDEX IF NOT EXISTS idx_articles_search ON articles USING gin(search_vector)');
365
-
366
- // Update search_vector for recitals
367
- await client.query(`
368
- UPDATE recitals
369
- SET search_vector = to_tsvector('english', text)
370
- `);
371
-
372
- // Create GIN index for recitals
373
- await client.query('CREATE INDEX IF NOT EXISTS idx_recitals_search ON recitals USING gin(search_vector)');
374
- }
375
-
376
- async function verifyMigration(sqlite: Database.Database, pgPool: pg.Pool) {
377
- console.log('Verifying migration...\n');
378
-
379
- const sqliteCounts = {
380
- regulations: sqlite.prepare('SELECT COUNT(*) as count FROM regulations').get().count,
381
- articles: sqlite.prepare('SELECT COUNT(*) as count FROM articles').get().count,
382
- recitals: sqlite.prepare('SELECT COUNT(*) as count FROM recitals').get().count,
383
- definitions: sqlite.prepare('SELECT COUNT(*) as count FROM definitions').get().count,
384
- control_mappings: sqlite.prepare('SELECT COUNT(*) as count FROM control_mappings').get().count,
385
- applicability_rules: sqlite.prepare('SELECT COUNT(*) as count FROM applicability_rules').get().count
386
- };
387
-
388
- const pgCounts = {
389
- regulations: (await pgPool.query('SELECT COUNT(*) as count FROM regulations')).rows[0].count,
390
- articles: (await pgPool.query('SELECT COUNT(*) as count FROM articles')).rows[0].count,
391
- recitals: (await pgPool.query('SELECT COUNT(*) as count FROM recitals')).rows[0].count,
392
- definitions: (await pgPool.query('SELECT COUNT(*) as count FROM definitions')).rows[0].count,
393
- control_mappings: (await pgPool.query('SELECT COUNT(*) as count FROM control_mappings')).rows[0].count,
394
- applicability_rules: (await pgPool.query('SELECT COUNT(*) as count FROM applicability_rules')).rows[0].count
395
- };
396
-
397
- console.log('Record counts:');
398
- console.log('Table | SQLite | PostgreSQL | Status');
399
- console.log('----------------------|--------|------------|-------');
400
-
401
- let allMatch = true;
402
- for (const table of Object.keys(sqliteCounts)) {
403
- const sqliteCount = sqliteCounts[table];
404
- const pgCount = parseInt(pgCounts[table]);
405
- const match = sqliteCount === pgCount;
406
- allMatch = allMatch && match;
407
- const status = match ? '✅' : '❌';
408
- console.log(`${table.padEnd(21)} | ${String(sqliteCount).padStart(6)} | ${String(pgCount).padStart(10)} | ${status}`);
409
- }
410
-
411
- console.log();
412
- if (allMatch) {
413
- console.log('✅ All tables match!\n');
414
- } else {
415
- console.log('❌ Some tables do not match\n');
416
- process.exit(1);
417
- }
418
-
419
- // Test FTS
420
- console.log('Testing full-text search...');
421
- const ftsResult = await pgPool.query(`
422
- SELECT regulation, article_number, title
423
- FROM articles
424
- WHERE search_vector @@ to_tsquery('english', 'incident')
425
- LIMIT 3
426
- `);
427
-
428
- if (ftsResult.rows.length > 0) {
429
- console.log(`✅ FTS working (found ${ftsResult.rows.length} results for "incident")\n`);
430
- ftsResult.rows.forEach(row => {
431
- console.log(` ${row.regulation} Article ${row.article_number}: ${row.title}`);
432
- });
433
- console.log();
434
- } else {
435
- console.log('❌ FTS not working\n');
436
- process.exit(1);
437
- }
438
-
439
- console.log('✅ Verification complete\n');
440
- }
441
-
442
- main().catch(error => {
443
- console.error('Fatal error:', error);
444
- process.exit(1);
445
- });