euparliamentmonitor 0.8.41 → 0.8.42

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 (31) hide show
  1. package/README.md +10 -4
  2. package/package.json +2 -2
  3. package/scripts/constants/language-ui.d.ts +2 -0
  4. package/scripts/constants/language-ui.js +17 -0
  5. package/scripts/constants/languages.d.ts +1 -1
  6. package/scripts/constants/languages.js +1 -1
  7. package/scripts/generators/news-enhanced.js +81 -40
  8. package/scripts/generators/pipeline/analysis-stage.d.ts +89 -0
  9. package/scripts/generators/pipeline/analysis-stage.js +259 -30
  10. package/scripts/generators/pipeline/fetch-stage.d.ts +1 -1
  11. package/scripts/generators/pipeline/fetch-stage.js +6 -6
  12. package/scripts/generators/political-intelligence-descriptions.d.ts +139 -0
  13. package/scripts/generators/political-intelligence-descriptions.js +2068 -0
  14. package/scripts/generators/political-intelligence.d.ts +118 -0
  15. package/scripts/generators/political-intelligence.js +1146 -0
  16. package/scripts/generators/sitemap.d.ts +13 -0
  17. package/scripts/generators/sitemap.js +616 -127
  18. package/scripts/index.d.ts +1 -1
  19. package/scripts/index.js +1 -1
  20. package/scripts/lint-prompts.js +21 -4
  21. package/scripts/mcp/ep-mcp-client.d.ts +9 -9
  22. package/scripts/mcp/ep-mcp-client.js +14 -14
  23. package/scripts/templates/section-builders.js +5 -3
  24. package/scripts/types/mcp.d.ts +14 -14
  25. package/scripts/utils/content-validator.d.ts +19 -3
  26. package/scripts/utils/content-validator.js +19 -3
  27. package/scripts/utils/file-utils.d.ts +54 -0
  28. package/scripts/utils/file-utils.js +86 -0
  29. package/scripts/utils/fix-articles.js +7 -57
  30. package/scripts/utils/world-bank-data.d.ts +67 -2
  31. package/scripts/utils/world-bank-data.js +105 -2
package/README.md CHANGED
@@ -131,11 +131,11 @@ 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
138
+ - **Agentic Workflows**: 18 gh-aw markdown workflows — 8 split-pair article types (`news-<type>-analysis.md` + `news-<type>-article.md`) + `news-article-generator.md` + `news-translate.md` (compiled with
139
139
  `gh-aw v0.69.3`) for automated news generation with AI-driven political
140
140
  intelligence analysis. See [`.github/workflows/README.md`](.github/workflows/README.md).
141
141
  - **Analysis Chain**: 5-stage pipeline (Data → Analysis → Completeness Gate →
@@ -479,7 +479,9 @@ npm run generate-news -- --types=week-ahead --languages=all
479
479
  # Generate language-specific index pages
480
480
  npm run generate-news-indexes
481
481
 
482
- # Generate sitemap.xml
482
+ # Generate sitemap.xml, sitemap_<lang>.html, and political-intelligence_<lang>.html
483
+ # (14 language-specific sitemap pages + 14 language-specific Political Intelligence
484
+ # pages that index every methodology, template, and daily analysis run)
483
485
  npm run generate-sitemap
484
486
  ```
485
487
 
@@ -523,7 +525,11 @@ euparliamentmonitor/
523
525
  ├── index-{lang}.html # Language-specific index pages
524
526
  ├── typedoc.json # TypeDoc configuration
525
527
  ├── tsconfig.json # TypeScript configuration
526
- ├── sitemap.xml # SEO sitemap
528
+ ├── sitemap.xml # SEO sitemap with hreflang alternates
529
+ ├── sitemap.html # Human-readable sitemap (English)
530
+ ├── sitemap_{lang}.html # Per-language human-readable sitemaps
531
+ ├── political-intelligence.html # Index of every methodology + template + daily analysis run
532
+ ├── political-intelligence_{lang}.html # Localized political-intelligence pages
527
533
  └── package.json # Project dependencies
528
534
  ```
529
535
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "euparliamentmonitor",
3
- "version": "0.8.41",
3
+ "version": "0.8.42",
4
4
  "type": "module",
5
5
  "description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
6
6
  "main": "scripts/index.js",
@@ -172,7 +172,7 @@
172
172
  "node": ">=25"
173
173
  },
