euparliamentmonitor 0.8.59 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +2 -2
  2. package/package.json +14 -4
  3. package/scripts/aggregator/article-meta.d.ts +1 -1
  4. package/scripts/aggregator/article-metadata.js +0 -1
  5. package/scripts/aggregator/artifacts/index.d.ts +11 -0
  6. package/scripts/aggregator/artifacts/index.js +5 -0
  7. package/scripts/aggregator/artifacts/types.d.ts +46 -0
  8. package/scripts/aggregator/artifacts/types.js +4 -0
  9. package/scripts/aggregator/clean-artifact.js +2 -2
  10. package/scripts/aggregator/content/index.d.ts +14 -0
  11. package/scripts/aggregator/content/index.js +7 -0
  12. package/scripts/aggregator/content/types.d.ts +42 -0
  13. package/scripts/aggregator/content/types.js +4 -0
  14. package/scripts/aggregator/markdown/index.d.ts +9 -0
  15. package/scripts/aggregator/markdown/index.js +4 -0
  16. package/scripts/aggregator/markdown-renderer.js +2 -2
  17. package/scripts/aggregator/metadata/index.d.ts +12 -0
  18. package/scripts/aggregator/metadata/index.js +5 -0
  19. package/scripts/aggregator/metadata/types.d.ts +28 -0
  20. package/scripts/aggregator/metadata/types.js +4 -0
  21. package/scripts/constants/committee-indicator-map.js +0 -1
  22. package/scripts/constants/language-core.js +0 -1
  23. package/scripts/generators/political-intelligence/copy.js +0 -1
  24. package/scripts/generators/political-intelligence-descriptions.js +22 -32
  25. package/scripts/generators/shared/html-escape.d.ts +41 -0
  26. package/scripts/generators/shared/html-escape.js +108 -0
  27. package/scripts/generators/shared/index.d.ts +16 -0
  28. package/scripts/generators/shared/index.js +7 -0
  29. package/scripts/generators/shared/template-helpers.d.ts +67 -0
  30. package/scripts/generators/shared/template-helpers.js +109 -0
  31. package/scripts/generators/shared/types.d.ts +97 -0
  32. package/scripts/generators/shared/types.js +4 -0
  33. package/scripts/mcp/ep-mcp-client.d.ts +19 -6
  34. package/scripts/mcp/ep-mcp-client.js +23 -7
  35. package/scripts/types/index.d.ts +1 -1
  36. package/scripts/types/mcp.d.ts +7 -0
  37. package/scripts/utils/intelligence-index.js +1 -8
  38. package/scripts/workflows/completeness-gate/constants.d.ts +89 -0
  39. package/scripts/workflows/completeness-gate/constants.js +115 -0
  40. package/scripts/workflows/completeness-gate/index.d.ts +10 -0
  41. package/scripts/workflows/completeness-gate/index.js +5 -0
  42. package/scripts/workflows/completeness-gate/types.d.ts +104 -0
  43. package/scripts/workflows/completeness-gate/types.js +4 -0
  44. package/scripts/workflows/completeness-gate/validators.d.ts +117 -0
  45. package/scripts/workflows/completeness-gate/validators.js +212 -0
  46. package/scripts/workflows/index.d.ts +21 -0
  47. package/scripts/workflows/index.js +8 -0
  48. package/scripts/workflows/infrastructure/index.d.ts +7 -0
  49. package/scripts/workflows/infrastructure/index.js +4 -0
  50. package/scripts/workflows/infrastructure/shell-safety.d.ts +62 -0
  51. package/scripts/workflows/infrastructure/shell-safety.js +106 -0
  52. package/scripts/workflows/safe-outputs/index.d.ts +7 -0
  53. package/scripts/workflows/safe-outputs/index.js +4 -0
  54. package/scripts/workflows/safe-outputs/types.d.ts +63 -0
  55. package/scripts/workflows/safe-outputs/types.js +39 -0
  56. package/scripts/workflows/types.d.ts +110 -0
  57. package/scripts/workflows/types.js +14 -0
package/README.md CHANGED
@@ -136,7 +136,7 @@ The published site is the audience-facing companion to this npm/TypeScript packa
136
136
 
137
137
  **MCP Server Integration**: The project uses the
138
138
  [European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server)
