Skip to content

Fix hardcoded riksmöte year strings and add regression-prevention tests#640

Merged
pethers merged 6 commits intomainfrom
copilot/add-test-enforcement-riksmote
Feb 27, 2026
Merged

Fix hardcoded riksmöte year strings and add regression-prevention tests#640
pethers merged 6 commits intomainfrom
copilot/add-test-enforcement-riksmote

Conversation

Copy link
Contributor

Copilot AI commented Feb 27, 2026

Three news generators hardcoded '2025/26' instead of calling getCurrentRiksmote(), with no tests to catch regression. The existing agentic workflow test actively documented the hardcoding pattern.

Generator fixes

  • weekly-review.ts: 4 occurrences → pre-computed rm variable reused across Steps 2 & 4 (import already present)
  • monthly-review.ts: add import { getCurrentRiksmote } from './motions.js'; 3 occurrences → pre-computed rm variable reused in Promise.all fetchers
  • propositions.ts: single import { getCurrentRiksmote } from './motions.js' (duplicate removed); 1 occurrence → getCurrentRiksmote()
// before
const rm = getCurrentRiksmote(today);
.then(() => client.fetchCommitteeReports(30, getCurrentRiksmote(today)))  // redundant re-call

// after
const rm = getCurrentRiksmote(today);
.then(() => client.fetchCommitteeReports(30, rm))  // reuse computed value

New regression-guard test (tests/news-types/riksmote-dynamic-calculation.test.ts)

  • Discovers all .ts files in scripts/news-types/ programmatically via readdirSync — all 8 generators are scanned (breaking-news, committee-reports, month-ahead, monthly-review, motions, propositions, week-ahead, weekly-review), and any future additions are covered automatically
  • Strips comments then asserts no '20XX/XX' / "20XX/XX" / `20XX/XX` literals remain in executable code
  • Asserts monthly-review.ts and propositions.ts import getCurrentRiksmote

