euparliamentmonitor 0.8.45 → 0.8.47

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 CHANGED
@@ -163,7 +163,7 @@ The published platform at **[euparliamentmonitor.com](https://euparliamentmonito
163
163
 
164
164
  **MCP Server Integration**: The project uses the
165
165
  [European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server)
166
- v1.2.13 for accessing real EU Parliament data via the Model Context Protocol.
166
+ v1.2.15 for accessing real EU Parliament data via the Model Context Protocol.
167
167
 
168
168
  - **MCP Server Status**: ✅ Fully operational — 60+ EP data tools available
169
169
  (feeds, direct lookups, analytical tools, intelligence correlation)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "euparliamentmonitor",
3
- "version": "0.8.45",
3
+ "version": "0.8.47",
4
4
  "type": "module",
5
5
  "description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
6
6
  "main": "scripts/index.js",
@@ -158,7 +158,7 @@
158
158
  "husky": "9.1.7",
159
159
  "jscpd": "4.0.9",
160
160
  "lint-staged": "16.4.0",
161
- "mermaid": "^11.14.0",
161
+ "mermaid": "11.14.0",
162
162
  "papaparse": "5.5.3",
163
163
  "prettier": "3.8.3",
164
164
  "ts-api-utils": "2.5.0",
@@ -171,7 +171,7 @@
171
171
  "node": ">=25"
172
172
  },
