From e5699246fbb679db6470ccda549a8eba5cef5712 Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Thu, 29 Oct 2026 20:05:51 +1100 Subject: [PATCH 1/2] fix(maintainer): add .ts extensions so node can load the sweep Node's type stripper does not resolve extensionless relative imports, so the scheduled sweep exits before it assigns anyone. --- scripts/maintainer/actions.ts | 2 +- scripts/maintainer/classify.test.ts | 6 ++--- scripts/maintainer/classify.ts | 8 +++---- scripts/maintainer/collect.ts | 6 ++--- scripts/maintainer/comments.ts | 2 +- scripts/maintainer/config.ts | 2 +- scripts/maintainer/discord.ts | 4 ++-- scripts/maintainer/fixtures.ts | 2 +- scripts/maintainer/metrics.test.ts | 10 ++++---- scripts/maintainer/metrics.ts | 8 +++---- scripts/maintainer/route.test.ts | 11 ++++++--- scripts/maintainer/route.ts | 4 ++-- scripts/maintainer/scorecard.ts | 14 +++++------ scripts/maintainer/sweep.test.ts | 36 ++++++++++++++++++++--------- scripts/maintainer/sweep.ts | 25 ++++++++++++-------- tsconfig.json | 3 +++ 16 files changed, 85 insertions(+), 58 deletions(-) diff --git a/scripts/maintainer/actions.ts b/scripts/maintainer/actions.ts index 59faa72f30..7ed8be2c2b 100644 --- a/scripts/maintainer/actions.ts +++ b/scripts/maintainer/actions.ts @@ -4,7 +4,7 @@ * through the REST API. */ -import type { GitHubClient } from './github' +import type { GitHubClient } from './github.ts' export const MANAGED_LABELS: Array<{ name: string diff --git a/scripts/maintainer/classify.test.ts b/scripts/maintainer/classify.test.ts index fdf51befd3..4e1c72b604 100644 --- a/scripts/maintainer/classify.test.ts +++ b/scripts/maintainer/classify.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { classifyDiscussion, classifyIssue, classifyPR } from './classify' -import { ACK_MARKER, REPRO_MARKER } from './comments' +import { classifyDiscussion, classifyIssue, classifyPR } from './classify.ts' +import { ACK_MARKER, REPRO_MARKER } from './comments.ts' import { NOW, comment, @@ -12,7 +12,7 @@ import { makeIssue, makePR, review, -} from './fixtures' +} from './fixtures.ts' describe('classifyPR — waiting-on state machine', () => { it('brand-new PR with no maintainer response waits on maintainer', () => { diff --git a/scripts/maintainer/classify.ts b/scripts/maintainer/classify.ts index 4256f7db7b..94e144097b 100644 --- a/scripts/maintainer/classify.ts +++ b/scripts/maintainer/classify.ts @@ -3,9 +3,9 @@ * heuristic, and per-item hygiene checks. No I/O — fixture-testable. */ -import { isRosterMaintainer } from './config' -import { ACK_MARKER, REPRO_MARKER } from './comments' -import { matchesAnyGlob } from './glob' +import { isRosterMaintainer } from './config.ts' +import { ACK_MARKER, REPRO_MARKER } from './comments.ts' +import { matchesAnyGlob } from './glob.ts' import type { DiscussionItem, DiscussionTriage, @@ -18,7 +18,7 @@ import type { ToolsetConfig, TriageResult, WaitingOn, -} from './types' +} from './types.ts' export const HOUR_MS = 60 * 60 * 1000 diff --git a/scripts/maintainer/collect.ts b/scripts/maintainer/collect.ts index ab28ea70da..92f44002b7 100644 --- a/scripts/maintainer/collect.ts +++ b/scripts/maintainer/collect.ts @@ -4,8 +4,8 @@ * the shapes in types.ts. Read-only — never mutates anything. */ -import { isBotLogin } from './config' -import type { GitHubClient } from './github' +import { isBotLogin } from './config.ts' +import type { GitHubClient } from './github.ts' import type { CIState, ClosedItem, @@ -15,7 +15,7 @@ import type { RepoSnapshot, TimelineEvent, ToolsetConfig, -} from './types' +} from './types.ts' const CLOSED_LOOKBACK_DAYS = 14 diff --git a/scripts/maintainer/comments.ts b/scripts/maintainer/comments.ts index b81a36b277..55a82cbbd3 100644 --- a/scripts/maintainer/comments.ts +++ b/scripts/maintainer/comments.ts @@ -1,4 +1,4 @@ -import type { PRTriage } from './types' +import type { PRTriage } from './types.ts' /** * HTML markers embedded in bot comments so sweeps stay idempotent: before diff --git a/scripts/maintainer/config.ts b/scripts/maintainer/config.ts index fd00199092..24fb00db10 100644 --- a/scripts/maintainer/config.ts +++ b/scripts/maintainer/config.ts @@ -1,5 +1,5 @@ import { readFile } from 'node:fs/promises' -import type { ToolsetConfig } from './types' +import type { ToolsetConfig } from './types.ts' export const DEFAULT_CONFIG_PATH = '.github/maintainers.json' diff --git a/scripts/maintainer/discord.ts b/scripts/maintainer/discord.ts index 0e04c07f34..c74d0496ae 100644 --- a/scripts/maintainer/discord.ts +++ b/scripts/maintainer/discord.ts @@ -3,8 +3,8 @@ * Rendering is pure; posting takes an injected fetch. */ -import { formatDuration } from './metrics' -import type { Scorecard } from './metrics' +import { formatDuration } from './metrics.ts' +import type { Scorecard } from './metrics.ts' const MAX_LINES_PER_SECTION = 12 const MAX_EMBED_DESCRIPTION = 4000 diff --git a/scripts/maintainer/fixtures.ts b/scripts/maintainer/fixtures.ts index a967c19c8f..1039694671 100644 --- a/scripts/maintainer/fixtures.ts +++ b/scripts/maintainer/fixtures.ts @@ -6,7 +6,7 @@ import type { PRItem, TimelineEvent, ToolsetConfig, -} from './types' +} from './types.ts' export const NOW = new Date('2026-07-16T00:00:00Z') diff --git a/scripts/maintainer/metrics.test.ts b/scripts/maintainer/metrics.test.ts index 684daf7175..469a833d98 100644 --- a/scripts/maintainer/metrics.test.ts +++ b/scripts/maintainer/metrics.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' -import { classifyAll } from './classify' -import { buildScorecardEmbeds, chunkEmbeds } from './discord' -import { computeScorecard, formatDuration, percentile } from './metrics' +import { classifyAll } from './classify.ts' +import { buildScorecardEmbeds, chunkEmbeds } from './discord.ts' +import { computeScorecard, formatDuration, percentile } from './metrics.ts' import { NOW, review, @@ -12,8 +12,8 @@ import { makeDiscussion, makeIssue, makePR, -} from './fixtures' -import type { RepoSnapshot } from './types' +} from './fixtures.ts' +import type { RepoSnapshot } from './types.ts' function snapshotFixture(): RepoSnapshot { return { diff --git a/scripts/maintainer/metrics.ts b/scripts/maintainer/metrics.ts index bba2885f99..41387fc9bc 100644 --- a/scripts/maintainer/metrics.ts +++ b/scripts/maintainer/metrics.ts @@ -3,9 +3,9 @@ * sections and stats. Pure — no I/O. */ -import { deriveResponseState } from './classify' -import { isRosterMaintainer } from './config' -import { computeLoad, routePR } from './route' +import { deriveResponseState } from './classify.ts' +import { isRosterMaintainer } from './config.ts' +import { computeLoad, routePR } from './route.ts' import type { DiscussionTriage, IssueTriage, @@ -13,7 +13,7 @@ import type { RepoSnapshot, ToolsetConfig, TriageResult, -} from './types' +} from './types.ts' const DAY_MS = 24 * 60 * 60 * 1000 diff --git a/scripts/maintainer/route.test.ts b/scripts/maintainer/route.test.ts index 45c30cefb3..eebc8f48df 100644 --- a/scripts/maintainer/route.test.ts +++ b/scripts/maintainer/route.test.ts @@ -1,7 +1,12 @@ import { describe, expect, it } from 'vitest' -import { globToRegExp, matchesGlob } from './glob' -import { computeLoad, requiresCoreReview, routeIssue, routePR } from './route' -import { config, daysAgo } from './fixtures' +import { globToRegExp, matchesGlob } from './glob.ts' +import { + computeLoad, + requiresCoreReview, + routeIssue, + routePR, +} from './route.ts' +import { config, daysAgo } from './fixtures.ts' describe('glob matcher', () => { it.each([ diff --git a/scripts/maintainer/route.ts b/scripts/maintainer/route.ts index 86861b0b5f..e2c9d5b227 100644 --- a/scripts/maintainer/route.ts +++ b/scripts/maintainer/route.ts @@ -4,8 +4,8 @@ * needing any stored state. */ -import { matchesAnyGlob, matchesGlob } from './glob' -import type { ClosedItem, MaintainerEntry, ToolsetConfig } from './types' +import { matchesAnyGlob, matchesGlob } from './glob.ts' +import type { ClosedItem, MaintainerEntry, ToolsetConfig } from './types.ts' export type AssignmentLoad = Map diff --git a/scripts/maintainer/scorecard.ts b/scripts/maintainer/scorecard.ts index 3c19c8efd8..1c6e1502d8 100644 --- a/scripts/maintainer/scorecard.ts +++ b/scripts/maintainer/scorecard.ts @@ -8,13 +8,13 @@ import { readdir } from 'node:fs/promises' import process from 'node:process' -import { classifyAll } from './classify' -import { collectSnapshot } from './collect' -import { loadConfig } from './config' -import { buildScorecardEmbeds, postToDiscord } from './discord' -import { createGitHubClient } from './github' -import { computeScorecard } from './metrics' -import { isDryRun, resolveToken, writeStepSummary } from './env' +import { classifyAll } from './classify.ts' +import { collectSnapshot } from './collect.ts' +import { loadConfig } from './config.ts' +import { buildScorecardEmbeds, postToDiscord } from './discord.ts' +import { createGitHubClient } from './github.ts' +import { computeScorecard } from './metrics.ts' +import { isDryRun, resolveToken, writeStepSummary } from './env.ts' async function countPendingChangesets(): Promise { try { diff --git a/scripts/maintainer/sweep.test.ts b/scripts/maintainer/sweep.test.ts index 6f8334492a..6a64fb6083 100644 --- a/scripts/maintainer/sweep.test.ts +++ b/scripts/maintainer/sweep.test.ts @@ -1,8 +1,10 @@ +import { execFile } from 'node:child_process' +import { promisify } from 'node:util' import { describe, expect, it } from 'vitest' -import { executeMutations, planLabelChanges } from './actions' -import { ACK_MARKER, REPRO_MARKER, buildAckComment } from './comments' -import { classifyPR } from './classify' -import { planSweep } from './sweep' +import { executeMutations, planLabelChanges } from './actions.ts' +import { ACK_MARKER, REPRO_MARKER, buildAckComment } from './comments.ts' +import { classifyPR } from './classify.ts' +import { planSweep } from './sweep.ts' import { NOW, review, @@ -12,8 +14,8 @@ import { hoursAgo, makeIssue, makePR, -} from './fixtures' -import type { RepoSnapshot } from './types' +} from './fixtures.ts' +import type { RepoSnapshot } from './types.ts' function makeSnapshot(overrides: Partial = {}): RepoSnapshot { return { @@ -376,11 +378,11 @@ describe('routing and review requests', () => { }) it('runs collection → routing → REST mutations → scorecard with paginated GitHub data', async () => { - const { collectSnapshot } = await import('./collect') - const { createGitHubClient } = await import('./github') - const { classifyAll } = await import('./classify') - const { computeScorecard } = await import('./metrics') - const { buildScorecardEmbeds } = await import('./discord') + const { collectSnapshot } = await import('./collect.ts') + const { createGitHubClient } = await import('./github.ts') + const { classifyAll } = await import('./classify.ts') + const { computeScorecard } = await import('./metrics.ts') + const { buildScorecardEmbeds } = await import('./discord.ts') const roster = { ...config, maintainers: config.maintainers.map((m) => ({ @@ -573,3 +575,15 @@ it('does not treat assignment or review-request events as contributor replies', expect(after.unansweredSince).toBe(before.unansweredSince) expect(after.firstResponseHours).toBe(before.firstResponseHours) }) + +const execFileAsync = promisify(execFile) + +it('links the sweep module when node loads the file', async () => { + const sweepHref = new URL('./sweep.ts', import.meta.url).href + const { stdout } = await execFileAsync(process.execPath, [ + '--input-type=module', + '--eval', + `await import(${JSON.stringify(sweepHref)}); console.log('sweep-module-linked')`, + ]) + expect(stdout).toContain('sweep-module-linked') +}) diff --git a/scripts/maintainer/sweep.ts b/scripts/maintainer/sweep.ts index 13ce99bad5..e36014c8e2 100644 --- a/scripts/maintainer/sweep.ts +++ b/scripts/maintainer/sweep.ts @@ -23,16 +23,21 @@ import { ensureManagedLabels, executeMutations, planLabelChanges, -} from './actions' -import { classifyAll } from './classify' -import { collectSnapshot } from './collect' -import { buildAckComment, buildReproComment } from './comments' -import { isRosterMaintainer, loadConfig } from './config' -import { createGitHubClient } from './github' -import { computeLoad, requiresCoreReview, routeIssue, routePR } from './route' -import { isDryRun, resolveToken, writeStepSummary } from './env' -import type { Mutation } from './actions' -import type { PRItem, RepoSnapshot, ToolsetConfig } from './types' +} from './actions.ts' +import { classifyAll } from './classify.ts' +import { collectSnapshot } from './collect.ts' +import { buildAckComment, buildReproComment } from './comments.ts' +import { isRosterMaintainer, loadConfig } from './config.ts' +import { createGitHubClient } from './github.ts' +import { + computeLoad, + requiresCoreReview, + routeIssue, + routePR, +} from './route.ts' +import { isDryRun, resolveToken, writeStepSummary } from './env.ts' +import type { Mutation } from './actions.ts' +import type { PRItem, RepoSnapshot, ToolsetConfig } from './types.ts' export interface SweepPlan { mutations: Array diff --git a/tsconfig.json b/tsconfig.json index 7b2458fd6f..b47f9d8ca4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,8 @@ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "./tsconfig.base.json", + "compilerOptions": { + "allowImportingTsExtensions": true + }, "include": ["scripts", "*.config.ts", "*.config.js"] } From f8bb6a1d004783ee4dddca247b0b68923b332491 Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Thu, 29 Oct 2026 20:35:04 +1100 Subject: [PATCH 2/2] fix(ai-angular): extend tsconfig.base.json like every other package The root tsconfig now sets allowImportingTsExtensions for scripts/. ng-packagr emits, so inheriting that flag fails the build with TS5096. --- packages/ai-angular/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ai-angular/tsconfig.json b/packages/ai-angular/tsconfig.json index 0b0e0bf7a9..c5578dc6f0 100644 --- a/packages/ai-angular/tsconfig.json +++ b/packages/ai-angular/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "noEmit": true, "module": "preserve",