139
- v1.2.21 for accessing real EU Parliament data via the Model Context Protocol.
139
+ v1.3.0 for accessing real EU Parliament data via the Model Context Protocol.
140
140
 
141
141
  - **MCP Server Status**: ✅ Fully operational — 60+ EP data tools available
142
142
  (feeds, direct lookups, analytical tools, intelligence correlation)
@@ -432,7 +432,7 @@ import type { ArticleCategory, LanguageCode } from 'euparliamentmonitor/types';
432
432
 
433
433
  ## 🔌 Data Sources
434
434
 
435
- **Primary — European Parliament MCP Server** ([Hack23/European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server) v1.2.21+, fully operational):
435
+ **Primary — European Parliament MCP Server** ([Hack23/European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server) v1.3.0+, fully operational):
436
436
 
437
437
  - 🗳️ Plenary sessions, voting records, roll-call votes
438
438
  - 📜 Adopted texts, motions, resolutions, urgency files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "euparliamentmonitor",
3
- "version": "0.8.59",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
6
6
  "main": "scripts/index.js",
@@ -33,6 +33,14 @@
33
33
  "./generators/*": {
34
34
  "import": "./scripts/generators/*.js",
35
35
  "types": "./scripts/generators/*.d.ts"
36
+ },
37
+ "./workflows": {
38
+ "import": "./scripts/workflows/index.js",
39
+ "types": "./scripts/workflows/index.d.ts"
40
+ },
41
+ "./workflows/*": {
42
+ "import": "./scripts/workflows/*.js",
43
+ "types": "./scripts/workflows/*.d.ts"
36
44
  }
37
45
  },
38
46
  "files": [
@@ -157,7 +165,7 @@
157
165
  "husky": "9.1.7",
158
166
  "jscpd": "4.0.9",
159
167
  "knip": "^6.7.0",
160
- "lint-staged": "16.4.0",
168
+ "lint-staged": "17.0.2",
161
169
  "mermaid": "11.14.0",
162
170
  "papaparse": "5.5.3",
163
171
  "prettier": "3.8.3",
@@ -171,7 +179,7 @@
171
179
  "node": ">=26"
172
180
  },
173
181
  "dependencies": {
174
- "european-parliament-mcp-server": "1.2.21",
182
+ "european-parliament-mcp-server": "1.3.0",
175
183
  "markdown-it": "^14.1.1",
176
184
  "markdown-it-anchor": "^9.2.0",
177
185
  "markdown-it-attrs": "^4.3.1",
@@ -183,6 +191,8 @@
183
191
  },
184
192
  "overrides": {
185
193
  "flatted": ">=3.4.2",
186
- "path-to-regexp": ">=8.4.0"
194
+ "path-to-regexp": ">=8.4.0",
195
+ "ip-address": ">=10.1.1",
196
+ "uuid": ">=11.1.1"
187
197
  }
188
198
  }
