@ansvar/us-regulations-mcp 1.0.0 → 1.2.1
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.
- package/README.md +422 -79
- package/data/regulations.db +0 -0
- package/data/seed/colorado-cpa.json +97 -0
- package/data/seed/ffiec.json +103 -0
- package/data/seed/mappings/ccpa-nist-csf.json +11 -1
- package/data/seed/mappings/hipaa-nist-800-53.json +10 -1
- package/data/seed/nydfs.json +122 -0
- package/data/seed/sox.json +109 -0
- package/dist/index.js +1 -1
- package/dist/ingest/adapters/colorado-public.d.ts +25 -0
- package/dist/ingest/adapters/colorado-public.d.ts.map +1 -0
- package/dist/ingest/adapters/colorado-public.js +76 -0
- package/dist/ingest/adapters/colorado-public.js.map +1 -0
- package/dist/ingest/adapters/connecticut-cga.d.ts +22 -0
- package/dist/ingest/adapters/connecticut-cga.d.ts.map +1 -0
- package/dist/ingest/adapters/connecticut-cga.js +116 -0
- package/dist/ingest/adapters/connecticut-cga.js.map +1 -0
- package/dist/ingest/adapters/ecfr.d.ts +46 -4
- package/dist/ingest/adapters/ecfr.d.ts.map +1 -1
- package/dist/ingest/adapters/ecfr.js +131 -16
- package/dist/ingest/adapters/ecfr.js.map +1 -1
- package/dist/ingest/adapters/ffiec.d.ts +42 -0
- package/dist/ingest/adapters/ffiec.d.ts.map +1 -0
- package/dist/ingest/adapters/ffiec.js +68 -0
- package/dist/ingest/adapters/ffiec.js.map +1 -0
- package/dist/ingest/adapters/nydfs.d.ts +42 -0
- package/dist/ingest/adapters/nydfs.d.ts.map +1 -0
- package/dist/ingest/adapters/nydfs.js +68 -0
- package/dist/ingest/adapters/nydfs.js.map +1 -0
- package/dist/ingest/adapters/regulations-gov.d.ts +11 -12
- package/dist/ingest/adapters/regulations-gov.d.ts.map +1 -1
- package/dist/ingest/adapters/regulations-gov.js +46 -43
- package/dist/ingest/adapters/regulations-gov.js.map +1 -1
- package/dist/ingest/adapters/utah-xcode.d.ts +19 -0
- package/dist/ingest/adapters/utah-xcode.d.ts.map +1 -0
- package/dist/ingest/adapters/utah-xcode.js +112 -0
- package/dist/ingest/adapters/utah-xcode.js.map +1 -0
- package/dist/ingest/adapters/virginia-law.d.ts +21 -0
- package/dist/ingest/adapters/virginia-law.d.ts.map +1 -0
- package/dist/ingest/adapters/virginia-law.js +111 -0
- package/dist/ingest/adapters/virginia-law.js.map +1 -0
- package/package.json +26 -4
- package/scripts/build-db.ts +50 -32
- package/scripts/check-updates.ts +184 -0
- package/scripts/ingest.ts +72 -25
- package/src/index.ts +1 -1
- package/src/ingest/adapters/colorado-public.ts +96 -0
- package/src/ingest/adapters/connecticut-cga.ts +150 -0
- package/src/ingest/adapters/ecfr.ts +158 -17
- package/src/ingest/adapters/ffiec.ts +77 -0
- package/src/ingest/adapters/nydfs.ts +77 -0
- package/src/ingest/adapters/regulations-gov.ts +48 -47
- package/src/ingest/adapters/utah-xcode.ts +143 -0
- package/src/ingest/adapters/virginia-law.ts +140 -0
- package/scripts/quality-test.ts +0 -346
- package/scripts/test-mcp-tools.ts +0 -187
- package/scripts/test-remaining-tools.ts +0 -107
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ansvar/us-regulations-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"mcpName": "us.ansvar/us-regulations-mcp",
|
|
5
|
-
"description": "MCP server for US cybersecurity and privacy regulations. Query HIPAA, CCPA, SOX, and
|
|
5
|
+
"description": "MCP server for US cybersecurity and privacy regulations. Query HIPAA, CCPA, SOX, GLBA, FERPA, COPPA, FDA 21 CFR Part 11, EPA RMP, FFIEC, NYDFS 500, and 4 state privacy laws (VA, CO, CT, UT) directly from Claude.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"clean": "rm -rf dist && find . -name '*.tsbuildinfo' -not -path './node_modules/*' -delete",
|
|
14
|
-
"test": "
|
|
14
|
+
"test": "tsx scripts/quality-test.ts && tsx scripts/test-remaining-tools.ts && tsx scripts/test-mcp-tools.ts && tsx scripts/test-ecfr-adapter.ts && tsx scripts/test-seed-adapters.ts && tsx scripts/test-v1.2-adapters.ts && tsx scripts/test-pr-2-enhancements.ts",
|
|
15
15
|
"test:watch": "vitest",
|
|
16
16
|
"dev": "tsx src/index.ts",
|
|
17
17
|
"start": "node dist/index.js",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"ingest": "tsx scripts/ingest.ts",
|
|
20
20
|
"load-seed": "tsx scripts/load-seed-data.ts",
|
|
21
21
|
"test:mcp": "tsx scripts/test-mcp-tools.ts",
|
|
22
|
+
"check-updates": "tsx scripts/check-updates.ts",
|
|
22
23
|
"lint": "eslint src --ext .ts",
|
|
23
24
|
"prepare": "test -f dist/index.js || npm run build",
|
|
24
25
|
"prepublishOnly": "npm run build"
|
|
@@ -30,13 +31,34 @@
|
|
|
30
31
|
"hipaa",
|
|
31
32
|
"ccpa",
|
|
32
33
|
"sox",
|
|
34
|
+
"glba",
|
|
35
|
+
"ferpa",
|
|
36
|
+
"coppa",
|
|
37
|
+
"fda",
|
|
38
|
+
"epa",
|
|
39
|
+
"ffiec",
|
|
40
|
+
"nydfs",
|
|
41
|
+
"virginia-cdpa",
|
|
42
|
+
"colorado-cpa",
|
|
43
|
+
"connecticut-ctdpa",
|
|
44
|
+
"utah-ucpa",
|
|
45
|
+
"state-privacy",
|
|
33
46
|
"us-regulations",
|
|
34
47
|
"cybersecurity",
|
|
35
48
|
"privacy",
|
|
36
49
|
"healthcare",
|
|
37
|
-
"financial",
|
|
50
|
+
"financial-services",
|
|
51
|
+
"education",
|
|
52
|
+
"children-privacy",
|
|
53
|
+
"pharmaceutical",
|
|
54
|
+
"environmental",
|
|
55
|
+
"banking",
|
|
38
56
|
"sarbanes-oxley",
|
|
39
57
|
"california-consumer-privacy-act",
|
|
58
|
+
"gramm-leach-bliley",
|
|
59
|
+
"pci-dss",
|
|
60
|
+
"payment-card-security",
|
|
61
|
+
"23-nycrr-500",
|
|
40
62
|
"claude",
|
|
41
63
|
"llm"
|
|
42
64
|
],
|
package/scripts/build-db.ts
CHANGED
|
@@ -129,16 +129,25 @@ CREATE TABLE IF NOT EXISTS source_registry (
|
|
|
129
129
|
`;
|
|
130
130
|
|
|
131
131
|
interface RegulationSeed {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
regulation: {
|
|
133
|
+
id: string;
|
|
134
|
+
full_name: string;
|
|
135
|
+
short_name?: string;
|
|
136
|
+
citation: string;
|
|
137
|
+
effective_date?: string;
|
|
138
|
+
source_url?: string;
|
|
139
|
+
jurisdiction?: string;
|
|
140
|
+
regulation_type?: string;
|
|
141
|
+
};
|
|
142
|
+
source?: {
|
|
143
|
+
official_url?: string;
|
|
144
|
+
publisher?: string;
|
|
145
|
+
last_verified?: string;
|
|
146
|
+
verification_method?: string;
|
|
147
|
+
disclaimer?: string;
|
|
148
|
+
};
|
|
140
149
|
sections: Array<{
|
|
141
|
-
|
|
150
|
+
sectionNumber: string;
|
|
142
151
|
title?: string;
|
|
143
152
|
text: string;
|
|
144
153
|
part?: string;
|
|
@@ -184,22 +193,30 @@ function buildDatabase() {
|
|
|
184
193
|
if (file.startsWith('mappings')) continue;
|
|
185
194
|
|
|
186
195
|
console.log(`Loading ${file}...`);
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
let seedData: RegulationSeed;
|
|
197
|
+
try {
|
|
198
|
+
const content = readFileSync(join(SEED_DIR, file), 'utf-8');
|
|
199
|
+
seedData = JSON.parse(content);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
202
|
+
console.error(`Failed to parse ${file}: ${message}`);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
const reg = seedData.regulation;
|
|
189
206
|
|
|
190
207
|
// Insert regulation
|
|
191
208
|
db.prepare(`
|
|
192
209
|
INSERT INTO regulations (id, full_name, short_name, citation, effective_date, source_url, jurisdiction, regulation_type)
|
|
193
210
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
194
211
|
`).run(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
212
|
+
reg.id,
|
|
213
|
+
reg.full_name,
|
|
214
|
+
reg.short_name || null,
|
|
215
|
+
reg.citation,
|
|
216
|
+
reg.effective_date || null,
|
|
217
|
+
seedData.source?.official_url || reg.source_url || null,
|
|
218
|
+
reg.jurisdiction || null,
|
|
219
|
+
reg.regulation_type || null
|
|
203
220
|
);
|
|
204
221
|
|
|
205
222
|
// Insert sections
|
|
@@ -208,10 +225,10 @@ function buildDatabase() {
|
|
|
208
225
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
209
226
|
`);
|
|
210
227
|
|
|
211
|
-
for (const section of
|
|
228
|
+
for (const section of seedData.sections) {
|
|
212
229
|
insertSection.run(
|
|
213
|
-
|
|
214
|
-
section.
|
|
230
|
+
reg.id,
|
|
231
|
+
section.sectionNumber,
|
|
215
232
|
section.title || null,
|
|
216
233
|
section.text,
|
|
217
234
|
section.part || null,
|
|
@@ -223,34 +240,35 @@ function buildDatabase() {
|
|
|
223
240
|
}
|
|
224
241
|
|
|
225
242
|
// Insert definitions
|
|
226
|
-
if (
|
|
243
|
+
if (seedData.definitions) {
|
|
227
244
|
const insertDefinition = db.prepare(`
|
|
228
245
|
INSERT OR IGNORE INTO definitions (regulation, term, definition, section)
|
|
229
246
|
VALUES (?, ?, ?, ?)
|
|
230
247
|
`);
|
|
231
248
|
|
|
232
|
-
for (const def of
|
|
233
|
-
insertDefinition.run(
|
|
249
|
+
for (const def of seedData.definitions) {
|
|
250
|
+
insertDefinition.run(reg.id, def.term, def.definition, def.section);
|
|
234
251
|
}
|
|
235
252
|
}
|
|
236
253
|
|
|
237
254
|
// Update source registry with timestamps
|
|
238
255
|
const now = new Date().toISOString();
|
|
239
|
-
const
|
|
256
|
+
const sourceUrl = seedData.source?.official_url || reg.source_url || '';
|
|
257
|
+
const sourceType = sourceUrl.includes('api') ? 'api' : sourceUrl.includes('.pdf') ? 'pdf' : 'seed';
|
|
240
258
|
db.prepare(`
|
|
241
259
|
INSERT INTO source_registry (regulation, source_type, source_url, api_endpoint, last_fetched, sections_expected, sections_parsed, quality_status)
|
|
242
260
|
VALUES (?, ?, ?, ?, ?, ?, ?, 'complete')
|
|
243
261
|
`).run(
|
|
244
|
-
|
|
262
|
+
reg.id,
|
|
245
263
|
sourceType,
|
|
246
|
-
|
|
247
|
-
|
|
264
|
+
sourceUrl,
|
|
265
|
+
sourceUrl.includes('api') ? sourceUrl : null,
|
|
248
266
|
now,
|
|
249
|
-
|
|
250
|
-
|
|
267
|
+
seedData.sections.length,
|
|
268
|
+
seedData.sections.length
|
|
251
269
|
);
|
|
252
270
|
|
|
253
|
-
console.log(` Loaded ${
|
|
271
|
+
console.log(` Loaded ${seedData.sections.length} sections, ${seedData.definitions?.length || 0} definitions`);
|
|
254
272
|
}
|
|
255
273
|
|
|
256
274
|
// Note: Control mappings and applicability rules are loaded separately
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Regulation Update Checker
|
|
4
|
+
*
|
|
5
|
+
* Checks official sources for regulation updates and reports changes.
|
|
6
|
+
* Exit codes:
|
|
7
|
+
* 0 = No updates found
|
|
8
|
+
* 1 = Updates found (triggers GitHub issue creation)
|
|
9
|
+
* 2 = Error occurred
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import Database from 'better-sqlite3';
|
|
13
|
+
import { join, dirname } from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
const DB_PATH = join(__dirname, '..', 'data', 'regulations.db');
|
|
19
|
+
|
|
20
|
+
interface SourceCheck {
|
|
21
|
+
regulation: string;
|
|
22
|
+
source_type: 'ecfr' | 'leginfo' | 'state' | 'seed';
|
|
23
|
+
last_checked?: string;
|
|
24
|
+
last_modified?: string;
|
|
25
|
+
status: 'current' | 'update_available' | 'check_failed';
|
|
26
|
+
message?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function checkEcfrUpdates(regulation: string, title: number, part: number): Promise<SourceCheck> {
|
|
30
|
+
const url = `https://www.ecfr.gov/api/versioner/v1/full/${new Date().toISOString().split('T')[0]}/title-${title}.xml`;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(url, { method: 'HEAD' });
|
|
34
|
+
const lastModified = response.headers.get('last-modified');
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
regulation,
|
|
38
|
+
source_type: 'ecfr',
|
|
39
|
+
last_checked: new Date().toISOString(),
|
|
40
|
+
last_modified: lastModified || undefined,
|
|
41
|
+
status: 'current',
|
|
42
|
+
message: `eCFR Title ${title} Part ${part} checked - ${lastModified || 'no date'}`
|
|
43
|
+
};
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
regulation,
|
|
47
|
+
source_type: 'ecfr',
|
|
48
|
+
last_checked: new Date().toISOString(),
|
|
49
|
+
status: 'check_failed',
|
|
50
|
+
message: `Failed to check eCFR: ${error}`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function checkCaliforniaLegInfo(): Promise<SourceCheck> {
|
|
56
|
+
// California LegInfo doesn't provide easy update detection
|
|
57
|
+
// Would need to scrape the bill page or use their search API
|
|
58
|
+
return {
|
|
59
|
+
regulation: 'CCPA',
|
|
60
|
+
source_type: 'leginfo',
|
|
61
|
+
last_checked: new Date().toISOString(),
|
|
62
|
+
status: 'current',
|
|
63
|
+
message: 'California LegInfo - manual check required (no API for amendments)'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function checkStateLegislature(regulation: string, state: string): Promise<SourceCheck> {
|
|
68
|
+
// State legislatures require manual checking or web scraping
|
|
69
|
+
return {
|
|
70
|
+
regulation,
|
|
71
|
+
source_type: 'state',
|
|
72
|
+
last_checked: new Date().toISOString(),
|
|
73
|
+
status: 'current',
|
|
74
|
+
message: `${state} legislature - manual check required (no standardized API)`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function runUpdateChecks(): Promise<void> {
|
|
79
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
80
|
+
console.log(' US REGULATIONS UPDATE CHECKER');
|
|
81
|
+
console.log(' ' + new Date().toISOString());
|
|
82
|
+
console.log('═══════════════════════════════════════════════════════════\n');
|
|
83
|
+
|
|
84
|
+
const checks: SourceCheck[] = [];
|
|
85
|
+
|
|
86
|
+
// Check eCFR sources
|
|
87
|
+
console.log('📡 Checking eCFR.gov sources...\n');
|
|
88
|
+
|
|
89
|
+
const ecfrSources = [
|
|
90
|
+
{ regulation: 'HIPAA', title: 45, part: 164 },
|
|
91
|
+
{ regulation: 'GLBA', title: 16, part: 314 },
|
|
92
|
+
{ regulation: 'FERPA', title: 34, part: 99 },
|
|
93
|
+
{ regulation: 'COPPA', title: 16, part: 312 },
|
|
94
|
+
{ regulation: 'FDA_CFR_11', title: 21, part: 11 },
|
|
95
|
+
{ regulation: 'EPA_RMP', title: 40, part: 68 },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const source of ecfrSources) {
|
|
99
|
+
const check = await checkEcfrUpdates(source.regulation, source.title, source.part);
|
|
100
|
+
checks.push(check);
|
|
101
|
+
console.log(` ${check.regulation}: ${check.message}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check California LegInfo
|
|
105
|
+
console.log('\n📡 Checking California LegInfo...\n');
|
|
106
|
+
const ccpaCheck = await checkCaliforniaLegInfo();
|
|
107
|
+
checks.push(ccpaCheck);
|
|
108
|
+
console.log(` ${ccpaCheck.regulation}: ${ccpaCheck.message}`);
|
|
109
|
+
|
|
110
|
+
// Check state legislatures
|
|
111
|
+
console.log('\n📡 Checking State Legislatures...\n');
|
|
112
|
+
const stateChecks = [
|
|
113
|
+
{ regulation: 'VIRGINIA_CDPA', state: 'Virginia' },
|
|
114
|
+
{ regulation: 'COLORADO_CPA', state: 'Colorado' },
|
|
115
|
+
{ regulation: 'CONNECTICUT_CTDPA', state: 'Connecticut' },
|
|
116
|
+
{ regulation: 'UTAH_UCPA', state: 'Utah' },
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
for (const state of stateChecks) {
|
|
120
|
+
const check = await checkStateLegislature(state.regulation, state.state);
|
|
121
|
+
checks.push(check);
|
|
122
|
+
console.log(` ${check.regulation}: ${check.message}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Seed data sources (FFIEC, NYDFS, SOX)
|
|
126
|
+
console.log('\n📡 Checking Seed Data Sources...\n');
|
|
127
|
+
const seedSources = ['FFIEC', 'NYDFS_500', 'SOX'];
|
|
128
|
+
for (const reg of seedSources) {
|
|
129
|
+
checks.push({
|
|
130
|
+
regulation: reg,
|
|
131
|
+
source_type: 'seed',
|
|
132
|
+
last_checked: new Date().toISOString(),
|
|
133
|
+
status: 'current',
|
|
134
|
+
message: `${reg} - seed data (manual updates only)`
|
|
135
|
+
});
|
|
136
|
+
console.log(` ${reg}: seed data (manual updates only)`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Summary
|
|
140
|
+
console.log('\n═══════════════════════════════════════════════════════════');
|
|
141
|
+
console.log(' SUMMARY');
|
|
142
|
+
console.log('═══════════════════════════════════════════════════════════\n');
|
|
143
|
+
|
|
144
|
+
const updatesCounts = {
|
|
145
|
+
current: checks.filter(c => c.status === 'current').length,
|
|
146
|
+
updates: checks.filter(c => c.status === 'update_available').length,
|
|
147
|
+
failed: checks.filter(c => c.status === 'check_failed').length,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
console.log(`✅ Current: ${updatesCounts.current}`);
|
|
151
|
+
console.log(`🔔 Updates Available: ${updatesCounts.updates}`);
|
|
152
|
+
console.log(`❌ Check Failed: ${updatesCounts.failed}`);
|
|
153
|
+
|
|
154
|
+
if (updatesCounts.updates > 0) {
|
|
155
|
+
console.log('\n⚠️ UPDATES DETECTED - GitHub issue will be created');
|
|
156
|
+
console.log('\nRegulations with updates:');
|
|
157
|
+
checks.filter(c => c.status === 'update_available').forEach(c => {
|
|
158
|
+
console.log(` - ${c.regulation}: ${c.message}`);
|
|
159
|
+
});
|
|
160
|
+
process.exit(1); // Exit with code 1 to trigger issue creation
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (updatesCounts.failed > 0) {
|
|
164
|
+
console.log('\n⚠️ Some checks failed:');
|
|
165
|
+
checks.filter(c => c.status === 'check_failed').forEach(c => {
|
|
166
|
+
console.log(` - ${c.regulation}: ${c.message}`);
|
|
167
|
+
});
|
|
168
|
+
// Don't exit with error for failed checks, just log them
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log('\n✅ All regulations are current');
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Handle errors
|
|
176
|
+
process.on('unhandledRejection', (error) => {
|
|
177
|
+
console.error('❌ Unhandled error:', error);
|
|
178
|
+
process.exit(2);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
runUpdateChecks().catch((error) => {
|
|
182
|
+
console.error('❌ Update check failed:', error);
|
|
183
|
+
process.exit(2);
|
|
184
|
+
});
|
package/scripts/ingest.ts
CHANGED
|
@@ -10,9 +10,22 @@
|
|
|
10
10
|
import Database from 'better-sqlite3';
|
|
11
11
|
import { join, dirname } from 'path';
|
|
12
12
|
import { fileURLToPath } from 'url';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createHipaaAdapter,
|
|
15
|
+
createGlbaAdapter,
|
|
16
|
+
createFerpaAdapter,
|
|
17
|
+
createCoppaAdapter,
|
|
18
|
+
createFdaAdapter,
|
|
19
|
+
createEpaRmpAdapter
|
|
20
|
+
} from '../src/ingest/adapters/ecfr.js';
|
|
14
21
|
import { createCcpaAdapter } from '../src/ingest/adapters/california-leginfo.js';
|
|
15
22
|
import { createSoxAdapter } from '../src/ingest/adapters/regulations-gov.js';
|
|
23
|
+
import { createFfiecAdapter } from '../src/ingest/adapters/ffiec.js';
|
|
24
|
+
import { createNydfsAdapter } from '../src/ingest/adapters/nydfs.js';
|
|
25
|
+
import { createVirginiaAdapter } from '../src/ingest/adapters/virginia-law.js';
|
|
26
|
+
import { createColoradoAdapter } from '../src/ingest/adapters/colorado-public.js';
|
|
27
|
+
import { createConnecticutAdapter } from '../src/ingest/adapters/connecticut-cga.js';
|
|
28
|
+
import { createUtahAdapter } from '../src/ingest/adapters/utah-xcode.js';
|
|
16
29
|
import type { SourceAdapter } from '../src/ingest/framework.js';
|
|
17
30
|
|
|
18
31
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -159,45 +172,79 @@ async function ingestRegulation(
|
|
|
159
172
|
}
|
|
160
173
|
}
|
|
161
174
|
|
|
175
|
+
/**
|
|
176
|
+
* All regulations to ingest
|
|
177
|
+
* Adding new regulations: add import + entry to this array
|
|
178
|
+
*/
|
|
179
|
+
const REGULATIONS = [
|
|
180
|
+
{ id: 'HIPAA', adapter: createHipaaAdapter() },
|
|
181
|
+
{ id: 'CCPA', adapter: createCcpaAdapter() },
|
|
182
|
+
{ id: 'SOX', adapter: createSoxAdapter() },
|
|
183
|
+
{ id: 'GLBA', adapter: createGlbaAdapter() },
|
|
184
|
+
{ id: 'FERPA', adapter: createFerpaAdapter() },
|
|
185
|
+
{ id: 'COPPA', adapter: createCoppaAdapter() },
|
|
186
|
+
{ id: 'FDA_CFR_11', adapter: createFdaAdapter() },
|
|
187
|
+
{ id: 'EPA_RMP', adapter: createEpaRmpAdapter() },
|
|
188
|
+
{ id: 'FFIEC', adapter: createFfiecAdapter() },
|
|
189
|
+
{ id: 'NYDFS_500', adapter: createNydfsAdapter() },
|
|
190
|
+
{ id: 'VIRGINIA_CDPA', adapter: createVirginiaAdapter() },
|
|
191
|
+
{ id: 'COLORADO_CPA', adapter: createColoradoAdapter() },
|
|
192
|
+
{ id: 'CONNECTICUT_CTDPA', adapter: createConnecticutAdapter() },
|
|
193
|
+
{ id: 'UTAH_UCPA', adapter: createUtahAdapter() },
|
|
194
|
+
];
|
|
195
|
+
|
|
162
196
|
/**
|
|
163
197
|
* Main ingestion function
|
|
164
198
|
*/
|
|
165
199
|
async function ingestAll(): Promise<IngestResult[]> {
|
|
166
|
-
console.log('🚀 Starting US Compliance MCP Ingestion...\n');
|
|
167
|
-
console.log(`Database: ${DB_PATH}`);
|
|
168
|
-
|
|
169
200
|
const db = Database(DB_PATH);
|
|
170
201
|
const results: IngestResult[] = [];
|
|
171
202
|
|
|
172
|
-
|
|
173
|
-
{ id: 'HIPAA', adapter: createHipaaAdapter() },
|
|
174
|
-
{ id: 'CCPA', adapter: createCcpaAdapter() },
|
|
175
|
-
{ id: 'SOX', adapter: createSoxAdapter() },
|
|
176
|
-
];
|
|
203
|
+
console.log(`🚀 Ingesting ${REGULATIONS.length} regulations...\n`);
|
|
177
204
|
|
|
178
|
-
|
|
205
|
+
// Sequential ingestion for now
|
|
206
|
+
for (const { id, adapter } of REGULATIONS) {
|
|
179
207
|
const result = await ingestRegulation(db, id, adapter);
|
|
180
208
|
results.push(result);
|
|
181
209
|
}
|
|
182
210
|
|
|
183
211
|
db.close();
|
|
184
212
|
|
|
213
|
+
// Summary report
|
|
185
214
|
console.log('\n📊 Ingestion Summary:');
|
|
186
|
-
console.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
215
|
+
console.log('━'.repeat(60));
|
|
216
|
+
|
|
217
|
+
let totalSections = 0;
|
|
218
|
+
let totalDefinitions = 0;
|
|
219
|
+
let successCount = 0;
|
|
220
|
+
|
|
221
|
+
results.forEach(r => {
|
|
222
|
+
const status = r.success ? '✅' : '❌';
|
|
223
|
+
const secStr = `${r.sections_added} sections`.padEnd(15);
|
|
224
|
+
const defStr = `${r.definitions_added} defs`.padEnd(10);
|
|
225
|
+
const timeStr = `${r.duration_ms}ms`;
|
|
226
|
+
|
|
227
|
+
console.log(`${status} ${r.regulation.padEnd(12)} ${secStr} ${defStr} ${timeStr}`);
|
|
228
|
+
|
|
229
|
+
if (r.success) {
|
|
230
|
+
successCount++;
|
|
231
|
+
totalSections += r.sections_added;
|
|
232
|
+
totalDefinitions += r.definitions_added;
|
|
233
|
+
} else {
|
|
234
|
+
console.error(` Error: ${r.error}`);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
console.log('━'.repeat(60));
|
|
239
|
+
console.log(`Total: ${successCount}/${results.length} regulations, ${totalSections} sections, ${totalDefinitions} definitions`);
|
|
240
|
+
|
|
241
|
+
// Exit with error if any failed
|
|
242
|
+
if (successCount < results.length) {
|
|
243
|
+
console.error('\n❌ Some regulations failed to ingest');
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.log('\n✅ All regulations ingested successfully!');
|
|
201
248
|
|
|
202
249
|
return results;
|
|
203
250
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colorado Privacy Act Adapter
|
|
3
|
+
*
|
|
4
|
+
* Fetches Colorado CPA from official Colorado General Assembly sources.
|
|
5
|
+
* Source: C.R.S. § 6-1-1301 to 6-1-1313
|
|
6
|
+
*
|
|
7
|
+
* Official Source: https://leg.colorado.gov/colorado-revised-statutes
|
|
8
|
+
*
|
|
9
|
+
* NOTE: Uses seed data extracted from official Colorado Revised Statutes.
|
|
10
|
+
* The Colorado General Assembly publishes statutes in PDF format at leg.colorado.gov.
|
|
11
|
+
* This adapter uses pre-verified seed data to ensure accuracy and avoid
|
|
12
|
+
* reliance on third-party aggregators.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
SourceAdapter,
|
|
17
|
+
RegulationMetadata,
|
|
18
|
+
Section,
|
|
19
|
+
Definition,
|
|
20
|
+
UpdateStatus,
|
|
21
|
+
} from '../framework.js';
|
|
22
|
+
import * as fs from 'fs';
|
|
23
|
+
import * as path from 'path';
|
|
24
|
+
import { fileURLToPath } from 'url';
|
|
25
|
+
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = path.dirname(__filename);
|
|
28
|
+
|
|
29
|
+
export class ColoradoLegAdapter implements SourceAdapter {
|
|
30
|
+
private readonly regulationId = 'COLORADO_CPA';
|
|
31
|
+
private readonly seedPath: string;
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
this.seedPath = path.join(__dirname, '../../../data/seed/colorado-cpa.json');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async fetchMetadata(): Promise<RegulationMetadata> {
|
|
38
|
+
return {
|
|
39
|
+
id: 'COLORADO_CPA',
|
|
40
|
+
full_name: 'Colorado Privacy Act',
|
|
41
|
+
citation: 'C.R.S. §6-1-1301 to 6-1-1313',
|
|
42
|
+
effective_date: '2023-07-01',
|
|
43
|
+
last_amended: '2024-01-01',
|
|
44
|
+
source_url: 'https://leg.colorado.gov/colorado-revised-statutes',
|
|
45
|
+
jurisdiction: 'colorado',
|
|
46
|
+
regulation_type: 'statute',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async *fetchSections(): AsyncGenerator<Section[]> {
|
|
51
|
+
console.log('Loading Colorado CPA from official seed data...');
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const seedData = JSON.parse(fs.readFileSync(this.seedPath, 'utf-8'));
|
|
55
|
+
|
|
56
|
+
if (!seedData.sections || !Array.isArray(seedData.sections)) {
|
|
57
|
+
throw new Error('Invalid seed data format');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const sections: Section[] = seedData.sections.map((s: any) => ({
|
|
61
|
+
sectionNumber: s.sectionNumber,
|
|
62
|
+
title: s.title,
|
|
63
|
+
text: s.text,
|
|
64
|
+
chapter: s.chapter || 'Colorado Privacy Act',
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
console.log(` Loaded ${sections.length} sections from seed data`);
|
|
68
|
+
|
|
69
|
+
// Validate minimum section count
|
|
70
|
+
if (sections.length < 10) {
|
|
71
|
+
throw new Error(`Expected at least 10 sections, got ${sections.length}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
yield sections;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error('Failed to load Colorado CPA seed data:', error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async extractDefinitions(): Promise<Definition[]> {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async checkForUpdates(lastFetched: Date): Promise<UpdateStatus> {
|
|
86
|
+
// Seed data is static - updates require manual verification
|
|
87
|
+
return {
|
|
88
|
+
hasChanges: false,
|
|
89
|
+
changes: ['Colorado CPA uses verified seed data. Check leg.colorado.gov for updates.']
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createColoradoAdapter(): SourceAdapter {
|
|
95
|
+
return new ColoradoLegAdapter();
|
|
96
|
+
}
|