From cbb471b666fe74e83d518b803fe310dde17873ba Mon Sep 17 00:00:00 2001 From: can1357 Date: Tue, 30 Jun 2026 23:51:16 +0200 Subject: [PATCH] chore: standardized code quality and formatting for tooling scripts - Added `scripts/**/*.ts` to the linter and formatter inclusion list in `biome.json`. - Replaced manual array index searches with standard `indexOf` operations. - Cleaned up unused variables, obsolete helpers, and command-line flags. - Applied consistent formatting and code style across tooling scripts. --- biome.json | 1 + scripts/check-spoofed-versions.ts | 12 +-- scripts/ci-build-native.ts | 2 +- scripts/ci-concurrency.test.ts | 8 +- scripts/ci-release-build-binaries.ts | 14 ++-- scripts/ci-release-notes.test.ts | 13 +--- scripts/ci-release-notes.ts | 24 +++--- scripts/ci-release-publish.ts | 13 +++- scripts/ci-test-ts.ts | 48 +++++------- scripts/ci-update-brew-formula.ts | 2 +- scripts/claude-trace.ts | 2 +- scripts/eval-bench-runs.ts | 34 ++++---- scripts/fix-changelogs.test.ts | 56 +++++++------- scripts/fix-changelogs.ts | 16 +--- scripts/fix-test-imports.ts | 13 ++-- scripts/inline-functions.test.ts | 2 +- scripts/inline-functions.ts | 41 +++++----- scripts/release.ts | 24 ++---- scripts/rewrite-changelog.ts | 111 +++++++++++++++------------ scripts/rewrite-system-prompt.ts | 17 ++-- scripts/run-rs-task.ts | 17 +--- scripts/session-stats/audit.test.ts | 5 +- scripts/session-stats/audit.ts | 45 ++++++++--- scripts/setup-npm-trust.ts | 20 ++++- scripts/sync-themes.ts | 2 +- scripts/sync-versions.ts | 9 ++- scripts/tool-prompt-usage.ts | 18 +++-- scripts/trace-loader.ts | 6 +- 28 files changed, 294 insertions(+), 281 deletions(-) diff --git a/biome.json b/biome.json index 7ff980d7f..285ae0e2e 100644 --- a/biome.json +++ b/biome.json @@ -56,6 +56,7 @@ "packages/*/examples/**/*.ts", "packages/*/scripts/**/*.ts", "packages/*/*.ts", + "scripts/**/*.ts", "!packages/natives/native/index.d.ts", "!**/vendor/**/*", "!**/node_modules/**/*", diff --git a/scripts/check-spoofed-versions.ts b/scripts/check-spoofed-versions.ts index 769d3c41c..af9b4c557 100755 --- a/scripts/check-spoofed-versions.ts +++ b/scripts/check-spoofed-versions.ts @@ -15,10 +15,7 @@ import * as path from "node:path"; -const PROVIDER_FILE = path.join( - import.meta.dir, - "../packages/catalog/src/wire/gemini-headers.ts", -); +const PROVIDER_FILE = path.join(import.meta.dir, "../packages/catalog/src/wire/gemini-headers.ts"); interface VersionCheck { /** Human label for the report. */ @@ -32,7 +29,10 @@ interface VersionCheck { } /** Fetch latest non-prerelease tag from a GitHub repo. */ -async function fetchLatestGitHubRelease(repo: string, parseTag: (tag: string) => string | null): Promise { +async function fetchLatestGitHubRelease( + repo: string, + parseTag: (tag: string) => string | null, +): Promise { try { // /releases/latest only returns non-prerelease, non-draft releases const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, { @@ -53,7 +53,7 @@ const checks: VersionCheck[] = [ name: "Gemini CLI", sourcePattern: /PI_AI_GEMINI_CLI_VERSION\s*\|\|\s*"(\d+\.\d+\.\d+)"/, repo: "google-gemini/gemini-cli", - parseTag: (tag) => SEMVER_RE.exec(tag)?.[1] ?? null, + parseTag: tag => SEMVER_RE.exec(tag)?.[1] ?? null, }, ]; diff --git a/scripts/ci-build-native.ts b/scripts/ci-build-native.ts index b732e26ab..86dcae6c5 100644 --- a/scripts/ci-build-native.ts +++ b/scripts/ci-build-native.ts @@ -25,7 +25,7 @@ function parseTargetVariants(): NativeBuildVariant[] { const rawVariants = (Bun.env.TARGET_VARIANTS ?? "").trim(); if (!rawVariants) return []; - return rawVariants.split(/\s+/).map((rawVariant) => { + return rawVariants.split(/\s+/).map(rawVariant => { const variant = variantConfigs[rawVariant as keyof typeof variantConfigs]; if (!variant) { throw new Error(`Unsupported TARGET_VARIANTS entry: ${rawVariant}. Expected baseline or modern.`); diff --git a/scripts/ci-concurrency.test.ts b/scripts/ci-concurrency.test.ts index 0311e0d1d..71231728f 100644 --- a/scripts/ci-concurrency.test.ts +++ b/scripts/ci-concurrency.test.ts @@ -237,12 +237,8 @@ const workflowYaml = await Bun.file(WORKFLOW_PATH).text(); const concurrencySection = workflowYaml.slice(workflowYaml.indexOf("\nconcurrency:") + 1); const groupRaw = /^\s*group:\s*(\S.*?)\s*$/m.exec(concurrencySection)?.[1]; const cancelRaw = /^\s*cancel-in-progress:\s*(\S.*?)\s*$/m.exec(concurrencySection)?.[1]; -const groupTemplate = - groupRaw && groupRaw.startsWith('"') && groupRaw.endsWith('"') ? groupRaw.slice(1, -1) : groupRaw; -const cancelTemplate = - cancelRaw && cancelRaw.startsWith('"') && cancelRaw.endsWith('"') - ? cancelRaw.slice(1, -1) - : cancelRaw; +const groupTemplate = groupRaw?.startsWith('"') && groupRaw.endsWith('"') ? groupRaw.slice(1, -1) : groupRaw; +const cancelTemplate = cancelRaw?.startsWith('"') && cancelRaw.endsWith('"') ? cancelRaw.slice(1, -1) : cancelRaw; if (!groupTemplate || !cancelTemplate) { throw new Error("could not locate concurrency.group / cancel-in-progress in ci.yml"); } diff --git a/scripts/ci-release-build-binaries.ts b/scripts/ci-release-build-binaries.ts index 5a3a6c3e0..43c82d59b 100644 --- a/scripts/ci-release-build-binaries.ts +++ b/scripts/ci-release-build-binaries.ts @@ -1,7 +1,7 @@ #!/usr/bin/env bun -import { createRequire } from "node:module"; import * as fs from "node:fs/promises"; +import { createRequire } from "node:module"; import * as path from "node:path"; interface BinaryTarget { @@ -66,11 +66,11 @@ const targets: BinaryTarget[] = [ ]; function parseRequestedTargets(): Set | null { - const flagIndex = process.argv.findIndex(arg => arg === "--targets"); + const flagIndex = process.argv.indexOf("--targets"); const flagValue = flagIndex >= 0 ? process.argv[flagIndex + 1] - : process.argv.find(arg => arg.startsWith("--targets="))?.split("=", 2)[1] ?? Bun.env.RELEASE_TARGETS; + : (process.argv.find(arg => arg.startsWith("--targets="))?.split("=", 2)[1] ?? Bun.env.RELEASE_TARGETS); if (!flagValue) { return null; @@ -147,9 +147,7 @@ async function buildBinary(target: BinaryTarget): Promise { return; } - const buildEnv = shouldAdhocSignDarwinBinary(target) - ? { ...Bun.env, BUN_NO_CODESIGN_MACHO_BINARY: "1" } - : Bun.env; + const buildEnv = shouldAdhocSignDarwinBinary(target) ? { ...Bun.env, BUN_NO_CODESIGN_MACHO_BINARY: "1" } : Bun.env; await runCommand(buildCompileCommand(target), repoRoot, buildEnv); // Bun 1.3.12 emits a truncated Mach-O signature on darwin builds. @@ -186,9 +184,7 @@ async function resetArtifacts(): Promise { async function main(): Promise { const requestedTargets = parseRequestedTargets(); - const selectedTargets = requestedTargets - ? targets.filter(target => requestedTargets.has(target.id)) - : targets; + const selectedTargets = requestedTargets ? targets.filter(target => requestedTargets.has(target.id)) : targets; if (requestedTargets) { const unknownTargets = [...requestedTargets].filter( diff --git a/scripts/ci-release-notes.test.ts b/scripts/ci-release-notes.test.ts index 3f6a77a76..08e329069 100644 --- a/scripts/ci-release-notes.test.ts +++ b/scripts/ci-release-notes.test.ts @@ -1,9 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { - compareVersions, - enumerateChangelogVersions, - mergePackageSection, -} from "./ci-release-notes"; +import { compareVersions, enumerateChangelogVersions, mergePackageSection } from "./ci-release-notes"; const FIXTURE = [ "# Changelog", @@ -86,9 +82,7 @@ describe("mergePackageSection", () => { it("includes every silent-tag section above floor up to target inclusive", () => { const merged = mergePackageSection(FIXTURE, "15.12.4", "15.13.0"); // 15.12.6 and 15.12.5 unique fingerprints must land. - expect(merged).toContain( - "Removed `writeLine`/`writeLineSync` from the public SessionStorageWriter contract.", - ); + expect(merged).toContain("Removed `writeLine`/`writeLineSync` from the public SessionStorageWriter contract."); expect(merged).toContain("Added package-level exports for session context."); expect(merged).toContain("Changed terminal resize handling to paint only the visible viewport."); // 15.12.4 entry stays excluded — it is the floor. @@ -120,8 +114,7 @@ describe("mergePackageSection", () => { }); it("returns empty string when no version in the requested range carries body content", () => { - const empty = ["# Changelog", "", "## [15.13.0] - 2026-06-14", "", "## [15.12.6] - 2026-06-14"] - .join("\n"); + const empty = ["# Changelog", "", "## [15.13.0] - 2026-06-14", "", "## [15.12.6] - 2026-06-14"].join("\n"); expect(mergePackageSection(empty, "15.12.5", "15.13.0")).toBe(""); }); diff --git a/scripts/ci-release-notes.ts b/scripts/ci-release-notes.ts index 7a663e9b8..1228d39dc 100755 --- a/scripts/ci-release-notes.ts +++ b/scripts/ci-release-notes.ts @@ -39,8 +39,14 @@ const CATEGORY_ORDER = ["Breaking Changes", "Added", "Changed", "Fixed", "Remove /** Compare two `X.Y.Z` (or `vX.Y.Z`) version strings; non-semver returns 0. */ export function compareVersions(a: string, b: string): number { - const am = a.replace(/^v/, "").trim().match(/^(\d+)\.(\d+)\.(\d+)$/); - const bm = b.replace(/^v/, "").trim().match(/^(\d+)\.(\d+)\.(\d+)$/); + const am = a + .replace(/^v/, "") + .trim() + .match(/^(\d+)\.(\d+)\.(\d+)$/); + const bm = b + .replace(/^v/, "") + .trim() + .match(/^(\d+)\.(\d+)\.(\d+)$/); if (!am || !bm) return 0; if (am[1] !== bm[1]) return Number(am[1]) - Number(bm[1]); if (am[2] !== bm[2]) return Number(am[2]) - Number(bm[2]); @@ -88,11 +94,7 @@ export function enumerateChangelogVersions(content: string): ChangelogVersionSpa * `floorExclusive === null` → take only the target version (legacy behavior). * Returns "" when no in-range version contributes any bullet. */ -export function mergePackageSection( - content: string, - floorExclusive: string | null, - targetInclusive: string, -): string { +export function mergePackageSection(content: string, floorExclusive: string | null, targetInclusive: string): string { const spans = enumerateChangelogVersions(content) .filter(v => { if (compareVersions(v.version, targetInclusive) > 0) return false; @@ -240,9 +242,7 @@ async function resolvePublishedFloorTag(targetVersion: string): Promise { const tagInput = process.argv[2] ?? process.env.GITHUB_REF_NAME ?? ""; if (!tagInput) { - console.error( - "Error: version not provided. Pass as argv (e.g. `v15.4.3`) or set GITHUB_REF_NAME.", - ); + console.error("Error: version not provided. Pass as argv (e.g. `v15.4.3`) or set GITHUB_REF_NAME."); process.exit(1); } const version = tagInput.replace(/^v/, "").trim(); @@ -267,9 +267,7 @@ async function main(): Promise { } if (sections.length === 0) { - console.warn( - `No CHANGELOG entries found for version ${version}; writing empty release notes to ${outputPath}.`, - ); + console.warn(`No CHANGELOG entries found for version ${version}; writing empty release notes to ${outputPath}.`); await Bun.write(outputPath, ""); process.exit(0); } diff --git a/scripts/ci-release-publish.ts b/scripts/ci-release-publish.ts index 3e1af3f9a..02624b05a 100644 --- a/scripts/ci-release-publish.ts +++ b/scripts/ci-release-publish.ts @@ -31,9 +31,9 @@ import * as os from "node:os"; import * as path from "node:path"; import { $ } from "bun"; import { + type GeneratedLeafPackage, generateNpmPackages, LEAF_TARGETS, - type GeneratedLeafPackage, } from "../packages/natives/scripts/gen-npm-packages.ts"; export interface PublishPackage { @@ -271,7 +271,12 @@ async function publishNativeLeafPackage(tag: string): Promise { const pkgDir = path.join(repoRoot, pkg.dir); const coreManifest = (await Bun.file(path.join(pkgDir, "package.json")).json()) as PackageManifest; if (typeof coreManifest.version !== "string") throw new Error(`Missing version in ${pkg.dir}/package.json`); - const leaves = await generateNpmPackages({ packageDir: pkgDir, dryRun: isDryRun, version: coreManifest.version, tags: [tag] }); + const leaves = await generateNpmPackages({ + packageDir: pkgDir, + dryRun: isDryRun, + version: coreManifest.version, + tags: [tag], + }); const leaf = leaves[0]; if (!leaf) throw new Error(`No native leaf generated for ${tag}`); await publishGeneratedLeafPackage(leaf); @@ -283,7 +288,9 @@ async function publishNativePackage(pkg: PublishPackage): Promise { const name = manifest.name ?? path.basename(pkg.dir); if (isDryRun) { console.log(`DRY RUN native core manifest rewrite (${pkg.dir})`); - console.log(JSON.stringify({ optionalDependencies: manifest.optionalDependencies, files: manifest.files }, null, "\t")); + console.log( + JSON.stringify({ optionalDependencies: manifest.optionalDependencies, files: manifest.files }, null, "\t"), + ); } await packAndPublish(pkgDir, name); } diff --git a/scripts/ci-test-ts.ts b/scripts/ci-test-ts.ts index 57cfdc1c3..65b41b5f7 100755 --- a/scripts/ci-test-ts.ts +++ b/scripts/ci-test-ts.ts @@ -59,7 +59,7 @@ const validModes: Record = { }; // `chunkSize` splits a bucket's file list into that-many-file groups, each run as a -// separate `bun --smol test` child process. A fresh process per chunk resets Bun's +// separate `bun test` child process. A fresh process per chunk resets Bun's // heap and reaps any dangling spawned children between groups, keeping peak RSS // under the CI runner's OOM ceiling (a single 170–370-file invocation gets // SIGKILLed at 137). The singleton/global-state bucket is left whole: its suites @@ -100,8 +100,7 @@ const nativeAndIntegrationPackages = [ // Packages the CI buckets deliberately skip but a local full run should still // cover. mnemopi's embedding suites need a ~270MB fastembed model absent from CI // runners (so it flakes/times out there); robomp-web lives under python/robomp -// and is outside every CI TS bucket. Both run with `--smol` to bound RSS when -// fanned out alongside everything else. +// and is outside every CI TS bucket. const localOnlyWorkspacePackages = ["packages/mnemopi", "python/robomp/web"]; // Repo-level script tests. CI's `workspace` bucket only runs the concurrency @@ -194,13 +193,7 @@ const codingAgentUiContentMarkers = [ "renderToString", ]; -const codingAgentRuntimeContentMarkers = [ - "AgentSession", - "SessionManager", - "AuthStorage", - "Bun.sleep", - "setTimeout(", -]; +const codingAgentRuntimeContentMarkers = ["AgentSession", "SessionManager", "AuthStorage", "Bun.sleep", "setTimeout("]; let codingAgentTestPartitionPromise: Promise | null = null; @@ -211,16 +204,12 @@ function shellQuote(value: string): string { return `'${value.replaceAll("'", `'\\''`)}'`; } -function workspaceTestCommand( - pkg: string, - parallel: number, - options: { smol?: boolean; extraArgs?: string[] } = {}, -): TestCommand { - const { smol = false, extraArgs = [] } = options; +function workspaceTestCommand(pkg: string, parallel: number, options: { extraArgs?: string[] } = {}): TestCommand { + const { extraArgs = [] } = options; return { label: pkg, cwd: pkg, - command: ["bun", ...(smol ? ["--smol"] : []), "test", `--parallel=${parallel}`, ...extraArgs], + command: ["bun", "test", `--parallel=${parallel}`, ...extraArgs], }; } @@ -273,10 +262,7 @@ function classifyCodingAgentTest(testFile: string, content: string): CodingAgent ) { return "native"; } - if ( - matchesAnyPath(testFile, codingAgentUiPathPatterns) || - hasAnyMarker(content, codingAgentUiContentMarkers) - ) { + if (matchesAnyPath(testFile, codingAgentUiPathPatterns) || hasAnyMarker(content, codingAgentUiContentMarkers)) { return "ui"; } if ( @@ -335,7 +321,7 @@ async function codingAgentTestCommands(bucket: CodingAgentBucket): Promise { case "local-ts": return [ ...fastWorkspacePackages.map(pkg => workspaceTestCommand(pkg, 8, { extraArgs: onlyFailuresArgs })), - ...nativeAndIntegrationPackages.map(pkg => - workspaceTestCommand(pkg, 4, { smol: true, extraArgs: onlyFailuresArgs }), - ), - ...localOnlyWorkspacePackages.map(pkg => - workspaceTestCommand(pkg, 4, { smol: true, extraArgs: onlyFailuresArgs }), - ), + ...nativeAndIntegrationPackages.map(pkg => workspaceTestCommand(pkg, 4, { extraArgs: onlyFailuresArgs })), + ...localOnlyWorkspacePackages.map(pkg => workspaceTestCommand(pkg, 4, { extraArgs: onlyFailuresArgs })), ...(await commandsForMode("coding-agent-heavy")), { label: "scripts", @@ -616,7 +598,11 @@ export function formatFailureReport(failures: ChunkOutcome[], total: number, rep const header = `${failures.length} of ${total} test chunk(s) FAILED`; const lines: string[] = ["", style.bold(style.red(`━━━ ${header} ━━━`))]; for (const failure of failures) { - lines.push("", style.bold(style.red(`✗ ${failure.label} (exit ${failure.exitCode})`)), style.dim(`$ ${failure.command}`)); + lines.push( + "", + style.bold(style.red(`✗ ${failure.label} (exit ${failure.exitCode})`)), + style.dim(`$ ${failure.command}`), + ); const failing = extractFailingTests(failure.output); // Fully attributed only when every failure carries its own bun block; // otherwise (no markers, or a marker with no preceding frame — timeouts, @@ -703,7 +689,9 @@ async function runTestCommandsInParallel(commands: TestCommand[], concurrency: n } if (quiet) { const totalSeconds = (performance.now() - runStartedAt) / 1000; - process.stdout.write(`${formatSummaryFooter(commands.length - failures.length, failures.length, totalSeconds)}\n`); + process.stdout.write( + `${formatSummaryFooter(commands.length - failures.length, failures.length, totalSeconds)}\n`, + ); } if (failures.length > 0) { process.exitCode = 1; diff --git a/scripts/ci-update-brew-formula.ts b/scripts/ci-update-brew-formula.ts index 96a72b7e7..c6c261859 100755 --- a/scripts/ci-update-brew-formula.ts +++ b/scripts/ci-update-brew-formula.ts @@ -23,7 +23,7 @@ interface ReleaseAsset { function parseArgs(argv: readonly string[]): { tag: string; out: string | null } { const rest = [...argv]; let out: string | null = null; - const outIdx = rest.findIndex(a => a === "--out"); + const outIdx = rest.indexOf("--out"); if (outIdx >= 0) { out = rest[outIdx + 1] ?? null; if (!out) throw new Error("--out requires a path"); diff --git a/scripts/claude-trace.ts b/scripts/claude-trace.ts index 5fee55399..1f1dbd45c 100755 --- a/scripts/claude-trace.ts +++ b/scripts/claude-trace.ts @@ -1,5 +1,5 @@ #!/usr/bin/env bun -import { runClaudeTraceCommand, type ClaudeTraceCommandArgs } from "../packages/coding-agent/src/cli/claude-trace-cli"; +import { type ClaudeTraceCommandArgs, runClaudeTraceCommand } from "../packages/coding-agent/src/cli/claude-trace-cli"; const HELP = `Usage: bun scripts/claude-trace.ts [options] diff --git a/scripts/eval-bench-runs.ts b/scripts/eval-bench-runs.ts index 1bf40edd3..db5fbe90f 100644 --- a/scripts/eval-bench-runs.ts +++ b/scripts/eval-bench-runs.ts @@ -90,7 +90,10 @@ function parseNumber(text: string): number { } function getCell(text: string, label: string): string | null { - const re = new RegExp(`^\\|\\s*\\*?\\*?${escapeRegex(label)}\\*?\\*?\\s*\\|\\s*\\*?\\*?(.+?)\\*?\\*?\\s*\\|\\s*$`, "m"); + const re = new RegExp( + `^\\|\\s*\\*?\\*?${escapeRegex(label)}\\*?\\*?\\s*\\|\\s*\\*?\\*?(.+?)\\*?\\*?\\s*\\|\\s*$`, + "m", + ); const m = text.match(re); return m ? m[1].trim() : null; } @@ -125,12 +128,6 @@ function parseRatePair(value: string | null): { numerator: number; denominator: }; } -function parseFraction(value: string | null): { num: number; denom: number } { - if (!value) return { num: 0, denom: 0 }; - const m = value.match(/([0-9,]+)\s*\/\s*([0-9,]+)/); - return m ? { num: parseNumber(m[1]), denom: parseNumber(m[2]) } : { num: 0, denom: 0 }; -} - async function parseReport(file: string): Promise { const text = await Bun.file(file).text(); @@ -221,11 +218,9 @@ function sortRows(rows: ReportRow[], by: typeof sortBy): ReportRow[] { }; const cmp: Record number> = { sep: (a, b) => - sepOrder.indexOf(a.sepSlug) - sepOrder.indexOf(b.sepSlug) || - modelOrder(a.model) - modelOrder(b.model), + sepOrder.indexOf(a.sepSlug) - sepOrder.indexOf(b.sepSlug) || modelOrder(a.model) - modelOrder(b.model), model: (a, b) => - modelOrder(a.model) - modelOrder(b.model) || - sepOrder.indexOf(a.sepSlug) - sepOrder.indexOf(b.sepSlug), + modelOrder(a.model) - modelOrder(b.model) || sepOrder.indexOf(a.sepSlug) - sepOrder.indexOf(b.sepSlug), task: (a, b) => b.taskSuccessPct - a.taskSuccessPct, edit: (a, b) => b.editSuccessPct - a.editSuccessPct, tokens: (a, b) => a.totalTokensAvg - b.totalTokensAvg, @@ -236,7 +231,9 @@ function sortRows(rows: ReportRow[], by: typeof sortBy): ReportRow[] { const entries = ( await Promise.all( resolvedDirs.map(async d => - (await fs.readdir(d, { withFileTypes: true })) + ( + await fs.readdir(d, { withFileTypes: true }) + ) .filter(e => e.isFile() && e.name.endsWith(".md")) .map(e => path.join(d, e.name)), ), @@ -283,13 +280,20 @@ function mergeRows(input: ReportRow[]): ReportRow[] { successfulRuns, taskSuccessPct: ratio(successfulRuns, totalRuns), verifiedPct: ratio(successfulRuns, totalRuns), - editToolUsagePct: ratio(sumField(list, r => Math.round((r.editToolUsagePct / 100) * r.totalRuns)), totalRuns), + editToolUsagePct: ratio( + sumField(list, r => Math.round((r.editToolUsagePct / 100) * r.totalRuns)), + totalRuns, + ), editSuccessPct: ratio(patchAttempts - patchFailures, patchAttempts), patchFailurePct: ratio(patchFailures, patchAttempts), patchFailures, patchAttempts, - mutationIntentPct: list.reduce((a, r) => a + (Number.isFinite(r.mutationIntentPct) ? r.mutationIntentPct : 0), 0) / list.length, - autocorrectFreePct: list.reduce((a, r) => a + (Number.isFinite(r.autocorrectFreePct) ? r.autocorrectFreePct : 0), 0) / list.length, + mutationIntentPct: + list.reduce((a, r) => a + (Number.isFinite(r.mutationIntentPct) ? r.mutationIntentPct : 0), 0) / + list.length, + autocorrectFreePct: + list.reduce((a, r) => a + (Number.isFinite(r.autocorrectFreePct) ? r.autocorrectFreePct : 0), 0) / + list.length, tasksAllPassing: sumField(list, r => r.tasksAllPassing), tasksFlakyFailing: sumField(list, r => r.tasksFlakyFailing), timeoutRuns: sumField(list, r => r.timeoutRuns), diff --git a/scripts/fix-changelogs.test.ts b/scripts/fix-changelogs.test.ts index 5a2fb181e..a7fd758cd 100644 --- a/scripts/fix-changelogs.test.ts +++ b/scripts/fix-changelogs.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from "bun:test"; -import { $ } from "bun"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; +import { $ } from "bun"; import { collectPromotableAddedItemLines, fixChangelogContent, runChangelogFixer } from "./fix-changelogs"; describe("collectPromotableAddedItemLines", () => { @@ -82,32 +82,34 @@ describe("fixChangelogContent", () => { expect(result.promotedItems).toBe(2); expect(result.mergedDuplicateHeadings).toBe(1); - expect(result.content).toBe([ - "# Changelog", - "", - "## [Unreleased]", - "", - "### Added", - "", - "- New addition in released section.", - "", - "### Fixed", - "", - "- Existing fix.", - "- Second fix.", - "- New fix in released section.", - "", - "## [1.0.0] - 2026-01-01", - "", - "### Added", - "", - "- Historical addition.", - "", - "### Fixed", - "", - "- Historical fix.", - "", - ].join("\n")); + expect(result.content).toBe( + [ + "# Changelog", + "", + "## [Unreleased]", + "", + "### Added", + "", + "- New addition in released section.", + "", + "### Fixed", + "", + "- Existing fix.", + "- Second fix.", + "- New fix in released section.", + "", + "## [1.0.0] - 2026-01-01", + "", + "### Added", + "", + "- Historical addition.", + "", + "### Fixed", + "", + "- Historical fix.", + "", + ].join("\n"), + ); }); it("drops Unreleased items that already appear verbatim in a released section", () => { diff --git a/scripts/fix-changelogs.ts b/scripts/fix-changelogs.ts index 06053e7b0..608d10684 100755 --- a/scripts/fix-changelogs.ts +++ b/scripts/fix-changelogs.ts @@ -1,7 +1,7 @@ #!/usr/bin/env bun -import { $, Glob } from "bun"; import * as path from "node:path"; +import { $, Glob } from "bun"; const CHANGELOG_GLOB = "packages/*/CHANGELOG.md"; const ORDERED_SECTION_TITLES = ["Breaking Changes", "Added", "Changed", "Fixed", "Removed"] as const; @@ -97,7 +97,6 @@ interface HistoricalReleaseRecovery { sectionsByTitle: Map; } - function isReleaseHeading(line: string): boolean { return /^## \[[^\]]+\]/.test(line); } @@ -377,7 +376,6 @@ function compactAdjacentListSpacing(lines: readonly string[]): string[] { return flattenedItems; } - function normalizeSection(section: ReleaseSection): FixCounters { const counters: FixCounters = { promotedItems: 0, @@ -456,7 +454,6 @@ function sortReleaseSections(document: ChangelogDocument): void { document.sections = [...unreleasedSections, ...releasedSections]; } - function rebuildReleasedSectionsFromHistory( content: string, historicalSectionsByTitle: ReadonlyMap, @@ -502,7 +499,6 @@ function rebuildReleasedSectionsFromHistory( return renderChangelog(document); } - export function renderChangelog(document: ChangelogDocument): string { const output: string[] = []; const prefix = trimBlankLines(numberedText(document.prefixLines)); @@ -590,7 +586,6 @@ function isAddedReleaseHeadingLine(line: string): boolean { return line.startsWith("+## ["); } - function itemKey(pathName: string, text: string): string { return `${pathName}\0${normalizeItemText(text)}`; } @@ -600,7 +595,6 @@ export function collectPromotableAddedItemLines(diffText: string): Map(); let currentPath = ""; - let oldLine = 0; let newLine = 0; let hunkIndex = -1; for (const rawLine of diffText.replace(/\r\n/g, "\n").split("\n")) { @@ -617,7 +611,6 @@ export function collectPromotableAddedItemLines(diffText: string): Map { const baseline = await changelogBaselineCommit(repoRoot); - const listArgs = baseline - ? ["tag", "--contains", baseline, "--sort=v:refname"] - : ["tag", "--sort=v:refname"]; + const listArgs = baseline ? ["tag", "--contains", baseline, "--sort=v:refname"] : ["tag", "--sort=v:refname"]; return (await git(listArgs, repoRoot)) .split("\n") .map(tag => tag.trim()) @@ -825,7 +814,6 @@ async function collectHistoricalReleaseRecovery( return recoveryByPath; } - export async function changelogPaths(repoRoot: string): Promise { const glob = new Glob(CHANGELOG_GLOB); diff --git a/scripts/fix-test-imports.ts b/scripts/fix-test-imports.ts index d8984d077..28ba5dba1 100644 --- a/scripts/fix-test-imports.ts +++ b/scripts/fix-test-imports.ts @@ -1,4 +1,6 @@ #!/usr/bin/env bun +import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { dirname, extname, join, relative, resolve, sep } from "node:path"; /** * Codemod: rewrite relative test imports that reach into a package's `src/` * into the package's public subpath import. @@ -17,16 +19,13 @@ * bun scripts/fix-test-imports.ts --write # apply the changes */ import { Glob } from "bun"; -import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"; -import { dirname, extname, join, relative, resolve, sep } from "node:path"; const ROOT = resolve(import.meta.dir, ".."); const WRITE = process.argv.includes("--write"); // Matches the module specifier of `from "x"`, `import "x"`, `import("x")`, // `require("x")` / `export ... from "x"` — but only when it starts with `./`/`../`. -const SPEC_RE = - /(\bfrom\s*|\bimport\s*\(\s*|\brequire\s*\(\s*|\bimport\s+)(["'])((?:\.\.?\/)[^"']*)\2/g; +const SPEC_RE = /(\bfrom\s*|\bimport\s*\(\s*|\brequire\s*\(\s*|\bimport\s+)(["'])((?:\.\.?\/)[^"']*)\2/g; // Source-module extensions. A specifier resolving to one of these has a public // `./*` -> `./src/*.ts` subpath; anything else (.json/.md/...) is an asset. @@ -148,7 +147,7 @@ const skippedAssets: string[] = []; for (const file of [...files].sort()) { const { content, changes, skipped } = rewriteFile(file); - if (skipped.length) skippedAssets.push(...skipped.map((s) => `${relative(ROOT, file)}: ${s}`)); + if (skipped.length) skippedAssets.push(...skipped.map(s => `${relative(ROOT, file)}: ${s}`)); if (!changes.length) continue; changedFiles++; totalChanges += changes.length; @@ -158,9 +157,7 @@ for (const file of [...files].sort()) { if (WRITE) writeFileSync(file, content); } -console.log( - `\n${WRITE ? "Applied" : "Would apply"} ${totalChanges} rewrite(s) across ${changedFiles} file(s).`, -); +console.log(`\n${WRITE ? "Applied" : "Would apply"} ${totalChanges} rewrite(s) across ${changedFiles} file(s).`); if (skippedAssets.length) { console.log(`\nSkipped ${skippedAssets.length} asset import(s) into src (no public subpath):`); for (const s of skippedAssets) console.log(` ${s}`); diff --git a/scripts/inline-functions.test.ts b/scripts/inline-functions.test.ts index 2353d16ad..dbe99a65e 100644 --- a/scripts/inline-functions.test.ts +++ b/scripts/inline-functions.test.ts @@ -414,7 +414,7 @@ describe("inline-functions: type soundness", () => { describe("inline-functions: formatting safety", () => { test("wraps the tail exactly one indent level deeper than the inverted guard", () => { const { text } = run( - 'function dispatch(r: Runtime): void {\n\thandle(r.item);\n}\n' + + "function dispatch(r: Runtime): void {\n\thandle(r.item);\n}\n" + 'function handle(item: Item | null): void {\n\tif (item?.type !== "x") return;\n\ttouch(item);\n}\n', ); // `if` sits at the function-body indent (one tab); its body is one deeper. diff --git a/scripts/inline-functions.ts b/scripts/inline-functions.ts index 8d75ba8d5..2b6d378dc 100755 --- a/scripts/inline-functions.ts +++ b/scripts/inline-functions.ts @@ -71,7 +71,6 @@ import * as os from "node:os"; import * as path from "node:path"; import { parseArgs } from "node:util"; import { $ } from "bun"; -import { IndentationText, Node, Project, SyntaxKind, VariableDeclarationKind } from "ts-morph"; import type { CallExpression, Expression, @@ -81,6 +80,7 @@ import type { SourceFile, Statement, } from "ts-morph"; +import { IndentationText, Node, Project, SyntaxKind, VariableDeclarationKind } from "ts-morph"; // --------------------------------------------------------------------------- // Types @@ -241,7 +241,8 @@ function isPureExpr(node: Node, strict: boolean): boolean { if (ASSIGNMENT_OPS.has(node.getOperatorToken().getText())) return false; return isPureExpr(node.getLeft(), strict) && isPureExpr(node.getRight(), strict); } - if (Node.isTemplateExpression(node)) return node.getTemplateSpans().every((s) => isPureExpr(s.getExpression(), strict)); + if (Node.isTemplateExpression(node)) + return node.getTemplateSpans().every(s => isPureExpr(s.getExpression(), strict)); // Defining a closure is itself side-effect-free; we substitute the value, never invoke it. if (Node.isArrowFunction(node) || Node.isFunctionExpression(node)) return true; return false; @@ -293,7 +294,7 @@ function argNeedsParens(node: Node): boolean { function subText(node: Node, edits: readonly Edit[]): string { const start = node.getStart(); const end = node.getEnd(); - const local = edits.filter((e) => e.start >= start && e.end <= end).sort((a, b) => b.start - a.start); + const local = edits.filter(e => e.start >= start && e.end <= end).sort((a, b) => b.start - a.start); let text = node.getText(); for (const e of local) text = text.slice(0, e.start - start) + e.text + text.slice(e.end - start); return text; @@ -349,9 +350,9 @@ function wrap(part: NegatedExpr, minPrec: number): string { /** Combine leading guard conditions into the single positive run-condition. */ function combineGuards(guards: readonly Expression[], edits: readonly Edit[]): string { - const parts = guards.map((g) => negate(g, edits)); + const parts = guards.map(g => negate(g, edits)); if (parts.length === 1) return parts[0].text; - return parts.map((p) => wrap(p, PREC_AND)).join(" && "); + return parts.map(p => wrap(p, PREC_AND)).join(" && "); } // --------------------------------------------------------------------------- @@ -410,15 +411,16 @@ function isNameOnly(id: Identifier): boolean { * exception — it names a real value binding even though it lives in a type node. */ function isTypePositioned(id: Identifier): boolean { - if (!id.getFirstAncestor((a) => Node.isTypeNode(a))) return false; - return !id.getFirstAncestor((a) => Node.isTypeQuery(a)); + if (!id.getFirstAncestor(a => Node.isTypeNode(a))) return false; + return !id.getFirstAncestor(a => Node.isTypeQuery(a)); } /** Free identifiers in the body that resolve outside it (module / global) — shadow-sensitive. */ function computeFreeNames(fn: FunctionDeclaration, paramNames: readonly string[]): Set { const body = fn.getBodyOrThrow(); const local = new Set(paramNames); - for (const v of body.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) collectBindingNames(v.getNameNode(), local); + for (const v of body.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) + collectBindingNames(v.getNameNode(), local); for (const f of body.getDescendantsOfKind(SyntaxKind.FunctionDeclaration)) { const n = f.getName(); if (n) local.add(n); @@ -536,7 +538,7 @@ function asGuard(stmt: Statement): Expression | null { } function nearestFunction(node: Node): Node | undefined { - return node.getFirstAncestor((a) => isFunctionLike(a)); + return node.getFirstAncestor(a => isFunctionLike(a)); } /** Does an unlabeled/labeled `break`/`continue` target something outside `root`? */ @@ -716,13 +718,16 @@ function analyze(fn: FunctionDeclaration, opts: Options): Candidate | null { const stmt = parent.getParent(); if (!stmt || !Node.isExpressionStatement(stmt)) return skip(opts, name, "call result is used"); const args = parent.getArguments(); - if (args.some((a) => Node.isSpreadElement(a))) return skip(opts, name, "call uses spread args"); + if (args.some(a => Node.isSpreadElement(a))) return skip(opts, name, "call uses spread args"); if (args.length > params.length) return skip(opts, name, "call passes more args than params"); callSites.push({ stmt, call: parent }); } if (callSites.length === 0) return skip(opts, name, "no call sites"); - const freeNames = computeFreeNames(fn, params.map((p) => p.name)); + const freeNames = computeFreeNames( + fn, + params.map(p => p.name), + ); for (const cs of callSites) { if (callSiteShadows(cs.call, freeNames)) return skip(opts, name, "would shadow a free identifier at a call site"); } @@ -785,11 +790,7 @@ function reservedFor(call: CallExpression, reserved: Map>): Se * land in the caller block — declarations nested in loops, blocks, or functions keep * their own scope and never collide, so they are left untouched. */ -function renameCollidingLocals( - candidate: Candidate, - call: CallExpression, - reserved: Map>, -): Edit[] { +function renameCollidingLocals(candidate: Candidate, call: CallExpression, reserved: Map>): Edit[] { const edits: Edit[] = []; const taken = reservedFor(call, reserved); const lo = candidate.tail[0].getStart(); @@ -890,18 +891,18 @@ function buildReplacement( return { prefix, condition: combineGuards(candidate.guards, paramEdits), - tail: candidate.tail.map((s) => subText(s, paramEdits)), + tail: candidate.tail.map(s => subText(s, paramEdits)), }; } const allEdits = paramEdits.concat(renameCollidingLocals(candidate, call, reserved)); - return { prefix, condition: null, tail: candidate.tail.map((s) => subText(s, allEdits)) }; + return { prefix, condition: null, tail: candidate.tail.map(s => subText(s, allEdits)) }; } function applyReplacement(stmt: Statement, repl: Replacement): void { const parent = stmt.getParent(); if (parent && Node.isStatemented(parent)) { - const idx = parent.getStatements().findIndex((s) => s === stmt); + const idx = parent.getStatements().indexOf(stmt); if (repl.condition === null) { const inserted = parent.insertStatements(idx, [...repl.prefix, ...repl.tail]); stmt.remove(); @@ -934,7 +935,7 @@ function applyReplacement(stmt: Statement, repl: Replacement): void { function inlineCandidate(candidate: Candidate, strict: boolean): void { const reserved = new Map>(); // Build every replacement before mutating, so positions stay valid. - const plans = candidate.callSites.map((cs) => ({ + const plans = candidate.callSites.map(cs => ({ stmt: cs.stmt, repl: buildReplacement(candidate, cs.call, reserved, strict), })); diff --git a/scripts/release.ts b/scripts/release.ts index 941df31bc..0399fce85 100755 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -40,11 +40,10 @@ async function watchCI(): Promise { // Check job-level status for in-progress runs (fail fast on first job failure) const failedJobs: Array<{ workflow: string; job: string; jobId: number; conclusion: string }> = []; - const inProgressRuns = runs.filter((r) => r.status === "in_progress" || r.status === "queued"); + const inProgressRuns = runs.filter(r => r.status === "in_progress" || r.status === "queued"); for (const run of inProgressRuns) { - const jobsOutput = - await $`gh run view ${run.databaseId} --json jobs`.quiet().nothrow().text(); + const jobsOutput = await $`gh run view ${run.databaseId} --json jobs`.quiet().nothrow().text(); try { const { jobs } = JSON.parse(jobsOutput) as { jobs: Array<{ name: string; databaseId: number; status: string; conclusion: string | null }>; @@ -80,9 +79,9 @@ async function watchCI(): Promise { } // Check workflow-level status - const pending = runs.filter((r) => r.status !== "completed"); - const failed = runs.filter((r) => r.status === "completed" && r.conclusion !== "success"); - const passed = runs.filter((r) => r.status === "completed" && r.conclusion === "success"); + const pending = runs.filter(r => r.status !== "completed"); + const failed = runs.filter(r => r.status === "completed" && r.conclusion !== "success"); + const passed = runs.filter(r => r.status === "completed" && r.conclusion === "success"); console.log(` ${passed.length} passed, ${pending.length} pending, ${failed.length} failed`); @@ -257,10 +256,7 @@ async function cmdRelease(versionOrBump: string): Promise { // Update @oh-my-pi/* catalog entries in root package.json console.log("Updating root catalog versions..."); let rootPkgRaw = await Bun.file("package.json").text(); - rootPkgRaw = rootPkgRaw.replace( - /("@oh-my-pi\/[^"]+":\s*)"[^"]+"/g, - `$1"${version}"`, - ); + rootPkgRaw = rootPkgRaw.replace(/("@oh-my-pi\/[^"]+":\s*)"[^"]+"/g, `$1"${version}"`); await Bun.write("package.json", rootPkgRaw); console.log(" Updated root catalog @oh-my-pi/* entries"); @@ -372,13 +368,7 @@ async function cmdRelease(versionOrBump: string): Promise { const tagRef = `v${version}`; const sha = (await git(["rev-parse", "HEAD"]).text()).trim(); await git(["tag", "-f", tagRef]); - await git([ - "push", - "--atomic", - "origin", - "refs/heads/main:refs/heads/main", - `${sha}:refs/tags/${tagRef}`, - ]); + await git(["push", "--atomic", "origin", "refs/heads/main:refs/heads/main", `${sha}:refs/tags/${tagRef}`]); console.log(); // 9. Watch CI diff --git a/scripts/rewrite-changelog.ts b/scripts/rewrite-changelog.ts index 78b7ed471..434d41eb2 100755 --- a/scripts/rewrite-changelog.ts +++ b/scripts/rewrite-changelog.ts @@ -31,13 +31,22 @@ import * as path from "node:path"; import { parseArgs } from "node:util"; -import { type Api, AuthStorage, completeSimple, Effort, type Model, SqliteAuthCredentialStore, type Tool, type ToolCall } from "@oh-my-pi/pi-ai"; +import { + type Api, + AuthStorage, + completeSimple, + Effort, + type Model, + SqliteAuthCredentialStore, + type Tool, + type ToolCall, +} from "@oh-my-pi/pi-ai"; import { type GeneratedProvider, getBundledModel } from "@oh-my-pi/pi-catalog/models"; import { getAgentDbPath } from "@oh-my-pi/pi-utils"; import { z } from "zod/v4"; import { - changelogPaths, type ChangelogDocument, + changelogPaths, type NumberedLine, parseChangelog, parseItems, @@ -143,12 +152,14 @@ interface RewrittenSection { } const REWRITE_RESPONSE = z.object({ - sections: z.array( - z.object({ - category: z.enum(["Breaking Changes", "Added", "Changed", "Fixed", "Removed"]), - items: z.array(z.string()), - }) - ).default([]), + sections: z + .array( + z.object({ + category: z.enum(["Breaking Changes", "Added", "Changed", "Fixed", "Removed"]), + items: z.array(z.string()), + }), + ) + .default([]), }); const REWRITE_PARAMETERS = { @@ -200,7 +211,10 @@ function validateRewrite(args: Record): RewrittenSection[] { } function normalizeRewriteItem(text: string): string[] { - const lines = text.trim().split("\n").map(l => l.trimEnd()); + const lines = text + .trim() + .split("\n") + .map(l => l.trimEnd()); if (lines.length === 0) return []; const first = lines[0] ?? ""; const content = first.startsWith("- ") ? first.slice(2) : first.startsWith("* ") ? first.slice(2) : first; @@ -212,7 +226,11 @@ function normalizeRewriteItem(text: string): string[] { return out; } -async function requestRewrite(model: RewriteModel, packageName: string, unreleasedBody: string): Promise { +async function requestRewrite( + model: RewriteModel, + packageName: string, + unreleasedBody: string, +): Promise { const userText = `Package: \`${packageName}\` Original \`[Unreleased]\` section body: @@ -239,7 +257,9 @@ Consolidate and rewrite this content into user-visible release notes. Keep all p continue; } - const call = response.content.find((content): content is ToolCall => content.type === "toolCall" && content.name === "rewrite"); + const call = response.content.find( + (content): content is ToolCall => content.type === "toolCall" && content.name === "rewrite", + ); if (!call) { lastError = "model returned no structured tool call"; continue; @@ -248,7 +268,6 @@ Consolidate and rewrite this content into user-visible release notes. Keep all p return validateRewrite(call.arguments); } catch (error) { lastError = error instanceof Error ? error.message : String(error); - continue; } } throw new Error(`rewrite call failed for ${packageName}: ${lastError}`); @@ -278,11 +297,13 @@ interface RunResult { } function applyRewrite(section: ReleaseSection, sections: RewrittenSection[]): void { - section.subsections = sections.map(sec => { - const rawLines = sec.items.flatMap(normalizeRewriteItem); - const lines: NumberedLine[] = rawLines.map(text => ({ text, lineNumber: 0 })); - return { title: sec.category, lines }; - }).filter(sub => sub.lines.length > 0); + section.subsections = sections + .map(sec => { + const rawLines = sec.items.flatMap(normalizeRewriteItem); + const lines: NumberedLine[] = rawLines.map(text => ({ text, lineNumber: 0 })); + return { title: sec.category, lines }; + }) + .filter(sub => sub.lines.length > 0); } async function run(options: RunOptions): Promise { @@ -300,41 +321,35 @@ async function run(options: RunOptions): Promise { const i = pathIndex++; const changelogPath = paths[i]; if (!changelogPath) continue; + const absolutePath = path.join(repoRoot, changelogPath); + const content = await Bun.file(absolutePath).text(); + const document = parseChangelog(content); + const section = unreleasedSection(document); + if (!section) continue; - try { - const absolutePath = path.join(repoRoot, changelogPath); - const content = await Bun.file(absolutePath).text(); - const document = parseChangelog(content); - const section = unreleasedSection(document); - if (!section) continue; + const originalCount = section.subsections.reduce((sum, sub) => sum + parseItems(sub.lines).length, 0); + if (originalCount === 0) continue; - const originalCount = section.subsections.reduce((sum, sub) => sum + parseItems(sub.lines).length, 0); - if (originalCount === 0) continue; + const unreleasedBody = renderChangelog({ prefixLines: [], sections: [section] }) + .replace(/^## \[Unreleased\]\n?/, "") + .trim(); - const unreleasedBody = renderChangelog({ prefixLines: [], sections: [section] }) - .replace(/^## \[Unreleased\]\n?/, "") - .trim(); + const rewritten = await requestRewrite(model, changelogPath, unreleasedBody); + applyRewrite(section, rewritten); + const next = renderChangelog(document); + if (next === content) continue; - const rewritten = await requestRewrite(model, changelogPath, unreleasedBody); - applyRewrite(section, rewritten); - const next = renderChangelog(document); - if (next === content) continue; - - const rewrittenCount = rewritten.reduce((sum, sec) => sum + sec.items.length, 0); - if (options.write) { - await Bun.write(absolutePath, next); - } - - results[i] = { - path: changelogPath, - originalCount, - rewrittenCount, - sections: rewritten, - }; - } catch (error) { - // Bubble errors from workers - throw error; + const rewrittenCount = rewritten.reduce((sum, sec) => sum + sec.items.length, 0); + if (options.write) { + await Bun.write(absolutePath, next); } + + results[i] = { + path: changelogPath, + originalCount, + rewrittenCount, + sections: rewritten, + }; } } @@ -447,4 +462,4 @@ if (import.meta.main) { await main(); } -export { applyRewrite, collectEntries, run, type RunResult, unreleasedSection, validateRewrite }; +export { applyRewrite, collectEntries, type RunResult, run, unreleasedSection, validateRewrite }; diff --git a/scripts/rewrite-system-prompt.ts b/scripts/rewrite-system-prompt.ts index c31d32611..c63b2c81d 100755 --- a/scripts/rewrite-system-prompt.ts +++ b/scripts/rewrite-system-prompt.ts @@ -1,4 +1,5 @@ #!/usr/bin/env bun + /** * Rewrite the natural-language prose of a prompt file into the terse * implementation-scratchpad voice (see `rewrite-system-prompt.style.md`), @@ -37,8 +38,8 @@ * --dry-run classify + chunk, print a plan, make no network calls */ -import { parseArgs } from "node:util"; import * as path from "node:path"; +import { parseArgs } from "node:util"; import STYLE_GUIDE from "./rewrite-system-prompt.style.md" with { type: "text" }; const DEFAULT_INPUT = "packages/coding-agent/src/prompts/system/system-prompt.md"; @@ -217,7 +218,7 @@ export function planRewrite(content: string): RewritePlan { if (isVerbatimLine(line)) continue; const { prefix, core, suffix } = peel(line); if (core.trim() === "") continue; // nothing rewritable after peeling - const tokens = Array.from(core.matchAll(FRAGILE_RE), (m) => m[0]); + const tokens = Array.from(core.matchAll(FRAGILE_RE), m => m[0]); prose.push({ lineIndex: i, prefix, suffix, core, tokens }); } return { lines, prose }; @@ -284,8 +285,8 @@ export async function rewriteAll( const groups = chunk(toRewrite, opts.chunkSize); let done = 0; - const resolved = await mapPool(groups, opts.concurrency, async (group) => { - const items: RewriteItem[] = group.map((e) => ({ + const resolved = await mapPool(groups, opts.concurrency, async group => { + const items: RewriteItem[] = group.map(e => ({ id: e.lineIndex, text: e.core, tokens: e.tokens, @@ -296,7 +297,7 @@ export async function rewriteAll( } catch { map = new Map(); } - const out = group.map((entry) => { + const out = group.map(entry => { const candidate = map.get(entry.lineIndex); return { entry, text: candidate ?? entry.core, ok: candidate != null }; }); @@ -392,7 +393,7 @@ const REWRITE_RESPONSE_FORMAT = { /** Build a {@link RewriteChunk} backed by an OpenRouter chat-completions endpoint. */ export function makeOpenRouterRewriter(opts: OpenRouterOptions): RewriteChunk { - return async (items) => { + return async items => { const result = new Map(); let pending = items; let lastErr: unknown; @@ -406,7 +407,7 @@ export function makeOpenRouterRewriter(opts: OpenRouterOptions): RewriteChunk { { role: "system", content: opts.system }, { role: "user", - content: JSON.stringify({ items: pending.map((p) => ({ id: p.id, text: p.text })) }), + content: JSON.stringify({ items: pending.map(p => ({ id: p.id, text: p.text })) }), }, ], }); @@ -445,7 +446,7 @@ export function makeOpenRouterRewriter(opts: OpenRouterOptions): RewriteChunk { } if (pending.length > 0) { console.error( - ` ${pending.length} line(s) [${pending.map((p) => p.id).join(",")}] kept original: ${String(lastErr ?? "rewrite dropped a token")}`, + ` ${pending.length} line(s) [${pending.map(p => p.id).join(",")}] kept original: ${String(lastErr ?? "rewrite dropped a token")}`, ); } return result; diff --git a/scripts/run-rs-task.ts b/scripts/run-rs-task.ts index 3d628cdce..a22f0fa29 100644 --- a/scripts/run-rs-task.ts +++ b/scripts/run-rs-task.ts @@ -1,7 +1,7 @@ #!/usr/bin/env bun -import { $ } from "bun"; import * as path from "node:path"; +import { $ } from "bun"; const RUST_AFFECTING_FILE_NAMES = [ "Cargo.toml", @@ -21,16 +21,7 @@ const TASK_COMMANDS = { ], "fix:rs": [ ["cargo", "fmt", "--all"], - [ - "cargo", - "clippy", - "--workspace", - "--fix", - "--allow-dirty", - "--no-deps", - "--allow-staged", - "--allow-no-vcs", - ], + ["cargo", "clippy", "--workspace", "--fix", "--allow-dirty", "--no-deps", "--allow-staged", "--allow-no-vcs"], ], "fmt:rs": [["cargo", "fmt", "--all"]], "lint:rs": [["cargo", "clippy", "--workspace", "--", "-D", "warnings"]], @@ -112,9 +103,7 @@ function isRustAffectingPath(changedPath: string): boolean { const normalized = changedPath.replace(/\\/g, "/"); const fileName = normalized.slice(normalized.lastIndexOf("/") + 1); return ( - normalized.endsWith(".rs") || - normalized.startsWith(".cargo/") || - isOneOf(fileName, RUST_AFFECTING_FILE_NAMES) + normalized.endsWith(".rs") || normalized.startsWith(".cargo/") || isOneOf(fileName, RUST_AFFECTING_FILE_NAMES) ); } diff --git a/scripts/session-stats/audit.test.ts b/scripts/session-stats/audit.test.ts index 9df7ec2e7..53eba5f78 100644 --- a/scripts/session-stats/audit.test.ts +++ b/scripts/session-stats/audit.test.ts @@ -120,7 +120,10 @@ it("scanFile recovers usage, turns, spawns, residency, and pruned result sizes", }, }), // second user turn + final answer with no tools - JSON.stringify({ type: "message", message: { role: "user", content: "thanks, also re-read it", timestamp: 4000 } }), + JSON.stringify({ + type: "message", + message: { role: "user", content: "thanks, also re-read it", timestamp: 4000 }, + }), asst({ ts: 5000, usage: { input: 400, output: 40, cacheRead: 600, cost: 2.0 }, diff --git a/scripts/session-stats/audit.ts b/scripts/session-stats/audit.ts index b012c356d..992ddd615 100755 --- a/scripts/session-stats/audit.ts +++ b/scripts/session-stats/audit.ts @@ -472,7 +472,9 @@ export async function scanFile(filePath: string): Promise const name = call?.name ?? (msg.toolName as string) ?? "?"; const textBlob = contentText(msg.content); const truncated = TRUNCATED_RESULT_RE.exec(textBlob); - const toks = truncated ? Math.max(estTokens(textBlob), Number.parseInt(truncated[1], 10)) : estTokens(textBlob); + const toks = truncated + ? Math.max(estTokens(textBlob), Number.parseInt(truncated[1], 10)) + : estTokens(textBlob); const agg = tool(name); agg.resultToks += toks; if (msg.isError === true) { @@ -571,7 +573,7 @@ async function discoverGroups(opts: CliOptions): Promise { } } for (const [id, main] of mains) { - let childPaths: string[] = []; + const childPaths: string[] = []; let mtime = main.mtime; if (subdirs.has(id)) { const dirPath = path.join(folderPath, id); @@ -616,7 +618,11 @@ async function scanGroup(d: DiscoveredGroup): Promise } /** Run `fn` over `items` with bounded concurrency, preserving order. */ -async function mapPool(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise): Promise { +async function mapPool( + items: readonly T[], + limit: number, + fn: (item: T, index: number) => Promise, +): Promise { const out = new Array(items.length); let next = 0; const workers = Array.from({ length: Math.min(limit, items.length) }, async () => { @@ -756,7 +762,8 @@ function buildDigest(g: SessionGroup): string { .slice(0, 8); if (repeats.length) { lines.push(`\n## Repeated reads of the same file (waste signal)`); - for (const [p, r] of repeats) lines.push(`${p} ×${r.count} (~${fmtTok(r.toks)}tok total, ~${fmtTok(r.residency)} residency)`); + for (const [p, r] of repeats) + lines.push(`${p} ×${r.count} (~${fmtTok(r.toks)}tok total, ~${fmtTok(r.residency)} residency)`); } if (m.topResults.length) { @@ -894,7 +901,16 @@ const SESSION_SCHEMA = { }, headline: { type: "string", description: "one-sentence takeaway for this session" }, }, - required: ["score", "multiTopic", "topics", "shouldHaveSplit", "handoffOpportunities", "spawnVerdicts", "waste", "headline"], + required: [ + "score", + "multiTopic", + "topics", + "shouldHaveSplit", + "handoffOpportunities", + "spawnVerdicts", + "waste", + "headline", + ], } as const; interface AggregateFindings { @@ -1215,9 +1231,12 @@ function printVerdicts(res: AuditResult): void { for (const g of ordered) { const v = res.verdicts.get(g.id); if (!v) continue; - console.log(`\n[${fmtMoney(g.usage.cost)}] "${clip(g.main.title ?? g.id, 60)}" (${g.folder}) — score ${v.score}/10`); + console.log( + `\n[${fmtMoney(g.usage.cost)}] "${clip(g.main.title ?? g.id, 60)}" (${g.folder}) — score ${v.score}/10`, + ); console.log(` ${v.headline}`); - if (v.multiTopic) console.log(` topics: ${v.topics.join(" | ")}${v.shouldHaveSplit ? " → should have split" : ""}`); + if (v.multiTopic) + console.log(` topics: ${v.topics.join(" | ")}${v.shouldHaveSplit ? " → should have split" : ""}`); for (const h of v.handoffOpportunities) console.log(` handoff: ${h}`); for (const s of v.spawnVerdicts) { if (s.verdict === "good") continue; @@ -1319,7 +1338,9 @@ async function main(): Promise { const sessionFilter = opts.session?.toLowerCase(); const matched = sessionFilter ? groups.filter( - g => g.id.toLowerCase().includes(sessionFilter) || (g.main.title ?? "").toLowerCase().includes(sessionFilter), + g => + g.id.toLowerCase().includes(sessionFilter) || + (g.main.title ?? "").toLowerCase().includes(sessionFilter), ) : groups.filter(g => g.usage.cost >= opts.minCost); const candidates = matched.sort((a, b) => b.usage.cost - a.usage.cost).slice(0, opts.maxLlm); @@ -1365,7 +1386,9 @@ async function main(): Promise { addUsage(res.classifierUsage, usage); process.stderr.write(` ✓ ${clip(g.main.title ?? g.id, 50)} (score ${verdict.score})\n`); } catch (err) { - process.stderr.write(` ✗ ${clip(g.main.title ?? g.id, 50)}: ${err instanceof Error ? err.message : err}\n`); + process.stderr.write( + ` ✗ ${clip(g.main.title ?? g.id, 50)}: ${err instanceof Error ? err.message : err}\n`, + ); } }); await saveVerdictCache(cache); @@ -1402,7 +1425,9 @@ async function main(): Promise { headline: v.headline, topics: v.topics, shouldHaveSplit: v.shouldHaveSplit, - spawnIssues: v.spawnVerdicts.filter(s => s.verdict !== "good").map(s => ({ label: s.label, verdict: s.verdict })), + spawnIssues: v.spawnVerdicts + .filter(s => s.verdict !== "good") + .map(s => ({ label: s.label, verdict: s.verdict })), waste: v.waste.map(w => ({ source: w.source, estTokens: w.estTokens, estUsd: round2(w.estUsd) })), }), ); diff --git a/scripts/setup-npm-trust.ts b/scripts/setup-npm-trust.ts index 30344edf6..f4959c53c 100755 --- a/scripts/setup-npm-trust.ts +++ b/scripts/setup-npm-trust.ts @@ -105,7 +105,12 @@ function parseArgs(argv: readonly string[]): Options { opts.workflow = argv[++i]; break; case "--only": - opts.only = new Set((argv[++i] ?? "").split(",").map(s => s.trim()).filter(Boolean)); + opts.only = new Set( + (argv[++i] ?? "") + .split(",") + .map(s => s.trim()) + .filter(Boolean), + ); break; default: console.error(`Unknown argument: ${arg}`); @@ -291,7 +296,10 @@ function placeholderReadme(name: string, target: NativeLeafTarget): string { async function publishNativeLeafPlaceholder(name: string, target: NativeLeafTarget, repo: string): Promise { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-native-placeholder-")); try { - await Bun.write(path.join(tmpDir, "package.json"), `${JSON.stringify(placeholderManifest(name, target, repo), null, "\t")}\n`); + await Bun.write( + path.join(tmpDir, "package.json"), + `${JSON.stringify(placeholderManifest(name, target, repo), null, "\t")}\n`, + ); await Bun.write(path.join(tmpDir, "README.md"), placeholderReadme(name, target)); return (await npmInteractive(["publish", tmpDir, "--access", "public"])) === 0; } finally { @@ -336,7 +344,9 @@ async function main(): Promise { const workflow = opts.workflow; if (!(await Bun.file(path.join(repoRoot, ".github", "workflows", workflow)).exists())) { - console.warn(`Warning: .github/workflows/${workflow} not found; npm will still accept it, but OIDC won't match a non-existent workflow.`); + console.warn( + `Warning: .github/workflows/${workflow} not found; npm will still accept it, but OIDC won't match a non-existent workflow.`, + ); } if (opts.dryRun) { @@ -369,7 +379,9 @@ async function main(): Promise { } console.log("The first mutating npm operation triggers 2FA. When prompted, complete it and choose"); - console.log("'skip 2FA for the next 5 minutes' on the npm site so placeholder publishes and trust setup run unattended.\n"); + console.log( + "'skip 2FA for the next 5 minutes' on the npm site so placeholder publishes and trust setup run unattended.\n", + ); const outcomes = new Map(); let bootstrapped = 0; diff --git a/scripts/sync-themes.ts b/scripts/sync-themes.ts index 7c5fbf400..7c3a92f4a 100755 --- a/scripts/sync-themes.ts +++ b/scripts/sync-themes.ts @@ -12,7 +12,7 @@ const INDEX_FILE = join(THEMES_DIR, "index.ts"); async function main() { const files = await readdir(THEMES_DIR); - const jsonFiles = files.filter((f) => f.endsWith(".json")).sort(); + const jsonFiles = files.filter(f => f.endsWith(".json")).sort(); const imports: string[] = []; const exportEntries: string[] = []; diff --git a/scripts/sync-versions.ts b/scripts/sync-versions.ts index 89be40166..9e4fd7c44 100755 --- a/scripts/sync-versions.ts +++ b/scripts/sync-versions.ts @@ -22,8 +22,8 @@ interface PackageInfo { const packagesDir = join(process.cwd(), "packages"); const packageDirs = readdirSync(packagesDir, { withFileTypes: true }) - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name); + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); // Read all package.json files and build version map const packages: Record = {}; @@ -61,7 +61,8 @@ console.log("\n✅ All packages at same version (lockstep)"); // Update all inter-package dependencies let totalUpdates = 0; -for (const [dir, pkg] of Object.entries(packages)) { +for (const dir in packages) { + const pkg = packages[dir]; let updated = false; // Check dependencies @@ -98,7 +99,7 @@ for (const [dir, pkg] of Object.entries(packages)) { // Write if updated if (updated) { - await Bun.write(pkg.path, JSON.stringify(pkg.data, null, "\t") + "\n"); + await Bun.write(pkg.path, `${JSON.stringify(pkg.data, null, "\t")}\n`); } } diff --git a/scripts/tool-prompt-usage.ts b/scripts/tool-prompt-usage.ts index 90e289bca..009d9148b 100755 --- a/scripts/tool-prompt-usage.ts +++ b/scripts/tool-prompt-usage.ts @@ -86,7 +86,7 @@ function parseEncoding(value: string | undefined): Encoding { const normalized = value.toLowerCase().replace(/-/g, "_"); if (normalized === "o200k" || normalized === "o200k_base") return Encoding.O200kBase; if (normalized === "cl100k" || normalized === "cl100k_base") return Encoding.Cl100kBase; - throw new Error(`Unknown encoding \"${value}\". Expected o200k_base or cl100k_base.`); + throw new Error(`Unknown encoding "${value}". Expected o200k_base or cl100k_base.`); } function parseCli(): CliOptions | null { @@ -116,7 +116,9 @@ function relativePath(filePath: string): string { async function collectPromptPaths(positionals: readonly string[]): Promise { if (positionals.length === 0) { - const files = await Array.fromAsync(new Bun.Glob("*.md").scan({ cwd: TOOL_PROMPT_DIR, absolute: true, onlyFiles: true })); + const files = await Array.fromAsync( + new Bun.Glob("*.md").scan({ cwd: TOOL_PROMPT_DIR, absolute: true, onlyFiles: true }), + ); return files.sort((a, b) => relativePath(a).localeCompare(relativePath(b))); } @@ -202,8 +204,6 @@ function renderContext(): Record { }; } - - async function estimatePrompt(filePath: string, encoding: Encoding): Promise { const template = await Bun.file(filePath).text(); const rendered = prompt.render(template, renderContext()); @@ -221,7 +221,11 @@ function printTable(estimates: PromptEstimate[], encoding: Encoding): void { const totalTokens = rows.reduce((sum, row) => sum + row.tokens, 0); const totalChars = rows.reduce((sum, row) => sum + row.chars, 0); const totalLines = rows.reduce((sum, row) => sum + row.lines, 0); - const tokenWidth = Math.max("tokens".length, String(totalTokens).length, ...rows.map(row => String(row.tokens).length)); + const tokenWidth = Math.max( + "tokens".length, + String(totalTokens).length, + ...rows.map(row => String(row.tokens).length), + ); const charWidth = Math.max("chars".length, String(totalChars).length, ...rows.map(row => String(row.chars).length)); const lineWidth = Math.max("lines".length, String(totalLines).length, ...rows.map(row => String(row.lines).length)); @@ -237,7 +241,9 @@ function printTable(estimates: PromptEstimate[], encoding: Encoding): void { ); } console.log(`${"-".repeat(tokenWidth)} ${"-".repeat(charWidth)} ${"-".repeat(lineWidth)} ${"-".repeat(6)}`); - console.log(`${String(totalTokens).padStart(tokenWidth)} ${String(totalChars).padStart(charWidth)} ${String(totalLines).padStart(lineWidth)} TOTAL (${rows.length} prompts)`); + console.log( + `${String(totalTokens).padStart(tokenWidth)} ${String(totalChars).padStart(charWidth)} ${String(totalLines).padStart(lineWidth)} TOTAL (${rows.length} prompts)`, + ); } async function run(): Promise { diff --git a/scripts/trace-loader.ts b/scripts/trace-loader.ts index 57f5d79c6..2a5fb6e8e 100755 --- a/scripts/trace-loader.ts +++ b/scripts/trace-loader.ts @@ -10,20 +10,20 @@ Bun.plugin({ name: "trace-loader", setup(build) { // Trace module resolution (doesn't interfere with loading) - build.onResolve({ filter: /.*/ }, (args) => { + build.onResolve({ filter: /.*/ }, args => { // Skip if already traced this path if (resolved.has(args.path)) { return undefined; } resolved.add(args.path); - + const elapsed = ((Bun.nanoseconds() - startTime) / 1e6).toFixed(1); // Only trace local/project files, not node_modules if (!args.path.includes("node_modules") && !args.path.startsWith("node:")) { const shortPath = args.path.replace(process.cwd(), "."); process.stderr.write(`[${elapsed}ms] resolve: ${shortPath}\n`); } - + // Return undefined to let Bun handle resolution normally return undefined; });