@@ -23,7 +23,7 @@ export interface ArticleMeta {
23
23
  readonly keyDates: readonly string[];
24
24
  /** Key actors / political groups identified by the artifacts. */
25
25
  readonly keyActors: readonly string[];
26
- /** Optional IMF / WorldBank macro hook surfaced as a sidebar callout. */
26
+ /** Optional IMF economic-context macro hook surfaced as a sidebar callout. Empty string when absent. */
27
27
  readonly macroContext: string;
28
28
  /**
29
29
  * Run-relative paths of every artifact whose content fed into this meta
@@ -324,7 +324,6 @@ export function isGenericHeading(heading, articleType, date) {
324
324
  return true;
325
325
  }
326
326
  // The bare `${human} — <anything>` with nothing extra is also generic.
327
- // eslint-disable-next-line security/detect-non-literal-regexp -- `human` derives from a sanitised slug via escapeRegex
328
327
  const trailingDateOnly = new RegExp(`^${escapeRegex(human)}\\s*[—–-]\\s*[\\d-]+$`, 'u');
329
328
  if (trailingDateOnly.test(normalized)) {
330
329
  return true;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @module Aggregator/Artifacts
3
+ * @description Public re-exports for the artifacts bounded context.
4
+ * Provides artifact ordering, sanitization, and type definitions.
5
+ */
6
+ export type { ArtifactContent, CleanedArtifactWithPath, ResolvedSection } from './types.js';
7
+ export type { ArtifactSection } from '../artifact-order.js';
8
+ export { ARTIFACT_SECTIONS, MANIFEST_SECTION_ID, MANIFEST_SECTION_TITLE, SUPPLEMENTARY_SECTION_ID, SUPPLEMENTARY_SECTION_TITLE, TRADECRAFT_SECTION_ID, TRADECRAFT_SECTION_TITLE, } from '../artifact-order.js';
9
+ export type { CleanArtifactOptions, CleanArtifactResult } from '../clean-artifact.js';
10
+ export { cleanArtifact, dedupMermaid, demoteHeadings, githubBlobUrl, githubRawUrl, resolveLink, rewriteLinks, stripArtifactMetadataPreamble, stripBanners, stripFrontMatter, stripSpdxTags, } from '../clean-artifact.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export { ARTIFACT_SECTIONS, MANIFEST_SECTION_ID, MANIFEST_SECTION_TITLE, SUPPLEMENTARY_SECTION_ID, SUPPLEMENTARY_SECTION_TITLE, TRADECRAFT_SECTION_ID, TRADECRAFT_SECTION_TITLE, } from '../artifact-order.js';
4
+ export { cleanArtifact, dedupMermaid, demoteHeadings, githubBlobUrl, githubRawUrl, resolveLink, rewriteLinks, stripArtifactMetadataPreamble, stripBanners, stripFrontMatter, stripSpdxTags, } from '../clean-artifact.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @module Aggregator/Artifacts/Types
3
+ * @description Strict type definitions for the artifacts bounded context.
4
+ * Defines the shape of artifact content, section mappings, and sanitization
5
+ * options used throughout the aggregator pipeline.
6
+ */
7
+ import type { CleanArtifactResult } from '../clean-artifact.js';
8
+ /**
9
+ * Represents raw artifact content read from an analysis run directory.
10
+ * Carries the original Markdown body along with its provenance metadata.
11
+ */
12
+ export interface ArtifactContent {
13
+ /** Run-relative path of the artifact (e.g. `intelligence/synthesis-summary.md`). */
14
+ readonly path: string;
15
+ /** Raw Markdown body as read from disk. */
16
+ readonly body: string;
17
+ /** Byte length of the raw content (before cleaning). */
18
+ readonly byteLength: number;
19
+ }
20
+ /**
21
+ * A {@link CleanArtifactResult} extended with provenance (the run-relative
22
+ * path of the source artifact). Use this type when you need to track which
23
+ * artifact produced a given cleaned output — e.g. for telemetry or
24
+ * source-mapping in `article-meta.json`.
25
+ *
26
+ * `CleanArtifactResult` is the raw return of `cleanArtifact()`;
27
+ * `CleanedArtifactWithPath` adds the origin path for pipeline bookkeeping.
28
+ */
29
+ export interface CleanedArtifactWithPath extends CleanArtifactResult {
30
+ /** Run-relative path of the source artifact. */
31
+ readonly path: string;
32
+ }
33
+ /**
34
+ * Represents a fully resolved section in the aggregated article, mapping
35
+ * from the canonical section definition to the actual artifact paths found
36
+ * in the run directory.
37
+ */
38
+ export interface ResolvedSection {
39
+ /** Stable section id used for HTML anchors. */
40
+ readonly id: string;
41
+ /** English section title. */
42
+ readonly title: string;
43
+ /** Ordered list of resolved artifact paths present in this section. */
44
+ readonly resolvedArtifacts: readonly string[];
45
+ }
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,4 @@
1
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -194,7 +194,7 @@ export function stripSpdxTags(md) {
194
194
  */
195
195
  function advanceFenceState(line, inFence, fenceMarker) {
196
196
  const fenceMatch = /^(\s*)(```+|~~~+)(.*)$/.exec(line);
197
- if (!fenceMatch || !fenceMatch[2])
197
+ if (!fenceMatch?.[2])
198
198
  return { inFence, fenceMarker, matched: false };
199
199
  const marker = fenceMatch[2];
200
200
  if (!inFence) {
@@ -224,7 +224,7 @@ function processHeadingLine(lines, index) {
224
224
  return { output: null, consumed: 2, h1Removed: true };
225
225
  }
226
226
  const atx = /^(\s*)(#{1,6})(\s.*)$/.exec(line);
227
- if (!atx || !atx[2]) {
227
+ if (!atx?.[2]) {
228
228
  return { output: line, consumed: 1, h1Removed: false };
229
229
  }
230
230
  const level = atx[2].length;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @module Aggregator/Content
3
+ * @description Public re-exports for the content extraction bounded context.
4
+ * Provides lead extraction, key takeaways synthesis, and intelligence guide
5
+ * generation.
6
+ */
7
+ export type { ExtractedLead, SynthesisedTakeaway, KeyTakeawaysResult } from './types.js';
8
+ export { extractExecutiveLead, extractLeadParagraph, trimToLeadSentence, MAX_LEAD_CHARS, } from '../lead-extractor.js';
9
+ export type { Takeaway, BuildKeyTakeawaysOptions } from '../key-takeaways.js';
10
+ export { buildKeyTakeaways, MIN_TAKEAWAYS, MAX_TAKEAWAYS, KEY_TAKEAWAYS_SECTION_ID, KEY_TAKEAWAYS_SECTION_TITLE, } from '../key-takeaways.js';
11
+ export { buildReaderIntelligenceGuideHtml } from '../reader-intelligence-guide.js';
12
+ export { READER_GUIDE_SECTION_ID, READER_GUIDE_SECTION_IDS, READER_GUIDE_SECTION_TITLE, } from '../reader-guide-constants.js';
13
+ export type { TocSection, IncludedArtifact } from '../reader-guide-constants.js';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,7 @@
1
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export { extractExecutiveLead, extractLeadParagraph, trimToLeadSentence, MAX_LEAD_CHARS, } from '../lead-extractor.js';
4
+ export { buildKeyTakeaways, MIN_TAKEAWAYS, MAX_TAKEAWAYS, KEY_TAKEAWAYS_SECTION_ID, KEY_TAKEAWAYS_SECTION_TITLE, } from '../key-takeaways.js';
5
+ export { buildReaderIntelligenceGuideHtml } from '../reader-intelligence-guide.js';
6
+ export { READER_GUIDE_SECTION_ID, READER_GUIDE_SECTION_IDS, READER_GUIDE_SECTION_TITLE, } from '../reader-guide-constants.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @module Aggregator/Content/Types
3
+ * @description Strict type definitions for the content extraction bounded
4
+ * context. Covers lead paragraph extraction, key takeaways synthesis,
5
+ * and section-to-artifact mapping.
6
+ */
7
+ /**
8
+ * Extracted lead paragraph — the journalistic "nut graf" from the article.
9
+ */
10
+ export interface ExtractedLead {
11
+ /** The extracted lead text (plain, no Markdown). */
12
+ readonly text: string;
13
+ /** Run-relative source artifact the lead was extracted from. */
14
+ readonly source: string;
15
+ /** Character length of the extracted lead. */
16
+ readonly length: number;
17
+ }
18
+ /**
19
+ * One synthesised key takeaway bullet ready for rendering.
20
+ */
21
+ export interface SynthesisedTakeaway {
22
+ /** Bullet body in Markdown (trimmed; no leading `- `). */
23
+ readonly body: string;
24
+ /** Run-relative path of the source artifact. */
25
+ readonly source: string;
26
+ }
27
+ /**
28
+ * Structured representation of key-takeaways synthesis output.
29
+ *
30
+ * **Note:** The current `buildKeyTakeaways` function returns a plain `string`
31
+ * (the rendered Markdown block). This interface is a forward-looking contract
32
+ * for future structured consumers that need both the raw takeaway items and
33
+ * the rendered output. Use {@link Takeaway} (from `key-takeaways.ts`) for
34
+ * per-item typing when processing the raw bullets.
35
+ */
36
+ export interface KeyTakeawaysResult {
37
+ /** Ordered list of synthesised takeaways (3–7 items). */
38
+ readonly takeaways: readonly SynthesisedTakeaway[];
39
+ /** Rendered Markdown block (empty string when below minimum threshold). */
40
+ readonly markdown: string;
41
+ }
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,4 @@
1
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @module Aggregator/Markdown
3
+ * @description Public re-exports for the Markdown rendering bounded context.
4
+ * Provides markdown-it based HTML rendering with plugin support and
5
+ * TOC generation.
6
+ */
7
+ export type { RenderOptions, RenderedMarkdown, TocEntry } from '../markdown-renderer.js';
8
+ export { buildMarkdownIt, renderMarkdown } from '../markdown-renderer.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export { buildMarkdownIt, renderMarkdown } from '../markdown-renderer.js';
4
+ //# sourceMappingURL=index.js.map
@@ -185,14 +185,14 @@ function harvestToc(tokens) {
185
185
  const out = [];
186
186
  for (let i = 0; i < tokens.length; i++) {
187
187
  const token = tokens[i];
188
- if (!token || token.type !== 'heading_open')
188
+ if (token?.type !== 'heading_open')
189
189
  continue;
190
190
  const level = Number.parseInt(token.tag.slice(1), 10);
191
191
  if (!Number.isFinite(level) || level < 2 || level > 6)
192
192
  continue;
193
193
  const slug = typeof token.attrGet === 'function' ? token.attrGet('id') : null;
194
194
  const inline = tokens[i + 1];
195
- if (!inline || inline.type !== 'inline')
195
+ if (inline?.type !== 'inline')
196
196
  continue;
197
197
  const text = (inline.content ?? '').trim();
198
198
  out.push({ level, slug: slug ?? slugify(text), text });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @module Aggregator/Metadata
3
+ * @description Public re-exports for the metadata bounded context.
4
+ * Provides article metadata building, SEO metadata resolution,
5
+ * and structured data contracts.
6
+ */
7
+ export type { PerLanguageMetadata, ArticleMetaSidecar } from './types.js';
8
+ export type { ArticleMeta, BuildArticleMetaOptions } from '../article-meta.js';
9
+ export { buildArticleMeta } from '../article-meta.js';
10
+ export type { ResolvedMetadataEntry, ResolvedMetadata } from '../article-metadata.js';
11
+ export { resolveArticleMetadata, stripInlineMarkdown } from '../article-metadata.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export { buildArticleMeta } from '../article-meta.js';
4
+ export { resolveArticleMetadata, stripInlineMarkdown } from '../article-metadata.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @module Aggregator/Metadata/Types
3
+ * @description Strict type definitions for the metadata bounded context.
4
+ * Covers article metadata, SEO metadata, and structured data contracts.
5
+ */
6
+ import type { LanguageCode } from '../../types/index.js';
7
+ /**
8
+ * Per-language resolved metadata entry containing the title and
9
+ * description that will be rendered into HTML meta tags and
10
+ * structured data (JSON-LD).
11
+ */
12
+ export interface PerLanguageMetadata {
13
+ /** Language code this entry is for. */
14
+ readonly lang: LanguageCode;
15
+ /** Resolved title for the article in this language. */
16
+ readonly title: string;
17
+ /** Resolved description for the article in this language. */
18
+ readonly description: string;
19
+ }
20
+ /**
21
+ * Complete article metadata sidecar shape (`article-meta.json`).
22
+ * This is a type alias for the canonical {@link ArticleMeta} interface
23
+ * defined in `article-meta.ts`. Use this name when referring to the
24
+ * sidecar contract in downstream consumers (HTML, RSS, sitemap, news
25
+ * indexes).
26
+ */
27
+ export type { ArticleMeta as ArticleMetaSidecar } from '../article-meta.js';
28
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,4 @@
1
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -1347,7 +1347,6 @@ export function getCategoryIndicators(category) {
1347
1347
  if (!Object.hasOwn(CATEGORY_INDICATOR_MAP, category)) {
1348
1348
  return getCategoryIndicators(ArticleCategory.BREAKING_NEWS);
1349
1349
  }
1350
- // eslint-disable-next-line security/detect-object-injection -- key validated via Object.hasOwn
1351
1350
  return CATEGORY_INDICATOR_MAP[category];
1352
1351
  }
1353
1352
  /**
@@ -66,7 +66,6 @@ export const LANGUAGE_NAMES = {
66
66
  */
67
67
  export function getLocalizedString(map, lang) {
68
68
  const code = lang;
69
- // eslint-disable-next-line security/detect-object-injection -- key validated via Object.hasOwn
70
69
  return Object.hasOwn(map, code) ? (map[code] ?? map.en) : map.en;
71
70
  }
72
71
  /**
@@ -442,7 +442,6 @@ export const PI_COPY = (() => {
442
442
  * @returns Fully-populated {@link PICopy}
443
443
  */
444
444
  export function getPICopy(lang) {
445
- // eslint-disable-next-line security/detect-object-injection
446
445
  const overrides = PI_COPY[lang] ?? {};
447
446
  return { ...DEFAULT_COPY, ...overrides };
448
447
  }
@@ -73,6 +73,9 @@ export const CURATED_DESCRIPTIONS = {
73
73
  zh: '欧盟范围选举分析方法论 — 预测、欧洲议会 361 席阈值及成员国层面的联盟数学,以及选民分群框架。',
74
74
  },
75
75
  },
76
+ 'analysis/methodologies/analytical-supplementary-methodology.md': {
77
+ description: 'Optional deep-dive methodology — PESTLE, Wildcards, SWOT scoring, and Media Framing v2.0.',
78
+ },
76
79
  'analysis/methodologies/imf-indicator-mapping.md': {
77
80
  description: 'Canonical mapping of IMF WEO, Fiscal Monitor, IFS, BOP, ER and PCPS indicators to European Parliament Monitor article types — the primary source for economic, monetary, fiscal, trade and FDI context.',
78
81
  i18n: {
@@ -313,7 +316,7 @@ export const CURATED_DESCRIPTIONS = {
313
316
  description: 'MCP reliability audit — endpoint health and uptime report for every European Parliament MCP tool invocation during a workflow run.',
314
317
  },
315
318
  'analysis/templates/media-framing-analysis.md': {
316
- description: 'Media framing analysismaps how narratives spread across outlets and languages, comparing national-media framings of EP events.',
319
+ description: 'Media framing & influence-operations DISARM TTPs, CIB detection, narrative-laundering, counter-resilience across EU-27.',
317
320
  },
318
321
  'analysis/templates/methodology-reflection.md': {
319
322
  description: 'Methodology reflection template — the final Step 10.5 artifact capturing lessons learned, protocol gaps and continuous-improvement notes for each run.',
@@ -1951,7 +1954,6 @@ function kindWord(relPath, lang) {
1951
1954
  * @returns The resolved string (never empty for well-formed records)
1952
1955
  */
1953
1956
  function getFromRecord(record, lang) {
1954
- // eslint-disable-next-line security/detect-object-injection
1955
1957
  return record[lang] ?? record.en;
1956
1958
  }
1957
1959
  /**
@@ -1968,7 +1970,6 @@ function getFromRecord(record, lang) {
1968
1970
  * @returns Fully localized description sentence
1969
1971
  */
1970
1972
  function buildGenericFallback(relPath, lang, title) {
1971
- // eslint-disable-next-line security/detect-object-injection
1972
1973
  const template = GENERIC_FALLBACK_I18N[lang] ?? GENERIC_FALLBACK_I18N.en;
1973
1974
  const kind = kindWord(relPath, lang);
1974
1975
  return template.replace('{title}', title).replace('{kind}', kind);
@@ -1996,10 +1997,14 @@ function buildGenericFallback(relPath, lang, title) {
1996
1997
  export function getCuratedDescription(relPath, lang, fallback = '') {
1997
1998
  // Normalise path separators so Windows callers don't silently miss entries.
1998
1999
  const key = relPath.replace(/\\/g, '/');
1999
- // eslint-disable-next-line security/detect-object-injection
2000
+ // Guard against prototype-chain lookups for keys like __proto__ or constructor.
2001
+ if (!Object.prototype.hasOwnProperty.call(CURATED_DESCRIPTIONS, key)) {
2002
+ // No curated entry — build a localized fallback from the file's title.
2003
+ const localizedTitle = getCuratedTitle(key, lang, fallback || stripEmojiAndPunct(key));
2004
+ return buildGenericFallback(key, lang, localizedTitle);
2005
+ }
2000
2006
  const entry = CURATED_DESCRIPTIONS[key];
2001
2007
  if (entry) {
2002
- // eslint-disable-next-line security/detect-object-injection
2003
2008
  const localized = entry.i18n?.[lang];
2004
2009
  if (localized)
2005
2010
  return localized;
@@ -2023,7 +2028,6 @@ export function getCuratedDescription(relPath, lang, fallback = '') {
2023
2028
  * @returns `true` when the curated table contains the file
2024
2029
  */
2025
2030
  export function hasCuratedDescription(relPath) {
2026
- // eslint-disable-next-line security/detect-object-injection
2027
2031
  return Object.prototype.hasOwnProperty.call(CURATED_DESCRIPTIONS, relPath.replace(/\\/g, '/'));
2028
2032
  }
2029
2033
  /**
@@ -2035,7 +2039,6 @@ export function hasCuratedDescription(relPath) {
2035
2039
  * @returns `true` when {@link CURATED_TITLES} contains the file
2036
2040
  */
2037
2041
  export function hasCuratedTitle(relPath) {
2038
- // eslint-disable-next-line security/detect-object-injection
2039
2042
  return Object.prototype.hasOwnProperty.call(CURATED_TITLES, relPath.replace(/\\/g, '/'));
2040
2043
  }
2041
2044
  /**
@@ -2062,27 +2065,20 @@ export function hasCuratedTitle(relPath) {
2062
2065
  */
2063
2066
  export function getCuratedTitle(relPath, lang, fallback) {
2064
2067
  const key = relPath.replace(/\\/g, '/');
2065
- // 1 + 2: curated title overlay
2066
- // eslint-disable-next-line security/detect-object-injection
2067
- const titleEntry = CURATED_TITLES[key];
2068
- if (titleEntry) {
2069
- // eslint-disable-next-line security/detect-object-injection
2070
- const localized = titleEntry[lang];
2071
- if (localized)
2072
- return localized;
2073
- if (titleEntry.en)
2074
- return titleEntry.en;
2068
+ // 1 + 2: curated title overlay — guard against prototype-chain lookups
2069
+ if (Object.prototype.hasOwnProperty.call(CURATED_TITLES, key)) {
2070
+ const titleEntry = CURATED_TITLES[key];
2071
+ if (titleEntry) {
2072
+ // Prefer localized, then English overlay
2073
+ return titleEntry[lang] ?? titleEntry.en ?? fallback;
2074
+ }
2075
2075
  }
2076
2076
  // 3 + 4: historic colocated title on CURATED_DESCRIPTIONS entry
2077
- // eslint-disable-next-line security/detect-object-injection
2078
- const descEntry = CURATED_DESCRIPTIONS[key];
2079
- if (descEntry) {
2080
- // eslint-disable-next-line security/detect-object-injection
2081
- const localized = descEntry.titleI18n?.[lang];
2082
- if (localized)
2083
- return localized;
2084
- if (descEntry.title)
2085
- return descEntry.title;
2077
+ if (Object.prototype.hasOwnProperty.call(CURATED_DESCRIPTIONS, key)) {
2078
+ const descEntry = CURATED_DESCRIPTIONS[key];
2079
+ if (descEntry) {
2080
+ return descEntry.titleI18n?.[lang] ?? descEntry.title ?? fallback;
2081
+ }
2086
2082
  }
2087
2083
  return fallback;
2088
2084
  }
@@ -2673,7 +2669,6 @@ export function parseRunSlug(slug) {
2673
2669
  const sorted = [...RUN_TYPE_SLUGS].sort((a, b) => b.length - a.length);
2674
2670
  for (const prefix of sorted) {
2675
2671
  if (lower === prefix || lower.startsWith(`${prefix}-`) || lower.startsWith(`${prefix}_`)) {
2676
- // eslint-disable-next-line security/detect-object-injection
2677
2672
  const canonical = RUN_TYPE_ALIASES[prefix];
2678
2673
  const tail = slug.slice(prefix.length).replace(/^[-_]+/, '');
2679
2674
  return { type: canonical, runId: tail };
@@ -2696,9 +2691,7 @@ export function parseRunSlug(slug) {
2696
2691
  export function getRunTypeInfo(slug, lang) {
2697
2692
  const { type, runId } = parseRunSlug(slug);
2698
2693
  if (type) {
2699
- // eslint-disable-next-line security/detect-object-injection
2700
2694
  const titleRecord = RUN_TYPE_TITLES[type];
2701
- // eslint-disable-next-line security/detect-object-injection
2702
2695
  const descRecord = RUN_TYPE_DESCRIPTIONS[type];
2703
2696
  const title = titleRecord ? getFromRecord(titleRecord, lang) : stripEmojiAndPunct(slug);
2704
2697
  const description = descRecord ? getFromRecord(descRecord, lang) : '';
@@ -2797,7 +2790,6 @@ function canonicalizeArtifactStem(stem) {
2797
2790
  'ai-voting-patterns': 'voting-patterns',
2798
2791
  };
2799
2792
  if (Object.prototype.hasOwnProperty.call(SYNONYMS, s)) {
2800
- // eslint-disable-next-line security/detect-object-injection
2801
2793
  const synonym = SYNONYMS[s];
2802
2794
  if (typeof synonym === 'string')
2803
2795
  return synonym;
@@ -3135,7 +3127,6 @@ export function getArtifactInfo(shortPath, lang) {
3135
3127
  // and we still guard with `hasOwn` to block any prototype-key surprise.
3136
3128
  const feed = parseFeedPrefix(rawStem);
3137
3129
  if (feed && Object.prototype.hasOwnProperty.call(FEED_PREFIX_LABELS, feed.feed)) {
3138
- // eslint-disable-next-line security/detect-object-injection
3139
3130
  const entry = FEED_PREFIX_LABELS[feed.feed];
3140
3131
  if (entry) {
3141
3132
  return {
@@ -3152,7 +3143,6 @@ export function getArtifactInfo(shortPath, lang) {
3152
3143
  // (e.g. a hypothetical `__proto__.md` file).
3153
3144
  const stemLower = stem.toLowerCase();
3154
3145
  if (Object.prototype.hasOwnProperty.call(ORPHAN_ARTIFACT_INFO, stemLower)) {
3155
- // eslint-disable-next-line security/detect-object-injection
3156
3146
  const orphan = ORPHAN_ARTIFACT_INFO[stemLower];
3157
3147
  if (orphan) {
3158
3148
  return {
@@ -0,0 +1,41 @@
1
+ import type { AbsoluteUrl, RelativeFilePath, SafeHtmlString, SafeXmlString } from './types.js';
2
+ /**
3
+ * HTML-entity-escape a raw string and brand the result as safe for HTML
4
+ * interpolation. Escapes `&`, `<`, `>`, `"`, and `'`.
5
+ *
6
+ * @param raw - Untrusted string (user input, file content, etc.)
7
+ * @returns Branded {@link SafeHtmlString} safe for use in HTML templates
8
+ */
9
+ export declare function toSafeHtml(raw: string): SafeHtmlString;
10
+ /**
11
+ * XML-entity-escape a raw string and brand the result as safe for XML
12
+ * interpolation. Escapes `&`, `<`, `>`, `"`, and `'`.
13
+ *
14
+ * @param raw - Untrusted string (user input, file content, etc.)
15
+ * @returns Branded {@link SafeXmlString} safe for use in XML templates
16
+ */
17
+ export declare function toSafeXml(raw: string): SafeXmlString;
18
+ /**
19
+ * Validate and brand a string as an absolute HTTPS URL.
20
+ * Validates using `new URL()` to ensure structural correctness, checks
21
+ * that the protocol is `https:`, and rejects characters that are unsafe
22
+ * in HTML attribute contexts (quotes, angle brackets, whitespace, control
23
+ * characters) to prevent attribute-injection/XSS.
24
+ *
25
+ * @param url - Candidate URL string
26
+ * @returns Branded {@link AbsoluteUrl}
27
+ * @throws {Error} when `url` is not a valid absolute HTTPS URL or
28
+ * contains characters unsafe for HTML attribute interpolation
29
+ */
30
+ export declare function toAbsoluteUrl(url: string): AbsoluteUrl;
31
+ /**
32
+ * Normalize and brand a file path as a relative POSIX path.
33
+ * Strips leading slashes, converts backslashes to forward slashes, and
34
+ * rejects path-traversal sequences (`..`) to prevent directory escape.
35
+ *
36
+ * @param filePath - Raw file path
37
+ * @returns Branded {@link RelativeFilePath}
38
+ * @throws {Error} when the path contains `..` traversal segments
39
+ */
40
+ export declare function toRelativeFilePath(filePath: string): RelativeFilePath;
41
+ //# sourceMappingURL=html-escape.d.ts.map