Updated agentic workflow test (tests/agentic-workflow-mcp-queries.test.ts)

  • Fixed broken it block (missing }); caused "Unexpected end of file" parse failure)
  • Replaced weak /calculat|dynamic|current/i with explicit calculation-instruction check: /(calculate|…)[\s\S]{0,120}(riksmöte|parliamentary\s+session)/i
  • Asserts no hardcoded rm literals with optional quotes: /rm:\s*["']?20\d{2}\/\d{2}["']?/i — catches rm: "2025/26", rm: '2025/26', and unquoted rm: 2025/26
Original prompt

This section details on the original issue you should resolve

<issue_title>Add test enforcement for dynamic riksmöte calculation across all generators</issue_title>
<issue_description>## 📋 Issue Type
Enhancement — Add test enforcement to prevent hardcoded riksmöte regression

🎯 Objective

Add tests that enforce dynamic riksmöte calculation across all TypeScript news-type generators and verify agentic workflow prompts include dynamic calculation instructions. Prevent regression of hardcoded parliamentary year strings.

📊 Current State

  • No test exists that verifies TypeScript generators use dynamic getCurrentRiksmote() instead of hardcoded '2025/26'
  • The existing test in tests/agentic-workflow-mcp-queries.test.ts (line ~262) actually documents hardcoding rather than preventing it:
    it('workflows should document riksmöte (rm) parameter usage', () => {
      expect(content).toMatch(/rm:.*"2025\/26"|"2024\/25"/);
    });
  • This means hardcoded riksmöte values can be re-introduced without any test catching it

🚀 Desired State

  • Test that scans all 8 TypeScript news-type generators for hardcoded rm string literals
  • Test that verifies monthly-review.ts and propositions.ts import getCurrentRiksmote
  • Updated agentic workflow test to verify dynamic calculation instructions exist
  • Tests catch any regression that re-introduces hardcoded parliamentary year strings

🔧 Implementation Approach

1. Add TypeScript generator scan test (~30 lines)

In tests/news-types/ or a new dedicated test file:

import { readFileSync } from 'fs';
import { join } from 'path';

describe('riksmöte dynamic calculation enforcement', () => {
  const newsTypesDir = join(__dirname, '../../scripts/news-types');
  const files = ['weekly-review.ts', 'monthly-review.ts', 'propositions.ts'];
  
  for (const file of files) {
    it(`${file} should not hardcode riksmöte year strings in code`, () => {
      const content = readFileSync(join(newsTypesDir, file), 'utf-8');
      // Remove comments before checking
      const codeOnly = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
      expect(codeOnly).not.toMatch(/'20\d{2}\/\d{2}'/);
    });
  }
  
  it('monthly-review.ts should import getCurrentRiksmote', () => {
    const content = readFileSync(join(newsTypesDir, 'monthly-review.ts'), 'utf-8');
    expect(content).toMatch(/import.*getCurrentRiksmote/);
  });
  
  it('propositions.ts should import getCurrentRiksmote', () => {
    const content = readFileSync(join(newsTypesDir, 'propositions.ts'), 'utf-8');
    expect(content).toMatch(/import.*getCurrentRiksmote/);
  });
});

2. Update agentic workflow test (~15 lines)

In tests/agentic-workflow-mcp-queries.test.ts, update the existing riksmöte test:

it('workflows should include dynamic riksmöte calculation instructions', () => {
  // Verify workflows document how to calculate rm dynamically
  expect(content).toMatch(/riksmöte|parliamentary.*session|September/i);
  expect(content).toMatch(/calculat|dynamic|current/i);
});

🤖 Recommended Agent

test-specialist — Test design and enforcement patterns

✅ Acceptance Criteria

  • Test scans TypeScript generators for hardcoded riksmöte strings
  • Test verifies getCurrentRiksmote import in monthly-review.ts and propositions.ts
  • All tests pass with npx vitest run
  • Test would fail if someone re-introduces '2025/26' in generator code
  • No false positives from comments containing year strings

📚 References

  • Dynamic utility: scripts/news-types/motions.ts:234-240getCurrentRiksmote() function
  • Existing test: tests/agentic-workflow-mcp-queries.test.ts:262-268 — needs updating
  • Test infrastructure: Vitest (npx vitest run)
    </issue_description>

<agent_instructions>Add a test file or section that verifies: (1) TypeScript news-type generators don't hardcode riksmöte strings like '2025/26' in executable code (exclude comments), (2) monthly-review.ts and propositions.ts import getCurrentRiksmote from motions.js. Use vitest patterns consistent with existing tests. Run npx vitest run to verify all tests pass.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

…rators

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copilot AI changed the title [WIP] Add test enforcement for dynamic riksmöte calculation Fix hardcoded riksmöte year strings and add regression-prevention tests Feb 27, 2026
@pethers pethers requested a review from Copilot February 27, 2026 17:03
@github-actions github-actions bot added testing Test coverage refactor Code refactoring size-m Medium change (50-250 lines) labels Feb 27, 2026
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR eliminates hardcoded riksmöte (parliamentary session) year strings from three news generators and adds regression-prevention tests to ensure the issue doesn't reoccur.

Changes:

  • Replaced hardcoded '2025/26' strings with dynamic getCurrentRiksmote() calls in weekly-review.ts (4 occurrences), monthly-review.ts (3 occurrences), and propositions.ts (1 occurrence)
  • Added necessary imports of getCurrentRiksmote from motions.ts to monthly-review.ts and propositions.ts
  • Created new test suite (riksmote-dynamic-calculation.test.ts) that enforces no hardcoded riksmöte patterns in executable code and verifies proper imports
  • Updated agentic workflow test to check for dynamic riksmöte calculation documentation instead of enforcing hardcoded values

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
scripts/news-types/weekly-review.ts Replaced 4 hardcoded '2025/26' strings with getCurrentRiksmote(today) calls (lines 827, 830, 833, 885)
scripts/news-types/monthly-review.ts Added getCurrentRiksmote import and replaced 3 hardcoded strings with dynamic calls (lines 36, 120, 122, 124)
scripts/news-types/propositions.ts Added getCurrentRiksmote import and replaced 1 hardcoded string with dynamic call (lines 171, 285)
tests/news-types/riksmote-dynamic-calculation.test.ts New test suite that scans generators for hardcoded riksmöte patterns (after stripping comments) and verifies imports
tests/agentic-workflow-mcp-queries.test.ts Updated test to verify workflow documents riksmöte concept and references current/dynamic context instead of enforcing hardcoded values

Signed-off-by: James Pether Sörling <pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

console.log(' 🔄 Step 4 — Fetching speeches from the period...');
const speeches = await Promise.resolve()
.then(() => client.searchSpeeches({ rm, from: fromStr, to: toStr, limit: 100 }) as Promise<unknown[]>)
.then(() => client.searchSpeeches({ rm: getCurrentRiksmote(today), from: fromStr, to: toStr, limit: 100 }) as Promise<unknown[]>)
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the speeches fetch, rm is already available from Step 2 but the call recalculates getCurrentRiksmote(today) again. Reuse the existing rm value for consistency and simpler control flow.

Suggested change
.then(() => client.searchSpeeches({ rm: getCurrentRiksmote(today), from: fromStr, to: toStr, limit: 100 }) as Promise<unknown[]>)
.then(() => client.searchSpeeches({ rm, from: fromStr, to: toStr, limit: 100 }) as Promise<unknown[]>)

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c73ead0 — the speeches fetch in Step 4 now passes the pre-computed rm directly.

Comment on lines 118 to 125
console.log(' 🔄 Step 2 — Fetching typed metadata (reports, propositions, motions)...');
const rm = getCurrentRiksmote(today);
const [recentReports, recentPropositions, recentMotions] = await Promise.all([
Promise.resolve().then(() => client.fetchCommitteeReports(30, rm))
Promise.resolve().then(() => client.fetchCommitteeReports(30, getCurrentRiksmote(today)))
.catch((err: unknown) => { console.error('Failed to fetch committee reports:', err); return [] as unknown[]; }),
Promise.resolve().then(() => client.fetchPropositions(20, rm))
Promise.resolve().then(() => client.fetchPropositions(20, getCurrentRiksmote(today)))
.catch((err: unknown) => { console.error('Failed to fetch propositions:', err); return [] as unknown[]; }),
Promise.resolve().then(() => client.fetchMotions(20, rm))
Promise.resolve().then(() => client.fetchMotions(20, getCurrentRiksmote(today)))
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rm is computed but not used; instead the Promise.all fetches recompute getCurrentRiksmote(today) for each call. Reuse the already-computed rm (or remove the unused rm variable) to reduce duplication and make the code easier to follow.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c73ead0 — the pre-computed rm is now passed to all three Promise.all fetchers; the redundant getCurrentRiksmote(today) calls are removed.

Comment on lines 268 to 269
// Should reference current/dynamic context rather than only static values
expect(content).toMatch(/calculat|dynamic|current/i);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is very permissive: /calculat|dynamic|current/i will match unrelated text like “Current UTC” and can pass even if the workflow still hardcodes an rm: "2025/26" value. To actually prevent regression, assert for explicit riksmöte calculation instructions (e.g., September boundary logic) and/or assert the workflow does not contain hardcoded 20XX/XX rm literals.

Suggested change
// Should reference current/dynamic context rather than only static values
expect(content).toMatch(/calculat|dynamic|current/i);
// Should include explicit instructions for how to calculate the current riksmöte dynamically
expect(content).toMatch(/(calculate|calculating|calculation|determine|compute).{0,120}(riksmöte|parliamentary\s+session)/i);
// Should not rely on hardcoded rm literals like rm: "2025/26"
expect(content).not.toMatch(/rm:\s*["']20\d{2}\/\d{2}["']/i);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c73ead0 — the broken it (missing });) is closed and the assertions are replaced with /(calculate|…)[\s\S]{0,120}(riksmöte|parliamentary\s+session)/i (explicit calculation instruction check) and not.toMatch(/rm:\s*["']20\d{2}\/\d{2}["']/i) (no hardcoded rm literals). Used [\s\S] instead of . so the look-ahead spans across the newline between the heading and the section body.

Comment on lines 170 to 172
import { MCPClient } from '../mcp-client.js';
import { getCurrentRiksmote } from './motions.js';
import {
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate import: getCurrentRiksmote is imported twice from ./motions.js (once in the newly added line and again later in the import block). This will fail TypeScript compilation with an “already been declared” error; remove one of the imports and keep a single import statement.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c73ead0 — the second import { getCurrentRiksmote } from './motions.js' is removed; only one import statement remains.

Comment on lines 825 to 829
const rm = getCurrentRiksmote(today);
const [reports, propositions, motions] = await Promise.all([
Promise.resolve()
.then(() => client.fetchCommitteeReports(50, rm) as Promise<unknown[]>)
.then(() => client.fetchCommitteeReports(50, getCurrentRiksmote(today)) as Promise<unknown[]>)
.catch((err: unknown) => { console.error('Failed to fetch committee reports:', err); return [] as unknown[]; }),
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rm is computed as getCurrentRiksmote(today) but the subsequent fetch calls recompute getCurrentRiksmote(today) again. Prefer passing the already-computed rm to avoid redundant calls and keep the intent clearer (and consistent with later code that uses rm).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c73ead0 — the rm computed on line 825 is now passed directly to all three fetchCommitteeReports/fetchPropositions/fetchMotions calls.

Signed-off-by: James Pether Sörling <pethers@users.noreply.github.com>
@pethers
Copy link
Member

pethers commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

…gthen test assertions

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

Applied all review feedback in c73ead0:

  • weekly-review.ts: removed 4 redundant getCurrentRiksmote(today) re-calls in Steps 2 & 4; the pre-computed rm (line 825) is now passed directly
  • monthly-review.ts: same fix — the rm variable computed before Promise.all is now passed to all 3 fetchers
  • propositions.ts: removed the duplicate import { getCurrentRiksmote } from './motions.js'
  • agentic-workflow test: fixed the missing }); that caused the "Unexpected end of file" compile error; replaced the two nested/broken it blocks with a single properly-closed one using /(calculate|…)[\s\S]{0,120}(riksmöte|parliamentary\s+session)/i + not.toMatch(/rm:\s*["']20\d{2}\/\d{2}["']/i)

@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment on lines 25 to 30
/** Generators that must use dynamic riksmöte calculation */
const DYNAMIC_RM_FILES = [
'weekly-review.ts',
'monthly-review.ts',
'propositions.ts',
];
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regression scan only checks 3 generator files, but the repository has 8 news-type generators under scripts/news-types. To prevent hardcoded riksmöte year strings from being reintroduced in the other generators, extend DYNAMIC_RM_FILES to include all generator .ts files (or discover them programmatically and filter exclusions like helper-only modules).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d77de7dDYNAMIC_RM_FILES is now populated via readdirSync(newsTypesDir).filter(f => f.endsWith('.ts')), so all 8 generators are scanned automatically (breaking-news, committee-reports, month-ahead, monthly-review, motions, propositions, week-ahead, weekly-review). Any new .ts file added to the directory will be covered without touching the test.

Comment on lines 266 to 269
// Should include explicit instructions for how to calculate the current riksmöte dynamically
expect(content).toMatch(/(calculate|calculating|calculation|determine|compute)[\s\S]{0,120}(riksmöte|parliamentary\s+session)/i);
// Should not rely on hardcoded rm literals like rm: "2025/26"
expect(content).not.toMatch(/rm:\s*["']20\d{2}\/\d{2}["']/i);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded-rm assertion only blocks quoted literals (rm: "2025/26" or rm: '2025/26'). A regression could still hardcode an unquoted value (e.g. rm: 2025/26), which this test would miss; consider allowing optional quotes (and/or matching both rm: and rm = if you intend to forbid hardcoding in examples too).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d77de7d — quotes are now optional: /rm:\s*["']?20\d{2}\/\d{2}["']?/i catches rm: "2025/26", rm: '2025/26', and rm: 2025/26.

@pethers
Copy link
Member

pethers commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

…ional

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

Applied both review changes in d77de7d — riksmöte scan now covers all 8 generators via readdirSync, and the rm assertion uses optional quotes to also catch unquoted year values.

@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@pethers pethers marked this pull request as ready for review February 27, 2026 18:42
@pethers pethers merged commit 11fb82c into main Feb 27, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Code refactoring size-m Medium change (50-250 lines) testing Test coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add test enforcement for dynamic riksmöte calculation across all generators

3 participants