174
174
  "dependencies": {
175
- "european-parliament-mcp-server": "1.2.11"
175
+ "european-parliament-mcp-server": "1.2.13"
176
176
  },
177
177
  "optionalDependencies": {
178
178
  "worldbank-mcp": "1.0.1"
@@ -191,4 +191,6 @@ export declare const FOOTER_DISCLAIMER_LABELS: LanguageMap;
191
191
  export declare const FOOTER_REPORT_ISSUES_LABELS: LanguageMap;
192
192
  /** Localized "{count} articles available" stats text used in footer About section */
193
193
  export declare const FOOTER_ARTICLES_AVAILABLE_LABELS: LanguageMap;
194
+ /** Localized "Political Intelligence" link label used in footer Quick Links section */
195
+ export declare const FOOTER_POLITICAL_INTELLIGENCE_LABELS: LanguageMap;
194
196
  //# sourceMappingURL=language-ui.d.ts.map
@@ -1849,4 +1849,21 @@ export const FOOTER_ARTICLES_AVAILABLE_LABELS = {
1849
1849
  ko: '{count}개 기사 이용 가능',
1850
1850
  zh: '{count}篇文章可用',
1851
1851
  };
1852
+ /** Localized "Political Intelligence" link label used in footer Quick Links section */
1853
+ export const FOOTER_POLITICAL_INTELLIGENCE_LABELS = {
1854
+ en: 'Political Intelligence',
1855
+ sv: 'Politisk underrättelse',
1856
+ da: 'Politisk efterretning',
1857
+ no: 'Politisk etterretning',
1858
+ fi: 'Poliittinen tiedustelu',
1859
+ de: 'Politische Aufklärung',
1860
+ fr: 'Intelligence politique',
1861
+ es: 'Inteligencia política',
1862
+ nl: 'Politieke intelligentie',
1863
+ ar: 'الاستخبارات السياسية',
1864
+ he: 'מודיעין פוליטי',
1865
+ ja: '政治インテリジェンス',
1866
+ ko: '정치 정보',
1867
+ zh: '政治情报',
1868
+ };
1852
1869
  //# sourceMappingURL=language-ui.js.map
@@ -8,7 +8,7 @@
8
8
  * - **language-articles** — Article-type title generators and body-text strings
9
9
  */
10
10
  export { ALL_LANGUAGES, LANGUAGE_PRESETS, LANGUAGE_FLAGS, LANGUAGE_NAMES, getLocalizedString, isSupportedLanguage, getTextDirection, } from './language-core.js';
11
- export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, 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, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, TOC_ARIA_LABELS, RELATED_ANALYSIS_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, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, } from './language-ui.js';
11
+ export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, 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, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, RELATED_ANALYSIS_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, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, } from './language-ui.js';
12
12
  export type { AISection, RelationshipLabels, RelatedAnalysisStrings } from './language-ui.js';
13
13
  export { WEEK_AHEAD_TITLES, MONTH_AHEAD_TITLES, WEEKLY_REVIEW_TITLES, MONTHLY_REVIEW_TITLES, MOTIONS_TITLES, BREAKING_NEWS_TITLES, COMMITTEE_REPORTS_TITLES, PROPOSITIONS_TITLES, PROPOSITIONS_STRINGS, EDITORIAL_STRINGS, MOTIONS_STRINGS, WEEK_AHEAD_STRINGS, WEEK_AHEAD_STAKEHOLDER_STRINGS, BREAKING_STRINGS, DEEP_ANALYSIS_STRINGS, COMMITTEE_ANALYSIS_CONTENT_STRINGS, SWOT_STRINGS, DASHBOARD_STRINGS, SWOT_BUILDER_STRINGS, DASHBOARD_BUILDER_STRINGS, LOCALIZED_KEYWORDS, MONTH_IN_REVIEW_STRINGS, ANALYSIS_QUALITY_LABELS, ANALYSIS_INSIGHTS_HEADING, } from './language-articles.js';
