euparliamentmonitor 0.8.20 → 0.8.21
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/package.json +3 -3
- package/scripts/generators/news-enhanced.js +4 -4
- package/scripts/generators/pipeline/analysis-stage.d.ts +1 -1
- package/scripts/generators/pipeline/analysis-stage.js +2 -2
- package/scripts/generators/pipeline/analysis-threats.d.ts +1 -1
- package/scripts/generators/pipeline/analysis-threats.js +1 -1
- package/scripts/generators/pipeline/fetch-stage.d.ts +10 -0
- package/scripts/generators/pipeline/fetch-stage.js +263 -87
- package/scripts/generators/strategies/article-strategy.d.ts +2 -2
- package/scripts/generators/strategies/article-strategy.js +3 -3
- package/scripts/index.d.ts +1 -1
- package/scripts/index.js +1 -1
- package/scripts/mcp/ep-mcp-client.d.ts +17 -1
- package/scripts/mcp/ep-mcp-client.js +32 -0
- package/scripts/mcp/mcp-connection.js +9 -1
- package/scripts/templates/article-template.js +15 -13
- package/scripts/templates/section-builders.js +2 -5
- package/scripts/types/index.d.ts +1 -1
- package/scripts/types/mcp.d.ts +7 -0
- package/scripts/types/political-classification.d.ts +1 -1
- package/scripts/utils/file-utils.d.ts +2 -2
- package/scripts/utils/file-utils.js +2 -2
- package/scripts/utils/html-sanitize.d.ts +10 -0
- package/scripts/utils/html-sanitize.js +32 -0
- package/scripts/utils/political-classification.d.ts +7 -7
- package/scripts/utils/political-classification.js +7 -7
- package/scripts/utils/political-risk-assessment.d.ts +1 -1
- package/scripts/utils/political-risk-assessment.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "euparliamentmonitor",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
|
|
6
6
|
"main": "scripts/index.js",
|
|
@@ -140,8 +140,8 @@
|
|
|
140
140
|
"@types/d3": "7.4.3",
|
|
141
141
|
"@types/node": "25.5.2",
|
|
142
142
|
"@types/papaparse": "5.5.2",
|
|
143
|
-
"@typescript-eslint/eslint-plugin": "8.58.
|
|
144
|
-
"@typescript-eslint/parser": "8.58.
|
|
143
|
+
"@typescript-eslint/eslint-plugin": "8.58.1",
|
|
144
|
+
"@typescript-eslint/parser": "8.58.1",
|
|
145
145
|
"@vitest/coverage-v8": "4.1.3",
|
|
146
146
|
"@vitest/ui": "4.1.3",
|
|
147
147
|
"chart.js": "4.5.1",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* When the `--analysis` flag is supplied (all 9 agentic workflows do this),
|
|
13
13
|
* the analysis stage runs **before** article generation, producing structured
|
|
14
|
-
* political intelligence artifacts under `analysis/{date}/{article-type}/`. These
|
|
14
|
+
* political intelligence artifacts under `analysis/daily/{date}/{article-type}/`. These
|
|
15
15
|
* artifacts are committed to the repository for review and improvement.
|
|
16
16
|
*
|
|
17
17
|
* Pipeline stages:
|
|
@@ -177,7 +177,7 @@ function parseAnalysisMethods() {
|
|
|
177
177
|
* Run the optional analysis stage (Fetch → Analysis) before article generation.
|
|
178
178
|
*
|
|
179
179
|
* This function is **side-effect-only**: it writes analysis markdown and a
|
|
180
|
-
* `manifest.json` to disk under `analysis/{date}/{article-type}/`. The returned
|
|
180
|
+
* `manifest.json` to disk under `analysis/daily/{date}/{article-type}/`. The returned
|
|
181
181
|
* {@link AnalysisContext} is informational; strategies read analysis output
|
|
182
182
|
* from disk rather than consuming the context object in-memory. Analysis
|
|
183
183
|
* artifacts are committed to the repository for review and political
|
|
@@ -203,7 +203,7 @@ async function maybeRunAnalysis(date, client) {
|
|
|
203
203
|
const trimmedAnalysisDirBase = rawAnalysisDirBase?.trim();
|
|
204
204
|
const analysisDirBase = trimmedAnalysisDirBase && trimmedAnalysisDirBase.length > 0
|
|
205
205
|
? trimmedAnalysisDirBase
|
|
206
|
-
: 'analysis';
|
|
206
|
+
: 'analysis/daily';
|
|
207
207
|
const enabledMethods = parseAnalysisMethods();
|
|
208
208
|
console.log('');
|
|
209
209
|
console.log('🔬 Running analysis stage...');
|
|
@@ -362,7 +362,7 @@ async function main() {
|
|
|
362
362
|
// Expose analysis dir/slug via env vars so strategies can locate analysis
|
|
363
363
|
// artifacts without hard-coding paths. Follows the EP_FEED_DATA_FILE pattern.
|
|
364
364
|
if (analysisCtx) {
|
|
365
|
-
// Base dir: parent of date-scoped dir (e.g. 'analysis' from 'analysis/2026-04-06/breaking')
|
|
365
|
+
// Base dir: parent of date-scoped dir (e.g. 'analysis/daily' from 'analysis/daily/2026-04-06/breaking')
|
|
366
366
|
const analysisOutputParent = path.dirname(analysisCtx.outputDir);
|
|
367
367
|
const analysisBaseDir = path.dirname(analysisOutputParent);
|
|
368
368
|
process.env['EP_ANALYSIS_DIR'] = analysisBaseDir;
|
|
@@ -26,7 +26,7 @@ export interface AnalysisStageOptions {
|
|
|
26
26
|
readonly articleTypes: readonly ArticleCategory[];
|
|
27
27
|
/** ISO date string (YYYY-MM-DD) for this analysis run */
|
|
28
28
|
readonly date: string;
|
|
29
|
-
/** Base output directory (e.g. 'analysis') */
|
|
29
|
+
/** Base output directory (e.g. 'analysis/daily') */
|
|
30
30
|
readonly outputDir: string;
|
|
31
31
|
/**
|
|
32
32
|
* Filesystem-safe slug identifying the article type for this run.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* news articles in all 14 languages.
|
|
12
12
|
*
|
|
13
13
|
* This stage is **side-effect-only**: it writes analysis markdown and a
|
|
14
|
-
* `manifest.json` to disk under `analysis/{date}/{article-type}/`. When
|
|
14
|
+
* `manifest.json` to disk under `analysis/daily/{date}/{article-type}/`. When
|
|
15
15
|
* `articleTypeSlug` is provided (recommended for agentic workflows), each
|
|
16
16
|
* article type writes to its own subdirectory, preventing merge conflicts
|
|
17
17
|
* when multiple workflows run concurrently on the same date.
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* const ctx = await runAnalysisStage(fetchedData, {
|
|
30
30
|
* articleTypes: [ArticleCategory.WEEK_AHEAD],
|
|
31
31
|
* date: '2026-03-26',
|
|
32
|
-
* outputDir: 'analysis',
|
|
32
|
+
* outputDir: 'analysis/daily',
|
|
33
33
|
* });
|
|
34
34
|
* console.log(ctx.completedMethods);
|
|
35
35
|
* ```
|
|
@@ -4,7 +4,7 @@ import type { AnalysisMethod } from './analysis-stage.js';
|
|
|
4
4
|
* Build markdown for the political threat landscape assessment.
|
|
5
5
|
*
|
|
6
6
|
* Uses the pipeline `date` parameter to ensure the assessment date in the
|
|
7
|
-
* generated markdown matches the `analysis/{date}/` folder, overriding
|
|
7
|
+
* generated markdown matches the `analysis/daily/{date}/` folder, overriding
|
|
8
8
|
* the `new Date()` timestamp that `assessPoliticalThreats()` stamps internally.
|
|
9
9
|
*
|
|
10
10
|
* @param fetchedData - Raw fetched EP data
|
|
@@ -17,7 +17,7 @@ import { sanitizeCell, safeArr, toThreatInput, buildMarkdownHeader, EMPTY_TABLE_
|
|
|
17
17
|
* Build markdown for the political threat landscape assessment.
|
|
18
18
|
*
|
|
19
19
|
* Uses the pipeline `date` parameter to ensure the assessment date in the
|
|
20
|
-
* generated markdown matches the `analysis/{date}/` folder, overriding
|
|
20
|
+
* generated markdown matches the `analysis/daily/{date}/` folder, overriding
|
|
21
21
|
* the `new Date()` timestamp that `assessPoliticalThreats()` stamps internally.
|
|
22
22
|
*
|
|
23
23
|
* @param fetchedData - Raw fetched EP data
|
|
@@ -217,6 +217,7 @@ export declare function fetchPipelineFromMCP(client: EuropeanParliamentMCPClient
|
|
|
217
217
|
export declare function fetchProcedureStatusFromMCP(client: EuropeanParliamentMCPClient | null, procedureId: string): Promise<string>;
|
|
218
218
|
/**
|
|
219
219
|
* Fetch adopted texts feed from MCP.
|
|
220
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
220
221
|
*
|
|
221
222
|
* @param client - MCP client or null
|
|
222
223
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -225,6 +226,8 @@ export declare function fetchProcedureStatusFromMCP(client: EuropeanParliamentMC
|
|
|
225
226
|
export declare function fetchAdoptedTextsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<AdoptedTextFeedItem[]>;
|
|
226
227
|
/**
|
|
227
228
|
* Fetch events feed from MCP.
|
|
229
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data
|
|
230
|
+
* (common during parliamentary recess when the EP API returns 404 for narrow windows).
|
|
228
231
|
*
|
|
229
232
|
* @param client - MCP client or null
|
|
230
233
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -233,6 +236,7 @@ export declare function fetchAdoptedTextsFeed(client: EuropeanParliamentMCPClien
|
|
|
233
236
|
export declare function fetchEventsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<EventFeedItem[]>;
|
|
234
237
|
/**
|
|
235
238
|
* Fetch procedures feed from MCP.
|
|
239
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
236
240
|
*
|
|
237
241
|
* @param client - MCP client or null
|
|
238
242
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -267,6 +271,7 @@ export declare function fetchMEPsFeedWithTotal(client: EuropeanParliamentMCPClie
|
|
|
267
271
|
}>;
|
|
268
272
|
/**
|
|
269
273
|
* Fetch documents feed from MCP.
|
|
274
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
270
275
|
*
|
|
271
276
|
* @param client - MCP client or null
|
|
272
277
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -275,6 +280,7 @@ export declare function fetchMEPsFeedWithTotal(client: EuropeanParliamentMCPClie
|
|
|
275
280
|
export declare function fetchDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
|
|
276
281
|
/**
|
|
277
282
|
* Fetch plenary documents feed from MCP.
|
|
283
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
278
284
|
*
|
|
279
285
|
* @param client - MCP client or null
|
|
280
286
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -283,6 +289,7 @@ export declare function fetchDocumentsFeed(client: EuropeanParliamentMCPClient |
|
|
|
283
289
|
export declare function fetchPlenaryDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
|
|
284
290
|
/**
|
|
285
291
|
* Fetch committee documents feed from MCP.
|
|
292
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
286
293
|
*
|
|
287
294
|
* @param client - MCP client or null
|
|
288
295
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -291,6 +298,7 @@ export declare function fetchPlenaryDocumentsFeed(client: EuropeanParliamentMCPC
|
|
|
291
298
|
export declare function fetchCommitteeDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
|
|
292
299
|
/**
|
|
293
300
|
* Fetch plenary session documents feed from MCP.
|
|
301
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
294
302
|
*
|
|
295
303
|
* @param client - MCP client or null
|
|
296
304
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -299,6 +307,7 @@ export declare function fetchCommitteeDocumentsFeed(client: EuropeanParliamentMC
|
|
|
299
307
|
export declare function fetchPlenarySessionDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
|
|
300
308
|
/**
|
|
301
309
|
* Fetch external documents feed from MCP.
|
|
310
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
302
311
|
*
|
|
303
312
|
* @param client - MCP client or null
|
|
304
313
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -307,6 +316,7 @@ export declare function fetchPlenarySessionDocumentsFeed(client: EuropeanParliam
|
|
|
307
316
|
export declare function fetchExternalDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
|
|
308
317
|
/**
|
|
309
318
|
* Fetch parliamentary questions feed from MCP.
|
|
319
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
310
320
|
*
|
|
311
321
|
* @param client - MCP client or null
|
|
312
322
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1050,6 +1050,28 @@ export async function fetchProcedureStatusFromMCP(client, procedureId) {
|
|
|
1050
1050
|
}
|
|
1051
1051
|
}
|
|
1052
1052
|
// ─── EP Feed-based fetches (Breaking News) ──────────────────────────────────
|
|
1053
|
+
/**
|
|
1054
|
+
* Ordered fallback chain for feed timeframes.
|
|
1055
|
+
* When a narrow timeframe returns empty/404 (common during recess), we widen
|
|
1056
|
+
* the window to retrieve at least *some* recent data for analysis.
|
|
1057
|
+
*/
|
|
1058
|
+
const TIMEFRAME_FALLBACK_CHAIN = new Map([
|
|
1059
|
+
['today', 'one-day'],
|
|
1060
|
+
['one-day', 'one-week'],
|
|
1061
|
+
['one-week', 'one-month'],
|
|
1062
|
+
['one-month', undefined],
|
|
1063
|
+
['three-months', undefined],
|
|
1064
|
+
['one-year', undefined],
|
|
1065
|
+
]);
|
|
1066
|
+
/**
|
|
1067
|
+
* Get the next wider timeframe for fallback, or `undefined` if no fallback exists.
|
|
1068
|
+
*
|
|
1069
|
+
* @param current - Current timeframe
|
|
1070
|
+
* @returns Next wider timeframe, or undefined when at widest
|
|
1071
|
+
*/
|
|
1072
|
+
function getWiderTimeframe(current) {
|
|
1073
|
+
return TIMEFRAME_FALLBACK_CHAIN.get(current);
|
|
1074
|
+
}
|
|
1053
1075
|
/**
|
|
1054
1076
|
* Parse a feed result from MCP into a flat array of items.
|
|
1055
1077
|
* EP API v2 feeds return items under the `data` key:
|
|
@@ -1134,6 +1156,7 @@ function mapFeedItemBase(item) {
|
|
|
1134
1156
|
}
|
|
1135
1157
|
/**
|
|
1136
1158
|
* Fetch adopted texts feed from MCP.
|
|
1159
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
1137
1160
|
*
|
|
1138
1161
|
* @param client - MCP client or null
|
|
1139
1162
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1142,19 +1165,37 @@ function mapFeedItemBase(item) {
|
|
|
1142
1165
|
export async function fetchAdoptedTextsFeed(client, timeframe = 'one-week') {
|
|
1143
1166
|
if (!client)
|
|
1144
1167
|
return [];
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1168
|
+
let currentTimeframe = timeframe;
|
|
1169
|
+
while (currentTimeframe) {
|
|
1170
|
+
const tf = currentTimeframe;
|
|
1171
|
+
try {
|
|
1172
|
+
console.log(`${MCP_FETCH_PREFIX} Fetching adopted texts feed (${currentTimeframe})...`);
|
|
1173
|
+
const result = await callMCP(() => client.getAdoptedTextsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_adopted_texts_feed');
|
|
1174
|
+
const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
|
|
1175
|
+
if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
|
|
1176
|
+
return items;
|
|
1177
|
+
console.log(`${INFO_PREFIX} adopted texts feed empty for ${currentTimeframe}, widening timeframe...`);
|
|
1178
|
+
currentTimeframe = getWiderTimeframe(currentTimeframe);
|
|
1179
|
+
}
|
|
1180
|
+
catch (error) {
|
|
1181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1182
|
+
const wider = getWiderTimeframe(tf);
|
|
1183
|
+
if (wider && (message.includes('404') || message.includes('timed out'))) {
|
|
1184
|
+
console.warn(`${WARN_PREFIX} get_adopted_texts_feed failed (${currentTimeframe}): ${message} — retrying with ${wider}`);
|
|
1185
|
+
currentTimeframe = wider;
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
console.warn(`${WARN_PREFIX} get_adopted_texts_feed failed:`, message);
|
|
1189
|
+
return [];
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1154
1192
|
}
|
|
1193
|
+
return [];
|
|
1155
1194
|
}
|
|
1156
1195
|
/**
|
|
1157
1196
|
* Fetch events feed from MCP.
|
|
1197
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data
|
|
1198
|
+
* (common during parliamentary recess when the EP API returns 404 for narrow windows).
|
|
1158
1199
|
*
|
|
1159
1200
|
* @param client - MCP client or null
|
|
1160
1201
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1163,22 +1204,39 @@ export async function fetchAdoptedTextsFeed(client, timeframe = 'one-week') {
|
|
|
1163
1204
|
export async function fetchEventsFeed(client, timeframe = 'one-week') {
|
|
1164
1205
|
if (!client)
|
|
1165
1206
|
return [];
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1207
|
+
let currentTimeframe = timeframe;
|
|
1208
|
+
while (currentTimeframe) {
|
|
1209
|
+
const tf = currentTimeframe;
|
|
1210
|
+
try {
|
|
1211
|
+
console.log(`${MCP_FETCH_PREFIX} Fetching events feed (${currentTimeframe})...`);
|
|
1212
|
+
const result = await callMCP(() => client.getEventsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_events_feed');
|
|
1213
|
+
const items = parseFeedResult(result).map((item) => ({
|
|
1214
|
+
...mapFeedItemBase(item),
|
|
1215
|
+
location: item['location'] ? String(item['location']) : undefined,
|
|
1216
|
+
}));
|
|
1217
|
+
if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
|
|
1218
|
+
return items;
|
|
1219
|
+
console.log(`${INFO_PREFIX} events feed empty for ${currentTimeframe}, widening timeframe...`);
|
|
1220
|
+
currentTimeframe = getWiderTimeframe(currentTimeframe);
|
|
1221
|
+
}
|
|
1222
|
+
catch (error) {
|
|
1223
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1224
|
+
const wider = getWiderTimeframe(tf);
|
|
1225
|
+
if (wider && (message.includes('404') || message.includes('timed out'))) {
|
|
1226
|
+
console.warn(`${WARN_PREFIX} get_events_feed failed (${currentTimeframe}): ${message} — retrying with ${wider}`);
|
|
1227
|
+
currentTimeframe = wider;
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
console.warn(`${WARN_PREFIX} get_events_feed failed:`, message);
|
|
1231
|
+
return [];
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1178
1234
|
}
|
|
1235
|
+
return [];
|
|
1179
1236
|
}
|
|
1180
1237
|
/**
|
|
1181
1238
|
* Fetch procedures feed from MCP.
|
|
1239
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
1182
1240
|
*
|
|
1183
1241
|
* @param client - MCP client or null
|
|
1184
1242
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1187,19 +1245,35 @@ export async function fetchEventsFeed(client, timeframe = 'one-week') {
|
|
|
1187
1245
|
export async function fetchProceduresFeed(client, timeframe = 'one-week') {
|
|
1188
1246
|
if (!client)
|
|
1189
1247
|
return [];
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1248
|
+
let currentTimeframe = timeframe;
|
|
1249
|
+
while (currentTimeframe) {
|
|
1250
|
+
const tf = currentTimeframe;
|
|
1251
|
+
try {
|
|
1252
|
+
console.log(`${MCP_FETCH_PREFIX} Fetching procedures feed (${currentTimeframe})...`);
|
|
1253
|
+
const result = await callMCP(() => client.getProceduresFeed({ timeframe: tf, limit: 20 }), undefined, 'get_procedures_feed');
|
|
1254
|
+
const items = parseFeedResult(result).map((item) => ({
|
|
1255
|
+
...mapFeedItemBase(item),
|
|
1256
|
+
stage: item['stage'] ? String(item['stage']) : undefined,
|
|
1257
|
+
}));
|
|
1258
|
+
if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
|
|
1259
|
+
return items;
|
|
1260
|
+
console.log(`${INFO_PREFIX} procedures feed empty for ${currentTimeframe}, widening timeframe...`);
|
|
1261
|
+
currentTimeframe = getWiderTimeframe(currentTimeframe);
|
|
1262
|
+
}
|
|
1263
|
+
catch (error) {
|
|
1264
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1265
|
+
const wider = getWiderTimeframe(tf);
|
|
1266
|
+
if (wider && (message.includes('404') || message.includes('timed out'))) {
|
|
1267
|
+
console.warn(`${WARN_PREFIX} get_procedures_feed failed (${currentTimeframe}): ${message} — retrying with ${wider}`);
|
|
1268
|
+
currentTimeframe = wider;
|
|
1269
|
+
}
|
|
1270
|
+
else {
|
|
1271
|
+
console.warn(`${WARN_PREFIX} get_procedures_feed failed:`, message);
|
|
1272
|
+
return [];
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1202
1275
|
}
|
|
1276
|
+
return [];
|
|
1203
1277
|
}
|
|
1204
1278
|
/**
|
|
1205
1279
|
* Fetch MEPs feed from MCP.
|
|
@@ -1252,6 +1326,7 @@ export async function fetchMEPsFeedWithTotal(client, timeframe = 'one-week') {
|
|
|
1252
1326
|
}
|
|
1253
1327
|
/**
|
|
1254
1328
|
* Fetch documents feed from MCP.
|
|
1329
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
1255
1330
|
*
|
|
1256
1331
|
* @param client - MCP client or null
|
|
1257
1332
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1260,19 +1335,36 @@ export async function fetchMEPsFeedWithTotal(client, timeframe = 'one-week') {
|
|
|
1260
1335
|
export async function fetchDocumentsFeed(client, timeframe = 'one-week') {
|
|
1261
1336
|
if (!client)
|
|
1262
1337
|
return [];
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1338
|
+
let currentTimeframe = timeframe;
|
|
1339
|
+
while (currentTimeframe) {
|
|
1340
|
+
const tf = currentTimeframe;
|
|
1341
|
+
try {
|
|
1342
|
+
console.log(`${MCP_FETCH_PREFIX} Fetching documents feed (${currentTimeframe})...`);
|
|
1343
|
+
const result = await callMCP(() => client.getDocumentsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_documents_feed');
|
|
1344
|
+
const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
|
|
1345
|
+
if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
|
|
1346
|
+
return items;
|
|
1347
|
+
console.log(`${INFO_PREFIX} documents feed empty for ${currentTimeframe}, widening timeframe...`);
|
|
1348
|
+
currentTimeframe = getWiderTimeframe(currentTimeframe);
|
|
1349
|
+
}
|
|
1350
|
+
catch (error) {
|
|
1351
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1352
|
+
const wider = getWiderTimeframe(tf);
|
|
1353
|
+
if (wider && (message.includes('404') || message.includes('timed out'))) {
|
|
1354
|
+
console.warn(`${WARN_PREFIX} get_documents_feed failed (${currentTimeframe}): ${message} — retrying with ${wider}`);
|
|
1355
|
+
currentTimeframe = wider;
|
|
1356
|
+
}
|
|
1357
|
+
else {
|
|
1358
|
+
console.warn(`${WARN_PREFIX} get_documents_feed failed:`, message);
|
|
1359
|
+
return [];
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1272
1362
|
}
|
|
1363
|
+
return [];
|
|
1273
1364
|
}
|
|
1274
1365
|
/**
|
|
1275
1366
|
* Fetch plenary documents feed from MCP.
|
|
1367
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
1276
1368
|
*
|
|
1277
1369
|
* @param client - MCP client or null
|
|
1278
1370
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1281,19 +1373,36 @@ export async function fetchDocumentsFeed(client, timeframe = 'one-week') {
|
|
|
1281
1373
|
export async function fetchPlenaryDocumentsFeed(client, timeframe = 'one-week') {
|
|
1282
1374
|
if (!client)
|
|
1283
1375
|
return [];
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1376
|
+
let currentTimeframe = timeframe;
|
|
1377
|
+
while (currentTimeframe) {
|
|
1378
|
+
const tf = currentTimeframe;
|
|
1379
|
+
try {
|
|
1380
|
+
console.log(`${MCP_FETCH_PREFIX} Fetching plenary documents feed (${currentTimeframe})...`);
|
|
1381
|
+
const result = await callMCP(() => client.getPlenaryDocumentsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_plenary_documents_feed');
|
|
1382
|
+
const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
|
|
1383
|
+
if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
|
|
1384
|
+
return items;
|
|
1385
|
+
console.log(`${INFO_PREFIX} plenary documents feed empty for ${currentTimeframe}, widening timeframe...`);
|
|
1386
|
+
currentTimeframe = getWiderTimeframe(currentTimeframe);
|
|
1387
|
+
}
|
|
1388
|
+
catch (error) {
|
|
1389
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1390
|
+
const wider = getWiderTimeframe(tf);
|
|
1391
|
+
if (wider && (message.includes('404') || message.includes('timed out'))) {
|
|
1392
|
+
console.warn(`${WARN_PREFIX} get_plenary_documents_feed failed (${currentTimeframe}): ${message} — retrying with ${wider}`);
|
|
1393
|
+
currentTimeframe = wider;
|
|
1394
|
+
}
|
|
1395
|
+
else {
|
|
1396
|
+
console.warn(`${WARN_PREFIX} get_plenary_documents_feed failed:`, message);
|
|
1397
|
+
return [];
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1293
1400
|
}
|
|
1401
|
+
return [];
|
|
1294
1402
|
}
|
|
1295
1403
|
/**
|
|
1296
1404
|
* Fetch committee documents feed from MCP.
|
|
1405
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
1297
1406
|
*
|
|
1298
1407
|
* @param client - MCP client or null
|
|
1299
1408
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1302,19 +1411,36 @@ export async function fetchPlenaryDocumentsFeed(client, timeframe = 'one-week')
|
|
|
1302
1411
|
export async function fetchCommitteeDocumentsFeed(client, timeframe = 'one-week') {
|
|
1303
1412
|
if (!client)
|
|
1304
1413
|
return [];
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1414
|
+
let currentTimeframe = timeframe;
|
|
1415
|
+
while (currentTimeframe) {
|
|
1416
|
+
const tf = currentTimeframe;
|
|
1417
|
+
try {
|
|
1418
|
+
console.log(`${MCP_FETCH_PREFIX} Fetching committee documents feed (${currentTimeframe})...`);
|
|
1419
|
+
const result = await callMCP(() => client.getCommitteeDocumentsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_committee_documents_feed');
|
|
1420
|
+
const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
|
|
1421
|
+
if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
|
|
1422
|
+
return items;
|
|
1423
|
+
console.log(`${INFO_PREFIX} committee documents feed empty for ${currentTimeframe}, widening timeframe...`);
|
|
1424
|
+
currentTimeframe = getWiderTimeframe(currentTimeframe);
|
|
1425
|
+
}
|
|
1426
|
+
catch (error) {
|
|
1427
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1428
|
+
const wider = getWiderTimeframe(tf);
|
|
1429
|
+
if (wider && (message.includes('404') || message.includes('timed out'))) {
|
|
1430
|
+
console.warn(`${WARN_PREFIX} get_committee_documents_feed failed (${currentTimeframe}): ${message} — retrying with ${wider}`);
|
|
1431
|
+
currentTimeframe = wider;
|
|
1432
|
+
}
|
|
1433
|
+
else {
|
|
1434
|
+
console.warn(`${WARN_PREFIX} get_committee_documents_feed failed:`, message);
|
|
1435
|
+
return [];
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1314
1438
|
}
|
|
1439
|
+
return [];
|
|
1315
1440
|
}
|
|
1316
1441
|
/**
|
|
1317
1442
|
* Fetch plenary session documents feed from MCP.
|
|
1443
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
1318
1444
|
*
|
|
1319
1445
|
* @param client - MCP client or null
|
|
1320
1446
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1323,19 +1449,36 @@ export async function fetchCommitteeDocumentsFeed(client, timeframe = 'one-week'
|
|
|
1323
1449
|
export async function fetchPlenarySessionDocumentsFeed(client, timeframe = 'one-week') {
|
|
1324
1450
|
if (!client)
|
|
1325
1451
|
return [];
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1452
|
+
let currentTimeframe = timeframe;
|
|
1453
|
+
while (currentTimeframe) {
|
|
1454
|
+
const tf = currentTimeframe;
|
|
1455
|
+
try {
|
|
1456
|
+
console.log(`${MCP_FETCH_PREFIX} Fetching plenary session documents feed (${currentTimeframe})...`);
|
|
1457
|
+
const result = await callMCP(() => client.getPlenarySessionDocumentsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_plenary_session_documents_feed');
|
|
1458
|
+
const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
|
|
1459
|
+
if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
|
|
1460
|
+
return items;
|
|
1461
|
+
console.log(`${INFO_PREFIX} plenary session docs feed empty for ${currentTimeframe}, widening timeframe...`);
|
|
1462
|
+
currentTimeframe = getWiderTimeframe(currentTimeframe);
|
|
1463
|
+
}
|
|
1464
|
+
catch (error) {
|
|
1465
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1466
|
+
const wider = getWiderTimeframe(tf);
|
|
1467
|
+
if (wider && (message.includes('404') || message.includes('timed out'))) {
|
|
1468
|
+
console.warn(`${WARN_PREFIX} get_plenary_session_documents_feed failed (${currentTimeframe}): ${message} — retrying with ${wider}`);
|
|
1469
|
+
currentTimeframe = wider;
|
|
1470
|
+
}
|
|
1471
|
+
else {
|
|
1472
|
+
console.warn(`${WARN_PREFIX} get_plenary_session_documents_feed failed:`, message);
|
|
1473
|
+
return [];
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1335
1476
|
}
|
|
1477
|
+
return [];
|
|
1336
1478
|
}
|
|
1337
1479
|
/**
|
|
1338
1480
|
* Fetch external documents feed from MCP.
|
|
1481
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
1339
1482
|
*
|
|
1340
1483
|
* @param client - MCP client or null
|
|
1341
1484
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1344,19 +1487,36 @@ export async function fetchPlenarySessionDocumentsFeed(client, timeframe = 'one-
|
|
|
1344
1487
|
export async function fetchExternalDocumentsFeed(client, timeframe = 'one-week') {
|
|
1345
1488
|
if (!client)
|
|
1346
1489
|
return [];
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
const
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1490
|
+
let currentTimeframe = timeframe;
|
|
1491
|
+
while (currentTimeframe) {
|
|
1492
|
+
const tf = currentTimeframe;
|
|
1493
|
+
try {
|
|
1494
|
+
console.log(`${MCP_FETCH_PREFIX} Fetching external documents feed (${currentTimeframe})...`);
|
|
1495
|
+
const result = await callMCP(() => client.getExternalDocumentsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_external_documents_feed');
|
|
1496
|
+
const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
|
|
1497
|
+
if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
|
|
1498
|
+
return items;
|
|
1499
|
+
console.log(`${INFO_PREFIX} external documents feed empty for ${currentTimeframe}, widening timeframe...`);
|
|
1500
|
+
currentTimeframe = getWiderTimeframe(currentTimeframe);
|
|
1501
|
+
}
|
|
1502
|
+
catch (error) {
|
|
1503
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1504
|
+
const wider = getWiderTimeframe(tf);
|
|
1505
|
+
if (wider && (message.includes('404') || message.includes('timed out'))) {
|
|
1506
|
+
console.warn(`${WARN_PREFIX} get_external_documents_feed failed (${currentTimeframe}): ${message} — retrying with ${wider}`);
|
|
1507
|
+
currentTimeframe = wider;
|
|
1508
|
+
}
|
|
1509
|
+
else {
|
|
1510
|
+
console.warn(`${WARN_PREFIX} get_external_documents_feed failed:`, message);
|
|
1511
|
+
return [];
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1356
1514
|
}
|
|
1515
|
+
return [];
|
|
1357
1516
|
}
|
|
1358
1517
|
/**
|
|
1359
1518
|
* Fetch parliamentary questions feed from MCP.
|
|
1519
|
+
* Falls back to a wider timeframe when the initial timeframe returns no data.
|
|
1360
1520
|
*
|
|
1361
1521
|
* @param client - MCP client or null
|
|
1362
1522
|
* @param timeframe - How far back to look (default: 'one-week')
|
|
@@ -1365,16 +1525,32 @@ export async function fetchExternalDocumentsFeed(client, timeframe = 'one-week')
|
|
|
1365
1525
|
export async function fetchQuestionsFeed(client, timeframe = 'one-week') {
|
|
1366
1526
|
if (!client)
|
|
1367
1527
|
return [];
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1528
|
+
let currentTimeframe = timeframe;
|
|
1529
|
+
while (currentTimeframe) {
|
|
1530
|
+
const tf = currentTimeframe;
|
|
1531
|
+
try {
|
|
1532
|
+
console.log(`${MCP_FETCH_PREFIX} Fetching parliamentary questions feed (${currentTimeframe})...`);
|
|
1533
|
+
const result = await callMCP(() => client.getParliamentaryQuestionsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_parliamentary_questions_feed');
|
|
1534
|
+
const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
|
|
1535
|
+
if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
|
|
1536
|
+
return items;
|
|
1537
|
+
console.log(`${INFO_PREFIX} questions feed empty for ${currentTimeframe}, widening timeframe...`);
|
|
1538
|
+
currentTimeframe = getWiderTimeframe(currentTimeframe);
|
|
1539
|
+
}
|
|
1540
|
+
catch (error) {
|
|
1541
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1542
|
+
const wider = getWiderTimeframe(tf);
|
|
1543
|
+
if (wider && (message.includes('404') || message.includes('timed out'))) {
|
|
1544
|
+
console.warn(`${WARN_PREFIX} get_parliamentary_questions_feed failed (${currentTimeframe}): ${message} — retrying with ${wider}`);
|
|
1545
|
+
currentTimeframe = wider;
|
|
1546
|
+
}
|
|
1547
|
+
else {
|
|
1548
|
+
console.warn(`${WARN_PREFIX} get_parliamentary_questions_feed failed:`, message);
|
|
1549
|
+
return [];
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1377
1552
|
}
|
|
1553
|
+
return [];
|
|
1378
1554
|
}
|
|
1379
1555
|
/**
|
|
1380
1556
|
* Fetch MEP declarations feed from MCP.
|
|
@@ -50,7 +50,7 @@ export interface LoadedAnalysisContext {
|
|
|
50
50
|
* Resolution order for base directory:
|
|
51
51
|
* 1. Explicit `baseDir` parameter (when non-default)
|
|
52
52
|
* 2. `EP_ANALYSIS_DIR` environment variable (set by orchestration)
|
|
53
|
-
* 3. Default `'analysis'`
|
|
53
|
+
* 3. Default `'analysis/daily'`
|
|
54
54
|
*
|
|
55
55
|
* Resolution order for slug:
|
|
56
56
|
* 1. `EP_ANALYSIS_SLUG` environment variable (set by orchestration)
|
|
@@ -58,7 +58,7 @@ export interface LoadedAnalysisContext {
|
|
|
58
58
|
*
|
|
59
59
|
* @param date - ISO 8601 date (YYYY-MM-DD) of the analysis run
|
|
60
60
|
* @param articleTypeSlug - Article type slug (e.g. 'breaking', 'week-ahead')
|
|
61
|
-
* @param baseDir - Base analysis directory (defaults to 'analysis')
|
|
61
|
+
* @param baseDir - Base analysis directory (defaults to 'analysis/daily')
|
|
62
62
|
* @returns Loaded analysis context or null when unavailable
|
|
63
63
|
*/
|
|
64
64
|
export declare function loadAnalysisContext(date: string, articleTypeSlug: string, baseDir?: string): LoadedAnalysisContext | null;
|
|
@@ -17,7 +17,7 @@ import { escapeHTML } from '../../utils/file-utils.js';
|
|
|
17
17
|
import { ANALYSIS_INSIGHTS_HEADING, getLocalizedString } from '../../constants/languages.js';
|
|
18
18
|
// ─── Analysis loading defaults ───────────────────────────────────────────────
|
|
19
19
|
/** Default base directory for analysis output */
|
|
20
|
-
const DEFAULT_ANALYSIS_BASE_DIR = 'analysis';
|
|
20
|
+
const DEFAULT_ANALYSIS_BASE_DIR = 'analysis/daily';
|
|
21
21
|
/**
|
|
22
22
|
* Environment variable name for overriding the analysis base directory.
|
|
23
23
|
* Set by the orchestration layer when `--analysis-dir` is provided.
|
|
@@ -51,7 +51,7 @@ const ANALYSIS_SUBDIRS = [
|
|
|
51
51
|
* Resolution order for base directory:
|
|
52
52
|
* 1. Explicit `baseDir` parameter (when non-default)
|
|
53
53
|
* 2. `EP_ANALYSIS_DIR` environment variable (set by orchestration)
|
|
54
|
-
* 3. Default `'analysis'`
|
|
54
|
+
* 3. Default `'analysis/daily'`
|
|
55
55
|
*
|
|
56
56
|
* Resolution order for slug:
|
|
57
57
|
* 1. `EP_ANALYSIS_SLUG` environment variable (set by orchestration)
|
|
@@ -59,7 +59,7 @@ const ANALYSIS_SUBDIRS = [
|
|
|
59
59
|
*
|
|
60
60
|
* @param date - ISO 8601 date (YYYY-MM-DD) of the analysis run
|
|
61
61
|
* @param articleTypeSlug - Article type slug (e.g. 'breaking', 'week-ahead')
|
|
62
|
-
* @param baseDir - Base analysis directory (defaults to 'analysis')
|
|
62
|
+
* @param baseDir - Base analysis directory (defaults to 'analysis/daily')
|
|
63
63
|
* @returns Loaded analysis context or null when unavailable
|
|
64
64
|
*/
|
|
65
65
|
export function loadAnalysisContext(date, articleTypeSlug, baseDir = DEFAULT_ANALYSIS_BASE_DIR) {
|
package/scripts/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export { enrichMetadataFromContent } from './utils/content-metadata.js';
|
|
|
41
41
|
export { buildMetadataDatabase, writeMetadataDatabase, readMetadataDatabase, updateMetadataDatabase, updateIntelligenceIndex, } from './utils/news-metadata.js';
|
|
42
42
|
export { pl, pl as pluralizeCount } from './utils/metadata-utils.js';
|
|
43
43
|
export { assessPoliticalThreats, buildActorThreatProfiles, buildConsequenceTree, analyzeLegislativeDisruption, generateThreatAssessmentMarkdown, ALL_THREAT_LANDSCAPE_DIMENSIONS, } from './utils/political-threat-assessment.js';
|
|
44
|
-
export { stripScriptBlocks } from './utils/html-sanitize.js';
|
|
44
|
+
export { stripHtmlTags, stripScriptBlocks } from './utils/html-sanitize.js';
|
|
45
45
|
export { parseArticleFilename, formatSlug, calculateReadTime, escapeHTML, isSafeURL, validateArticleHTML, type ArticleValidationResult, } from './utils/file-utils.js';
|
|
46
46
|
export { detectCategory } from './utils/article-category.js';
|
|
47
47
|
export { EU_COUNTRY_CODES, EU_AGGREGATE_CODE, POLICY_INDICATORS, parseWorldBankCSV, formatIndicatorValue, getMostRecentValue, buildEconomicContext, getWorldBankCountryCode, isEUMemberState, buildEconomicContextHTML, } from './utils/world-bank-data.js';
|
package/scripts/index.js
CHANGED
|
@@ -56,7 +56,7 @@ export { pl, pl as pluralizeCount } from './utils/metadata-utils.js';
|
|
|
56
56
|
// ─── Political Threat Assessment ─────────────────────────────────────────────
|
|
57
57
|
export { assessPoliticalThreats, buildActorThreatProfiles, buildConsequenceTree, analyzeLegislativeDisruption, generateThreatAssessmentMarkdown, ALL_THREAT_LANDSCAPE_DIMENSIONS, } from './utils/political-threat-assessment.js';
|
|
58
58
|
// ─── HTML Utilities ──────────────────────────────────────────────────────────
|
|
59
|
-
export { stripScriptBlocks } from './utils/html-sanitize.js';
|
|
59
|
+
export { stripHtmlTags, stripScriptBlocks } from './utils/html-sanitize.js';
|
|
60
60
|
export { parseArticleFilename, formatSlug, calculateReadTime, escapeHTML, isSafeURL, validateArticleHTML, } from './utils/file-utils.js';
|
|
61
61
|
// ─── Article Category Detection ──────────────────────────────────────────────
|
|
62
62
|
export { detectCategory } from './utils/article-category.js';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* built on top of the generic {@link MCPConnection} transport.
|
|
5
5
|
*/
|
|
6
6
|
import { MCPConnection } from './mcp-connection.js';
|
|
7
|
-
import type { MCPClientOptions, MCPToolResult, GetMEPsOptions, GetPlenarySessionsOptions, SearchDocumentsOptions, GetParliamentaryQuestionsOptions, GetCommitteeInfoOptions, MonitorLegislativePipelineOptions, AssessMEPInfluenceOptions, AnalyzeCoalitionDynamicsOptions, DetectVotingAnomaliesOptions, ComparePoliticalGroupsOptions, VotingRecordsOptions, VotingPatternsOptions, GenerateReportOptions, AnalyzeLegislativeEffectivenessOptions, AnalyzeCommitteeActivityOptions, TrackMEPAttendanceOptions, AnalyzeCountryDelegationOptions, GeneratePoliticalLandscapeOptions, GetCurrentMEPsOptions, GetSpeechesOptions, GetProceduresOptions, GetAdoptedTextsOptions, GetEventsOptions, GetMeetingActivitiesOptions, GetMeetingDecisionsOptions, GetMEPDeclarationsOptions, GetIncomingMEPsOptions, GetOutgoingMEPsOptions, GetHomonymMEPsOptions, GetPlenaryDocumentsOptions, GetCommitteeDocumentsOptions, GetPlenarySessionDocumentsOptions, GetPlenarySessionDocumentItemsOptions, GetControlledVocabulariesOptions, GetExternalDocumentsOptions, GetMeetingForeseenActivitiesOptions, GetProcedureEventsOptions, GetMeetingPlenarySessionDocumentsOptions, GetMeetingPlenarySessionDocumentItemsOptions, NetworkAnalysisOptions, SentimentTrackerOptions, EarlyWarningSystemOptions, ComparativeIntelligenceOptions, CorrelateIntelligenceOptions, GetAllGeneratedStatsOptions, GetMEPsFeedOptions, GetEventsFeedOptions, GetProceduresFeedOptions, GetAdoptedTextsFeedOptions, GetMEPDeclarationsFeedOptions, GetDocumentsFeedOptions, GetPlenaryDocumentsFeedOptions, GetCommitteeDocumentsFeedOptions, GetPlenarySessionDocumentsFeedOptions, GetExternalDocumentsFeedOptions, GetParliamentaryQuestionsFeedOptions, GetCorporateBodiesFeedOptions, GetControlledVocabulariesFeedOptions } from '../types/index.js';
|
|
7
|
+
import type { MCPClientOptions, MCPToolResult, GetMEPsOptions, GetPlenarySessionsOptions, SearchDocumentsOptions, GetParliamentaryQuestionsOptions, GetCommitteeInfoOptions, MonitorLegislativePipelineOptions, AssessMEPInfluenceOptions, AnalyzeCoalitionDynamicsOptions, DetectVotingAnomaliesOptions, ComparePoliticalGroupsOptions, VotingRecordsOptions, VotingPatternsOptions, GenerateReportOptions, AnalyzeLegislativeEffectivenessOptions, AnalyzeCommitteeActivityOptions, TrackMEPAttendanceOptions, AnalyzeCountryDelegationOptions, GeneratePoliticalLandscapeOptions, GetCurrentMEPsOptions, GetSpeechesOptions, GetProceduresOptions, GetAdoptedTextsOptions, GetEventsOptions, GetMeetingActivitiesOptions, GetMeetingDecisionsOptions, GetMEPDeclarationsOptions, GetIncomingMEPsOptions, GetOutgoingMEPsOptions, GetHomonymMEPsOptions, GetPlenaryDocumentsOptions, GetCommitteeDocumentsOptions, GetPlenarySessionDocumentsOptions, GetPlenarySessionDocumentItemsOptions, GetControlledVocabulariesOptions, GetExternalDocumentsOptions, GetMeetingForeseenActivitiesOptions, GetProcedureEventsOptions, GetMeetingPlenarySessionDocumentsOptions, GetMeetingPlenarySessionDocumentItemsOptions, NetworkAnalysisOptions, SentimentTrackerOptions, EarlyWarningSystemOptions, ComparativeIntelligenceOptions, CorrelateIntelligenceOptions, GetAllGeneratedStatsOptions, GetMEPsFeedOptions, GetEventsFeedOptions, GetProceduresFeedOptions, GetAdoptedTextsFeedOptions, GetMEPDeclarationsFeedOptions, GetDocumentsFeedOptions, GetPlenaryDocumentsFeedOptions, GetCommitteeDocumentsFeedOptions, GetPlenarySessionDocumentsFeedOptions, GetExternalDocumentsFeedOptions, GetParliamentaryQuestionsFeedOptions, GetCorporateBodiesFeedOptions, GetControlledVocabulariesFeedOptions, GetProcedureEventByIdOptions } from '../types/index.js';
|
|
8
8
|
/**
|
|
9
9
|
* MCP Client for European Parliament data access.
|
|
10
10
|
* Extends {@link MCPConnection} with EP-specific tool wrapper methods.
|
|
@@ -473,6 +473,22 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
473
473
|
* @returns Controlled vocabularies feed data
|
|
474
474
|
*/
|
|
475
475
|
getControlledVocabulariesFeed(options?: GetControlledVocabulariesFeedOptions): Promise<MCPToolResult>;
|
|
476
|
+
/**
|
|
477
|
+
* Get a specific event linked to a legislative procedure.
|
|
478
|
+
* Returns a single event for the specified procedure and event identifiers.
|
|
479
|
+
*
|
|
480
|
+
* @param options - Options including required processId and eventId
|
|
481
|
+
* @returns Procedure event data
|
|
482
|
+
*/
|
|
483
|
+
getProcedureEventById(options: GetProcedureEventByIdOptions): Promise<MCPToolResult>;
|
|
484
|
+
/**
|
|
485
|
+
* Check server health and feed availability status.
|
|
486
|
+
* Returns server version, uptime, per-feed health status, and overall availability.
|
|
487
|
+
* Does not make upstream API calls — reports cached status from recent tool invocations.
|
|
488
|
+
*
|
|
489
|
+
* @returns Server health and feed availability data
|
|
490
|
+
*/
|
|
491
|
+
getServerHealth(): Promise<MCPToolResult>;
|
|
476
492
|
}
|
|
477
493
|
/**
|
|
478
494
|
* Get or create singleton MCP client instance
|
|
@@ -22,6 +22,10 @@ const ITEMS_FALLBACK = '{"items": []}';
|
|
|
22
22
|
const INTELLIGENCE_FALLBACK = '{"analysis": null}';
|
|
23
23
|
/** Fallback payload for precomputed statistics */
|
|
24
24
|
const STATS_FALLBACK = '{"stats": null}';
|
|
25
|
+
/** Fallback payload for single procedure event lookup */
|
|
26
|
+
const PROCEDURE_EVENT_FALLBACK = '{"event": null}';
|
|
27
|
+
/** Fallback payload for server health status */
|
|
28
|
+
const SERVER_HEALTH_FALLBACK = '{"server": null, "feeds": []}';
|
|
25
29
|
/**
|
|
26
30
|
* MCP Client for European Parliament data access.
|
|
27
31
|
* Extends {@link MCPConnection} with EP-specific tool wrapper methods.
|
|
@@ -783,6 +787,34 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
783
787
|
async getControlledVocabulariesFeed(options = {}) {
|
|
784
788
|
return this.safeCallTool('get_controlled_vocabularies_feed', options, EuropeanParliamentMCPClient.FEED_FALLBACK);
|
|
785
789
|
}
|
|
790
|
+
/**
|
|
791
|
+
* Get a specific event linked to a legislative procedure.
|
|
792
|
+
* Returns a single event for the specified procedure and event identifiers.
|
|
793
|
+
*
|
|
794
|
+
* @param options - Options including required processId and eventId
|
|
795
|
+
* @returns Procedure event data
|
|
796
|
+
*/
|
|
797
|
+
async getProcedureEventById(options) {
|
|
798
|
+
if (typeof options.processId !== 'string' || options.processId.trim().length === 0) {
|
|
799
|
+
console.warn('get_procedure_event_by_id called without valid processId (non-empty string required)');
|
|
800
|
+
return { content: [{ type: 'text', text: PROCEDURE_EVENT_FALLBACK }] };
|
|
801
|
+
}
|
|
802
|
+
if (typeof options.eventId !== 'string' || options.eventId.trim().length === 0) {
|
|
803
|
+
console.warn('get_procedure_event_by_id called without valid eventId (non-empty string required)');
|
|
804
|
+
return { content: [{ type: 'text', text: PROCEDURE_EVENT_FALLBACK }] };
|
|
805
|
+
}
|
|
806
|
+
return this.safeCallTool('get_procedure_event_by_id', { processId: options.processId.trim(), eventId: options.eventId.trim() }, PROCEDURE_EVENT_FALLBACK);
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Check server health and feed availability status.
|
|
810
|
+
* Returns server version, uptime, per-feed health status, and overall availability.
|
|
811
|
+
* Does not make upstream API calls — reports cached status from recent tool invocations.
|
|
812
|
+
*
|
|
813
|
+
* @returns Server health and feed availability data
|
|
814
|
+
*/
|
|
815
|
+
async getServerHealth() {
|
|
816
|
+
return this.safeCallTool('get_server_health', {}, SERVER_HEALTH_FALLBACK);
|
|
817
|
+
}
|
|
786
818
|
}
|
|
787
819
|
let clientInstance = null;
|
|
788
820
|
/**
|
|
@@ -19,7 +19,7 @@ const BINARY_NAME = 'european-parliament-mcp-server';
|
|
|
19
19
|
const BINARY_FILE = process.platform === 'win32' ? `${BINARY_NAME}.cmd` : BINARY_NAME;
|
|
20
20
|
/** Default binary resolved from node_modules/.bin relative to this file's compiled location */
|
|
21
21
|
const DEFAULT_SERVER_BINARY = resolve(dirname(fileURLToPath(import.meta.url)), `../../node_modules/.bin/${BINARY_FILE}`);
|
|
22
|
-
/** Default request timeout in milliseconds — EU Parliament API responses commonly take 30-
|
|
22
|
+
/** Default request timeout in milliseconds — EU Parliament API responses commonly take 30-120+ seconds for large datasets */
|
|
23
23
|
const DEFAULT_REQUEST_TIMEOUT_MS = 180_000;
|
|
24
24
|
/**
|
|
25
25
|
* Effective request timeout, configurable via `EP_REQUEST_TIMEOUT_MS` env var.
|
|
@@ -482,8 +482,16 @@ export class MCPConnection {
|
|
|
482
482
|
const isJavaScriptFile = this.serverPath.toLowerCase().endsWith('.js');
|
|
483
483
|
const command = isJavaScriptFile ? process.execPath : this.serverPath;
|
|
484
484
|
const args = isJavaScriptFile ? [this.serverPath] : [];
|
|
485
|
+
// Ensure EP_REQUEST_TIMEOUT_MS is propagated to the MCP server subprocess.
|
|
486
|
+
// The EP MCP server defaults to only 10 seconds; we need 90+ seconds for
|
|
487
|
+
// slow EP API feed endpoints (events, procedures, documents, etc.).
|
|
488
|
+
const childEnv = { ...process.env };
|
|
489
|
+
if (!childEnv['EP_REQUEST_TIMEOUT_MS']) {
|
|
490
|
+
childEnv['EP_REQUEST_TIMEOUT_MS'] = String(REQUEST_TIMEOUT_MS);
|
|
491
|
+
}
|
|
485
492
|
this.process = spawn(command, args, {
|
|
486
493
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
494
|
+
env: childEnv,
|
|
487
495
|
});
|
|
488
496
|
let buffer = '';
|
|
489
497
|
let startupError = null;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { createHash } from 'crypto';
|
|
8
8
|
import { ALL_LANGUAGES, LANGUAGE_FLAGS, LANGUAGE_NAMES, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, SKIP_LINK_TEXTS, SOURCES_HEADING_LABELS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LINKS_LABELS, FOOTER_BUILT_BY_LABELS, FOOTER_LANGUAGES_LABELS, ANALYSIS_TRANSPARENCY_LABELS, ANALYSIS_SUMMARY_LABELS, METHODOLOGY_LABELS, TRANSPARENCY_DISCLOSURE_LABELS, CLASSIFICATION_ANALYSIS_LABELS, THREAT_ASSESSMENT_LABELS, RISK_SCORING_LABELS, DEEP_ANALYSIS_LABELS, VIEW_SOURCE_LABELS, OPEN_SOURCE_NOTE_LABELS, AI_ANALYSIS_GUIDE_LABELS, SWOT_FRAMEWORK_LABELS, RISK_METHODOLOGY_LABELS, THREAT_FRAMEWORK_LABELS, CLASSIFICATION_GUIDE_LABELS, STYLE_GUIDE_LABELS, SIGNIFICANCE_CLASSIFICATION_LABELS, ACTOR_MAPPING_LABELS, FORCES_ANALYSIS_LABELS, IMPACT_MATRIX_LABELS, POLITICAL_THREAT_LANDSCAPE_LABELS, ACTOR_THREAT_PROFILING_LABELS, CONSEQUENCE_TREES_LABELS, LEGISLATIVE_DISRUPTION_LABELS, RISK_MATRIX_LABELS, QUANTITATIVE_SWOT_LABELS, POLITICAL_CAPITAL_RISK_LABELS, LEGISLATIVE_VELOCITY_RISK_LABELS, AGENT_RISK_WORKFLOW_LABELS, STAKEHOLDER_IMPACT_LABELS, COALITION_DYNAMICS_LABELS, VOTING_PATTERNS_LABELS, CROSS_SESSION_INTELLIGENCE_LABELS, getLocalizedString, getTextDirection, } from '../constants/languages.js';
|
|
9
9
|
import { escapeHTML, isSafeURL } from '../utils/file-utils.js';
|
|
10
|
+
import { stripHtmlTags } from '../utils/html-sanitize.js';
|
|
10
11
|
import { APP_VERSION, createThemeToggleButton, THEME_TOGGLE_SCRIPT, THEME_TOGGLE_SCRIPT_CONTENT, } from '../constants/config.js';
|
|
11
12
|
/** Pattern for valid article dates (YYYY-MM-DD) */
|
|
12
13
|
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
|
|
@@ -16,6 +17,11 @@ const SLUG_PATTERN = /^[a-z0-9-]+$/u;
|
|
|
16
17
|
const SRI_HASH_PATTERN = /^sha(?:256|384|512)-[A-Za-z0-9+/]+={0,2}$/u;
|
|
17
18
|
/** Words per minute for read-time calculation */
|
|
18
19
|
const TEMPLATE_WORDS_PER_MINUTE = 250;
|
|
20
|
+
/**
|
|
21
|
+
* Base URL for the deployed site, constructed via the URL API so that CodeQL
|
|
22
|
+
* recognises it as a validated URL rather than a potential regex pattern.
|
|
23
|
+
*/
|
|
24
|
+
const SITE_BASE_URL = new URL('/euparliamentmonitor', 'https://hack23.github.io').href;
|
|
19
25
|
/**
|
|
20
26
|
* BCP47 / Open Graph locale mapping for og:locale meta tag.
|
|
21
27
|
* Maps our 2-letter language codes to proper BCP47 locale strings.
|
|
@@ -116,11 +122,7 @@ export function generateArticleHTML(options) {
|
|
|
116
122
|
const categoryLabel = categoryLabels[category] ?? category;
|
|
117
123
|
const readTimeFormatter = getLocalizedString(READ_TIME_LABELS, lang);
|
|
118
124
|
// Auto-compute read-time from content word count if not explicitly set
|
|
119
|
-
const contentWordCount = content
|
|
120
|
-
.replace(/<[^>]+>/gu, ' ')
|
|
121
|
-
.replace(/\s+/gu, ' ')
|
|
122
|
-
.trim()
|
|
123
|
-
.split(' ').length;
|
|
125
|
+
const contentWordCount = stripHtmlTags(content).replace(/\s+/gu, ' ').trim().split(' ').length;
|
|
124
126
|
const computedReadTime = Math.max(1, Math.ceil(contentWordCount / TEMPLATE_WORDS_PER_MINUTE));
|
|
125
127
|
const effectiveReadTime = readTime > 0 ? readTime : computedReadTime;
|
|
126
128
|
const readTimeLabel = readTimeFormatter(effectiveReadTime);
|
|
@@ -156,12 +158,12 @@ export function generateArticleHTML(options) {
|
|
|
156
158
|
publisher: {
|
|
157
159
|
'@type': 'Organization',
|
|
158
160
|
name: 'EU Parliament Monitor',
|
|
159
|
-
url:
|
|
161
|
+
url: SITE_BASE_URL,
|
|
160
162
|
},
|
|
161
163
|
keywords: keywords.join(', '),
|
|
162
164
|
mainEntityOfPage: {
|
|
163
165
|
'@type': 'WebPage',
|
|
164
|
-
'@id':
|
|
166
|
+
'@id': `${SITE_BASE_URL}/news/${date}-${slug}-${lang}.html`,
|
|
165
167
|
},
|
|
166
168
|
}, null, 4);
|
|
167
169
|
// Validate and escape stylesHash — only allow valid SRI hash format
|
|
@@ -220,10 +222,10 @@ export function generateArticleHTML(options) {
|
|
|
220
222
|
<meta property="og:type" content="article">
|
|
221
223
|
<meta property="og:title" content="${safeTitle}">
|
|
222
224
|
<meta property="og:description" content="${safeSubtitle}">
|
|
223
|
-
<meta property="og:url" content="
|
|
225
|
+
<meta property="og:url" content="${SITE_BASE_URL}/news/${date}-${slug}-${lang}.html">
|
|
224
226
|
<meta property="og:site_name" content="EU Parliament Monitor">
|
|
225
227
|
<meta property="og:locale" content="${OG_LOCALE_MAP[lang] ?? lang}">
|
|
226
|
-
<meta property="og:image" content="
|
|
228
|
+
<meta property="og:image" content="${SITE_BASE_URL}/images/og-image.jpg">
|
|
227
229
|
<meta property="og:image:width" content="1200">
|
|
228
230
|
<meta property="og:image:height" content="630">
|
|
229
231
|
<meta property="og:image:alt" content="EU Parliament Monitor — AI-Disrupted Parliamentary Intelligence">
|
|
@@ -232,10 +234,10 @@ export function generateArticleHTML(options) {
|
|
|
232
234
|
<meta name="twitter:card" content="summary_large_image">
|
|
233
235
|
<meta name="twitter:title" content="${safeTitle}">
|
|
234
236
|
<meta name="twitter:description" content="${safeSubtitle}">
|
|
235
|
-
<meta name="twitter:image" content="
|
|
237
|
+
<meta name="twitter:image" content="${SITE_BASE_URL}/images/og-image.jpg">
|
|
236
238
|
<meta name="twitter:image:alt" content="EU Parliament Monitor — AI-Disrupted Parliamentary Intelligence">
|
|
237
239
|
|
|
238
|
-
<link rel="canonical" href="
|
|
240
|
+
<link rel="canonical" href="${SITE_BASE_URL}/news/${date}-${slug}-${lang}.html">
|
|
239
241
|
<link rel="stylesheet" href="../styles.css"${safeSriAttrs}>
|
|
240
242
|
|
|
241
243
|
<!-- Schema.org structured data -->
|
|
@@ -415,8 +417,8 @@ function renderAnalysisTransparencySection(date, slug, lang, analysisDir) {
|
|
|
415
417
|
const styleGuideLabel = escapeHTML(getLocalizedString(STYLE_GUIDE_LABELS, lang));
|
|
416
418
|
const repoBase = 'https://github.com/Hack23/euparliamentmonitor/blob/main';
|
|
417
419
|
const treeDirBase = 'https://github.com/Hack23/euparliamentmonitor/tree/main';
|
|
418
|
-
const analysisDirUrl = `${treeDirBase}/analysis/${safeDate}/${safeAnalysisDirName}`;
|
|
419
|
-
const analysisFileBase = `${repoBase}/analysis/${safeDate}/${safeAnalysisDirName}`;
|
|
420
|
+
const analysisDirUrl = `${treeDirBase}/analysis/daily/${safeDate}/${safeAnalysisDirName}`;
|
|
421
|
+
const analysisFileBase = `${repoBase}/analysis/daily/${safeDate}/${safeAnalysisDirName}`;
|
|
420
422
|
const methodologyDir = `${repoBase}/analysis/methodologies`;
|
|
421
423
|
// Per-file localized link labels
|
|
422
424
|
const significanceLabel = escapeHTML(getLocalizedString(SIGNIFICANCE_CLASSIFICATION_LABELS, lang));
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { escapeHTML } from '../utils/file-utils.js';
|
|
9
9
|
import { getLocalizedString, TOC_ARIA_LABELS } from '../constants/languages.js';
|
|
10
|
-
import { stripScriptBlocks } from '../utils/html-sanitize.js';
|
|
10
|
+
import { stripScriptBlocks, stripHtmlTags } from '../utils/html-sanitize.js';
|
|
11
11
|
/**
|
|
12
12
|
* Count occurrences of a regex pattern in a string.
|
|
13
13
|
*
|
|
@@ -52,10 +52,7 @@ export function computeArticleQualityScore(content) {
|
|
|
52
52
|
// Uses iterative scanning instead of regex to avoid CodeQL js/bad-tag-filter.
|
|
53
53
|
const noScripts = stripScriptBlocks(content);
|
|
54
54
|
// Strip HTML tags to get plain text, then count words
|
|
55
|
-
const plainText = noScripts
|
|
56
|
-
.replace(/<[^>]*>/g, ' ')
|
|
57
|
-
.replace(/\s+/g, ' ')
|
|
58
|
-
.trim();
|
|
55
|
+
const plainText = stripHtmlTags(noScripts).replace(/\s+/g, ' ').trim();
|
|
59
56
|
const wordCount = plainText.length > 0 ? plainText.split(' ').filter((w) => w.length > 0).length : 0;
|
|
60
57
|
// All further counting uses script-stripped HTML to avoid false positives
|
|
61
58
|
// from embedded JSON-LD or interactive script blocks.
|
package/scripts/types/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
export { type LanguageCode, type RTLLanguageCode, type AnyLanguageCode, ArticleCategory, ArticlePerspective, TimePeriod, AnalysisPerspective, CATEGORY_PERSPECTIVE, CATEGORY_TIME_PERIOD, type LanguagePreset, type LanguageMap, type ArticleCategoryLabels, type LangTitleSubtitle, type PropositionsStrings, type EditorialStrings, type MotionsStrings, type WeekAheadStrings, type WeekAheadStakeholderStrings, type StakeholderImpactRow, type StakeholderImpactSection, type PoliticalTemperatureBand, type PoliticalTemperature, type BreakingStrings, type ActionConsequence, type StakeholderOutcome, type PoliticalMistake, type DeepAnalysis, type DeepAnalysisStrings, type CommitteeAnalysisContentStrings, } from './common.js';
|
|
13
13
|
export type { ParliamentEvent, CommitteeMeeting, LegislativeDocument, LegislativeProcedure, ParliamentaryQuestion, WeekAheadData, CommitteeDocument, CommitteeData, VotingRecord, VotingPattern, VotingAnomaly, MotionsQuestion, VotingAnomalyIntelligence, CoalitionIntelligence, MEPInfluenceScore, LegislativeVelocity, EPFeedItem, AdoptedTextFeedItem, EventFeedItem, ProcedureFeedItem, MEPFeedItem, DocumentFeedItem, QuestionFeedItem, DeclarationFeedItem, CorporateBodyFeedItem, BreakingNewsFeedData, EPFeedData, VotingIntensity, CoalitionShift, PolarizationIndex, PoliticalSignificanceScore, VotingTrend, CoalitionStabilityReport, LegislativeVelocityReport, } from './parliament.js';
|
|
14
14
|
export type { ParsedArticle, ArticleSource, ArticleOptions, SitemapUrl, ArticleMetadataEntry, NewsMetadataDatabase, DateRange, GenerationStats, GenerationResult, ArticleQualityScore, TOCEntry, } from './generation.js';
|
|
15
|
-
export { type MCPClientOptions, type MCPContentItem, type MCPToolResult, type JSONRPCRequest, type JSONRPCResponse, type PendingRequest, type GetMEPsOptions, type GetPlenarySessionsOptions, type SearchDocumentsOptions, type GetParliamentaryQuestionsOptions, type GetCommitteeInfoOptions, type MonitorLegislativePipelineOptions, type AssessMEPInfluenceOptions, type AnalyzeCoalitionDynamicsOptions, type DetectVotingAnomaliesOptions, type ComparePoliticalGroupsOptions, type AnalyzeLegislativeEffectivenessOptions, type VotingRecordsOptions, type VotingPatternsOptions, type ReportType, type GenerateReportOptions, type AnalyzeCommitteeActivityOptions, type TrackMEPAttendanceOptions, type AnalyzeCountryDelegationOptions, type GeneratePoliticalLandscapeOptions, type GetCurrentMEPsOptions, type GetSpeechesOptions, type GetProceduresOptions, type GetAdoptedTextsOptions, type GetEventsOptions, type GetMeetingActivitiesOptions, type GetMeetingDecisionsOptions, type GetMEPDeclarationsOptions, type GetIncomingMEPsOptions, type GetOutgoingMEPsOptions, type GetHomonymMEPsOptions, type GetPlenaryDocumentsOptions, type GetCommitteeDocumentsOptions, type GetPlenarySessionDocumentsOptions, type GetPlenarySessionDocumentItemsOptions, type GetControlledVocabulariesOptions, type GetExternalDocumentsOptions, type GetMeetingForeseenActivitiesOptions, type GetProcedureEventsOptions, type GetMeetingPlenarySessionDocumentsOptions, type GetMeetingPlenarySessionDocumentItemsOptions, type NetworkAnalysisOptions, type SentimentTrackerOptions, type EarlyWarningSystemOptions, type ComparativeIntelligenceOptions, type CorrelateIntelligenceOptions, type GeneratedStatsCategory, type GetAllGeneratedStatsOptions, type FeedTimeframe, type FeedBaseOptions, type GetMEPsFeedOptions, type GetEventsFeedOptions, type GetProceduresFeedOptions, type GetAdoptedTextsFeedOptions, type GetMEPDeclarationsFeedOptions, type GetDocumentsFeedOptions, type GetPlenaryDocumentsFeedOptions, type GetCommitteeDocumentsFeedOptions, type GetPlenarySessionDocumentsFeedOptions, type GetExternalDocumentsFeedOptions, type GetParliamentaryQuestionsFeedOptions, type GetCorporateBodiesFeedOptions, type GetControlledVocabulariesFeedOptions, } from './mcp.js';
|
|
15
|
+
export { type MCPClientOptions, type MCPContentItem, type MCPToolResult, type JSONRPCRequest, type JSONRPCResponse, type PendingRequest, type GetMEPsOptions, type GetPlenarySessionsOptions, type SearchDocumentsOptions, type GetParliamentaryQuestionsOptions, type GetCommitteeInfoOptions, type MonitorLegislativePipelineOptions, type AssessMEPInfluenceOptions, type AnalyzeCoalitionDynamicsOptions, type DetectVotingAnomaliesOptions, type ComparePoliticalGroupsOptions, type AnalyzeLegislativeEffectivenessOptions, type VotingRecordsOptions, type VotingPatternsOptions, type ReportType, type GenerateReportOptions, type AnalyzeCommitteeActivityOptions, type TrackMEPAttendanceOptions, type AnalyzeCountryDelegationOptions, type GeneratePoliticalLandscapeOptions, type GetCurrentMEPsOptions, type GetSpeechesOptions, type GetProceduresOptions, type GetAdoptedTextsOptions, type GetEventsOptions, type GetMeetingActivitiesOptions, type GetMeetingDecisionsOptions, type GetMEPDeclarationsOptions, type GetIncomingMEPsOptions, type GetOutgoingMEPsOptions, type GetHomonymMEPsOptions, type GetPlenaryDocumentsOptions, type GetCommitteeDocumentsOptions, type GetPlenarySessionDocumentsOptions, type GetPlenarySessionDocumentItemsOptions, type GetControlledVocabulariesOptions, type GetExternalDocumentsOptions, type GetMeetingForeseenActivitiesOptions, type GetProcedureEventsOptions, type GetMeetingPlenarySessionDocumentsOptions, type GetMeetingPlenarySessionDocumentItemsOptions, type NetworkAnalysisOptions, type SentimentTrackerOptions, type EarlyWarningSystemOptions, type ComparativeIntelligenceOptions, type CorrelateIntelligenceOptions, type GeneratedStatsCategory, type GetAllGeneratedStatsOptions, type FeedTimeframe, type FeedBaseOptions, type GetMEPsFeedOptions, type GetEventsFeedOptions, type GetProceduresFeedOptions, type GetAdoptedTextsFeedOptions, type GetMEPDeclarationsFeedOptions, type GetDocumentsFeedOptions, type GetPlenaryDocumentsFeedOptions, type GetCommitteeDocumentsFeedOptions, type GetPlenarySessionDocumentsFeedOptions, type GetExternalDocumentsFeedOptions, type GetParliamentaryQuestionsFeedOptions, type GetCorporateBodiesFeedOptions, type GetControlledVocabulariesFeedOptions, type GetProcedureEventByIdOptions, } from './mcp.js';
|
|
16
16
|
export type { WBMCPClientOptions, WorldBankIndicator, WorldBankCountry, EconomicContext, EconomicIndicatorSummary, EUCountryCodeMap, PolicyRelevantIndicators, } from './world-bank.js';
|
|
17
17
|
export type { SwotItem, SwotAnalysis, SwotStrings, SwotBuilderStrings, SwotDimensionName, StakeholderType, SwotDimension, SwotCrossReference, TemporalSwotAssessment, MultiDimensionalSwot, MultiDimensionalSwotStrings, DashboardMetric, ChartDataset, ChartData, ChartConfig, DashboardPanel, DashboardConfig, DashboardStrings, DashboardBuilderStrings, MindmapNodeCategory, PolicyConnectionType, PolicyConnectionStrength, ActorType, MindmapBranchColor, MindmapNode, MindmapLayer, PolicyConnection, ActorNode, InfluenceWeight, IntelligenceMindmap, VotingBloc, VoteHighlight, CoalitionMetrics, LegislativePipeline, TrendMetric, TrendAnalytics, StakeholderMetric, } from './visualization.js';
|
|
18
18
|
export type { ArticleGrade, AnalysisDepthScore, StakeholderCoverage, VisualizationQuality, ArticleQualityReport, } from './quality.js';
|
package/scripts/types/mcp.d.ts
CHANGED
|
@@ -415,4 +415,11 @@ export interface GetCorporateBodiesFeedOptions extends FeedBaseOptions {
|
|
|
415
415
|
/** Options for getControlledVocabulariesFeed */
|
|
416
416
|
export interface GetControlledVocabulariesFeedOptions extends FeedBaseOptions {
|
|
417
417
|
}
|
|
418
|
+
/** Options for getProcedureEventById */
|
|
419
|
+
export interface GetProcedureEventByIdOptions {
|
|
420
|
+
/** Procedure process ID (required) */
|
|
421
|
+
processId: string;
|
|
422
|
+
/** Event identifier (required) */
|
|
423
|
+
eventId: string;
|
|
424
|
+
}
|
|
418
425
|
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -136,7 +136,7 @@ export interface PoliticalForcesAnalysis {
|
|
|
136
136
|
*/
|
|
137
137
|
export type ClassificationMethod = 'impact-matrix' | 'actor-mapping' | 'forces-analysis' | 'significance-assessment';
|
|
138
138
|
/**
|
|
139
|
-
* Metadata record written to `analysis/{date}/{article-type}/manifest.json`.
|
|
139
|
+
* Metadata record written to `analysis/daily/{date}/{article-type}/manifest.json`.
|
|
140
140
|
* Describes a single analysis run: when it ran, which article types were
|
|
141
141
|
* analysed, and which analytical methods were applied.
|
|
142
142
|
*/
|
|
@@ -69,9 +69,9 @@ export declare function ensureDirectoryExists(dirPath: string): void;
|
|
|
69
69
|
* `mkdirSync`, preventing TOCTOU races when concurrent workflow runs
|
|
70
70
|
* attempt to claim the same candidate.
|
|
71
71
|
*
|
|
72
|
-
* @param baseDir - The preferred directory path (e.g. `analysis/2026-04-02/breaking`)
|
|
72
|
+
* @param baseDir - The preferred directory path (e.g. `analysis/daily/2026-04-02/breaking`)
|
|
73
73
|
* @returns The original `baseDir` when no completed run exists there, or a
|
|
74
|
-
* suffixed variant (e.g. `analysis/2026-04-02/breaking-2`) otherwise.
|
|
74
|
+
* suffixed variant (e.g. `analysis/daily/2026-04-02/breaking-2`) otherwise.
|
|
75
75
|
*/
|
|
76
76
|
export declare function resolveUniqueAnalysisDir(baseDir: string): string;
|
|
77
77
|
/**
|
|
@@ -157,9 +157,9 @@ function claimDir(dirPath) {
|
|
|
157
157
|
* `mkdirSync`, preventing TOCTOU races when concurrent workflow runs
|
|
158
158
|
* attempt to claim the same candidate.
|
|
159
159
|
*
|
|
160
|
-
* @param baseDir - The preferred directory path (e.g. `analysis/2026-04-02/breaking`)
|
|
160
|
+
* @param baseDir - The preferred directory path (e.g. `analysis/daily/2026-04-02/breaking`)
|
|
161
161
|
* @returns The original `baseDir` when no completed run exists there, or a
|
|
162
|
-
* suffixed variant (e.g. `analysis/2026-04-02/breaking-2`) otherwise.
|
|
162
|
+
* suffixed variant (e.g. `analysis/daily/2026-04-02/breaking-2`) otherwise.
|
|
163
163
|
*/
|
|
164
164
|
export function resolveUniqueAnalysisDir(baseDir) {
|
|
165
165
|
// If the directory doesn't exist yet or has no manifest from a prior
|
|
@@ -14,5 +14,15 @@
|
|
|
14
14
|
* @param html - HTML string to strip
|
|
15
15
|
* @returns The HTML with script blocks replaced by spaces
|
|
16
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* Strip all HTML tags from a string, replacing each tag with a single space.
|
|
19
|
+
*
|
|
20
|
+
* Uses iterative index-based scanning instead of regex to avoid polynomial
|
|
21
|
+
* backtracking (CodeQL `js/polynomial-redos`).
|
|
22
|
+
*
|
|
23
|
+
* @param html - HTML string to strip
|
|
24
|
+
* @returns The text content with tags replaced by spaces
|
|
25
|
+
*/
|
|
26
|
+
export declare function stripHtmlTags(html: string): string;
|
|
17
27
|
export declare function stripScriptBlocks(html: string): string;
|
|
18
28
|
//# sourceMappingURL=html-sanitize.d.ts.map
|
|
@@ -16,6 +16,38 @@
|
|
|
16
16
|
* @param html - HTML string to strip
|
|
17
17
|
* @returns The HTML with script blocks replaced by spaces
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* Strip all HTML tags from a string, replacing each tag with a single space.
|
|
21
|
+
*
|
|
22
|
+
* Uses iterative index-based scanning instead of regex to avoid polynomial
|
|
23
|
+
* backtracking (CodeQL `js/polynomial-redos`).
|
|
24
|
+
*
|
|
25
|
+
* @param html - HTML string to strip
|
|
26
|
+
* @returns The text content with tags replaced by spaces
|
|
27
|
+
*/
|
|
28
|
+
export function stripHtmlTags(html) {
|
|
29
|
+
let result = '';
|
|
30
|
+
let pos = 0;
|
|
31
|
+
while (pos < html.length) {
|
|
32
|
+
const openIdx = html.indexOf('<', pos);
|
|
33
|
+
if (openIdx < 0) {
|
|
34
|
+
result += html.slice(pos);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
// Copy text before the tag
|
|
38
|
+
result += html.slice(pos, openIdx);
|
|
39
|
+
// Find the closing '>'
|
|
40
|
+
const closeIdx = html.indexOf('>', openIdx + 1);
|
|
41
|
+
if (closeIdx < 0) {
|
|
42
|
+
// Unclosed tag — keep the rest as-is
|
|
43
|
+
result += html.slice(openIdx);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
result += ' ';
|
|
47
|
+
pos = closeIdx + 1;
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
19
51
|
export function stripScriptBlocks(html) {
|
|
20
52
|
const OPEN = '<script';
|
|
21
53
|
const CLOSE = '</script';
|
|
@@ -72,7 +72,7 @@ export declare function classifyPoliticalActors(data: ClassificationInput): Poli
|
|
|
72
72
|
*/
|
|
73
73
|
export declare function analyzePoliticalForces(data: ClassificationInput): PoliticalForcesAnalysis;
|
|
74
74
|
/**
|
|
75
|
-
* Initialize the `analysis/{date}/` directory structure.
|
|
75
|
+
* Initialize the `analysis/daily/{date}/` directory structure.
|
|
76
76
|
*
|
|
77
77
|
* Creates the following sub-directories if they do not already exist:
|
|
78
78
|
* - `classification/` — Political classification results
|
|
@@ -87,7 +87,7 @@ export declare function analyzePoliticalForces(data: ClassificationInput): Polit
|
|
|
87
87
|
* - `data/mcp-responses/` — Raw MCP tool call responses
|
|
88
88
|
*
|
|
89
89
|
* When article-type scoping is used (recommended for agentic workflows),
|
|
90
|
-
* the caller should pass a scoped path such as `analysis/{date}/{slug}`.
|
|
90
|
+
* the caller should pass a scoped path such as `analysis/daily/{date}/{slug}`.
|
|
91
91
|
*
|
|
92
92
|
* @param baseDir - Base directory for analysis output (typically `analysis/`)
|
|
93
93
|
* @param date - ISO date string used as the run folder name (YYYY-MM-DD).
|
|
@@ -98,10 +98,10 @@ export declare function analyzePoliticalForces(data: ClassificationInput): Polit
|
|
|
98
98
|
* @example
|
|
99
99
|
* ```ts
|
|
100
100
|
* const runDir = initializeAnalysisDirectory('./analysis', '2026-03-26');
|
|
101
|
-
* // Creates: ./analysis/2026-03-26/classification/
|
|
102
|
-
* // ./analysis/2026-03-26/data/
|
|
103
|
-
* // ./analysis/2026-03-26/threat-assessment/
|
|
104
|
-
* // ./analysis/2026-03-26/risk-scoring/
|
|
101
|
+
* // Creates: ./analysis/daily/2026-03-26/classification/
|
|
102
|
+
* // ./analysis/daily/2026-03-26/data/
|
|
103
|
+
* // ./analysis/daily/2026-03-26/threat-assessment/
|
|
104
|
+
* // ./analysis/daily/2026-03-26/risk-scoring/
|
|
105
105
|
* ```
|
|
106
106
|
*/
|
|
107
107
|
export declare function initializeAnalysisDirectory(baseDir: string, date: string): string;
|
|
@@ -135,7 +135,7 @@ export declare function serializeFrontmatter(fm: AnalysisFrontmatter): string;
|
|
|
135
135
|
* @example
|
|
136
136
|
* ```ts
|
|
137
137
|
* // {article-type-slug} varies by workflow (e.g. 'plenary-session', 'week-ahead')
|
|
138
|
-
* writeAnalysisFile('./analysis/2026-03-26/{article-type-slug}/classification/significance-classification.md', fm, body);
|
|
138
|
+
* writeAnalysisFile('./analysis/daily/2026-03-26/{article-type-slug}/classification/significance-classification.md', fm, body);
|
|
139
139
|
* ```
|
|
140
140
|
*/
|
|
141
141
|
export declare function writeAnalysisFile(filePath: string, frontmatter: AnalysisFrontmatter, content: string): void;
|
|
@@ -681,7 +681,7 @@ export function analyzePoliticalForces(data) {
|
|
|
681
681
|
}
|
|
682
682
|
// ─── Analysis directory & file utilities ─────────────────────────────────────
|
|
683
683
|
/**
|
|
684
|
-
* Initialize the `analysis/{date}/` directory structure.
|
|
684
|
+
* Initialize the `analysis/daily/{date}/` directory structure.
|
|
685
685
|
*
|
|
686
686
|
* Creates the following sub-directories if they do not already exist:
|
|
687
687
|
* - `classification/` — Political classification results
|
|
@@ -696,7 +696,7 @@ export function analyzePoliticalForces(data) {
|
|
|
696
696
|
* - `data/mcp-responses/` — Raw MCP tool call responses
|
|
697
697
|
*
|
|
698
698
|
* When article-type scoping is used (recommended for agentic workflows),
|
|
699
|
-
* the caller should pass a scoped path such as `analysis/{date}/{slug}`.
|
|
699
|
+
* the caller should pass a scoped path such as `analysis/daily/{date}/{slug}`.
|
|
700
700
|
*
|
|
701
701
|
* @param baseDir - Base directory for analysis output (typically `analysis/`)
|
|
702
702
|
* @param date - ISO date string used as the run folder name (YYYY-MM-DD).
|
|
@@ -707,10 +707,10 @@ export function analyzePoliticalForces(data) {
|
|
|
707
707
|
* @example
|
|
708
708
|
* ```ts
|
|
709
709
|
* const runDir = initializeAnalysisDirectory('./analysis', '2026-03-26');
|
|
710
|
-
* // Creates: ./analysis/2026-03-26/classification/
|
|
711
|
-
* // ./analysis/2026-03-26/data/
|
|
712
|
-
* // ./analysis/2026-03-26/threat-assessment/
|
|
713
|
-
* // ./analysis/2026-03-26/risk-scoring/
|
|
710
|
+
* // Creates: ./analysis/daily/2026-03-26/classification/
|
|
711
|
+
* // ./analysis/daily/2026-03-26/data/
|
|
712
|
+
* // ./analysis/daily/2026-03-26/threat-assessment/
|
|
713
|
+
* // ./analysis/daily/2026-03-26/risk-scoring/
|
|
714
714
|
* ```
|
|
715
715
|
*/
|
|
716
716
|
export function initializeAnalysisDirectory(baseDir, date) {
|
|
@@ -807,7 +807,7 @@ export function serializeFrontmatter(fm) {
|
|
|
807
807
|
* @example
|
|
808
808
|
* ```ts
|
|
809
809
|
* // {article-type-slug} varies by workflow (e.g. 'plenary-session', 'week-ahead')
|
|
810
|
-
* writeAnalysisFile('./analysis/2026-03-26/{article-type-slug}/classification/significance-classification.md', fm, body);
|
|
810
|
+
* writeAnalysisFile('./analysis/daily/2026-03-26/{article-type-slug}/classification/significance-classification.md', fm, body);
|
|
811
811
|
* ```
|
|
812
812
|
*/
|
|
813
813
|
export function writeAnalysisFile(filePath, frontmatter, content) {
|
|
@@ -91,7 +91,7 @@ export declare function runAgentRiskAssessment(assessmentId: string, date: strin
|
|
|
91
91
|
/**
|
|
92
92
|
* Generate a structured markdown document from an agent risk assessment workflow.
|
|
93
93
|
* Produces a YAML-frontmatter header and all risk sections in markdown format
|
|
94
|
-
* suitable for writing to `analysis/{date}/risk-scoring/agent-risk-workflow.md`.
|
|
94
|
+
* suitable for writing to `analysis/daily/{date}/risk-scoring/agent-risk-workflow.md`.
|
|
95
95
|
*
|
|
96
96
|
* @param assessment - Completed agent risk assessment workflow
|
|
97
97
|
* @returns Markdown string with YAML frontmatter and full risk analysis
|
|
@@ -385,7 +385,7 @@ export function runAgentRiskAssessment(assessmentId, date, articleType, identifi
|
|
|
385
385
|
/**
|
|
386
386
|
* Generate a structured markdown document from an agent risk assessment workflow.
|
|
387
387
|
* Produces a YAML-frontmatter header and all risk sections in markdown format
|
|
388
|
-
* suitable for writing to `analysis/{date}/risk-scoring/agent-risk-workflow.md`.
|
|
388
|
+
* suitable for writing to `analysis/daily/{date}/risk-scoring/agent-risk-workflow.md`.
|
|
389
389
|
*
|
|
390
390
|
* @param assessment - Completed agent risk assessment workflow
|
|
391
391
|
* @returns Markdown string with YAML frontmatter and full risk analysis
|