euparliamentmonitor 0.8.41 → 0.8.43

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 (135) hide show
  1. package/README.md +136 -26
  2. package/SECURITY.md +11 -6
  3. package/package.json +11 -9
  4. package/scripts/aggregator/analysis-aggregator.d.ts +168 -0
  5. package/scripts/aggregator/analysis-aggregator.js +478 -0
  6. package/scripts/aggregator/article-generator.d.ts +141 -0
  7. package/scripts/aggregator/article-generator.js +693 -0
  8. package/scripts/aggregator/article-html.d.ts +90 -0
  9. package/scripts/aggregator/article-html.js +215 -0
  10. package/scripts/aggregator/article-metadata.d.ts +186 -0
  11. package/scripts/aggregator/article-metadata.js +701 -0
  12. package/scripts/aggregator/artifact-order.d.ts +52 -0
  13. package/scripts/aggregator/artifact-order.js +148 -0
  14. package/scripts/aggregator/clean-artifact.d.ts +138 -0
  15. package/scripts/aggregator/clean-artifact.js +609 -0
  16. package/scripts/aggregator/markdown-renderer.d.ts +69 -0
  17. package/scripts/aggregator/markdown-renderer.js +169 -0
  18. package/scripts/backport-article-seo.js +1037 -0
  19. package/scripts/constants/language-ui.d.ts +2 -0
  20. package/scripts/constants/language-ui.js +17 -0
  21. package/scripts/constants/languages.d.ts +1 -1
  22. package/scripts/constants/languages.js +1 -1
  23. package/scripts/generators/political-intelligence-descriptions.d.ts +200 -0
  24. package/scripts/generators/political-intelligence-descriptions.js +3043 -0
  25. package/scripts/generators/political-intelligence.d.ts +118 -0
  26. package/scripts/generators/political-intelligence.js +1260 -0
  27. package/scripts/generators/sitemap.d.ts +13 -0
  28. package/scripts/generators/sitemap.js +616 -127
  29. package/scripts/index.d.ts +18 -65
  30. package/scripts/index.js +24 -85
  31. package/scripts/index.old.js +125 -0
  32. package/scripts/lint-prompts.js +42 -4
  33. package/scripts/mcp/ep-mcp-client.d.ts +9 -9
  34. package/scripts/mcp/ep-mcp-client.js +14 -14
  35. package/scripts/templates/section-builders.js +5 -3
  36. package/scripts/types/generation.d.ts +13 -0
  37. package/scripts/types/index.d.ts +1 -2
  38. package/scripts/types/mcp.d.ts +14 -14
  39. package/scripts/utils/content-metadata.d.ts +1 -1
  40. package/scripts/utils/file-utils.d.ts +87 -9
  41. package/scripts/utils/file-utils.js +176 -15
  42. package/scripts/generators/analysis-builders.d.ts +0 -12
  43. package/scripts/generators/analysis-builders.js +0 -14
  44. package/scripts/generators/breaking-content.d.ts +0 -45
  45. package/scripts/generators/breaking-content.js +0 -518
  46. package/scripts/generators/builders/breaking-builders.d.ts +0 -52
  47. package/scripts/generators/builders/breaking-builders.js +0 -400
  48. package/scripts/generators/builders/committee-builders.d.ts +0 -47
  49. package/scripts/generators/builders/committee-builders.js +0 -324
  50. package/scripts/generators/builders/index.d.ts +0 -13
  51. package/scripts/generators/builders/index.js +0 -15
  52. package/scripts/generators/builders/propositions-builders.d.ts +0 -53
  53. package/scripts/generators/builders/propositions-builders.js +0 -536
  54. package/scripts/generators/builders/prospective-builders.d.ts +0 -50
  55. package/scripts/generators/builders/prospective-builders.js +0 -369
  56. package/scripts/generators/builders/shared-builders.d.ts +0 -155
  57. package/scripts/generators/builders/shared-builders.js +0 -328
  58. package/scripts/generators/builders/voting-builders.d.ts +0 -65
  59. package/scripts/generators/builders/voting-builders.js +0 -565
  60. package/scripts/generators/committee-helpers.d.ts +0 -54
  61. package/scripts/generators/committee-helpers.js +0 -154
  62. package/scripts/generators/dashboard-content.d.ts +0 -95
  63. package/scripts/generators/dashboard-content.js +0 -631
  64. package/scripts/generators/deep-analysis-content.d.ts +0 -22
  65. package/scripts/generators/deep-analysis-content.js +0 -853
  66. package/scripts/generators/mindmap-content.d.ts +0 -22
  67. package/scripts/generators/mindmap-content.js +0 -447
  68. package/scripts/generators/motions-content.d.ts +0 -50
  69. package/scripts/generators/motions-content.js +0 -391
  70. package/scripts/generators/news-enhanced.d.ts +0 -50
  71. package/scripts/generators/news-enhanced.js +0 -545
  72. package/scripts/generators/pipeline/analysis-stage.d.ts +0 -100
  73. package/scripts/generators/pipeline/analysis-stage.js +0 -203
  74. package/scripts/generators/pipeline/fetch-stage.d.ts +0 -370
  75. package/scripts/generators/pipeline/fetch-stage.js +0 -1726
  76. package/scripts/generators/pipeline/generate-stage.d.ts +0 -73
  77. package/scripts/generators/pipeline/generate-stage.js +0 -268
  78. package/scripts/generators/pipeline/output-stage.d.ts +0 -54
  79. package/scripts/generators/pipeline/output-stage.js +0 -156
  80. package/scripts/generators/pipeline/transform-stage.d.ts +0 -57
  81. package/scripts/generators/pipeline/transform-stage.js +0 -111
  82. package/scripts/generators/propositions-content.d.ts +0 -29
  83. package/scripts/generators/propositions-content.js +0 -80
  84. package/scripts/generators/strategies/article-strategy.d.ts +0 -265
  85. package/scripts/generators/strategies/article-strategy.js +0 -514
  86. package/scripts/generators/strategies/breaking-news-strategy.d.ts +0 -64
  87. package/scripts/generators/strategies/breaking-news-strategy.js +0 -340
  88. package/scripts/generators/strategies/committee-reports-strategy.d.ts +0 -93
  89. package/scripts/generators/strategies/committee-reports-strategy.js +0 -470
  90. package/scripts/generators/strategies/month-ahead-strategy.d.ts +0 -60
  91. package/scripts/generators/strategies/month-ahead-strategy.js +0 -196
  92. package/scripts/generators/strategies/monthly-review-strategy.d.ts +0 -66
  93. package/scripts/generators/strategies/monthly-review-strategy.js +0 -220
  94. package/scripts/generators/strategies/motions-strategy.d.ts +0 -61
  95. package/scripts/generators/strategies/motions-strategy.js +0 -235
  96. package/scripts/generators/strategies/propositions-strategy.d.ts +0 -60
  97. package/scripts/generators/strategies/propositions-strategy.js +0 -263
  98. package/scripts/generators/strategies/week-ahead-strategy.d.ts +0 -57
  99. package/scripts/generators/strategies/week-ahead-strategy.js +0 -196
  100. package/scripts/generators/strategies/weekly-review-strategy.d.ts +0 -63
  101. package/scripts/generators/strategies/weekly-review-strategy.js +0 -232
  102. package/scripts/generators/swot-content.d.ts +0 -18
  103. package/scripts/generators/swot-content.js +0 -105
  104. package/scripts/generators/synthesis-summary.d.ts +0 -93
  105. package/scripts/generators/synthesis-summary.js +0 -364
  106. package/scripts/generators/week-ahead-content.d.ts +0 -103
  107. package/scripts/generators/week-ahead-content.js +0 -603
  108. package/scripts/templates/article-template.d.ts +0 -32
  109. package/scripts/templates/article-template.js +0 -702
  110. package/scripts/utils/article-quality-scorer.d.ts +0 -108
  111. package/scripts/utils/article-quality-scorer.js +0 -1214
  112. package/scripts/utils/content-validator.d.ts +0 -253
  113. package/scripts/utils/content-validator.js +0 -1610
  114. package/scripts/utils/fix-articles.d.ts +0 -27
  115. package/scripts/utils/fix-articles.js +0 -510
  116. package/scripts/utils/imf-data.d.ts +0 -216
  117. package/scripts/utils/imf-data.js +0 -577
  118. package/scripts/utils/intelligence-analysis.d.ts +0 -223
  119. package/scripts/utils/intelligence-analysis.js +0 -1229
  120. package/scripts/utils/political-classification.d.ts +0 -191
  121. package/scripts/utils/political-classification.js +0 -885
  122. package/scripts/utils/political-risk-assessment.d.ts +0 -197
  123. package/scripts/utils/political-risk-assessment.js +0 -980
  124. package/scripts/utils/political-threat-assessment.d.ts +0 -116
  125. package/scripts/utils/political-threat-assessment.js +0 -1438
  126. package/scripts/utils/retrofit-analysis-links.d.ts +0 -14
  127. package/scripts/utils/retrofit-analysis-links.js +0 -265
  128. package/scripts/utils/significance-scoring.d.ts +0 -140
  129. package/scripts/utils/significance-scoring.js +0 -294
  130. package/scripts/utils/validate-analysis-completeness.d.ts +0 -80
  131. package/scripts/utils/validate-analysis-completeness.js +0 -882
  132. package/scripts/utils/validate-articles.d.ts +0 -2
  133. package/scripts/utils/validate-articles.js +0 -402
  134. package/scripts/utils/world-bank-data.d.ts +0 -116
  135. package/scripts/utils/world-bank-data.js +0 -414