14
14
  //# sourceMappingURL=languages.d.ts.map
@@ -10,6 +10,6 @@
10
10
  * - **language-articles** — Article-type title generators and body-text strings
11
11
  */
12
12
  export { ALL_LANGUAGES, LANGUAGE_PRESETS, LANGUAGE_FLAGS, LANGUAGE_NAMES, getLocalizedString, isSupportedLanguage, getTextDirection, } from './language-core.js';
13
- export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, 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, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, TOC_ARIA_LABELS, RELATED_ANALYSIS_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, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, } from './language-ui.js';
13
+ export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, 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, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, RELATED_ANALYSIS_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, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, } from './language-ui.js';
14
14
  export { WEEK_AHEAD_TITLES, MONTH_AHEAD_TITLES, WEEKLY_REVIEW_TITLES, MONTHLY_REVIEW_TITLES, MOTIONS_TITLES, BREAKING_NEWS_TITLES, COMMITTEE_REPORTS_TITLES, PROPOSITIONS_TITLES, PROPOSITIONS_STRINGS, EDITORIAL_STRINGS, MOTIONS_STRINGS, WEEK_AHEAD_STRINGS, WEEK_AHEAD_STAKEHOLDER_STRINGS, BREAKING_STRINGS, DEEP_ANALYSIS_STRINGS, COMMITTEE_ANALYSIS_CONTENT_STRINGS, SWOT_STRINGS, DASHBOARD_STRINGS, SWOT_BUILDER_STRINGS, DASHBOARD_BUILDER_STRINGS, LOCALIZED_KEYWORDS, MONTH_IN_REVIEW_STRINGS, ANALYSIS_QUALITY_LABELS, ANALYSIS_INSIGHTS_HEADING, } from './language-articles.js';
15
15
  //# sourceMappingURL=languages.js.map
@@ -49,7 +49,7 @@ import { ensureDirectoryExists } from '../utils/file-utils.js';
49
49
  import { initializeMCPClient, fetchEPFeedData } from './pipeline/fetch-stage.js';
50
50
  import { createStrategyRegistry, generateArticleForStrategy, setAIMetadata, } from './pipeline/generate-stage.js';
51
51
  import { writeGenerationMetadata } from './pipeline/output-stage.js';
52
- import { runAnalysisStage, ALL_ANALYSIS_METHODS, VALID_ANALYSIS_METHODS, hasSubstantiveData, deriveArticleTypeSlug, } from './pipeline/analysis-stage.js';
52
+ import { runAnalysisStage, ALL_ANALYSIS_METHODS, VALID_ANALYSIS_METHODS, hasSubstantiveData, deriveArticleTypeSlug, isResolvedAnalysisDir, } from './pipeline/analysis-stage.js';
53
53
  import { discoverAnalysisFileEntries } from '../utils/file-utils.js';
54
54
  // ─── Content-module imports (bounded contexts) ───────────────────────────────
55
55
  import { parsePlenarySessions, parseEPEvents, parseCommitteeMeetings, parseLegislativeDocuments, parseLegislativePipeline, parseParliamentaryQuestions, buildWeekAheadContent, buildKeywords, PLACEHOLDER_EVENTS, buildWhatToWatchSection, buildStakeholderImpactMatrix, computeWeekPoliticalTemperature, } from './week-ahead-content.js';
@@ -208,6 +208,56 @@ function parseAnalysisMethods() {
208
208
  }
209
209
  return methods;
210
210
  }