173
173
  "dependencies": {
174
- "european-parliament-mcp-server": "1.2.14",
174
+ "european-parliament-mcp-server": "1.2.15",
175
175
  "markdown-it": "^14.1.1",
176
176
  "markdown-it-anchor": "^9.2.0",
177
177
  "markdown-it-attrs": "^4.3.1",
@@ -2,6 +2,17 @@ import { type ArtifactSection } from './artifact-order.js';
2
2
  /** Raw manifest shape as committed by the analysis pipeline. */
3
3
  export interface AnalysisManifest {
4
4
  readonly articleType: string;
5
+ /**
6
+ * Legacy plural variant emitted by some pre-aggregator-pipeline workflows.
7
+ * Used as a fallback when `articleType` is absent so historic runs with
8
+ * `articleTypes: ["<slug>"]` can still be aggregated.
9
+ */
10
+ readonly articleTypes?: readonly string[];
11
+ /**
12
+ * Legacy field emitted by older breaking-run manifests. Used as the last
13
+ * fallback when neither `articleType` nor `articleTypes` is present.
14
+ */
15
+ readonly runType?: string;
5
16
  readonly runId?: string;
6
17
  readonly date?: string;
7
18
  readonly analysisDir?: string;
@@ -167,6 +178,20 @@ export declare function renderAnalysisIndex(included: readonly IncludedArtifact[
167
178
  * @returns Markdown block containing the guide table
168
179
  */
169
180
  export declare function renderReaderIntelligenceGuide(sections: readonly TocSection[], included: readonly IncludedArtifact[]): string;
181
+ /**
182
+ * Resolve the article-type slug from a manifest, tolerating legacy schemas.
183
+ *
184
+ * Resolution order (highest precedence first):
185
+ * 1. `articleType` — canonical singular field
186
+ * 2. `articleTypes[0]` — pre-aggregator-pipeline plural array
187
+ * 3. `runType` — legacy field on older breaking-run manifests
188
+ *
189
+ * Falls back to `'unknown'` when none of the above is a non-empty string.
190
+ *
191
+ * @param manifest - Parsed manifest (any of the supported schemas)
192
+ * @returns Article-type slug usable as a filename component
193
+ */
194
+ export declare function resolveArticleTypeFromManifest(manifest: AnalysisManifest): string;
170
195
  /**
171
196
  * Read, clean, and concatenate every artifact declared by the run's manifest
172
197
  * (with discovery fallback when manifest.files is missing), returning a
@@ -471,6 +471,32 @@ function appendSection(runDir, runDirRelPath, sectionId, sectionTitle, paths, se
471
471
  }
472
472
  sectionMarkdown.push('');
473
473
  }
474
+ /**
475
+ * Resolve the article-type slug from a manifest, tolerating legacy schemas.
476
+ *
477
+ * Resolution order (highest precedence first):
478
+ * 1. `articleType` — canonical singular field
479
+ * 2. `articleTypes[0]` — pre-aggregator-pipeline plural array
480
+ * 3. `runType` — legacy field on older breaking-run manifests
481
+ *
482
+ * Falls back to `'unknown'` when none of the above is a non-empty string.
483
+ *
484
+ * @param manifest - Parsed manifest (any of the supported schemas)
485
+ * @returns Article-type slug usable as a filename component
486
+ */
487
+ export function resolveArticleTypeFromManifest(manifest) {
488
+ if (typeof manifest.articleType === 'string' && manifest.articleType) {
489
+ return manifest.articleType;
490
+ }
491
+ const first = manifest.articleTypes?.[0];
492
+ if (typeof first === 'string' && first) {
493
+ return first;
494
+ }
495
+ if (typeof manifest.runType === 'string' && manifest.runType) {
496
+ return manifest.runType;
497
+ }
498
+ return 'unknown';
499
+ }
474
500
  /**
475
501
  * Read, clean, and concatenate every artifact declared by the run's manifest
476
502
  * (with discovery fallback when manifest.files is missing), returning a
@@ -516,7 +542,7 @@ export function aggregateAnalysisRun(options) {
516
542
  consumed.add(p);
517
543
  }
518
544
  const tradecraftFiles = options.tradecraftFiles ?? discoverTradecraftFiles(repoRoot);
519
- const articleType = manifest.articleType ?? 'unknown';
545
+ const articleType = resolveArticleTypeFromManifest(manifest);
520
546
  const date = manifest.date ?? guessDateFromRunDir(runDirRelPath);
521
547
  const runId = manifest.runId ?? path.basename(runDir);
522
548
  const gateResult = latestGateResult(manifest);
@@ -19,7 +19,7 @@
19
19
  import fs from 'fs';
20
20
  import path from 'path';
21
21
  import { pathToFileURL } from 'url';
22
- import { aggregateAnalysisRun } from './analysis-aggregator.js';
22
+ import { aggregateAnalysisRun, resolveArticleTypeFromManifest, } from './analysis-aggregator.js';
23
23
  import { resolveArticleMetadata, extractStrongProseLine, } from './article-metadata.js';
24
24
  import { renderMarkdown } from './markdown-renderer.js';
25
25
  import { wrapArticleHtml, getArticleFilename } from './article-html.js';
@@ -528,14 +528,21 @@ function readRunCandidate(runDir, manifestPath) {
528
528
  catch {
529
529
  return null;
530
530
  }
531
- const articleType = typeof parsed.articleType === 'string' ? parsed.articleType : '';
531
+ // Resolve via the same precedence used by the aggregator (articleType
532
+ // articleTypes[0] → runType) so legacy-schema manifests are picked up by
533
+ // batch mode rather than silently skipped.
534
+ const articleType = resolveArticleTypeFromManifest(parsed);
532
535
  if (!articleType || articleType === 'unknown')
533
536
  return null;
534
537
  const dateFromManifest = typeof parsed.date === 'string' ? parsed.date : '';
535
538
  const date = /^\d{4}-\d{2}-\d{2}$/.test(dateFromManifest)
536
539
  ? dateFromManifest
537
540
  : dateFromRunPath(runDir);
538
- const runId = typeof parsed.runId === 'string' && parsed.runId ? parsed.runId : path.basename(runDir);
541
+ const runId = typeof parsed.runId === 'string' && parsed.runId
542
+ ? parsed.runId
543
+ : typeof parsed.runId === 'number'
544
+ ? String(parsed.runId)
545
+ : path.basename(runDir);
539
546
  return { runDir, articleType, date, runId };
540
547
  }
541
548
  /**
@@ -609,8 +616,9 @@ function readManifestMetadata(runDir) {
609
616
  try {
610
617
  const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
611
618
  const manifest = {};
612
- if (typeof parsed.articleType === 'string') {
613
- Object.assign(manifest, { articleType: parsed.articleType });
619
+ const resolvedType = resolveArticleTypeFromManifest(parsed);
620
+ if (resolvedType && resolvedType !== 'unknown') {
621
+ Object.assign(manifest, { articleType: resolvedType });
614
622
  }
615
623
  if (typeof parsed.date === 'string') {
616
624
  Object.assign(manifest, { date: parsed.date });
@@ -10,11 +10,15 @@
10
10
  * consistent with the rest of the site.
11
11
  *
12
12
  * The output is a complete HTML5 document. No inline `<script>` is emitted
13
- * in the body. Mermaid is loaded from the vendored ESM bundle via
14
- * `<script type="module" src="js/vendor/mermaid.esm.min.mjs">` so CSP
15
- * stays `script-src 'self'`.
13
+ * in the body. Mermaid is loaded from the same-origin vendored ESM bundle
14
+ * (copied to `js/vendor/mermaid/` by `scripts/copy-vendor.js`) via
15
+ * `<script type="module" src="../js/mermaid-init.js?v=<MERMAID_VERSION>" defer>`
16
+ * so CSP stays `script-src 'self'`. The `?v=` query parameter is sourced
17
+ * from `devDependencies.mermaid` in `package.json` (a fixed pin like
18
+ * `11.14.0`); regenerating articles after a Mermaid bump invalidates
19
+ * browser and CloudFront caches automatically.
16
20
  */
17
- import { BASE_URL, createThemeToggleButton, THEME_TOGGLE_SCRIPT } from '../constants/config.js';
21
+ import { BASE_URL, createThemeToggleButton, MERMAID_VERSION, THEME_TOGGLE_SCRIPT, } from '../constants/config.js';
18
22
  import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, SKIP_LINK_TEXTS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, TOC_ARIA_LABELS, getLocalizedString, getTextDirection, } from '../constants/languages.js';
19
23
  import { escapeHTML } from '../utils/file-utils.js';
20
24
  import { buildSiteFooter } from '../templates/section-builders.js';
@@ -181,7 +185,7 @@ ${hreflangLinks}
181
185
  <meta name="theme-color" content="#003399">
182
186
  <link rel="stylesheet" href="../styles.css">
183
187
  <script type="application/ld+json">${jsonLdString}</script>
184
- <script type="module" src="../js/mermaid-init.js" defer></script>
188
+ <script type="module" src="../js/mermaid-init.js?v=${MERMAID_VERSION}" defer></script>
185
189
  </head>
186
190
  <body>
187
191
  <a href="#main" class="skip-link">${escapeHTML(skipLinkText)}</a>
@@ -33,6 +33,17 @@ export declare const ARTICLE_TYPE_MONTH_IN_REVIEW = ArticleCategory.MONTH_IN_REV
33
33
  export declare const ARG_SEPARATOR = "=";
34
34
  /** Application version read from package.json */
35
35
  export declare const APP_VERSION: string;
36
+ /**
37
+ * Pinned Mermaid bundle version, read from `devDependencies.mermaid` in
38
+ * `package.json`. Used as a cache-busting query parameter on the
39
+ * `mermaid-init.js` script tag in generated article HTML so a Mermaid
40
+ * version bump in `package.json` automatically invalidates browser /
41
+ * CloudFront caches the next time articles are regenerated. Any leading
42
+ * semver range character (`^`, `~`, `>=`) is stripped — the contract for
43
+ * this repo is a fixed pin (e.g. `"mermaid": "11.14.0"`), but stripping
44
+ * keeps us robust if the pin is briefly relaxed during a dependency update.
45
+ */
46
+ export declare const MERMAID_VERSION: string;
36
47
  /**
37
48
  * Generate theme toggle HTML button markup with a localized aria-label.
38
49
  * Renders a moon (light→dark) and sun (dark→light) icon; CSS controls visibility.
@@ -61,6 +61,37 @@ export const APP_VERSION = (() => {
61
61
  return '0.0.0';
62
62
  }
63
63
  })();
64
+ /**
65
+ * Pinned Mermaid bundle version, read from `devDependencies.mermaid` in
66
+ * `package.json`. Used as a cache-busting query parameter on the
67
+ * `mermaid-init.js` script tag in generated article HTML so a Mermaid
68
+ * version bump in `package.json` automatically invalidates browser /
69
+ * CloudFront caches the next time articles are regenerated. Any leading
70
+ * semver range character (`^`, `~`, `>=`) is stripped — the contract for
71
+ * this repo is a fixed pin (e.g. `"mermaid": "11.14.0"`), but stripping
72
+ * keeps us robust if the pin is briefly relaxed during a dependency update.
73
+ */
74
+ export const MERMAID_VERSION = (() => {
75
+ try {
76
+ const pkgPath = path.join(PROJECT_ROOT, 'package.json');
77
+ const parsed = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
78
+ if (typeof parsed === 'object' && parsed !== null && 'devDependencies' in parsed) {
79
+ const devDeps = parsed.devDependencies;
80
+ if (typeof devDeps === 'object' && devDeps !== null && 'mermaid' in devDeps) {
81
+ const raw = devDeps.mermaid;
82
+ if (typeof raw === 'string' && raw.trim() !== '') {
83
+ return raw.replace(/^[\^~><=\s]+/, '').trim();
84
+ }
85
+ }
86
+ }
87
+ console.warn('Invalid or missing "devDependencies.mermaid" in package.json, falling back to 0.0.0');
88
+ return '0.0.0';
89
+ }
90
+ catch (err) {
91
+ console.warn('Failed to read mermaid version from package.json:', err);
92
+ return '0.0.0';
93
+ }
94
+ })();
64
95
  /**
65
96
  * Generate theme toggle HTML button markup with a localized aria-label.
66
97
  * Renders a moon (light→dark) and sun (dark→light) icon; CSS controls visibility.
@@ -7,7 +7,7 @@ import { MCPConnection } from './mcp-connection.js';
7
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, GetFreshProceduresOptions } from '../types/index.js';
8
8
  /**
9
9
  * Canonical list of tools exposed by the European Parliament MCP gateway
10
- * (`european-parliament-mcp-server@1.2.14`). The news workflows, prompt
10
+ * (`european-parliament-mcp-server@1.2.15`). The news workflows, prompt
11
11
  * library (`.github/prompts/07-mcp-reference.md`), and the integration test
12
12
  * suite all reference this list so a regression that adds/removes a tool
13
13
  * fails a single drift guard
@@ -22,7 +22,7 @@ export declare const EP_MCP_TOOLS: readonly string[];
22
22
  * covering the two shapes historically emitted by the EP MCP server.
23
23
  *
24
24
  * 1. **Uniform envelope** (all feeds as of
25
- * `european-parliament-mcp-server@1.2.14`) —
25
+ * `european-parliament-mcp-server@1.2.15`) —
26
26
  * `{status:"unavailable", items:[], generatedAt:"..."}` established by
27
27
  * Hack23/European-Parliament-MCP-Server#301 and extended to
28
28
  * `get_events_feed`/`get_procedures_feed` by
@@ -133,21 +133,20 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
133
133
  *
134
134
  * @remarks
135
135
  * This repository is currently documented/configured against
136
- * `european-parliament-mcp-server@1.2.13`.
136
+ * `european-parliament-mcp-server@1.2.15`.
137
137
  *
138
- * **Conditional upstream note (v1.2.14+ only):** If the configured EP-MCP server is upgraded
139
- * to v1.2.14 or newer, the upstream server applies a client-side post-filter on
140
- * `dateFrom`/`dateTo` before serialisation, because the EP Open Data Portal `/meetings`
141
- * endpoint silently ignores its `date-from`/`date-to` query parameters (Defect #5).
142
- * Under that newer upstream contract:
138
+ * **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.15 server):** the upstream server
139
+ * applies a server-side post-filter on `dateFrom`/`dateTo` before serialisation, because the
140
+ * EP Open Data Portal `/meetings` endpoint silently ignores its `date-from`/`date-to` query
141
+ * parameters (Defect #5). Under this contract:
143
142
  * - `data[]` contains only sessions within the requested window.
144
143
  * - `total` reflects the **filtered** count, not the raw upstream count.
145
144
  * - Per-window session counts are reproducible because the EP-side regression is masked by
146
145
  * the upstream post-filter.
147
146
  *
148
- * No local post-filter is applied here. When running against the pinned v1.2.13 baseline,
149
- * callers should not assume the v1.2.14+ date-filter guarantees unless the server/runtime
150
- * documentation has been updated accordingly.
147
+ * No local post-filter is applied here. The repository is pinned to v1.2.15, so the
148
+ * date-filter guarantees above apply; consumers running against an older server image
149
+ * (pre-v1.2.14) must not assume them.
151
150
  */
152
151
  getPlenarySessions(options?: GetPlenarySessionsOptions): Promise<MCPToolResult>;
153
152
  /**
@@ -10,7 +10,7 @@ import { ProcedureSeenCache } from './procedure-seen-cache.js';
10
10
  import { recordPendingDocument, markDocumentResolved, getPendingDocumentsForReprobe, escalateExpiredDocuments, getPendingDocumentsSummary, } from './pending-documents.js';
11
11
  /**
12
12
  * Canonical list of tools exposed by the European Parliament MCP gateway
13
- * (`european-parliament-mcp-server@1.2.14`). The news workflows, prompt
13
+ * (`european-parliament-mcp-server@1.2.15`). The news workflows, prompt
14
14
  * library (`.github/prompts/07-mcp-reference.md`), and the integration test
15
15
  * suite all reference this list so a regression that adds/removes a tool
16
16
  * fails a single drift guard
@@ -114,7 +114,7 @@ const CONTENT_NOT_YET_AVAILABLE_SUBSTRING = 'document indexed but content not ye
114
114
  /**
115
115
  * Classify an error message into a diagnostic error category.
116
116
  *
117
- * Maps EP MCP Server v1.2.14 structured error codes and generic HTTP/network
117
+ * Maps EP MCP Server v1.2.15 structured error codes and generic HTTP/network
118
118
  * errors into one of six broad categories used for logging and retry decisions:
119
119
  *
120
120
  * Returned categories (priority order):
@@ -130,7 +130,7 @@ const CONTENT_NOT_YET_AVAILABLE_SUBSTRING = 'document indexed but content not ye
130
130
  */
131
131
  function classifyToolError(message) {
132
132
  const lowerMsg = message.toLowerCase();
133
- // EP MCP Server v1.2.14 structured error codes (matched case-insensitively)
133
+ // EP MCP Server v1.2.15 structured error codes (matched case-insensitively)
134
134
  if (lowerMsg.includes('internal_error')) {
135
135
  return 'INTERNAL_ERROR';
136
136
  }
@@ -189,7 +189,7 @@ function _parseResultPayload(result) {
189
189
  * covering the two shapes historically emitted by the EP MCP server.
190
190
  *
191
191
  * 1. **Uniform envelope** (all feeds as of
192
- * `european-parliament-mcp-server@1.2.14`) —
192
+ * `european-parliament-mcp-server@1.2.15`) —
193
193
  * `{status:"unavailable", items:[], generatedAt:"..."}` established by
194
194
  * Hack23/European-Parliament-MCP-Server#301 and extended to
195
195
  * `get_events_feed`/`get_procedures_feed` by
@@ -449,21 +449,20 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
449
449
  *
450
450
  * @remarks
451
451
  * This repository is currently documented/configured against
452
- * `european-parliament-mcp-server@1.2.13`.
452
+ * `european-parliament-mcp-server@1.2.15`.
453
453
  *
454
- * **Conditional upstream note (v1.2.14+ only):** If the configured EP-MCP server is upgraded
455
- * to v1.2.14 or newer, the upstream server applies a client-side post-filter on
456
- * `dateFrom`/`dateTo` before serialisation, because the EP Open Data Portal `/meetings`
457
- * endpoint silently ignores its `date-from`/`date-to` query parameters (Defect #5).
458
- * Under that newer upstream contract:
454
+ * **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.15 server):** the upstream server
455
+ * applies a server-side post-filter on `dateFrom`/`dateTo` before serialisation, because the
456
+ * EP Open Data Portal `/meetings` endpoint silently ignores its `date-from`/`date-to` query
457
+ * parameters (Defect #5). Under this contract:
459
458
  * - `data[]` contains only sessions within the requested window.
460
459
  * - `total` reflects the **filtered** count, not the raw upstream count.
461
460
  * - Per-window session counts are reproducible because the EP-side regression is masked by
462
461
  * the upstream post-filter.
463
462
  *
464
- * No local post-filter is applied here. When running against the pinned v1.2.13 baseline,
465
- * callers should not assume the v1.2.14+ date-filter guarantees unless the server/runtime
466
- * documentation has been updated accordingly.
463
+ * No local post-filter is applied here. The repository is pinned to v1.2.15, so the
464
+ * date-filter guarantees above apply; consumers running against an older server image
465
+ * (pre-v1.2.14) must not assume them.
467
466
  */
468
467
  async getPlenarySessions(options = {}) {
469
468
  return this.safeCallTool('get_plenary_sessions', options, '{"data": [], "total": 0}');
@@ -404,7 +404,7 @@ export type FeedTimeframe = 'today' | 'one-day' | 'one-week' | 'one-month' | 'cu
404
404
  * These feeds serve a server-defined window. Historically (pre-v1.2.13) they
405
405
  * rejected `timeframe`/`startDate` with `INVALID_PARAMS`
406
406
  * (Hack23/European-Parliament-MCP-Server#377); as of
407
- * `european-parliament-mcp-server@1.2.13` (PR #379) the server silently
407
+ * `european-parliament-mcp-server@1.2.15` (PR #379) the server silently
408
408
  * ignores those params on fixed-window tools. The client continues to omit
409
409
  * them so intent matches behaviour and so we remain compatible with any
410
410
  * older pinned server version in downstream environments.