euparliamentmonitor 0.8.58 → 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.
- package/README.md +4 -4
- package/package.json +15 -5
- package/scripts/aggregator/article-meta.d.ts +1 -1
- package/scripts/aggregator/article-metadata.js +0 -1
- package/scripts/aggregator/artifacts/index.d.ts +11 -0
- package/scripts/aggregator/artifacts/index.js +5 -0
- package/scripts/aggregator/artifacts/types.d.ts +46 -0
- package/scripts/aggregator/artifacts/types.js +4 -0
- package/scripts/aggregator/clean-artifact.js +2 -2
- package/scripts/aggregator/content/index.d.ts +14 -0
- package/scripts/aggregator/content/index.js +7 -0
- package/scripts/aggregator/content/types.d.ts +42 -0
- package/scripts/aggregator/content/types.js +4 -0
- package/scripts/aggregator/markdown/index.d.ts +9 -0
- package/scripts/aggregator/markdown/index.js +4 -0
- package/scripts/aggregator/markdown-renderer.js +2 -2
- package/scripts/aggregator/metadata/index.d.ts +12 -0
- package/scripts/aggregator/metadata/index.js +5 -0
- package/scripts/aggregator/metadata/types.d.ts +28 -0
- package/scripts/aggregator/metadata/types.js +4 -0
- package/scripts/config/article-horizons.js +8 -8
- package/scripts/constants/committee-indicator-map.js +0 -1
- package/scripts/constants/language-core.js +0 -1
- package/scripts/generators/political-intelligence/copy.js +0 -1
- package/scripts/generators/political-intelligence-descriptions.js +22 -32
- package/scripts/generators/shared/html-escape.d.ts +41 -0
- package/scripts/generators/shared/html-escape.js +108 -0
- package/scripts/generators/shared/index.d.ts +16 -0
- package/scripts/generators/shared/index.js +7 -0
- package/scripts/generators/shared/template-helpers.d.ts +67 -0
- package/scripts/generators/shared/template-helpers.js +109 -0
- package/scripts/generators/shared/types.d.ts +97 -0
- package/scripts/generators/shared/types.js +4 -0
- package/scripts/mcp/ep-mcp-client.d.ts +19 -6
- package/scripts/mcp/ep-mcp-client.js +23 -7
- package/scripts/mcp/fetch-proxy-server.d.ts +91 -0
- package/scripts/mcp/fetch-proxy-server.js +249 -0
- package/scripts/mcp/html-lang-patcher.d.ts +48 -0
- package/scripts/mcp/html-lang-patcher.js +138 -0
- package/scripts/mcp/imf-mcp-client.d.ts +5 -4
- package/scripts/mcp/imf-mcp-client.js +13 -5
- package/scripts/mcp/mcp-config-reader.d.ts +61 -0
- package/scripts/mcp/mcp-config-reader.js +143 -0
- package/scripts/types/index.d.ts +1 -1
- package/scripts/types/mcp.d.ts +7 -0
- package/scripts/utils/intelligence-index.js +1 -8
- package/scripts/validate-analysis-completeness.js +47 -2
- package/scripts/workflows/completeness-gate/constants.d.ts +89 -0
- package/scripts/workflows/completeness-gate/constants.js +115 -0
- package/scripts/workflows/completeness-gate/index.d.ts +10 -0
- package/scripts/workflows/completeness-gate/index.js +5 -0
- package/scripts/workflows/completeness-gate/types.d.ts +104 -0
- package/scripts/workflows/completeness-gate/types.js +4 -0
- package/scripts/workflows/completeness-gate/validators.d.ts +117 -0
- package/scripts/workflows/completeness-gate/validators.js +212 -0
- package/scripts/workflows/index.d.ts +21 -0
- package/scripts/workflows/index.js +8 -0
- package/scripts/workflows/infrastructure/index.d.ts +7 -0
- package/scripts/workflows/infrastructure/index.js +4 -0
- package/scripts/workflows/infrastructure/shell-safety.d.ts +62 -0
- package/scripts/workflows/infrastructure/shell-safety.js +106 -0
- package/scripts/workflows/safe-outputs/index.d.ts +7 -0
- package/scripts/workflows/safe-outputs/index.js +4 -0
- package/scripts/workflows/safe-outputs/types.d.ts +63 -0
- package/scripts/workflows/safe-outputs/types.js +39 -0
- package/scripts/workflows/types.d.ts +110 -0
- 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.
|
|
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.
|
|
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
|
|
@@ -492,8 +492,8 @@ EU Parliament Monitor implements **security-by-design** under the [Hack23 ISMS](
|
|
|
492
492
|
|
|
493
493
|
### Requirements
|
|
494
494
|
|
|
495
|
-
- **Node.js**
|
|
496
|
-
- **npm** 10 or higher (ships with Node.js
|
|
495
|
+
- **Node.js** 26 or higher
|
|
496
|
+
- **npm** 10 or higher (ships with Node.js 26)
|
|
497
497
|
- **Git**
|
|
498
498
|
|
|
499
499
|
### From source
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "euparliamentmonitor",
|
|
3
|
-
"version": "0.
|
|
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": "
|
|
168
|
+
"lint-staged": "17.0.2",
|
|
161
169
|
"mermaid": "11.14.0",
|
|
162
170
|
"papaparse": "5.5.3",
|
|
163
171
|
"prettier": "3.8.3",
|
|
@@ -168,10 +176,10 @@
|
|
|
168
176
|
"vitest": "4.1.5"
|
|
169
177
|
},
|
|
170
178
|
"engines": {
|
|
171
|
-
"node": ">=
|
|
179
|
+
"node": ">=26"
|
|
172
180
|
},
|
|
173
181
|
"dependencies": {
|
|
174
|
-
"european-parliament-mcp-server": "1.
|
|
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
|
|
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
|
|
@@ -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
|
|
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
|
|
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,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
|
|
@@ -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 (
|
|
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 (
|
|
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,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
|
|
@@ -157,7 +157,7 @@ export const ARTICLE_HORIZONS = {
|
|
|
157
157
|
perspective: CATEGORY_PERSPECTIVE[ArticleCategory.WEEK_AHEAD],
|
|
158
158
|
timePeriod: CATEGORY_TIME_PERIOD[ArticleCategory.WEEK_AHEAD],
|
|
159
159
|
dataWindow: { direction: 'forward', days: 7, anchor: 'today' },
|
|
160
|
-
cadence: { cron: '0
|
|
160
|
+
cadence: { cron: '0 7 * * 5', description: 'Weekly — Friday 07:00 UTC' },
|
|
161
161
|
primaryFeeds: [...STANDARD_FEEDS],
|
|
162
162
|
mandatoryArtifacts: [...PROSPECTIVE_MANDATORY, A_FORWARD_PROJECTION],
|
|
163
163
|
optionalArtifacts: [A_EXEC_BRIEF],
|
|
@@ -171,7 +171,7 @@ export const ARTICLE_HORIZONS = {
|
|
|
171
171
|
perspective: CATEGORY_PERSPECTIVE[ArticleCategory.MONTH_AHEAD],
|
|
172
172
|
timePeriod: CATEGORY_TIME_PERIOD[ArticleCategory.MONTH_AHEAD],
|
|
173
173
|
dataWindow: { direction: 'forward', days: 30, anchor: 'today' },
|
|
174
|
-
cadence: { cron: '0
|
|
174
|
+
cadence: { cron: '0 8 1 * *', description: 'Monthly — 1st @ 08:00 UTC' },
|
|
175
175
|
primaryFeeds: [...STANDARD_FEEDS],
|
|
176
176
|
mandatoryArtifacts: [...PROSPECTIVE_MANDATORY, A_FORWARD_PROJECTION],
|
|
177
177
|
optionalArtifacts: [A_EXEC_BRIEF],
|
|
@@ -222,7 +222,7 @@ export const ARTICLE_HORIZONS = {
|
|
|
222
222
|
timePeriod: CATEGORY_TIME_PERIOD[ArticleCategory.WEEK_IN_REVIEW],
|
|
223
223
|
// ADR-006: D-8 → D-36 reporting window for roll-call publication delay.
|
|
224
224
|
dataWindow: { direction: 'backward', days: 28, anchor: 'today' },
|
|
225
|
-
cadence: { cron: '0
|
|
225
|
+
cadence: { cron: '0 9 * * 6', description: 'Weekly — Saturday 09:00 UTC' },
|
|
226
226
|
primaryFeeds: [...STANDARD_FEEDS, 'get_voting_records'],
|
|
227
227
|
mandatoryArtifacts: [...RETROSPECTIVE_MANDATORY],
|
|
228
228
|
optionalArtifacts: [A_EXEC_BRIEF],
|
|
@@ -236,7 +236,7 @@ export const ARTICLE_HORIZONS = {
|
|
|
236
236
|
perspective: CATEGORY_PERSPECTIVE[ArticleCategory.MONTH_IN_REVIEW],
|
|
237
237
|
timePeriod: CATEGORY_TIME_PERIOD[ArticleCategory.MONTH_IN_REVIEW],
|
|
238
238
|
dataWindow: { direction: 'backward', days: 30, anchor: 'today' },
|
|
239
|
-
cadence: { cron: '0
|
|
239
|
+
cadence: { cron: '0 10 28 * *', description: 'Monthly — 28th @ 10:00 UTC' },
|
|
240
240
|
primaryFeeds: [...STANDARD_FEEDS, 'get_voting_records'],
|
|
241
241
|
mandatoryArtifacts: [...RETROSPECTIVE_MANDATORY],
|
|
242
242
|
optionalArtifacts: [A_EXEC_BRIEF],
|
|
@@ -328,7 +328,7 @@ export const ARTICLE_HORIZONS = {
|
|
|
328
328
|
slug: 'breaking',
|
|
329
329
|
perspective: CATEGORY_PERSPECTIVE[ArticleCategory.BREAKING_NEWS],
|
|
330
330
|
dataWindow: { direction: 'point', anchor: 'today' },
|
|
331
|
-
cadence: { cron: '0 */
|
|
331
|
+
cadence: { cron: '0 */6 * * *', description: 'Every 6 hours' },
|
|
332
332
|
primaryFeeds: [...STANDARD_FEEDS],
|
|
333
333
|
mandatoryArtifacts: [
|
|
334
334
|
A_SIGNIFICANCE,
|
|
@@ -357,7 +357,7 @@ export const ARTICLE_HORIZONS = {
|
|
|
357
357
|
slug: 'committee-reports',
|
|
358
358
|
perspective: CATEGORY_PERSPECTIVE[ArticleCategory.COMMITTEE_REPORTS],
|
|
359
359
|
dataWindow: { direction: 'backward', days: 30, anchor: 'today' },
|
|
360
|
-
cadence: { cron: '0
|
|
360
|
+
cadence: { cron: '0 4 * * 1-5', description: 'Weekdays — Mon–Fri 04:00 UTC' },
|
|
361
361
|
primaryFeeds: [...STANDARD_FEEDS, 'get_committee_documents'],
|
|
362
362
|
mandatoryArtifacts: [...RETROSPECTIVE_MANDATORY],
|
|
363
363
|
optionalArtifacts: [A_EXEC_BRIEF],
|
|
@@ -370,7 +370,7 @@ export const ARTICLE_HORIZONS = {
|
|
|
370
370
|
slug: 'motions',
|
|
371
371
|
perspective: CATEGORY_PERSPECTIVE[ArticleCategory.MOTIONS],
|
|
372
372
|
dataWindow: { direction: 'backward', days: 30, anchor: 'today' },
|
|
373
|
-
cadence: { cron: '0
|
|
373
|
+
cadence: { cron: '0 6 * * 1-5', description: 'Weekdays — Mon–Fri 06:00 UTC' },
|
|
374
374
|
primaryFeeds: [...STANDARD_FEEDS, 'get_voting_records'],
|
|
375
375
|
mandatoryArtifacts: [...RETROSPECTIVE_MANDATORY],
|
|
376
376
|
optionalArtifacts: [A_EXEC_BRIEF],
|
|
@@ -383,7 +383,7 @@ export const ARTICLE_HORIZONS = {
|
|
|
383
383
|
slug: 'propositions',
|
|
384
384
|
perspective: CATEGORY_PERSPECTIVE[ArticleCategory.PROPOSITIONS],
|
|
385
385
|
dataWindow: { direction: 'forward', days: 90, anchor: 'today' },
|
|
386
|
-
cadence: { cron: '0
|
|
386
|
+
cadence: { cron: '0 5 * * 1-5', description: 'Weekdays — Mon–Fri 05:00 UTC' },
|
|
387
387
|
primaryFeeds: [...STANDARD_FEEDS, 'get_procedures'],
|
|
388
388
|
mandatoryArtifacts: [...PROSPECTIVE_MANDATORY],
|
|
389
389
|
optionalArtifacts: [A_PIPELINE_FORECAST, A_EXEC_BRIEF],
|
|
@@ -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
|
|
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
|
-
//
|
|
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
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
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
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
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 {
|