211
+ /**
212
+ * Fetch EP feed data (month-level timeframe for month-level article types,
213
+ * one-week otherwise), populate `fetchedData` with the result, and enforce
214
+ * the "substantive data required" check.
215
+ *
216
+ * Extracted from {@link maybeRunAnalysis} to keep that function's cognitive
217
+ * complexity under the repo's SonarJS limit. Mutates `fetchedData` in place
218
+ * and throws when no substantive EP data is available.
219
+ *
220
+ * @param fetchedData - Target record to populate (mutated)
221
+ * @param client - Connected MCP client or null
222
+ * @param articleTypes - Requested article types (drives timeframe selection)
223
+ */
224
+ async function fetchAndValidateEPData(fetchedData, client, articleTypes) {
225
+ const MONTH_LEVEL_TYPES = ['month-ahead', 'month-in-review', 'committee-reports', 'motions'];
226
+ const needsMonthData = articleTypes
227
+ .map((t) => t.trim())
228
+ .some((t) => MONTH_LEVEL_TYPES.includes(t));
229
+ const feedTimeframe = needsMonthData ? 'one-month' : 'one-week';
230
+ // fetchEPFeedData handles a null client gracefully (returns undefined) and
231
+ // also loads from EP_FEED_DATA_FILE when set, so we call it unconditionally.
232
+ const feedData = await fetchEPFeedData(client, feedTimeframe);
233
+ if (feedData) {
234
+ fetchedData['events'] = feedData.events ?? [];
235
+ fetchedData['documents'] = feedData.documents ?? [];
236
+ fetchedData['adoptedTexts'] = feedData.adoptedTexts ?? [];
237
+ fetchedData['procedures'] = feedData.procedures ?? [];
238
+ fetchedData['mepUpdates'] = feedData.mepUpdates ?? [];
239
+ fetchedData['plenaryDocuments'] = feedData.plenaryDocuments ?? [];
240
+ fetchedData['committeeDocuments'] = feedData.committeeDocuments ?? [];
241
+ fetchedData['plenarySessionDocuments'] = feedData.plenarySessionDocuments ?? [];
242
+ fetchedData['externalDocuments'] = feedData.externalDocuments ?? [];
243
+ fetchedData['questions'] = feedData.questions ?? [];
244
+ fetchedData['declarations'] = feedData.declarations ?? [];
245
+ fetchedData['corporateBodies'] = feedData.corporateBodies ?? [];
246
+ }
247
+ if (!fetchedData['events']) {
248
+ // No MCP or feed-data file available — populate empty arrays so builders don't fail
249
+ fetchedData['events'] = [];
250
+ fetchedData['sessions'] = [];
251
+ fetchedData['documents'] = [];
252
+ }
253
+ // Agentic workflows must not proceed with empty data — analysis on empty
254
+ // data produces hollow output that should never feed article generation.
255
+ if (!hasSubstantiveData(fetchedData)) {
256
+ throw new Error('❌ Analysis aborted: no substantive EP data was fetched. ' +
257
+ 'MCP data fetch must succeed before analysis can run. ' +
258
+ 'Check MCP connection, feed data file, or EP API availability.');
259
+ }
260
+ }
211
261
  /**
212
262
  * Run the analysis discovery stage (Fetch → Discover) before article generation.
213
263
  *
@@ -248,16 +298,24 @@ async function maybeRunAnalysis(date, client) {
248
298
  console.log(` Output dir: ${analysisDirBase}/${date}`);
249
299
  console.log(` Methods: ${enabledMethods.length} enabled`);
250
300
  console.log('');
251
- // Derive the feed timeframe from the requested article types so the analysis
252
- // window matches the generation window. Month-level types need 'one-month'.
253
- const MONTH_LEVEL_TYPES = ['month-ahead', 'month-in-review', 'committee-reports', 'motions'];
254
- const normalizedArticleTypes = articleTypes.map((t) => t.trim());
255
- const needsMonthData = normalizedArticleTypes.some((t) => MONTH_LEVEL_TYPES.includes(t));
256
- const feedTimeframe = needsMonthData ? 'one-month' : 'one-week';
257
- // Fetch comprehensive EP feed data. fetchEPFeedData handles a null client
258
- // gracefully (returns undefined) and also loads from EP_FEED_DATA_FILE when
259
- // set, so we call it unconditionally.
260
- //
301
+ // Detect whether `--analysis-dir` already points at a fully-resolved
302
+ // per-run analysis directory (agentic workflows pre-populate
303
+ // `analysis/daily/<date>/<slug>-run<N>` with AI-authored artifacts and
304
+ // pass it verbatim). When so, honour the path as-is; otherwise treat it
305
+ // as a base and compose `<base>/<date>/<slug>` below.
306
+ const analysisDirIsResolved = isResolvedAnalysisDir(analysisDirBase);
307
+ if (analysisDirIsResolved) {
308
+ console.log(` Analysis dir treated as pre-resolved run directory`);
309
+ }
310
+ // Short-circuit for `--analysis-only` wrap-up on a pre-resolved dir:
311
+ // the agent has already completed Stage A (data collection) and Stage B
312
+ // (artifact authoring); runAnalysisStage with outputDirIsResolved=true
313
+ // performs pure filesystem discovery and does not need EP data. Skip
314
+ // the expensive fetchEPFeedData call — a slow one-month feed pull here
315
+ // can hang the pipeline wrap-up long enough for the safeoutputs MCP
316
+ // session to expire, which then blocks PR creation (see failed run
317
+ // #24802174815 for the cascade).
318
+ const skipDataFetch = analysisOnlyArg && analysisDirIsResolved;
261
319
  // Always initialise voting-derived keys (`patterns`, `votingRecords`) to
262
320
  // empty arrays so coalition/voting/cross-session analyses never receive
263
321
  // undefined. These feeds are not yet exposed by fetchEPFeedData, so they
@@ -267,37 +325,19 @@ async function maybeRunAnalysis(date, client) {
267
325
  patterns: [],
268
326
  votingRecords: [],
269
327
  };
270
- const feedData = await fetchEPFeedData(client, feedTimeframe);
271
- if (feedData) {
272
- fetchedData['events'] = feedData.events ?? [];
273
- fetchedData['documents'] = feedData.documents ?? [];
274
- fetchedData['adoptedTexts'] = feedData.adoptedTexts ?? [];
275
- fetchedData['procedures'] = feedData.procedures ?? [];
276
- fetchedData['mepUpdates'] = feedData.mepUpdates ?? [];
277
- fetchedData['plenaryDocuments'] = feedData.plenaryDocuments ?? [];
278
- fetchedData['committeeDocuments'] = feedData.committeeDocuments ?? [];
279
- fetchedData['plenarySessionDocuments'] = feedData.plenarySessionDocuments ?? [];
280
- fetchedData['externalDocuments'] = feedData.externalDocuments ?? [];
281
- fetchedData['questions'] = feedData.questions ?? [];
282
- fetchedData['declarations'] = feedData.declarations ?? [];
283
- fetchedData['corporateBodies'] = feedData.corporateBodies ?? [];
284
- }
285
- if (!fetchedData['events']) {
286
- // No MCP or feed-data file available — populate empty arrays so builders don't fail
328
+ if (skipDataFetch) {
329
+ console.log(` Skipping EP data fetch — --analysis-only wrap-up on pre-resolved dir (agent owns Stage A/B)`);
330
+ // Populate empty arrays so downstream builders don't trip on undefined.
287
331
  fetchedData['events'] = [];
288
332
  fetchedData['sessions'] = [];
289
333
  fetchedData['documents'] = [];
290
334
  }
291
- // Validate that substantive EP data was actually fetched.
292
- // Agentic workflows must not proceed with empty data — analysis on empty
293
- // data produces hollow output that should never feed article generation.
294
- if (!hasSubstantiveData(fetchedData)) {
295
- const msg = '❌ Analysis aborted: no substantive EP data was fetched. ' +
296
- 'MCP data fetch must succeed before analysis can run. ' +
297
- 'Check MCP connection, feed data file, or EP API availability.';
298
- throw new Error(msg);
335
+ else {
336
+ await fetchAndValidateEPData(fetchedData, client, articleTypes);
299
337
  }
300
- const validArticleTypes = normalizedArticleTypes.filter((t) => VALID_ARTICLE_CATEGORIES.includes(t));
338
+ const validArticleTypes = articleTypes
339
+ .map((t) => t.trim())
340
+ .filter((t) => VALID_ARTICLE_CATEGORIES.includes(t));
301
341
  // Derive a slug from the article types to scope analysis output per workflow,
302
342
  // preventing merge conflicts when multiple workflows run on the same date.
303
343
  // When a run ID is provided (e.g. GITHUB_RUN_NUMBER), append it to the slug
@@ -308,8 +348,8 @@ async function maybeRunAnalysis(date, client) {
308
348
  console.log(` Article type slug: ${slug}`);
309
349
  if (runId)
310
350
  console.log(` Run ID: ${runId}`);
311
- // Pass requireData=true so runAnalysisStage enforces data availability
312
- // and aborts when no substantive data is available — discovery on empty data produces no artifacts.
351
+ // Require data only when we actually fetched it. The pre-resolved
352
+ // --analysis-only wrap-up path performs discovery-only and needs no data.
313
353
  const ctx = await runAnalysisStage(fetchedData, {
314
354
  articleTypes: validArticleTypes,
315
355
  date,
@@ -318,7 +358,8 @@ async function maybeRunAnalysis(date, client) {
318
358
  enabledMethods,
319
359
  skipCompleted: true,
320
360
  verbose: analysisVerboseArg,
321
- requireData: true,
361
+ requireData: !skipDataFetch,
362
+ outputDirIsResolved: analysisDirIsResolved,
322
363
  });
323
364
  const totalMethods = ctx.manifest.methods.length;
324
365
  const completedCount = ctx.manifest.methods.filter((method) => method.status === 'completed').length;
@@ -1,5 +1,6 @@
1
1
  import type { ArticleCategory } from '../../types/index.js';
2
2
  import type { ConfidenceLevel } from '../../types/index.js';
3
+ import { type AnalysisManifestHistoryEntry } from '../../utils/file-utils.js';
3
4
  /**
4
5
  * Union type of all recognised analysis method identifiers.
5
6
  */