package/README.md CHANGED
@@ -131,15 +131,24 @@ import {
131
131
 
132
132
  **MCP Server Integration**: The project uses the
133
133
  [European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server)
134
- v1.2.11 for accessing real EU Parliament data via the Model Context Protocol.
134
+ v1.2.13 for accessing real EU Parliament data via the Model Context Protocol.
135
135
 
136
136
  - **MCP Server Status**: ✅ Fully operational — 60+ EP data tools available
137
137
  (feeds, direct lookups, analytical tools, intelligence correlation)
138
- - **Agentic Workflows**: 10 gh-aw markdown workflows (compiled with
139
- `gh-aw v0.69.3`) for automated news generation with AI-driven political
138
+ - **Agentic Workflows**: 9 unified gh-aw markdown workflows — 8 article types (`news-<type>.md`, Stages A → B → C → D → E in one session) + `news-translate.md` (14-language flush translation) — compiled with
139
+ `gh-aw v0.69.3` to `.lock.yml` for automated news generation with AI-driven political
140
140
  intelligence analysis. See [`.github/workflows/README.md`](.github/workflows/README.md).
141
- - **Analysis Chain**: 5-stage pipeline (Data Analysis Completeness Gate →
142
- Article Single PR) producing 39 structured analysis templates per run.
141
+ - **Analysis-Artifact-Driven Article Pipeline**: Agents author the full
142
+ Stage-B analysis-artifact set (`analysis/daily/<date>/<slug>-run<NN>/`, 39
143
+ structured templates per run) and commit it; the deterministic aggregator
144
+ (`src/aggregator/**`, invoked via
145
+ `npm run generate-article -- --run <analysis-run-dir>` or
146
+ `npm run generate-article:all` for batch regen) then walks `manifest.json`,
147
+ cleans each artifact, and emits the final Markdown source + HTML with the
148
+ shared site chrome (stacked header + embedded 14-language switcher + TOC
149
+ sidebar + footer stats) and 14-language `<link rel="alternate" hreflang>`
150
+ entries. There is no AI-authored HTML step; article quality is guaranteed
151
+ editorially at the Stage-C completeness review over the artifact markdown.
143
152
  See [`analysis/README.md`](analysis/README.md) and
144
153
  [`analysis/methodologies/ai-driven-analysis-guide.md`](analysis/methodologies/ai-driven-analysis-guide.md).
145
154
  - **Fallback Mode**: News generation can work with reduced data when EP API
@@ -168,6 +177,13 @@ covering:
168
177
  - 🤖 **GitHub Actions Integration**: Automated daily news generation
169
178
  - 📊 **SEO Optimized**: Proper metadata, structured data, and sitemap generation
170
179
  - ✅ **Code Quality**: ESLint, Prettier, and automated quality gates
180
+ - 🌐 **IMF-grade Economic Context**: IMF-primary citations are the
181
+ editorial standard for every policy article — see
182
+ [`analysis/imf/README.md`](analysis/imf/README.md) and
183
+ [`analysis/methodologies/imf-indicator-mapping.md`](analysis/methodologies/imf-indicator-mapping.md).
184
+ World Bank is retained only for non-economic indicators (health,
185
+ education, social, environment, demographics, defence, agriculture,
186
+ innovation, governance).
171
187
 
172
188
  ## 🔒 Security Architecture
173
189
 
@@ -398,19 +414,17 @@ import {
398
414
  // MCP Client for EU Parliament data
399
415
  EuropeanParliamentMCPClient,
400
416
  getEPMCPClient,
401
- // Intelligence analysis
402
- scoreVotingAnomaly,
403
- analyzeCoalitionCohesion,
404
- assessPoliticalThreats,
405
- // Article generation
406
- generateArticleHTML,
407
- scoreArticleQuality,
417
+ // Intelligence index (cross-article relationships)
418
+ buildIndexFromEntries,
419
+ findRelatedArticles,
420
+ generateCrossReferences,
421
+ // Article generation (aggregator pipeline)
422
+ generateArticle,
423
+ aggregateAnalysisRun,
424
+ renderMarkdown,
408
425
  // Multi-language support (14 languages)
409
426
  ALL_LANGUAGES,
410
427
  LANGUAGE_NAMES,
411
- // Content validation
412
- validateArticleContent,
413
- validateTranslationCompleteness,
414
428
  } from 'euparliamentmonitor';
415
429
 
416
430
  // Or import specific modules for tree-shaking
@@ -459,27 +473,119 @@ without it using placeholder content.
459
473
 
460
474
  ### Generate News Articles
461
475
 
476
+ Agentic workflows generate articles automatically. For manual article generation from an analysis run:
477
+
462
478
  ```bash
463
- # Generate week ahead article in English
464
- npm run generate-news -- --types=week-ahead --languages=en
479
+ # Render one article from a specific analysis run directory
480
+ npm run generate-article -- --run analysis/daily/2025-01-01/breaking
465
481
 
466
- # Generate multiple article types in multiple languages
467
- npm run generate-news -- --types=week-ahead,committee-reports --languages=en,de,fr
482
+ # Regenerate EVERY committed analysis run in one pass (skips runs whose
483
+ # manifest.json has no valid articleType; collision-suffixes same-date /
484
+ # same-type runs with a sanitised runId)
485
+ npm run generate-article:all
468
486
 
469
- # Generate in all eu-core preset languages
470
- npm run generate-news -- --types=week-ahead --languages=eu-core
487
+ # Only regenerate runs from a given date onward
488
+ npm run generate-article -- --all --since 2026-04-01
489
+ ```
490
+
491
+ Each invocation writes:
492
+
493
+ - `news/<slug>.en.md` — aggregated Markdown (provenance block + 19-section
494
+ ordered artifact set + Tradecraft References appendix + Analysis Index appendix)
495
+ - `news/<slug>-<lang>.html` — 14 language variants, each wrapped in the
496
+ shared site chrome (stacked header with embedded language switcher,
497
+ article-level Table of Contents sidebar, shared footer with live
498
+ article-count stats)
499
+
500
+ The aggregator lives under `src/aggregator/` — see the
501
+ [aggregator pipeline](ARCHITECTURE.md#aggregator-pipeline) section of the
502
+ architecture doc for the full data flow.
503
+
504
+ #### Editorial-highlight metadata resolver
505
+
506
+ The `<title>` / `<meta description>` / Open Graph / Twitter / JSON-LD
507
+ `NewsArticle` fields for every article are resolved by
508
+ `src/aggregator/article-metadata.ts` through a 5-tier priority ladder:
509
+
510
+ 1. **Manifest override** — the Stage-B agent writes one of these keys in
511
+ `manifest.json` when it has an editorial headline:
512
+
513
+ ```jsonc
514
+ {
515
+ "articleType": "breaking",
516
+ // String form — applied to all 14 language variants (recommended
517
+ // when only an English headline is available):
518
+ "title": "Banking Union Breakthrough and Anti-Corruption Landmark",
519
+ "description": "The plenary closes a six-year debate and triggers immediate criticism from two national delegations about implementation timelines.",
520
+ // OR per-language form when translations already exist:
521
+ "title": { "en": "…", "sv": "…", "de": "…" },
522
+ "description": { "en": "…", "sv": "…" }
523
+ }
524
+ ```
525
+
526
+ 2. **First artefact H1** — the aggregator walks the manifest's ordered
527
+ artefact list (`intelligence/synthesis-summary.md`,
528
+ `executive-summary.md`, `breaking-news-analysis.md`, …) and promotes
529
+ the first non-generic `# …` heading.
530
+ 3. **Aggregated-markdown H1** — any non-generic top-level heading in the
531
+ rendered Markdown.
532
+ 4. **First strong prose paragraph** — with a tightened leak filter that
533
+ blocks mermaid `%%{init}` blocks, `title …` directives, emoji-banner
534
+ metadata, and `Analysis Date:` / `Classification:` / `Run:` / `Window:`
535
+ / `Purpose:` / `BLUF (ICD-203):` style rows.
536
+ 5. **Localized template** — `*_TITLES(date)` from
537
+ `src/constants/language-articles.ts` — last resort when no editorial
538
+ content exists at all.
539
+
540
+ Downstream generators (`news-indexes.ts`, `sitemap.ts`, political-intelligence
541
+ cards, RSS) call `extractArticleMeta()` in `src/utils/file-utils.ts`, which
542
+ reads the `<head><title>` value (with the ` — EU Parliament Monitor`
543
+ suffix stripped) as the primary title, so the resolver's output
544
+ propagates everywhere without separate code changes in each generator.
545
+
546
+ #### Backport SEO metadata into existing articles
547
+
548
+ `scripts/backport-article-seo.js` rewrites `<title>`, the meta
549
+ description, Open Graph, Twitter, and JSON-LD `headline`/`description`
550
+ for every already-rendered `news/*-<lang>.html` file by extracting the
551
+ first editorial H1 and first strong prose paragraph from the article
552
+ body itself. The rewrite is idempotent — re-running over a backported
553
+ file is a byte-identical no-op — and the article body is never
554
+ modified.
555
+
556
+ ```bash
557
+ # Preview the change-set (dry-run is the default; prints a per-article-
558
+ # type summary and up to 12 sample before/after diffs):
559
+ node scripts/backport-article-seo.js
471
560
 
472
- # Generate in all supported languages
473
- npm run generate-news -- --types=week-ahead --languages=all
561
+ # Write changes:
562
+ node scripts/backport-article-seo.js --apply
563
+
564
+ # Scope to a subset of article types:
565
+ node scripts/backport-article-seo.js --apply --only breaking,motions
566
+
567
+ # Use a non-default news directory:
568
+ node scripts/backport-article-seo.js --apply --dir news
569
+ ```
570
+
571
+ After backporting, regenerate the downstream surfaces that depend on
572
+ article metadata:
573
+
574
+ ```bash
575
+ node scripts/generators/news-indexes.js # 14 language index pages
576
+ node scripts/generators/sitemap.js # 14 sitemap HTMLs + sitemap.xml + rss.xml
474
577
  ```
475
578
 
476
579
  ### Generate Indexes and Sitemap
477
580
 
478
581
  ```bash
479
- # Generate language-specific index pages
582
+ # Generate per-run news index pages (consumed by the aggregator's
583
+ # transparency footer and political-intelligence.html)
480
584
  npm run generate-news-indexes
481
585
 
482
- # Generate sitemap.xml
586
+ # Generate sitemap.xml, sitemap_<lang>.html, and political-intelligence_<lang>.html
587
+ # (14 language-specific sitemap pages + 14 language-specific Political Intelligence
588
+ # pages that index every methodology, template, and daily analysis run)
483
589
  npm run generate-sitemap
484
590
  ```
485
591
 
@@ -523,7 +629,11 @@ euparliamentmonitor/
523
629
  ├── index-{lang}.html # Language-specific index pages
524
630
  ├── typedoc.json # TypeDoc configuration
525
631
  ├── tsconfig.json # TypeScript configuration
526
- ├── sitemap.xml # SEO sitemap
632
+ ├── sitemap.xml # SEO sitemap with hreflang alternates
633
+ ├── sitemap.html # Human-readable sitemap (English)
634
+ ├── sitemap_{lang}.html # Per-language human-readable sitemaps
635
+ ├── political-intelligence.html # Index of every methodology + template + daily analysis run
636
+ ├── political-intelligence_{lang}.html # Localized political-intelligence pages
527
637
  └── package.json # Project dependencies
528
638
  ```
529
639
 
package/SECURITY.md CHANGED
@@ -88,15 +88,20 @@ See [SECURITY_ARCHITECTURE.md - Security Testing](SECURITY_ARCHITECTURE.md#-secu
88
88
  ### Scope
89
89
 
90
90
  **In Scope**:
91
- - News generation scripts (scripts/)
91
+ - News generation scripts (`scripts/`)
92
+ - Analysis-artifact aggregator (`src/aggregator/**` — `artifact-order.ts`, `clean-artifact.ts`, `analysis-aggregator.ts`, `markdown-renderer.ts`, `article-html.ts`, `article-generator.ts` CLI)
93
+ - HTML sanitiser (`src/utils/html-sanitize.ts`) and the `markdown-it` render pipeline with plugins (`markdown-it-anchor`, `markdown-it-footnote`, `markdown-it-attrs`, `markdown-it-deflist`)
94
+ - MCP clients (`src/mcp/**` — European Parliament, IMF, World Bank)
95
+ - Committed analysis artifacts under `analysis/daily/**` (as attack surface for aggregator rendering)
96
+ - Vendored client-side diagram renderer (`js/mermaid.esm.min.mjs`) and its CSP `script-src 'self'` constraint
92
97
  - HTML templates and output
93
- - MCP client integration
94
- - GitHub Actions workflows
95
- - Dependencies and supply chain
98
+ - GitHub Actions workflows and gh-aw agentic workflows (`.github/workflows/news-*.md`)
99
+ - AWS S3 + CloudFront deployment pipeline (`deploy-s3.yml`)
100
+ - Dependencies and supply chain (OpenSSF Scorecard + SLSA L3 provenance)
96
101
 
97
102
  **Out of Scope**:
98
- - Third-party services (GitHub, European Parliament APIs)
99
- - Infrastructure (GitHub Pages hosting)
103
+ - Third-party services (GitHub, European Parliament APIs, IMF SDMX REST, World Bank MCP)
104
+ - Infrastructure (AWS account-level, GitHub Pages hosting as fallback)
100
105
  - Client-side browser vulnerabilities (not controlled by this project)
101
106
 
102
107
  ### Recognition and Anonymity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "euparliamentmonitor",
3
- "version": "0.8.41",
3
+ "version": "0.8.43",
4
4
  "type": "module",
5
5
  "description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
6
6
  "main": "scripts/index.js",
@@ -60,15 +60,11 @@
60
60
  "build": "tsc",
61
61
  "build:check": "tsc --noEmit",
62
62
  "build:check-tests": "tsc --project tsconfig.test.json --noEmit",
63
- "copy-vendor": "mkdir -p js/vendor && cp node_modules/chart.js/dist/chart.umd.min.js js/vendor/ && cp node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js js/vendor/ && cp node_modules/d3/dist/d3.min.js js/vendor/",
64
- "generate-news": "node scripts/generators/news-enhanced.js",
63
+ "copy-vendor": "mkdir -p js/vendor && cp node_modules/chart.js/dist/chart.umd.min.js js/vendor/ && cp node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js js/vendor/ && cp node_modules/d3/dist/d3.min.js js/vendor/ && (test -f node_modules/mermaid/dist/mermaid.esm.min.mjs && cp node_modules/mermaid/dist/mermaid.esm.min.mjs js/vendor/ || echo 'mermaid vendor asset not installed; skipping')",
64
+ "generate-article": "node scripts/aggregator/article-generator.js",
65
+ "generate-article:all": "node scripts/aggregator/article-generator.js --all",
65
66
  "generate-news-indexes": "node scripts/generators/news-indexes.js",
66
67
  "generate-sitemap": "node scripts/generators/sitemap.js",
67
- "fix-articles": "npx tsx src/utils/fix-articles.ts",
68
- "retrofit-analysis": "npx tsx src/utils/retrofit-analysis-links.ts",
69
- "validate-articles": "npx tsx src/utils/validate-articles.ts",
70
- "validate-articles:strict": "npx tsx src/utils/validate-articles.ts --strict",
71
- "validate-analysis": "npx tsx src/utils/validate-analysis-completeness.ts",
72
68
  "validate-ep-api": "npx tsx src/utils/validate-ep-api.ts",
73
69
  "lint:prompts": "node scripts/lint-prompts.js",
74
70
  "htmlhint": "sh -c 'htmlhint *.html; set -- news/*.html; if [ -e \"$1\" ]; then htmlhint \"$@\"; else echo \"No news/*.html files to lint\"; fi'",
@@ -141,6 +137,7 @@
141
137
  "@eslint/js": "10.0.1",
142
138
  "@playwright/test": "1.59.1",
143
139
  "@types/d3": "7.4.3",
140
+ "@types/markdown-it": "^14.1.2",
144
141
  "@types/node": "25.6.0",
145
142
  "@types/papaparse": "5.5.2",
146
143
  "@typescript-eslint/eslint-plugin": "8.59.0",
@@ -172,7 +169,12 @@
172
169
  "node": ">=25"
173
170
  },
174
171
  "dependencies": {
175
- "european-parliament-mcp-server": "1.2.11"
172
+ "european-parliament-mcp-server": "1.2.13",
173
+ "markdown-it": "^14.1.1",
174
+ "markdown-it-anchor": "^9.2.0",
175
+ "markdown-it-attrs": "^4.3.1",
176
+ "markdown-it-deflist": "^3.0.0",
177
+ "markdown-it-footnote": "^4.0.0"
176
178
  },
177
179
  "optionalDependencies": {
178
180
  "worldbank-mcp": "1.0.1"
@@ -0,0 +1,168 @@
1
+ import { type ArtifactSection } from './artifact-order.js';
2
+ /** Raw manifest shape as committed by the analysis pipeline. */
3
+ export interface AnalysisManifest {
4
+ readonly articleType: string;
5
+ readonly runId?: string;
6
+ readonly date?: string;
7
+ readonly analysisDir?: string;
8
+ readonly files?: ManifestFiles;
9
+ readonly history?: readonly ManifestHistoryEntry[];
10
+ }
11
+ /** `manifest.files` can be nested category → paths or flat path → description. */
12
+ export type ManifestFiles = Record<string, readonly string[] | Record<string, string>>;
13
+ /** One entry in `manifest.history[]`; only fields we read are typed. */
14
+ export interface ManifestHistoryEntry {
15
+ readonly stage?: string;
16
+ readonly completedAt?: string;
17
+ readonly startedAt?: string;
18
+ readonly finishedAt?: string;
19
+ readonly runId?: string;
20
+ readonly gateResult?: string;
21
+ readonly summary?: string;
22
+ readonly filesWritten?: readonly string[];
23
+ }
24
+ /** Result of {@link aggregateAnalysisRun}. */
25
+ export interface AggregatedRun {
26
+ /** Final Markdown document (provenance + sections + appendices). */
27
+ readonly markdown: string;
28
+ /** Repo-relative path of the run dir (e.g. `analysis/daily/2026-01-15/breaking-run1`). */
29
+ readonly runDirRelPath: string;
30
+ /** Article type slug from the manifest. */
31
+ readonly articleType: string;
32
+ /** ISO date string of the run (YYYY-MM-DD). */
33
+ readonly date: string;
34
+ /** List of every artifact included, in the order they appear. */
35
+ readonly includedArtifacts: readonly IncludedArtifact[];
36
+ /** Latest resolved gate result read from `manifest.history[]`. */
37
+ readonly gateResult: string;
38
+ /**
39
+ * Ordered list of top-level (H2) sections that were actually emitted into
40
+ * the aggregate — used by the HTML renderer to build the article
41
+ * table-of-contents sidebar. Includes canonical sections, the
42
+ * supplementary-intelligence bucket, the tradecraft-references appendix,
43
+ * and the analysis-index appendix, in document order.
44
+ */
45
+ readonly sectionToc: readonly TocSection[];
46
+ }
47
+ /** One entry in the article-level table of contents (H2 level). */
48
+ export interface TocSection {
49
+ /** Fragment identifier — matches the `id="…"` on the rendered H2. */
50
+ readonly id: string;
51
+ /** Display title shown in the sidebar nav. */
52
+ readonly title: string;
53
+ }
54
+ /** Metadata for one artifact included in the aggregate. */
55
+ export interface IncludedArtifact {
56
+ /** Path relative to the run dir. */
57
+ readonly runRelPath: string;
58
+ /** Path relative to the repo root. */
59
+ readonly repoRelPath: string;
60
+ /** Id of the section this artifact belongs to. */
61
+ readonly sectionId: string;
62
+ }
63
+ /** Options for {@link aggregateAnalysisRun}. */
64
+ export interface AggregateOptions {
65
+ /** Absolute path to the analysis run directory. */
66
+ readonly runDir: string;
67
+ /** Absolute path to the repo root (used to build repo-relative paths). */
68
+ readonly repoRoot: string;
69
+ /**
70
+ * Optional: all methodology files and template files that should be
71
+ * listed in the tradecraft appendix. If omitted, the aggregator
72
+ * discovers them under `analysis/methodologies/*.md` +
73
+ * `analysis/templates/*.md`.
74
+ */
75
+ readonly tradecraftFiles?: readonly string[];
76
+ }
77
+ /**
78
+ * Normalise `manifest.files` into a flat list of `runRelPath` strings.
79
+ *
80
+ * @param files - Manifest `files` section (nested or flat)
81
+ * @returns De-duplicated list of run-relative artifact paths
82
+ */
83
+ export declare function flattenManifestFiles(files: ManifestFiles | undefined): string[];
84
+ /**
85
+ * Pick the latest non-PENDING gateResult from `manifest.history[]`, falling
86
+ * back to `PENDING` if none is recorded. Mirrors the behaviour of
87
+ * {@link readLatestResolvedGateResult} in `src/utils/file-utils.ts` but
88
+ * operates on an in-memory manifest.
89
+ *
90
+ * @param manifest - Parsed manifest object
91
+ * @returns The latest non-PENDING gate result, or `"PENDING"` when none found
92
+ */
93
+ export declare function latestGateResult(manifest: AnalysisManifest): string;
94
+ /**
95
+ * Expand an `artifacts` entry from {@link ArtifactSection} into a list of
96
+ * concrete artifact paths. Exact paths are kept as-is; directory prefixes
97
+ * ending in `/` expand to every remaining `.md` under that directory
98
+ * (lexical order), excluding files already claimed by higher-priority
99
+ * sections.
100
+ *
101
+ * @param section - Canonical section descriptor from {@link ARTIFACT_SECTIONS}
102
+ * @param available - Set of every known artifact path (run-relative)
103
+ * @param consumed - Mutable set of paths already claimed by earlier sections
104
+ * @returns Ordered list of artifact paths that belong to this section
105
+ */
106
+ export declare function expandSectionArtifacts(section: ArtifactSection, available: Set<string>, consumed: Set<string>): string[];
107
+ /**
108
+ * Discover tradecraft files (methodologies + templates) under a repo root.
109
+ * Returned paths are repo-relative with POSIX separators and sorted
110
+ * lexically.
111
+ *
112
+ * @param repoRoot - Absolute path of the repo root
113
+ * @returns Sorted list of `analysis/methodologies/*.md` + `analysis/templates/*.md`
114
+ */
115
+ export declare function discoverTradecraftFiles(repoRoot: string): string[];
116
+ /**
117
+ * Render the provenance block shown at the very top of the aggregated
118
+ * document. Shows run metadata, gate result, and a direct link to the
119
+ * manifest on GitHub so the reader can audit the full artifact set.
120
+ *
121
+ * @param params - Provenance metadata for the aggregated run
122
+ * @param params.articleType - Article type slug (e.g. `breaking`)
123
+ * @param params.date - ISO date of the run (`YYYY-MM-DD`)
124
+ * @param params.runId - Stable identifier for the run
125
+ * @param params.gateResult - Latest non-PENDING gate result
126
+ * @param params.runDirRelPath - Repo-relative path of the run directory
127
+ * @param params.manifestRelPath - Repo-relative path of `manifest.json`
128
+ * @returns Markdown blockquote ready to be concatenated into the aggregate
129
+ */
130
+ export declare function renderProvenanceBlock(params: {
131
+ articleType: string;
132
+ date: string;
133
+ runId: string;
134
+ gateResult: string;
135
+ runDirRelPath: string;
136
+ manifestRelPath: string;
137
+ }): string;
138
+ /**
139
+ * Render the tradecraft-references appendix — one bullet per
140
+ * methodology/template file with a GitHub blob link.
141
+ *
142
+ * @param files - Repo-relative paths under `analysis/methodologies/` and
143
+ * `analysis/templates/`
144
+ * @returns Markdown block with two subsections (methodologies, templates)
145
+ */
146
+ export declare function renderTradecraftAppendix(files: readonly string[]): string;
147
+ /**
148
+ * Render the analysis-index appendix — a compact table of every included
149
+ * artifact and its section, plus a direct link to the manifest.
150
+ *
151
+ * @param included - Artifacts that contributed to the aggregated document
152
+ * @param manifestRelPath - Repo-relative path of `manifest.json`
153
+ * @returns Markdown block with the index table
154
+ */
155
+ export declare function renderAnalysisIndex(included: readonly IncludedArtifact[], manifestRelPath: string): string;
156
+ /**
157
+ * Read, clean, and concatenate every artifact declared by the run's manifest
158
+ * (with discovery fallback when manifest.files is missing), returning a
159
+ * single aggregated Markdown document.
160
+ *
161
+ * The function is deterministic: given the same inputs it produces the
162
+ * same output byte-for-byte.
163
+ *
164
+ * @param options - Aggregation options (run dir, repo root, tradecraft files)
165
+ * @returns {@link AggregatedRun} describing the rendered document
166
+ */
167
+ export declare function aggregateAnalysisRun(options: AggregateOptions): AggregatedRun;
168
+ //# sourceMappingURL=analysis-aggregator.d.ts.map