euparliamentmonitor 0.8.51 → 0.8.52
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 +2 -2
- package/package.json +4 -3
- package/scripts/aggregator/article-html.js +6 -1
- package/scripts/aggregator/prior-run-diff.js +52 -20
- package/scripts/constants/build-info-meta.d.ts +10 -0
- package/scripts/constants/build-info-meta.js +45 -0
- package/scripts/constants/config.d.ts +20 -0
- package/scripts/constants/config.js +57 -0
- package/scripts/constants/language-ui.d.ts +18 -0
- package/scripts/constants/language-ui.js +154 -0
- package/scripts/constants/languages.d.ts +1 -1
- package/scripts/constants/languages.js +1 -1
- package/scripts/generators/build-info.js +73 -0
- package/scripts/generators/news-indexes.js +6 -1
- package/scripts/generators/political-intelligence/html.js +6 -1
- package/scripts/generators/sitemap/html.js +6 -1
- package/scripts/mcp/ep-mcp-client.d.ts +5 -5
- package/scripts/mcp/ep-mcp-client.js +7 -7
- package/scripts/templates/icons.d.ts +30 -0
- package/scripts/templates/icons.js +32 -0
- package/scripts/templates/section-builders.js +22 -10
- package/scripts/validate-analysis-completeness.js +68 -6
package/README.md
CHANGED
|
@@ -136,7 +136,7 @@ The published site is the audience-facing companion to this npm/TypeScript packa
|
|
|
136
136
|
|
|
137
137
|
**MCP Server Integration**: The project uses the
|
|
138
138
|
[European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server)
|
|
139
|
-
v1.2.
|
|
139
|
+
v1.2.18 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)
|
|
@@ -426,7 +426,7 @@ import type { ArticleCategory, LanguageCode } from 'euparliamentmonitor/types';
|
|
|
426
426
|
|
|
427
427
|
## 🔌 Data Sources
|
|
428
428
|
|
|
429
|
-
**Primary — European Parliament MCP Server** ([Hack23/European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server) v1.2.
|
|
429
|
+
**Primary — European Parliament MCP Server** ([Hack23/European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server) v1.2.18+, fully operational):
|
|
430
430
|
|
|
431
431
|
- 🗳️ Plenary sessions, voting records, roll-call votes
|
|
432
432
|
- 📜 Adopted texts, motions, resolutions, urgency files
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "euparliamentmonitor",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
|
|
6
6
|
"main": "scripts/index.js",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"registry": "https://registry.npmjs.org/"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
|
-
"prebuild": "node scripts/generators/news-indexes.js && node scripts/generators/sitemap.js",
|
|
51
|
+
"prebuild": "node scripts/generators/build-info.js && node scripts/generators/news-indexes.js && node scripts/generators/sitemap.js",
|
|
52
|
+
"generate-build-info": "node scripts/generators/build-info.js",
|
|
52
53
|
"build": "tsc",
|
|
53
54
|
"build:check": "tsc --noEmit",
|
|
54
55
|
"build:check-tests": "tsc --project tsconfig.test.json --noEmit",
|
|
@@ -168,7 +169,7 @@
|
|
|
168
169
|
"node": ">=25"
|
|
169
170
|
},
|
|
170
171
|
"dependencies": {
|
|
171
|
-
"european-parliament-mcp-server": "1.2.
|
|
172
|
+
"european-parliament-mcp-server": "1.2.18",
|
|
172
173
|
"markdown-it": "^14.1.1",
|
|
173
174
|
"markdown-it-anchor": "^9.2.0",
|
|
174
175
|
"markdown-it-attrs": "^4.3.1",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* browser and CloudFront caches automatically.
|
|
20
20
|
*/
|
|
21
21
|
import { BASE_URL, MERMAID_VERSION } from '../constants/config.js';
|
|
22
|
-
import {
|
|
22
|
+
import { buildHeadFreshnessTags } from '../constants/build-info-meta.js';
|
|
23
|
+
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, SKIP_LINK_TEXTS, TOC_ARIA_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, getLocalizedString, getTextDirection, } from '../constants/languages.js';
|
|
23
24
|
import { escapeHTML } from '../utils/file-utils.js';
|
|
24
25
|
import { buildSiteFooter, buildSiteHeader, buildPageBanner, } from '../templates/section-builders.js';
|
|
25
26
|
/**
|
|
@@ -189,6 +190,10 @@ ${hreflangLinks}
|
|
|
189
190
|
<link rel="manifest" href="../site.webmanifest">
|
|
190
191
|
<meta name="theme-color" content="#003399">
|
|
191
192
|
<link rel="stylesheet" href="../styles.css">
|
|
193
|
+
<meta name="ep-i18n-update-text" content="${escapeHTML(getLocalizedString(UPDATE_AVAILABLE_LABELS, safeLang))}">
|
|
194
|
+
<meta name="ep-i18n-update-cta" content="${escapeHTML(getLocalizedString(UPDATE_REFRESH_CTA_LABELS, safeLang))}">
|
|
195
|
+
<meta name="ep-i18n-dismiss" content="${escapeHTML(getLocalizedString(UPDATE_DISMISS_LABELS, safeLang))}">
|
|
196
|
+
${buildHeadFreshnessTags('../')}
|
|
192
197
|
<script type="application/ld+json">${jsonLdString}</script>
|
|
193
198
|
<script type="module" src="../js/mermaid-init.js?v=${MERMAID_VERSION}" defer></script>
|
|
194
199
|
<script src="../js/article-runtime.js" defer></script>
|
|
@@ -3,24 +3,33 @@
|
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Prior-run diff helper for the re-run
|
|
6
|
+
* Prior-run diff helper for the re-run improve/extend rule.
|
|
7
7
|
*
|
|
8
8
|
* Reads `manifest.json.history[]` from a same-day analysis folder and
|
|
9
|
-
* classifies every artifact as **at-floor** (carry-forward) or
|
|
10
|
-
* (rewrite). The result — a `priorRunDiff` plan
|
|
11
|
-
*
|
|
9
|
+
* classifies every artifact as **at-floor** (must-extend / carry-forward) or
|
|
10
|
+
* **below-floor** (rewrite). The result — a `priorRunDiff` plan with
|
|
11
|
+
* `mode: "improve-and-extend"` — is written to stdout as JSON and is
|
|
12
|
+
* consumed by Stage B of the analysis workflow.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* **Re-run semantics (never no-op).** Entries listed under `carryForward[]`
|
|
15
|
+
* are **NOT** skipped on re-runs — they are must-extend targets. Stage B
|
|
16
|
+
* MUST raise their depth: each prior artifact's `priorLines` becomes the new
|
|
17
|
+
* floor and the agent must add ≥1 new section, ≥3 new evidence citations, or
|
|
18
|
+
* ≥1 new chart, ending at `lines >= max(floor, priorLines + 20)`. Entries in
|
|
19
|
+
* `rewrite[]` are still written from scratch to the catalog floor.
|
|
20
|
+
*
|
|
21
|
+
* Always-on. The `ENABLE_PRIOR_RUN_MERGE` environment variable is no longer
|
|
22
|
+
* read — the helper runs unconditionally so re-runs cannot accidentally
|
|
23
|
+
* regress to the legacy "skip-write" behaviour. The `buildPriorRunDiff(..,
|
|
24
|
+
* enabled)` parameter is kept for back-compat with unit tests but the CLI
|
|
25
|
+
* always passes `true`.
|
|
17
26
|
*
|
|
18
27
|
* Invocation:
|
|
19
28
|
* node scripts/aggregator/prior-run-diff.js <runDir>
|
|
20
29
|
* npm run prior-run-diff -- analysis/daily/2026-04-26/week-in-review
|
|
21
30
|
*
|
|
22
31
|
* Exit codes:
|
|
23
|
-
* 0 — plan emitted successfully
|
|
32
|
+
* 0 — plan emitted successfully
|
|
24
33
|
* 1 — runDir missing or invalid
|
|
25
34
|
* 2 — bad CLI usage
|
|
26
35
|
*
|
|
@@ -28,6 +37,7 @@
|
|
|
28
37
|
* ```json
|
|
29
38
|
* {
|
|
30
39
|
* "enabled": true,
|
|
40
|
+
* "mode": "improve-and-extend",
|
|
31
41
|
* "runDir": "analysis/daily/2026-04-26/week-in-review",
|
|
32
42
|
* "articleType": "week-in-review",
|
|
33
43
|
* "priorRunId": "week-in-review-run-1714128000",
|
|
@@ -35,8 +45,10 @@
|
|
|
35
45
|
* {
|
|
36
46
|
* "relativePath": "intelligence/synthesis-summary.md",
|
|
37
47
|
* "lines": 250,
|
|
48
|
+
* "priorLines": 250,
|
|
38
49
|
* "floor": 180,
|
|
39
|
-
* "
|
|
50
|
+
* "extendFloor": 270,
|
|
51
|
+
* "source": "extend-from-prior:week-in-review-run-1714128000"
|
|
40
52
|
* }
|
|
41
53
|
* ],
|
|
42
54
|
* "rewrite": [
|
|
@@ -50,10 +62,13 @@
|
|
|
50
62
|
* }
|
|
51
63
|
* ```
|
|
52
64
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
65
|
+
* - `priorLines` exposes the prior-run line count so Stage B knows the lower
|
|
66
|
+
* bound it must beat.
|
|
67
|
+
* - `extendFloor` = `max(floor, priorLines + 20)` — the minimum line count
|
|
68
|
+
* the new pass MUST reach for this artifact.
|
|
69
|
+
* - The `source` value follows the schema `"extend-from-prior:<runId>"`,
|
|
70
|
+
* which Stage B writes into `manifest.json.artifactSources` (additive,
|
|
71
|
+
* back-compat with prior `"carry-forward-from:<runId>"` consumers).
|
|
57
72
|
*/
|
|
58
73
|
|
|
59
74
|
import fs from 'node:fs';
|
|
@@ -63,6 +78,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
63
78
|
|
|
64
79
|
const ROOT = process.cwd();
|
|
65
80
|
const DEFAULT_MIN_LINES = 30;
|
|
81
|
+
const EXTEND_DELTA_LINES = 20;
|
|
66
82
|
|
|
67
83
|
// Artifacts that must contain at least one Mermaid fenced block.
|
|
68
84
|
// Mirrors the directory-based heuristic in validate-analysis-completeness.js.
|
|
@@ -86,9 +102,10 @@ function usage(code = 2) {
|
|
|
86
102
|
'',
|
|
87
103
|
' <runDir> Path to analysis/daily/<date>/<slug>/',
|
|
88
104
|
'',
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
105
|
+
'Always-on. The helper unconditionally classifies prior-run artifacts as',
|
|
106
|
+
'must-extend (carryForward[]) or below-floor rewrite (rewrite[]) so re-runs',
|
|
107
|
+
'can never accidentally no-op. The legacy ENABLE_PRIOR_RUN_MERGE env flag',
|
|
108
|
+
'is no longer read.',
|
|
92
109
|
'',
|
|
93
110
|
'Example:',
|
|
94
111
|
' npm run prior-run-diff -- analysis/daily/2026-04-26/week-in-review',
|
|
@@ -190,9 +207,14 @@ export function classifyArtifact(runDir, relativePath, floor, mermaidRequiredLis
|
|
|
190
207
|
/**
|
|
191
208
|
* Build the `priorRunDiff` plan for a same-day analysis folder.
|
|
192
209
|
*
|
|
210
|
+
* Mode is always **improve-and-extend**: `carryForward[]` entries are
|
|
211
|
+
* must-extend targets (their `priorLines` and `extendFloor` exposed), not
|
|
212
|
+
* skip-write targets. The `enabled` parameter is preserved for back-compat
|
|
213
|
+
* with the legacy unit-test signature; the CLI always passes `true`.
|
|
214
|
+
*
|
|
193
215
|
* @param {string} runDir - Absolute path to the run folder.
|
|
194
216
|
* @param {object|null} thresholdsJson - Parsed reference-quality-thresholds.json.
|
|
195
|
-
* @param {boolean} enabled - Whether the feature is enabled.
|
|
217
|
+
* @param {boolean} enabled - Whether the feature is enabled (CLI: always true).
|
|
196
218
|
* @returns {object} The diff plan (serialisable to JSON).
|
|
197
219
|
*/
|
|
198
220
|
export function buildPriorRunDiff(runDir, thresholdsJson, enabled) {
|
|
@@ -210,6 +232,7 @@ export function buildPriorRunDiff(runDir, thresholdsJson, enabled) {
|
|
|
210
232
|
if (!enabled) {
|
|
211
233
|
return {
|
|
212
234
|
enabled: false,
|
|
235
|
+
mode: 'improve-and-extend',
|
|
213
236
|
runDir: relRunDir,
|
|
214
237
|
articleType,
|
|
215
238
|
priorRunId: null,
|
|
@@ -222,6 +245,7 @@ export function buildPriorRunDiff(runDir, thresholdsJson, enabled) {
|
|
|
222
245
|
if (history.length === 0) {
|
|
223
246
|
return {
|
|
224
247
|
enabled: true,
|
|
248
|
+
mode: 'improve-and-extend',
|
|
225
249
|
runDir: relRunDir,
|
|
226
250
|
articleType,
|
|
227
251
|
priorRunId: null,
|
|
@@ -248,11 +272,14 @@ export function buildPriorRunDiff(runDir, thresholdsJson, enabled) {
|
|
|
248
272
|
const floor = Math.max(DEFAULT_MIN_LINES, perArtifactFloors[relativePath] ?? 0);
|
|
249
273
|
const result = classifyArtifact(runDir, relativePath, floor, mermaidRequiredList);
|
|
250
274
|
if (result.atFloor) {
|
|
275
|
+
const extendFloor = Math.max(floor, result.lines + EXTEND_DELTA_LINES);
|
|
251
276
|
carryForward.push({
|
|
252
277
|
relativePath,
|
|
253
278
|
lines: result.lines,
|
|
279
|
+
priorLines: result.lines,
|
|
254
280
|
floor: result.floor,
|
|
255
|
-
|
|
281
|
+
extendFloor,
|
|
282
|
+
source: `extend-from-prior:${priorRunId}`,
|
|
256
283
|
});
|
|
257
284
|
} else {
|
|
258
285
|
rewrite.push({
|
|
@@ -266,6 +293,7 @@ export function buildPriorRunDiff(runDir, thresholdsJson, enabled) {
|
|
|
266
293
|
|
|
267
294
|
return {
|
|
268
295
|
enabled: true,
|
|
296
|
+
mode: 'improve-and-extend',
|
|
269
297
|
runDir: relRunDir,
|
|
270
298
|
articleType,
|
|
271
299
|
priorRunId,
|
|
@@ -338,7 +366,11 @@ function main() {
|
|
|
338
366
|
process.exit(1);
|
|
339
367
|
}
|
|
340
368
|
|
|
341
|
-
|
|
369
|
+
// Re-run improve/extend rule is always-on. The legacy ENABLE_PRIOR_RUN_MERGE
|
|
370
|
+
// env flag is no longer read — re-runs cannot accidentally regress to the
|
|
371
|
+
// pre-2026-05 skip-write behaviour. See .github/prompts/02-analysis-protocol.md
|
|
372
|
+
// §"Re-run improve/extend rule".
|
|
373
|
+
const enabled = true;
|
|
342
374
|
const thresholdsJson = loadThresholds(opts.thresholdsPath);
|
|
343
375
|
const plan = buildPriorRunDiff(runDir, thresholdsJson, enabled);
|
|
344
376
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the shared freshness/PWA `<head>` block.
|
|
3
|
+
*
|
|
4
|
+
* @param pathPrefix - Asset path prefix (`''` for root pages, `'../'`
|
|
5
|
+
* for `news/` pages).
|
|
6
|
+
* @returns Multi-line HTML string. Caller is responsible for placing it
|
|
7
|
+
* inside `<head>…</head>`. The result is already HTML-escaped.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildHeadFreshnessTags(pathPrefix: string): string;
|
|
10
|
+
//# sourceMappingURL=build-info-meta.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* @module Constants/BuildInfoMeta
|
|
5
|
+
* @description Shared helper that emits the `<head>` freshness tags every
|
|
6
|
+
* generator must include so the PWA layer (`js/pwa-register.js`) can:
|
|
7
|
+
*
|
|
8
|
+
* - Read the embedded build commit SHA + timestamp from `<meta>` tags.
|
|
9
|
+
* - Load the same-origin service-worker registration script.
|
|
10
|
+
* - Tell browsers + intermediate proxies to revalidate every navigation
|
|
11
|
+
* (`Cache-Control: no-cache`, `Pragma: no-cache`).
|
|
12
|
+
*
|
|
13
|
+
* Every value is HTML-escaped — `BUILD_ID`/`BUILD_TIME` are tightly
|
|
14
|
+
* formatted (40-char hex / ISO 8601) but defence-in-depth is cheap.
|
|
15
|
+
*
|
|
16
|
+
* The output is a multi-line string; callers should drop it into the
|
|
17
|
+
* `<head>` block alongside other `<meta>` tags. CSP stays `script-src
|
|
18
|
+
* 'self'` because the only emitted `<script>` references a same-origin
|
|
19
|
+
* file with a `defer` attribute.
|
|
20
|
+
*/
|
|
21
|
+
import { BUILD_ID, BUILD_TIME } from './config.js';
|
|
22
|
+
import { escapeHTML } from '../utils/file-utils.js';
|
|
23
|
+
/**
|
|
24
|
+
* Build the shared freshness/PWA `<head>` block.
|
|
25
|
+
*
|
|
26
|
+
* @param pathPrefix - Asset path prefix (`''` for root pages, `'../'`
|
|
27
|
+
* for `news/` pages).
|
|
28
|
+
* @returns Multi-line HTML string. Caller is responsible for placing it
|
|
29
|
+
* inside `<head>…</head>`. The result is already HTML-escaped.
|
|
30
|
+
*/
|
|
31
|
+
export function buildHeadFreshnessTags(pathPrefix) {
|
|
32
|
+
const safeBuildId = escapeHTML(BUILD_ID);
|
|
33
|
+
const safeBuildTime = escapeHTML(BUILD_TIME);
|
|
34
|
+
// Path prefix is built from controlled string literals (`''` or `'../'`),
|
|
35
|
+
// but escape it anyway to keep the helper safe under future callers.
|
|
36
|
+
const safePrefix = escapeHTML(pathPrefix);
|
|
37
|
+
return [
|
|
38
|
+
` <meta name="build-id" content="${safeBuildId}">`,
|
|
39
|
+
` <meta name="build-time" content="${safeBuildTime}">`,
|
|
40
|
+
` <meta http-equiv="Cache-Control" content="no-cache">`,
|
|
41
|
+
` <meta http-equiv="Pragma" content="no-cache">`,
|
|
42
|
+
` <script src="${safePrefix}js/pwa-register.js" defer></script>`,
|
|
43
|
+
].join('\n');
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=build-info-meta.js.map
|
|
@@ -64,4 +64,24 @@ export declare const THEME_TOGGLE_SCRIPT_CONTENT = "\n (function(){\n var do
|
|
|
64
64
|
* Detects system theme on first click when no explicit preference is saved.
|
|
65
65
|
*/
|
|
66
66
|
export declare const THEME_TOGGLE_SCRIPT = "\n <script>\n (function(){\n var docEl=document.documentElement;\n var t=localStorage.getItem('ep-theme');\n var storedTheme=t==='light'?'light':t==='dark'?'dark':null;\n if(storedTheme){\n docEl.setAttribute('data-theme',storedTheme);\n }else if(t){\n localStorage.removeItem('ep-theme');\n }\n var btn=document.querySelector('.theme-toggle');\n if(!btn)return;\n btn.addEventListener('click',function(){\n var cur=docEl.getAttribute('data-theme');\n if(!cur){\n cur=(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'light';\n }\n var next=cur==='dark'?'light':'dark';\n docEl.setAttribute('data-theme',next);\n localStorage.setItem('ep-theme',next);\n });\n })();\n </script>";
|
|
67
|
+
/**
|
|
68
|
+
* Full git commit SHA (40 chars) for the running build. Resolved via env
|
|
69
|
+
* (`BUILD_ID`), then `git rev-parse HEAD`, then a deterministic placeholder
|
|
70
|
+
* (`'0'.repeat(40)`). Never empty, never throws.
|
|
71
|
+
*/
|
|
72
|
+
export declare const BUILD_ID: string;
|
|
73
|
+
/** First 7 chars of {@link BUILD_ID} — the conventional short SHA. */
|
|
74
|
+
export declare const BUILD_SHORT: string;
|
|
75
|
+
/**
|
|
76
|
+
* ISO 8601 timestamp for when this build was produced. Precedence:
|
|
77
|
+
* 1. `process.env.BUILD_TIME` (CI sets this in the workflow)
|
|
78
|
+
* 2. `new Date().toISOString()` fallback
|
|
79
|
+
*/
|
|
80
|
+
export declare const BUILD_TIME: string;
|
|
81
|
+
/**
|
|
82
|
+
* Optional release tag (e.g. `v0.8.51`). Empty string when no tag was
|
|
83
|
+
* supplied via `process.env.RELEASE_TAG`. Surfaced in `build-info.json`
|
|
84
|
+
* for clients that want a human-readable label.
|
|
85
|
+
*/
|
|
86
|
+
export declare const RELEASE_TAG: string;
|
|
67
87
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @module Constants/Config
|
|
5
5
|
* @description Shared configuration constants
|
|
6
6
|
*/
|
|
7
|
+
import { execSync } from 'child_process';
|
|
7
8
|
import fs from 'fs';
|
|
8
9
|
import path from 'path';
|
|
9
10
|
import { fileURLToPath } from 'url';
|
|
@@ -137,4 +138,60 @@ export const THEME_TOGGLE_SCRIPT_CONTENT = `
|
|
|
137
138
|
*/
|
|
138
139
|
export const THEME_TOGGLE_SCRIPT = `
|
|
139
140
|
<script>${THEME_TOGGLE_SCRIPT_CONTENT}</script>`;
|
|
141
|
+
/**
|
|
142
|
+
* Resolve the current build commit SHA. Precedence:
|
|
143
|
+
* 1. `process.env.BUILD_ID` (CI sets this from `${{ github.sha }}`)
|
|
144
|
+
* 2. `git rev-parse HEAD` (works in dev clones / local builds)
|
|
145
|
+
* 3. `'0'.repeat(40)` (deterministic, never throws)
|
|
146
|
+
*
|
|
147
|
+
* Always returns a 40-char lowercase hex string. Never throws — generator
|
|
148
|
+
* scripts must be safe to run on machines without git installed.
|
|
149
|
+
*
|
|
150
|
+
* @returns 40-char lowercase hex commit SHA, or `'0'.repeat(40)` placeholder.
|
|
151
|
+
*/
|
|
152
|
+
function resolveBuildId() {
|
|
153
|
+
const fromEnv = (process.env.BUILD_ID ?? '').trim();
|
|
154
|
+
if (/^[0-9a-f]{40}$/i.test(fromEnv)) {
|
|
155
|
+
return fromEnv.toLowerCase();
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const fromGit = execSync('git rev-parse HEAD', {
|
|
159
|
+
encoding: 'utf-8',
|
|
160
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
161
|
+
cwd: PROJECT_ROOT,
|
|
162
|
+
}).trim();
|
|
163
|
+
if (/^[0-9a-f]{40}$/i.test(fromGit)) {
|
|
164
|
+
return fromGit.toLowerCase();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
/* git unavailable or not a repo — fall through to placeholder */
|
|
169
|
+
}
|
|
170
|
+
return '0'.repeat(40);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Full git commit SHA (40 chars) for the running build. Resolved via env
|
|
174
|
+
* (`BUILD_ID`), then `git rev-parse HEAD`, then a deterministic placeholder
|
|
175
|
+
* (`'0'.repeat(40)`). Never empty, never throws.
|
|
176
|
+
*/
|
|
177
|
+
export const BUILD_ID = resolveBuildId();
|
|
178
|
+
/** First 7 chars of {@link BUILD_ID} — the conventional short SHA. */
|
|
179
|
+
export const BUILD_SHORT = BUILD_ID.slice(0, 7);
|
|
180
|
+
/**
|
|
181
|
+
* ISO 8601 timestamp for when this build was produced. Precedence:
|
|
182
|
+
* 1. `process.env.BUILD_TIME` (CI sets this in the workflow)
|
|
183
|
+
* 2. `new Date().toISOString()` fallback
|
|
184
|
+
*/
|
|
185
|
+
export const BUILD_TIME = (() => {
|
|
186
|
+
const fromEnv = (process.env.BUILD_TIME ?? '').trim();
|
|
187
|
+
if (fromEnv)
|
|
188
|
+
return fromEnv;
|
|
189
|
+
return new Date().toISOString();
|
|
190
|
+
})();
|
|
191
|
+
/**
|
|
192
|
+
* Optional release tag (e.g. `v0.8.51`). Empty string when no tag was
|
|
193
|
+
* supplied via `process.env.RELEASE_TAG`. Surfaced in `build-info.json`
|
|
194
|
+
* for clients that want a human-readable label.
|
|
195
|
+
*/
|
|
196
|
+
export const RELEASE_TAG = (process.env.RELEASE_TAG ?? '').trim();
|
|
140
197
|
//# sourceMappingURL=config.js.map
|
|
@@ -193,4 +193,22 @@ export declare const FOOTER_REPORT_ISSUES_LABELS: LanguageMap;
|
|
|
193
193
|
export declare const FOOTER_ARTICLES_AVAILABLE_LABELS: LanguageMap;
|
|
194
194
|
/** Localized "Political Intelligence" link label used in footer Quick Links section */
|
|
195
195
|
export declare const FOOTER_POLITICAL_INTELLIGENCE_LABELS: LanguageMap;
|
|
196
|
+
/** "Install app" CTA label (PWA install hint). */
|
|
197
|
+
export declare const INSTALL_APP_LABELS: LanguageMap;
|
|
198
|
+
/** Toast message shown when a newer build of the site is detected. */
|
|
199
|
+
export declare const UPDATE_AVAILABLE_LABELS: LanguageMap;
|
|
200
|
+
/** "Refresh" CTA on the update toast — reloads the page. */
|
|
201
|
+
export declare const UPDATE_REFRESH_CTA_LABELS: LanguageMap;
|
|
202
|
+
/** "Dismiss" aria-label on the update toast close button. */
|
|
203
|
+
export declare const UPDATE_DISMISS_LABELS: LanguageMap;
|
|
204
|
+
/** Offline page title. */
|
|
205
|
+
export declare const OFFLINE_TITLE_LABELS: LanguageMap;
|
|
206
|
+
/** Offline page body explaining the situation. */
|
|
207
|
+
export declare const OFFLINE_BODY_LABELS: LanguageMap;
|
|
208
|
+
/** "Try again" button on the offline page. */
|
|
209
|
+
export declare const OFFLINE_RETRY_LABELS: LanguageMap;
|
|
210
|
+
/** "Build" label for the build-id link in the footer. */
|
|
211
|
+
export declare const BUILD_INFO_COMMIT_LABELS: LanguageMap;
|
|
212
|
+
/** "Deployed" label preceding the build timestamp in the footer. */
|
|
213
|
+
export declare const BUILD_INFO_DEPLOYED_LABELS: LanguageMap;
|
|
196
214
|
//# sourceMappingURL=language-ui.d.ts.map
|
|
@@ -1866,4 +1866,158 @@ export const FOOTER_POLITICAL_INTELLIGENCE_LABELS = {
|
|
|
1866
1866
|
ko: '정치 정보',
|
|
1867
1867
|
zh: '政治情报',
|
|
1868
1868
|
};
|
|
1869
|
+
/* ─── PWA + freshness UI labels (Phase 3) ────────────────────────── */
|
|
1870
|
+
/** "Install app" CTA label (PWA install hint). */
|
|
1871
|
+
export const INSTALL_APP_LABELS = {
|
|
1872
|
+
en: 'Install app',
|
|
1873
|
+
sv: 'Installera app',
|
|
1874
|
+
da: 'Installer app',
|
|
1875
|
+
no: 'Installer app',
|
|
1876
|
+
fi: 'Asenna sovellus',
|
|
1877
|
+
de: 'App installieren',
|
|
1878
|
+
fr: "Installer l'application",
|
|
1879
|
+
es: 'Instalar aplicación',
|
|
1880
|
+
nl: 'App installeren',
|
|
1881
|
+
ar: 'تثبيت التطبيق',
|
|
1882
|
+
he: 'התקן את האפליקציה',
|
|
1883
|
+
ja: 'アプリをインストール',
|
|
1884
|
+
ko: '앱 설치',
|
|
1885
|
+
zh: '安装应用',
|
|
1886
|
+
};
|
|
1887
|
+
/** Toast message shown when a newer build of the site is detected. */
|
|
1888
|
+
export const UPDATE_AVAILABLE_LABELS = {
|
|
1889
|
+
en: 'Updated content available',
|
|
1890
|
+
sv: 'Uppdaterat innehåll tillgängligt',
|
|
1891
|
+
da: 'Opdateret indhold tilgængeligt',
|
|
1892
|
+
no: 'Oppdatert innhold tilgjengelig',
|
|
1893
|
+
fi: 'Päivitetty sisältö saatavilla',
|
|
1894
|
+
de: 'Aktualisierte Inhalte verfügbar',
|
|
1895
|
+
fr: 'Contenu mis à jour disponible',
|
|
1896
|
+
es: 'Contenido actualizado disponible',
|
|
1897
|
+
nl: 'Bijgewerkte inhoud beschikbaar',
|
|
1898
|
+
ar: 'يتوفر محتوى محدث',
|
|
1899
|
+
he: 'תוכן מעודכן זמין',
|
|
1900
|
+
ja: '最新のコンテンツが利用可能です',
|
|
1901
|
+
ko: '업데이트된 콘텐츠가 있습니다',
|
|
1902
|
+
zh: '有可用的更新内容',
|
|
1903
|
+
};
|
|
1904
|
+
/** "Refresh" CTA on the update toast — reloads the page. */
|
|
1905
|
+
export const UPDATE_REFRESH_CTA_LABELS = {
|
|
1906
|
+
en: 'Refresh',
|
|
1907
|
+
sv: 'Uppdatera',
|
|
1908
|
+
da: 'Opdater',
|
|
1909
|
+
no: 'Oppdater',
|
|
1910
|
+
fi: 'Päivitä',
|
|
1911
|
+
de: 'Aktualisieren',
|
|
1912
|
+
fr: 'Actualiser',
|
|
1913
|
+
es: 'Actualizar',
|
|
1914
|
+
nl: 'Vernieuwen',
|
|
1915
|
+
ar: 'تحديث',
|
|
1916
|
+
he: 'רענן',
|
|
1917
|
+
ja: '更新',
|
|
1918
|
+
ko: '새로고침',
|
|
1919
|
+
zh: '刷新',
|
|
1920
|
+
};
|
|
1921
|
+
/** "Dismiss" aria-label on the update toast close button. */
|
|
1922
|
+
export const UPDATE_DISMISS_LABELS = {
|
|
1923
|
+
en: 'Dismiss',
|
|
1924
|
+
sv: 'Stäng',
|
|
1925
|
+
da: 'Luk',
|
|
1926
|
+
no: 'Lukk',
|
|
1927
|
+
fi: 'Sulje',
|
|
1928
|
+
de: 'Schließen',
|
|
1929
|
+
fr: 'Fermer',
|
|
1930
|
+
es: 'Cerrar',
|
|
1931
|
+
nl: 'Sluiten',
|
|
1932
|
+
ar: 'إغلاق',
|
|
1933
|
+
he: 'סגור',
|
|
1934
|
+
ja: '閉じる',
|
|
1935
|
+
ko: '닫기',
|
|
1936
|
+
zh: '关闭',
|
|
1937
|
+
};
|
|
1938
|
+
/** Offline page title. */
|
|
1939
|
+
export const OFFLINE_TITLE_LABELS = {
|
|
1940
|
+
en: "You're offline",
|
|
1941
|
+
sv: 'Du är offline',
|
|
1942
|
+
da: 'Du er offline',
|
|
1943
|
+
no: 'Du er frakoblet',
|
|
1944
|
+
fi: 'Olet offline-tilassa',
|
|
1945
|
+
de: 'Sie sind offline',
|
|
1946
|
+
fr: 'Vous êtes hors ligne',
|
|
1947
|
+
es: 'Estás sin conexión',
|
|
1948
|
+
nl: 'Je bent offline',
|
|
1949
|
+
ar: 'أنت غير متصل',
|
|
1950
|
+
he: 'אתה במצב לא מקוון',
|
|
1951
|
+
ja: 'オフラインです',
|
|
1952
|
+
ko: '오프라인 상태입니다',
|
|
1953
|
+
zh: '您当前处于离线状态',
|
|
1954
|
+
};
|
|
1955
|
+
/** Offline page body explaining the situation. */
|
|
1956
|
+
export const OFFLINE_BODY_LABELS = {
|
|
1957
|
+
en: "EU Parliament Monitor is unavailable while you're offline. Reconnect to load the latest political intelligence.",
|
|
1958
|
+
sv: 'EU Parliament Monitor är otillgänglig medan du är offline. Anslut igen för att läsa den senaste politiska underrättelsen.',
|
|
1959
|
+
da: 'EU Parliament Monitor er utilgængelig, mens du er offline. Opret forbindelse igen for at hente den nyeste politiske efterretning.',
|
|
1960
|
+
no: 'EU Parliament Monitor er utilgjengelig mens du er frakoblet. Koble til på nytt for å laste inn den nyeste politiske etterretningen.',
|
|
1961
|
+
fi: 'EU Parliament Monitor ei ole käytettävissä, kun olet offline-tilassa. Yhdistä uudelleen ladataksesi uusimman poliittisen tiedustelun.',
|
|
1962
|
+
de: 'EU Parliament Monitor ist offline nicht verfügbar. Stellen Sie die Verbindung wieder her, um die neuesten politischen Informationen zu laden.',
|
|
1963
|
+
fr: 'EU Parliament Monitor est indisponible hors ligne. Reconnectez-vous pour charger les dernières informations politiques.',
|
|
1964
|
+
es: 'EU Parliament Monitor no está disponible sin conexión. Vuelve a conectarte para cargar la última inteligencia política.',
|
|
1965
|
+
nl: 'EU Parliament Monitor is offline niet beschikbaar. Maak opnieuw verbinding om de laatste politieke intelligentie te laden.',
|
|
1966
|
+
ar: 'مراقب البرلمان الأوروبي غير متوفر أثناء عدم الاتصال. أعد الاتصال لتحميل أحدث المعلومات السياسية.',
|
|
1967
|
+
he: 'EU Parliament Monitor אינו זמין במצב לא מקוון. התחבר מחדש כדי לטעון את המודיעין הפוליטי העדכני ביותר.',
|
|
1968
|
+
ja: 'オフライン中は EU Parliament Monitor を利用できません。再接続して最新の政治情報を読み込んでください。',
|
|
1969
|
+
ko: '오프라인 상태에서는 EU Parliament Monitor를 이용할 수 없습니다. 다시 연결하여 최신 정치 정보를 불러오세요.',
|
|
1970
|
+
zh: '您处于离线状态,无法使用 EU Parliament Monitor。重新连接以加载最新的政治情报。',
|
|
1971
|
+
};
|
|
1972
|
+
/** "Try again" button on the offline page. */
|
|
1973
|
+
export const OFFLINE_RETRY_LABELS = {
|
|
1974
|
+
en: 'Try again',
|
|
1975
|
+
sv: 'Försök igen',
|
|
1976
|
+
da: 'Prøv igen',
|
|
1977
|
+
no: 'Prøv igjen',
|
|
1978
|
+
fi: 'Yritä uudelleen',
|
|
1979
|
+
de: 'Erneut versuchen',
|
|
1980
|
+
fr: 'Réessayer',
|
|
1981
|
+
es: 'Intentar de nuevo',
|
|
1982
|
+
nl: 'Opnieuw proberen',
|
|
1983
|
+
ar: 'حاول مرة أخرى',
|
|
1984
|
+
he: 'נסה שוב',
|
|
1985
|
+
ja: '再試行',
|
|
1986
|
+
ko: '다시 시도',
|
|
1987
|
+
zh: '重试',
|
|
1988
|
+
};
|
|
1989
|
+
/** "Build" label for the build-id link in the footer. */
|
|
1990
|
+
export const BUILD_INFO_COMMIT_LABELS = {
|
|
1991
|
+
en: 'Build',
|
|
1992
|
+
sv: 'Bygg',
|
|
1993
|
+
da: 'Build',
|
|
1994
|
+
no: 'Build',
|
|
1995
|
+
fi: 'Build',
|
|
1996
|
+
de: 'Build',
|
|
1997
|
+
fr: 'Build',
|
|
1998
|
+
es: 'Build',
|
|
1999
|
+
nl: 'Build',
|
|
2000
|
+
ar: 'البناء',
|
|
2001
|
+
he: 'בנייה',
|
|
2002
|
+
ja: 'ビルド',
|
|
2003
|
+
ko: '빌드',
|
|
2004
|
+
zh: '构建',
|
|
2005
|
+
};
|
|
2006
|
+
/** "Deployed" label preceding the build timestamp in the footer. */
|
|
2007
|
+
export const BUILD_INFO_DEPLOYED_LABELS = {
|
|
2008
|
+
en: 'Deployed',
|
|
2009
|
+
sv: 'Driftsatt',
|
|
2010
|
+
da: 'Implementeret',
|
|
2011
|
+
no: 'Distribuert',
|
|
2012
|
+
fi: 'Julkaistu',
|
|
2013
|
+
de: 'Veröffentlicht',
|
|
2014
|
+
fr: 'Déployé',
|
|
2015
|
+
es: 'Desplegado',
|
|
2016
|
+
nl: 'Geïmplementeerd',
|
|
2017
|
+
ar: 'تم النشر',
|
|
2018
|
+
he: 'נפרס',
|
|
2019
|
+
ja: 'デプロイ済み',
|
|
2020
|
+
ko: '배포됨',
|
|
2021
|
+
zh: '已部署',
|
|
2022
|
+
};
|
|
1869
2023
|
//# 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, 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';
|
|
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, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_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, 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';
|
|
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, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_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
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate `build-info.json` (and the runtime `sw.js` from `sw.js.template`)
|
|
7
|
+
* at the project root. Called by `npm run prebuild` BEFORE the TypeScript
|
|
8
|
+
* compile step, so it stays in pure JS (no tsc dependency).
|
|
9
|
+
*
|
|
10
|
+
* The file is published at `/build-info.json` and polled by
|
|
11
|
+
* `js/pwa-register.js` to detect deploys. Schema:
|
|
12
|
+
*
|
|
13
|
+
* {
|
|
14
|
+
* "buildId": "40-char-lowercase-hex",
|
|
15
|
+
* "buildShort": "1234567",
|
|
16
|
+
* "buildTime": "2026-04-30T12:34:56.000Z",
|
|
17
|
+
* "appVersion": "0.8.51",
|
|
18
|
+
* "releaseTag": "" // optional, empty when no tag
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Build metadata is resolved by `scripts/constants/config.js` so the
|
|
22
|
+
* generator, TypeScript templates, and runtime metadata share one source of
|
|
23
|
+
* truth for precedence, validation, and fallbacks.
|
|
24
|
+
*
|
|
25
|
+
* Idempotent: rerunning produces a byte-identical file when env + repo
|
|
26
|
+
* state are unchanged (ignoring BUILD_TIME drift).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import fs from 'fs';
|
|
30
|
+
import path from 'path';
|
|
31
|
+
import { fileURLToPath } from 'url';
|
|
32
|
+
import {
|
|
33
|
+
APP_VERSION,
|
|
34
|
+
BUILD_ID,
|
|
35
|
+
BUILD_SHORT,
|
|
36
|
+
BUILD_TIME,
|
|
37
|
+
RELEASE_TAG,
|
|
38
|
+
} from '../constants/config.js';
|
|
39
|
+
|
|
40
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
41
|
+
const __dirname = path.dirname(__filename);
|
|
42
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
43
|
+
|
|
44
|
+
function main() {
|
|
45
|
+
const payload = {
|
|
46
|
+
buildId: BUILD_ID,
|
|
47
|
+
buildShort: BUILD_SHORT,
|
|
48
|
+
buildTime: BUILD_TIME,
|
|
49
|
+
appVersion: APP_VERSION,
|
|
50
|
+
releaseTag: RELEASE_TAG,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const outPath = path.join(PROJECT_ROOT, 'build-info.json');
|
|
54
|
+
fs.writeFileSync(outPath, JSON.stringify(payload, null, 2) + '\n', 'utf-8');
|
|
55
|
+
console.log(`✅ Wrote build-info.json (buildShort=${BUILD_SHORT}, appVersion=${APP_VERSION})`);
|
|
56
|
+
|
|
57
|
+
// Render the service-worker from its template — substitutes the build id
|
|
58
|
+
// into `CACHE_VERSION` so old caches are evicted on every deploy.
|
|
59
|
+
const tplPath = path.join(PROJECT_ROOT, 'sw.js.template');
|
|
60
|
+
const swPath = path.join(PROJECT_ROOT, 'sw.js');
|
|
61
|
+
if (fs.existsSync(tplPath)) {
|
|
62
|
+
const tpl = fs.readFileSync(tplPath, 'utf-8');
|
|
63
|
+
const rendered = tpl
|
|
64
|
+
.replace(/__BUILD_ID__/g, BUILD_ID)
|
|
65
|
+
.replace(/__BUILD_SHORT__/g, BUILD_SHORT);
|
|
66
|
+
fs.writeFileSync(swPath, rendered, 'utf-8');
|
|
67
|
+
console.log(`✅ Rendered sw.js from template (CACHE_VERSION=${BUILD_SHORT})`);
|
|
68
|
+
} else {
|
|
69
|
+
console.warn(`⚠️ sw.js.template not found at ${tplPath} — skipping sw.js render`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
main();
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import path, { resolve } from 'path';
|
|
11
11
|
import { pathToFileURL } from 'url';
|
|
12
12
|
import { PROJECT_ROOT, APP_VERSION, NEWS_DIR } from '../constants/config.js';
|
|
13
|
-
import {
|
|
13
|
+
import { buildHeadFreshnessTags } from '../constants/build-info-meta.js';
|
|
14
|
+
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, AI_SECTION_CONTENT, FILTER_LABELS, ARTICLE_TYPE_LABELS, HEADER_SUBTITLE_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, getLocalizedString, getTextDirection, } from '../constants/languages.js';
|
|
14
15
|
import { buildSiteFooter, buildSiteHeader } from '../templates/section-builders.js';
|
|
15
16
|
import { getNewsArticles, groupArticlesByLanguage, formatSlug, parseArticleFilename, extractArticleMeta, escapeHTML, atomicWrite, } from '../utils/file-utils.js';
|
|
16
17
|
import { writeMetadataDatabase } from '../utils/news-metadata.js';
|
|
@@ -197,6 +198,10 @@ export function generateIndexHTML(lang, articles, metaMap = new Map()) {
|
|
|
197
198
|
<meta name="theme-color" content="#003399">
|
|
198
199
|
<link rel="alternate" type="application/rss+xml" title="EU Parliament Monitor RSS" href="rss.xml">
|
|
199
200
|
<link rel="stylesheet" href="styles.css">
|
|
201
|
+
<meta name="ep-i18n-update-text" content="${escapeHTML(getLocalizedString(UPDATE_AVAILABLE_LABELS, lang))}">
|
|
202
|
+
<meta name="ep-i18n-update-cta" content="${escapeHTML(getLocalizedString(UPDATE_REFRESH_CTA_LABELS, lang))}">
|
|
203
|
+
<meta name="ep-i18n-dismiss" content="${escapeHTML(getLocalizedString(UPDATE_DISMISS_LABELS, lang))}">
|
|
204
|
+
${buildHeadFreshnessTags('')}
|
|
200
205
|
</head>
|
|
201
206
|
<body>
|
|
202
207
|
<a href="#main" class="skip-link">${skipLinkText}</a>
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* tradecraft behind every published article.
|
|
14
14
|
*/
|
|
15
15
|
import { BASE_URL, THEME_TOGGLE_SCRIPT } from '../../constants/config.js';
|
|
16
|
-
import {
|
|
16
|
+
import { buildHeadFreshnessTags } from '../../constants/build-info-meta.js';
|
|
17
|
+
import { ALL_LANGUAGES, LANGUAGE_FLAGS, LANGUAGE_NAMES, PAGE_TITLES, SKIP_LINK_TEXTS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, getLocalizedString, getTextDirection, } from '../../constants/languages.js';
|
|
17
18
|
import { FOOTER_SITEMAP_LABELS } from '../../constants/language-ui.js';
|
|
18
19
|
import { buildSiteFooter, buildSiteHeader, buildPageBanner, } from '../../templates/section-builders.js';
|
|
19
20
|
import { escapeHTML } from '../../utils/file-utils.js';
|
|
@@ -366,6 +367,10 @@ ${hreflangLinks}
|
|
|
366
367
|
<link rel="manifest" href="site.webmanifest">
|
|
367
368
|
<meta name="theme-color" content="#003399">
|
|
368
369
|
<link rel="stylesheet" href="styles.css">
|
|
370
|
+
<meta name="ep-i18n-update-text" content="${escapeHTML(getLocalizedString(UPDATE_AVAILABLE_LABELS, lang))}">
|
|
371
|
+
<meta name="ep-i18n-update-cta" content="${escapeHTML(getLocalizedString(UPDATE_REFRESH_CTA_LABELS, lang))}">
|
|
372
|
+
<meta name="ep-i18n-dismiss" content="${escapeHTML(getLocalizedString(UPDATE_DISMISS_LABELS, lang))}">
|
|
373
|
+
${buildHeadFreshnessTags('')}
|
|
369
374
|
<script type="application/ld+json">${jsonLdString}</script>
|
|
370
375
|
</head>
|
|
371
376
|
<body>
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
* golden snapshots taken from `npm run prebuild`).
|
|
21
21
|
*/
|
|
22
22
|
import { BASE_URL, THEME_TOGGLE_SCRIPT } from '../../constants/config.js';
|
|
23
|
-
import {
|
|
23
|
+
import { buildHeadFreshnessTags } from '../../constants/build-info-meta.js';
|
|
24
|
+
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, PAGE_DESCRIPTIONS, SKIP_LINK_TEXTS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, getLocalizedString, getTextDirection, } from '../../constants/languages.js';
|
|
24
25
|
import { escapeHTML } from '../../utils/file-utils.js';
|
|
25
26
|
import { detectCategory } from '../../utils/article-category.js';
|
|
26
27
|
import { ARTICLE_TYPE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, } from '../../constants/language-ui.js';
|
|
@@ -245,6 +246,10 @@ ${hreflangLinks}
|
|
|
245
246
|
<link rel="manifest" href="site.webmanifest">
|
|
246
247
|
<meta name="theme-color" content="#003399">
|
|
247
248
|
<link rel="stylesheet" href="styles.css">
|
|
249
|
+
<meta name="ep-i18n-update-text" content="${escapeHTML(getLocalizedString(UPDATE_AVAILABLE_LABELS, lang))}">
|
|
250
|
+
<meta name="ep-i18n-update-cta" content="${escapeHTML(getLocalizedString(UPDATE_REFRESH_CTA_LABELS, lang))}">
|
|
251
|
+
<meta name="ep-i18n-dismiss" content="${escapeHTML(getLocalizedString(UPDATE_DISMISS_LABELS, lang))}">
|
|
252
|
+
${buildHeadFreshnessTags('')}
|
|
248
253
|
<script type="application/ld+json">${jsonLdString}</script>
|
|
249
254
|
</head>
|
|
250
255
|
<body>
|
|
@@ -7,7 +7,7 @@ import { MCPConnection } from './mcp-connection.js';
|
|
|
7
7
|
import type { MCPClientOptions, MCPToolResult, GetMEPsOptions, GetPlenarySessionsOptions, SearchDocumentsOptions, GetParliamentaryQuestionsOptions, GetCommitteeInfoOptions, MonitorLegislativePipelineOptions, AssessMEPInfluenceOptions, AnalyzeCoalitionDynamicsOptions, DetectVotingAnomaliesOptions, ComparePoliticalGroupsOptions, VotingRecordsOptions, VotingPatternsOptions, GenerateReportOptions, AnalyzeLegislativeEffectivenessOptions, AnalyzeCommitteeActivityOptions, TrackMEPAttendanceOptions, AnalyzeCountryDelegationOptions, GeneratePoliticalLandscapeOptions, GetCurrentMEPsOptions, GetSpeechesOptions, GetProceduresOptions, GetAdoptedTextsOptions, GetEventsOptions, GetMeetingActivitiesOptions, GetMeetingDecisionsOptions, GetMEPDeclarationsOptions, GetIncomingMEPsOptions, GetOutgoingMEPsOptions, GetHomonymMEPsOptions, GetPlenaryDocumentsOptions, GetCommitteeDocumentsOptions, GetPlenarySessionDocumentsOptions, GetPlenarySessionDocumentItemsOptions, GetControlledVocabulariesOptions, GetExternalDocumentsOptions, GetMeetingForeseenActivitiesOptions, GetProcedureEventsOptions, GetMeetingPlenarySessionDocumentsOptions, GetMeetingPlenarySessionDocumentItemsOptions, NetworkAnalysisOptions, SentimentTrackerOptions, EarlyWarningSystemOptions, ComparativeIntelligenceOptions, CorrelateIntelligenceOptions, GetAllGeneratedStatsOptions, GetMEPsFeedOptions, GetEventsFeedOptions, GetProceduresFeedOptions, GetAdoptedTextsFeedOptions, GetMEPDeclarationsFeedOptions, GetDocumentsFeedOptions, GetPlenaryDocumentsFeedOptions, GetCommitteeDocumentsFeedOptions, GetPlenarySessionDocumentsFeedOptions, GetExternalDocumentsFeedOptions, GetParliamentaryQuestionsFeedOptions, GetCorporateBodiesFeedOptions, GetControlledVocabulariesFeedOptions, GetProcedureEventByIdOptions, GetFreshProceduresOptions } from '../types/index.js';
|
|
8
8
|
/**
|
|
9
9
|
* Canonical list of tools exposed by the European Parliament MCP gateway
|
|
10
|
-
* (`european-parliament-mcp-server@1.2.
|
|
10
|
+
* (`european-parliament-mcp-server@1.2.18`). The news workflows, prompt
|
|
11
11
|
* library (`.github/prompts/07-mcp-reference.md`), and the integration test
|
|
12
12
|
* suite all reference this list so a regression that adds/removes a tool
|
|
13
13
|
* fails a single drift guard
|
|
@@ -22,7 +22,7 @@ export declare const EP_MCP_TOOLS: readonly string[];
|
|
|
22
22
|
* covering the two shapes historically emitted by the EP MCP server.
|
|
23
23
|
*
|
|
24
24
|
* 1. **Uniform envelope** (all feeds as of
|
|
25
|
-
* `european-parliament-mcp-server@1.2.
|
|
25
|
+
* `european-parliament-mcp-server@1.2.18`) —
|
|
26
26
|
* `{status:"unavailable", items:[], generatedAt:"..."}` established by
|
|
27
27
|
* Hack23/European-Parliament-MCP-Server#301 and extended to
|
|
28
28
|
* `get_events_feed`/`get_procedures_feed` by
|
|
@@ -168,9 +168,9 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
168
168
|
*
|
|
169
169
|
* @remarks
|
|
170
170
|
* This repository is currently documented/configured against
|
|
171
|
-
* `european-parliament-mcp-server@1.2.
|
|
171
|
+
* `european-parliament-mcp-server@1.2.18`.
|
|
172
172
|
*
|
|
173
|
-
* **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.
|
|
173
|
+
* **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.18 server):** the upstream server
|
|
174
174
|
* applies a server-side post-filter on `dateFrom`/`dateTo` before serialisation, because the
|
|
175
175
|
* EP Open Data Portal `/meetings` endpoint silently ignores its `date-from`/`date-to` query
|
|
176
176
|
* parameters (Defect #5). Under this contract:
|
|
@@ -179,7 +179,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
179
179
|
* - Per-window session counts are reproducible because the EP-side regression is masked by
|
|
180
180
|
* the upstream post-filter.
|
|
181
181
|
*
|
|
182
|
-
* No local post-filter is applied here. The repository is pinned to v1.2.
|
|
182
|
+
* No local post-filter is applied here. The repository is pinned to v1.2.18, so the
|
|
183
183
|
* date-filter guarantees above apply; consumers running against an older server image
|
|
184
184
|
* (pre-v1.2.14) must not assume them.
|
|
185
185
|
*/
|
|
@@ -10,7 +10,7 @@ import { ProcedureSeenCache } from './procedure-seen-cache.js';
|
|
|
10
10
|
import { recordPendingDocument, markDocumentResolved, getPendingDocumentsForReprobe, escalateExpiredDocuments, getPendingDocumentsSummary, } from './pending-documents.js';
|
|
11
11
|
/**
|
|
12
12
|
* Canonical list of tools exposed by the European Parliament MCP gateway
|
|
13
|
-
* (`european-parliament-mcp-server@1.2.
|
|
13
|
+
* (`european-parliament-mcp-server@1.2.18`). The news workflows, prompt
|
|
14
14
|
* library (`.github/prompts/07-mcp-reference.md`), and the integration test
|
|
15
15
|
* suite all reference this list so a regression that adds/removes a tool
|
|
16
16
|
* fails a single drift guard
|
|
@@ -114,7 +114,7 @@ const CONTENT_NOT_YET_AVAILABLE_SUBSTRING = 'document indexed but content not ye
|
|
|
114
114
|
/**
|
|
115
115
|
* Classify an error message into a diagnostic error category.
|
|
116
116
|
*
|
|
117
|
-
* Maps EP MCP Server v1.2.
|
|
117
|
+
* Maps EP MCP Server v1.2.18 structured error codes and generic HTTP/network
|
|
118
118
|
* errors into one of six broad categories used for logging and retry decisions:
|
|
119
119
|
*
|
|
120
120
|
* Returned categories (priority order):
|
|
@@ -130,7 +130,7 @@ const CONTENT_NOT_YET_AVAILABLE_SUBSTRING = 'document indexed but content not ye
|
|
|
130
130
|
*/
|
|
131
131
|
function classifyToolError(message) {
|
|
132
132
|
const lowerMsg = message.toLowerCase();
|
|
133
|
-
// EP MCP Server v1.2.
|
|
133
|
+
// EP MCP Server v1.2.18 structured error codes (matched case-insensitively)
|
|
134
134
|
if (lowerMsg.includes('internal_error')) {
|
|
135
135
|
return 'INTERNAL_ERROR';
|
|
136
136
|
}
|
|
@@ -189,7 +189,7 @@ function _parseResultPayload(result) {
|
|
|
189
189
|
* covering the two shapes historically emitted by the EP MCP server.
|
|
190
190
|
*
|
|
191
191
|
* 1. **Uniform envelope** (all feeds as of
|
|
192
|
-
* `european-parliament-mcp-server@1.2.
|
|
192
|
+
* `european-parliament-mcp-server@1.2.18`) —
|
|
193
193
|
* `{status:"unavailable", items:[], generatedAt:"..."}` established by
|
|
194
194
|
* Hack23/European-Parliament-MCP-Server#301 and extended to
|
|
195
195
|
* `get_events_feed`/`get_procedures_feed` by
|
|
@@ -548,9 +548,9 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
548
548
|
*
|
|
549
549
|
* @remarks
|
|
550
550
|
* This repository is currently documented/configured against
|
|
551
|
-
* `european-parliament-mcp-server@1.2.
|
|
551
|
+
* `european-parliament-mcp-server@1.2.18`.
|
|
552
552
|
*
|
|
553
|
-
* **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.
|
|
553
|
+
* **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.18 server):** the upstream server
|
|
554
554
|
* applies a server-side post-filter on `dateFrom`/`dateTo` before serialisation, because the
|
|
555
555
|
* EP Open Data Portal `/meetings` endpoint silently ignores its `date-from`/`date-to` query
|
|
556
556
|
* parameters (Defect #5). Under this contract:
|
|
@@ -559,7 +559,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
559
559
|
* - Per-window session counts are reproducible because the EP-side regression is masked by
|
|
560
560
|
* the upstream post-filter.
|
|
561
561
|
*
|
|
562
|
-
* No local post-filter is applied here. The repository is pinned to v1.2.
|
|
562
|
+
* No local post-filter is applied here. The repository is pinned to v1.2.18, so the
|
|
563
563
|
* date-filter guarantees above apply; consumers running against an older server image
|
|
564
564
|
* (pre-v1.2.14) must not assume them.
|
|
565
565
|
*/
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Templates/Icons
|
|
3
|
+
* @description Inline SVG icon set used by the shared site header / footer
|
|
4
|
+
* to upgrade visual consistency across browsers (emoji rendering varies
|
|
5
|
+
* wildly between Windows, macOS, Android and Linux).
|
|
6
|
+
*
|
|
7
|
+
* Every icon is a 24×24 outline glyph that inherits `currentColor`. SVGs
|
|
8
|
+
* are emitted with `aria-hidden="true"` and `focusable="false"` so they
|
|
9
|
+
* stay decorative — call sites should keep their existing emoji span
|
|
10
|
+
* (also `aria-hidden`) so the visual diff is small while progressively
|
|
11
|
+
* enhancing the chrome.
|
|
12
|
+
*
|
|
13
|
+
* Returning `<span aria-hidden="true">` (empty) for unknown names keeps
|
|
14
|
+
* call sites safe even if a future caller passes a typo'd name.
|
|
15
|
+
*/
|
|
16
|
+
/** Identifiers for the icons exported from this module. */
|
|
17
|
+
export type IconName = 'sponsor' | 'security' | 'rss' | 'github' | 'sitemap' | 'pi' | 'install' | 'refresh' | 'close';
|
|
18
|
+
export interface IconOptions {
|
|
19
|
+
/** Pixel size for both width and height (default 18). */
|
|
20
|
+
readonly size?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Render an inline SVG icon.
|
|
24
|
+
*
|
|
25
|
+
* @param name - One of the {@link IconName} values.
|
|
26
|
+
* @param opts - Optional overrides (currently size only).
|
|
27
|
+
* @returns SVG markup string, or an empty `<span aria-hidden>` for unknown names.
|
|
28
|
+
*/
|
|
29
|
+
export declare function icon(name: IconName, opts?: IconOptions): string;
|
|
30
|
+
//# sourceMappingURL=icons.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
const PATHS = {
|
|
4
|
+
// Heart-in-circle for "Sponsor".
|
|
5
|
+
sponsor: '<path d="M12 21s-7-4.5-7-10a4 4 0 0 1 7-2.7A4 4 0 0 1 19 11c0 5.5-7 10-7 10Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>',
|
|
6
|
+
// Shield + checkmark.
|
|
7
|
+
security: '<path d="M12 3 4.5 6v6a8 8 0 0 0 7.5 8 8 8 0 0 0 7.5-8V6L12 3Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="m9 12 2 2 4-4" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
8
|
+
rss: '<path d="M5 5a14 14 0 0 1 14 14M5 11a8 8 0 0 1 8 8M6 17.5a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>',
|
|
9
|
+
github: '<path d="M12 3a9 9 0 0 0-2.85 17.54c.45.08.62-.2.62-.43v-1.5c-2.5.55-3.04-1.06-3.04-1.06-.4-1.05-1-1.32-1-1.32-.83-.57.06-.55.06-.55.92.07 1.4.94 1.4.94.81 1.4 2.13 1 2.65.76.08-.6.32-1 .58-1.23-2-.23-4.1-1-4.1-4.4 0-.97.34-1.77.9-2.4-.1-.23-.4-1.13.08-2.36 0 0 .73-.23 2.4.92a8.3 8.3 0 0 1 4.36 0c1.66-1.15 2.4-.92 2.4-.92.48 1.23.18 2.13.08 2.36.56.63.9 1.43.9 2.4 0 3.4-2.1 4.16-4.1 4.39.32.28.62.84.62 1.7v2.52c0 .24.17.52.62.43A9 9 0 0 0 12 3Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>',
|
|
10
|
+
sitemap: '<path d="M12 4v4M12 12v4M6 16v4M18 16v4M6 16h12M9 8h6v4H9V8ZM4 18h4v3H4v-3ZM16 18h4v3h-4v-3Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/>',
|
|
11
|
+
// Compass for "Political Intelligence".
|
|
12
|
+
pi: '<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="m9 15 2-6 6-2-2 6-6 2Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/>',
|
|
13
|
+
install: '<path d="M12 3v12m0 0-4-4m4 4 4-4M5 19h14" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
14
|
+
refresh: '<path d="M21 12a9 9 0 1 1-3.5-7M21 4v5h-5" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
15
|
+
close: '<path d="M6 6l12 12M18 6 6 18" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Render an inline SVG icon.
|
|
19
|
+
*
|
|
20
|
+
* @param name - One of the {@link IconName} values.
|
|
21
|
+
* @param opts - Optional overrides (currently size only).
|
|
22
|
+
* @returns SVG markup string, or an empty `<span aria-hidden>` for unknown names.
|
|
23
|
+
*/
|
|
24
|
+
export function icon(name, opts = {}) {
|
|
25
|
+
const path = PATHS[name];
|
|
26
|
+
if (!path) {
|
|
27
|
+
return '<span class="icon icon-inline" aria-hidden="true"></span>';
|
|
28
|
+
}
|
|
29
|
+
const size = typeof opts.size === 'number' && opts.size > 0 ? opts.size : 18;
|
|
30
|
+
return `<svg class="icon icon-inline" width="${size}" height="${size}" viewBox="0 0 24 24" role="img" aria-hidden="true" focusable="false">${path}</svg>`;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=icons.js.map
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* timeline sections, comparison tables, and key figures bars.
|
|
8
8
|
*/
|
|
9
9
|
import { escapeHTML } from '../utils/file-utils.js';
|
|
10
|
-
import { ALL_LANGUAGES, LANGUAGE_FLAGS, LANGUAGE_NAMES, getLocalizedString, TOC_ARIA_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, 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, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, } from '../constants/languages.js';
|
|
11
|
-
import { APP_VERSION, createThemeToggleButton } from '../constants/config.js';
|
|
10
|
+
import { ALL_LANGUAGES, LANGUAGE_FLAGS, LANGUAGE_NAMES, getLocalizedString, TOC_ARIA_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, 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, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_LABELS, } from '../constants/languages.js';
|
|
11
|
+
import { APP_VERSION, BUILD_ID, BUILD_SHORT, BUILD_TIME, createThemeToggleButton, } from '../constants/config.js';
|
|
12
|
+
import { icon } from './icons.js';
|
|
12
13
|
import { stripScriptBlocks, stripHtmlTags } from '../utils/html-sanitize.js';
|
|
13
14
|
/**
|
|
14
15
|
* Count occurrences of a regex pattern in a string.
|
|
@@ -281,9 +282,9 @@ export function buildSiteHeader(options) {
|
|
|
281
282
|
</span>
|
|
282
283
|
</a>
|
|
283
284
|
<div class="site-header__actions">
|
|
284
|
-
<a class="site-header__cta site-header__cta--sponsor" href="https://github.com/sponsors/Hack23"
|
|
285
|
+
<a class="site-header__cta site-header__cta--sponsor" href="https://github.com/sponsors/Hack23">${icon('sponsor')}<span class="emoji" aria-hidden="true">💖</span> Sponsor Hack23</a>
|
|
285
286
|
<a class="site-header__cta" href="https://www.hack23.com">Become a sponsor</a>
|
|
286
|
-
<a class="site-header__cta site-header__cta--security" href="https://github.com/Hack23/euparliamentmonitor/blob/main/SECURITY.md"
|
|
287
|
+
<a class="site-header__cta site-header__cta--security" href="https://github.com/Hack23/euparliamentmonitor/blob/main/SECURITY.md">${icon('security')}<span class="emoji" aria-hidden="true">🔐</span> Commitment to Transparency and Security</a>
|
|
287
288
|
${createThemeToggleButton(themeToggleLabel)}
|
|
288
289
|
</div>
|
|
289
290
|
<nav class="site-header__langs" role="navigation" aria-label="Language selection">
|
|
@@ -366,6 +367,17 @@ export function buildSiteFooter(options) {
|
|
|
366
367
|
? `\n <p class="footer-stats">${escapeHTML(getLocalizedString(FOOTER_ARTICLES_AVAILABLE_LABELS, lang).replace('{count}', String(articleCount)))}</p>`
|
|
367
368
|
: '';
|
|
368
369
|
const langGrid = buildFooterLangGrid(lang, pathPrefix);
|
|
370
|
+
const buildLabel = escapeHTML(getLocalizedString(BUILD_INFO_COMMIT_LABELS, lang));
|
|
371
|
+
const deployedLabel = escapeHTML(getLocalizedString(BUILD_INFO_DEPLOYED_LABELS, lang));
|
|
372
|
+
const safeBuildId = escapeHTML(BUILD_ID);
|
|
373
|
+
const safeBuildShort = escapeHTML(BUILD_SHORT);
|
|
374
|
+
const safeBuildTime = escapeHTML(BUILD_TIME);
|
|
375
|
+
const buildLine = `v${escapeHTML(APP_VERSION)} · ` +
|
|
376
|
+
`<a href="https://github.com/Hack23/euparliamentmonitor/commit/${safeBuildId}" ` +
|
|
377
|
+
`class="footer-build" title="${buildLabel} ${safeBuildShort}" rel="noopener">` +
|
|
378
|
+
`<code>${safeBuildShort}</code></a> · ` +
|
|
379
|
+
`<span class="footer-build-deployed">${deployedLabel}</span> ` +
|
|
380
|
+
`<time class="footer-build-time" datetime="${safeBuildTime}" data-relative-time>${safeBuildTime}</time>`;
|
|
369
381
|
return `<footer class="site-footer" role="contentinfo">
|
|
370
382
|
<div class="footer-content">
|
|
371
383
|
<div class="footer-section">
|
|
@@ -378,17 +390,17 @@ export function buildSiteFooter(options) {
|
|
|
378
390
|
<ul>
|
|
379
391
|
<li><a href="${homeHref}">${homeLabel}</a></li>
|
|
380
392
|
<li><a href="${homeHref}#main">News</a></li>
|
|
381
|
-
<li><a href="${analysisDocsHref}">📊 Analysis & Reports</a></li>
|
|
393
|
+
<li><a href="${analysisDocsHref}">📊 Analysis & Reports</a></li>
|
|
382
394
|
<li><a href="${pathPrefix}docs/index.html">Dashboard</a></li>
|
|
383
|
-
<li><a href="${politicalIntelligenceHref}"
|
|
384
|
-
<li><a href="${sitemapHref}"
|
|
395
|
+
<li><a href="${politicalIntelligenceHref}">${icon('pi')}<span class="emoji" aria-hidden="true">🧠</span> ${politicalIntelligenceLabel}</a></li>
|
|
396
|
+
<li><a href="${sitemapHref}">${icon('sitemap')}<span class="emoji" aria-hidden="true">🗺️</span> ${sitemapLabel}</a></li>
|
|
385
397
|
<li><a href="${apiDocsHref}">📚 API Documentation (TypeDoc)</a></li>
|
|
386
|
-
<li><a href="${pathPrefix}rss.xml">${rssLabel}</a></li>
|
|
398
|
+
<li><a href="${pathPrefix}rss.xml">${icon('rss')}${rssLabel}</a></li>
|
|
387
399
|
<li><a href="https://hack23.com/euparliamentmonitor.html">EU Parliament Monitor by Hack23</a></li>
|
|
388
400
|
<li><a href="https://hack23.com/euparliamentmonitor-features.html">EU Parliament Monitor Features</a></li>
|
|
389
401
|
<li><a href="https://hack23.com/cia-features.html">CIA Platform</a></li>
|
|
390
402
|
<li><a href="https://www.riksdagen.se/">Sveriges Riksdag</a></li>
|
|
391
|
-
<li><a href="https://github.com/Hack23/euparliamentmonitor">${githubLabel}</a></li>
|
|
403
|
+
<li><a href="https://github.com/Hack23/euparliamentmonitor">${icon('github')}${githubLabel}</a></li>
|
|
392
404
|
<li><a href="https://github.com/Hack23/euparliamentmonitor/issues">${reportIssuesLabel}</a></li>
|
|
393
405
|
<li><a href="https://github.com/Hack23/euparliamentmonitor/blob/main/LICENSE">${licenseLabel}</a></li>
|
|
394
406
|
<li><a href="https://www.europarl.europa.eu/">${europarlLabel}</a></li>
|
|
@@ -426,7 +438,7 @@ export function buildSiteFooter(options) {
|
|
|
426
438
|
</div>
|
|
427
439
|
</div>
|
|
428
440
|
<div class="footer-bottom">
|
|
429
|
-
<p>© 2008-${year} <a href="https://hack23.com">Hack23 AB</a> (Org.nr 5595347807) | Gothenburg, Sweden |
|
|
441
|
+
<p>© 2008-${year} <a href="https://hack23.com">Hack23 AB</a> (Org.nr 5595347807) | Gothenburg, Sweden | ${buildLine}</p>
|
|
430
442
|
<p class="footer-disclaimer"><span aria-hidden="true">⚠️</span> ${disclaimerText} <a href="https://github.com/Hack23/euparliamentmonitor/issues">${reportIssuesLabel}</a>.</p>
|
|
431
443
|
</div>
|
|
432
444
|
</footer>`;
|
|
@@ -803,6 +803,51 @@ function main() {
|
|
|
803
803
|
const forwardRegistryResult = validateForwardStatementsRegistryCoverage(runDir, articleType);
|
|
804
804
|
mergeForwardRegistryResult(results, forwardRegistryResult);
|
|
805
805
|
|
|
806
|
+
// ── Re-run improve/extend enforcement ────────────────────────────────────
|
|
807
|
+
// Detect whether this is a re-run of an existing same-day analysis by
|
|
808
|
+
// checking manifest.history[]. When prior runs exist the agent MUST extend
|
|
809
|
+
// every artifact (rewriteCount must equal total artifact count, and each
|
|
810
|
+
// carry-forward artifact must reach its extendFloor). These are hard-RED
|
|
811
|
+
// violations, not warnings. See `.github/prompts/02-analysis-protocol.md`
|
|
812
|
+
// §"Re-run improve/extend rule".
|
|
813
|
+
const isRerun =
|
|
814
|
+
Array.isArray(manifest.history) && manifest.history.length > 0;
|
|
815
|
+
|
|
816
|
+
// Load prior-run-diff.json if present (produced unconditionally by
|
|
817
|
+
// `npm run prior-run-diff` in Stage A). Use a safeReadJson wrapper so a
|
|
818
|
+
// corrupt file degrades gracefully.
|
|
819
|
+
let priorRunDiff = null;
|
|
820
|
+
const priorRunDiffPath = path.join(runDir, 'runs', 'prior-run-diff.json');
|
|
821
|
+
if (fs.existsSync(priorRunDiffPath)) {
|
|
822
|
+
const raw = safeReadJson(priorRunDiffPath);
|
|
823
|
+
if (!raw.__error) priorRunDiff = raw;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Build a quick lookup: relativePath → extendFloor, used below.
|
|
827
|
+
const extendFloorByPath = new Map();
|
|
828
|
+
if (priorRunDiff?.carryForward && Array.isArray(priorRunDiff.carryForward)) {
|
|
829
|
+
for (const entry of priorRunDiff.carryForward) {
|
|
830
|
+
if (entry.relativePath && typeof entry.extendFloor === 'number') {
|
|
831
|
+
extendFloorByPath.set(entry.relativePath, entry.extendFloor);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// On re-runs, check every carry-forward artifact's new line count against
|
|
837
|
+
// its extendFloor. Failures are hard-RED violations injected directly into
|
|
838
|
+
// the per-artifact result (not warnings) because a skip-write on a
|
|
839
|
+
// carry-forward target defeats the never-no-op contract.
|
|
840
|
+
if (isRerun && extendFloorByPath.size > 0) {
|
|
841
|
+
for (const r of results) {
|
|
842
|
+
const extendFloor = extendFloorByPath.get(r.relativePath);
|
|
843
|
+
if (extendFloor === undefined) continue;
|
|
844
|
+
if (!r.exists) continue; // already flagged as missing
|
|
845
|
+
if (r.lines < extendFloor) {
|
|
846
|
+
r.issues.push(`extend:below-extendFloor(${r.lines}<${extendFloor})`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
806
851
|
// Orphans are reported as warnings (not blocking) — they may be valid extras.
|
|
807
852
|
const summary = summarize(results);
|
|
808
853
|
const offending = results.filter((r) => r.issues.length > 0);
|
|
@@ -825,13 +870,15 @@ function main() {
|
|
|
825
870
|
);
|
|
826
871
|
}
|
|
827
872
|
|
|
828
|
-
|
|
829
|
-
|
|
873
|
+
// ── pass2 rewriteCount enforcement ───────────────────────────────────────
|
|
830
874
|
// Pass-2-skipped heuristic: warn when manifest.pass2 is absent, malformed,
|
|
831
875
|
// or `rewriteCount === 0` AND at least one artifact sits at exactly its
|
|
832
|
-
// line floor.
|
|
833
|
-
//
|
|
834
|
-
//
|
|
876
|
+
// line floor. On a re-run, `rewriteCount === 0` is a hard-RED violation
|
|
877
|
+
// (not a warning) because every artifact must be extended — a zero count
|
|
878
|
+
// means Stage B was a no-op. See `.github/prompts/02-analysis-protocol.md`
|
|
879
|
+
// §"Re-run improve/extend rule".
|
|
880
|
+
//
|
|
881
|
+
// A malformed pass2 block (non-numeric, non-finite, negative, non-integer
|
|
835
882
|
// rewriteCount, or missing/non-string startedAt/endedAt timestamps) is
|
|
836
883
|
// treated like an absent block so the enforcement can't be bypassed by
|
|
837
884
|
// typos or malformed values.
|
|
@@ -876,7 +923,19 @@ function main() {
|
|
|
876
923
|
);
|
|
877
924
|
}
|
|
878
925
|
|
|
879
|
-
|
|
926
|
+
// On re-runs, a zero-rewrite pass2 is a hard-RED gate violation because
|
|
927
|
+
// every artifact must be extended. On first runs, it remains a warning.
|
|
928
|
+
let rerunZeroRewritesRed = false;
|
|
929
|
+
if (isRerun && pass2ZeroRewrites) {
|
|
930
|
+
rerunZeroRewritesRed = true;
|
|
931
|
+
process.stderr.write(
|
|
932
|
+
`RED rerun-no-op: manifest.pass2.rewriteCount === 0 on a re-run ` +
|
|
933
|
+
`(history[] non-empty) — Stage B must extend every artifact. ` +
|
|
934
|
+
`See .github/prompts/02-analysis-protocol.md §"Re-run improve/extend rule".\n`,
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (!rerunZeroRewritesRed && (pass2Absent || pass2Invalid || pass2ZeroRewrites)) {
|
|
880
939
|
const atFloor = results.filter(
|
|
881
940
|
(r) => r.exists && r.lines > 0 && r.lines === r.minLines,
|
|
882
941
|
);
|
|
@@ -892,6 +951,8 @@ function main() {
|
|
|
892
951
|
}
|
|
893
952
|
}
|
|
894
953
|
|
|
954
|
+
const green = offending.length === 0 && !rerunZeroRewritesRed;
|
|
955
|
+
|
|
895
956
|
const gateLine = green
|
|
896
957
|
? `STAGE_C_GATE: GREEN articleType=${articleType} artifacts=${results.length} lines=${summary.totalLines}`
|
|
897
958
|
: `STAGE_C_GATE: RED articleType=${articleType} missing=${summary.missing} short=${summary.short} placeholders=${summary.placeholders} mermaid_missing=${summary.mermaidMissing} other=${summary.other}`;
|
|
@@ -905,6 +966,7 @@ function main() {
|
|
|
905
966
|
articleType,
|
|
906
967
|
runDir: path.relative(ROOT, runDir) || runDir,
|
|
907
968
|
artifacts: results.length,
|
|
969
|
+
isRerun,
|
|
908
970
|
summary,
|
|
909
971
|
results,
|
|
910
972
|
orphans,
|