@@ -34,7 +35,44 @@ export interface AnalysisStageOptions {
34
35
  * Retained for backward compatibility with agentic workflow invocations.
35
36
  */
36
37
  readonly requireData?: boolean;
38
+ /**
39
+ * When true, treat {@link AnalysisStageOptions.outputDir} as a fully
40
+ * resolved analysis directory and skip the `<outputDir>/<date>/<slug>`
41
+ * composition.
42
+ *
43
+ * This supports agentic workflows that pre-populate a per-run directory
44
+ * (e.g. `analysis/daily/<date>/<slug>-run<N>`) with AI-authored artifacts
45
+ * and then invoke `news-enhanced.ts --analysis-dir=<that dir>` for the
46
+ * discovery + article-generation phase.
47
+ */
48
+ readonly outputDirIsResolved?: boolean;
49
+ /**
50
+ * Optional stable run identifier used to record a history entry in the
51
+ * manifest when `outputDirIsResolved` is true. Enables repeated analysis
52
+ * runs against the same `analysis/daily/${DATE}/${TYPE}/` folder to
53
+ * accumulate an audit trail via `manifest.json.history[]` instead of
54
+ * triggering the `-2` suffix. When unset, a UUID-based run id is used.
55
+ */
56
+ readonly runId?: string;
57
+ /**
58
+ * Optional Stage-C gate result recorded in the manifest history entry
59
+ * written after discovery. Defaults to `'PENDING'` — the Stage-C
60
+ * validator or the caller updates this later.
61
+ */
62
+ readonly gateResult?: AnalysisManifestHistoryEntry['gateResult'];
37
63
  }
64
+ /**
65
+ * Detect whether `candidate` looks like a pre-populated, fully-resolved
66
+ * analysis run directory.
67
+ *
68
+ * Returns `true` when the directory exists AND either contains a
69
+ * `manifest.json` from a prior run or at least one of the canonical
70
+ * analysis subdirectories in {@link RESOLVED_ANALYSIS_SUBDIRS}.
71
+ *
72
+ * @param candidate - Directory path to inspect.
73
+ * @returns `true` when the directory is a resolved analysis run dir.
74
+ */
75
+ export declare function isResolvedAnalysisDir(candidate: string): boolean;
38
76
  /** Status record written into the manifest for each method */
39
77
  export interface AnalysisMethodStatus {
40
78
  readonly method: AnalysisMethod;
@@ -44,15 +82,60 @@ export interface AnalysisMethodStatus {
44
82
  readonly duration: number;
45
83
  readonly summary: string;
46
84
  }
85
+ /**
86
+ * `manifest.files.*` structure — artifact paths grouped by subdirectory.
87
+ *
88
+ * Mirrors the shape consumed by
89
+ * `src/utils/validate-analysis-completeness.ts:extractListedPaths` (nested
90
+ * `{ category: string[] }` variant). Each key is the first path segment of
91
+ * an artifact's relative path (e.g. `intelligence`, `classification`,
92
+ * `risk-scoring`), and the value is the list of relative paths under that
93
+ * subdir.
94
+ *
95
+ * Hyphenated keys (e.g. `risk-scoring`, `threat-assessment`) match the
96
+ * canonical on-disk subdirectory names defined in
97
+ * `scripts/resolve-analysis-dir.sh` and `DISCOVERY_SUBDIRS` /
98
+ * `RESOLVED_ANALYSIS_SUBDIRS`. They must be quoted in TypeScript because
99
+ * `-` is not a valid identifier character.
100
+ */
101
+ export interface AnalysisManifestFiles {
102
+ readonly classification?: readonly string[];
103
+ readonly 'risk-scoring'?: readonly string[];
104
+ readonly 'threat-assessment'?: readonly string[];
105
+ readonly intelligence?: readonly string[];
106
+ readonly documents?: readonly string[];
107
+ readonly existing?: readonly string[];
108
+ readonly data?: readonly string[];
109
+ readonly runs?: readonly string[];
110
+ readonly [key: string]: readonly string[] | undefined;
111
+ }
47
112
  /** Metadata record written to manifest.json for each analysis run */
48
113
  export interface AnalysisManifest {
49
114
  readonly runId: string;
50
115
  readonly date: string;
116
+ /**
117
+ * Top-level article-type slug (e.g. `committee-reports`, `breaking`).
118
+ *
119
+ * Required by the Stage-C completeness gate (see
120
+ * `03-analysis-completeness-gate.md` §2 item 8 and
121
+ * `src/utils/validate-analysis-completeness.ts:loadManifest`). Kept in
122
+ * addition to the legacy `articleTypeSlug` field for backward compatibility.
123
+ */
124
+ readonly articleType?: string | undefined;
51
125
  readonly articleTypeSlug?: string | undefined;
52
126
  readonly startTime: string;
53
127
  readonly endTime: string;
54
128
  readonly articleTypes: readonly ArticleCategory[];
55
129
  readonly methods: readonly AnalysisMethodStatus[];
130
+ /**
131
+ * Artifact paths grouped by subdirectory (e.g.
132
+ * `{ intelligence: ['intelligence/pestle-analysis.md', ...] }`).
133
+ *
134
+ * Required by the Stage-C completeness gate — see
135
+ * `src/utils/validate-analysis-completeness.ts:loadManifest` which rejects
136
+ * any manifest without a top-level `files` object.
137
+ */
138
+ readonly files?: AnalysisManifestFiles;
56
139
  readonly overallConfidence: ConfidenceLevel;
57
140
  readonly dataSourcesUsed: readonly string[];
58
141
  readonly documentsAnalyzed?: number;
@@ -92,6 +175,12 @@ export declare function deriveArticleTypeSlug(articleTypes: readonly (ArticleCat
92
175
  * (if one doesn't already exist) so downstream consumers that check for
93
176
  * the manifest continue to work.
94
177
  *
178
+ * When `options.outputDirIsResolved` is true, `outputDir` is honoured
179
+ * verbatim and the uniqueness-suffix step is bypassed — agentic workflows
180
+ * pre-populate `analysis/daily/<date>/<slug>-run<N>/` with `manifest.json`
181
+ * plus artifacts during Stage B, and discovery must consume that exact
182
+ * path rather than being redirected to a `-2` suffix.
183
+ *
95
184
  * @param fetchedData - Raw EP data (used only for the requireData check)
96
185
  * @param options - Analysis stage configuration
97
186
  * @returns Analysis context with discovered methods