diff --git a/biome.json b/biome.json index 0d0f948a9..82a04738e 100644 --- a/biome.json +++ b/biome.json @@ -27,7 +27,16 @@ "rules": { "recommended": true, "correctness": { - "noUnreachable": "off" + "noUnreachable": "off", + "useExhaustiveDependencies": "off" + }, + "a11y": { + "noLabelWithoutControl": "off", + "noStaticElementInteractions": "off", + "noSvgWithoutTitle": "off", + "useAriaPropsSupportedByRole": "off", + "useKeyWithClickEvents": "off", + "useSemanticElements": "off" }, "style": { "noNonNullAssertion": "off", @@ -35,7 +44,9 @@ }, "suspicious": { "noAssignInExpressions": "off", + "noArrayIndexKey": "off", "noControlCharactersInRegex": "off", + "noDocumentCookie": "off", "noDuplicateTestHooks": "off", "noExplicitAny": "off", "noImplicitAnyLet": "off", diff --git a/docs/dev/ADR-021-versioned-documents-and-upgrade-path.md b/docs/dev/ADR-021-versioned-documents-and-upgrade-path.md index 865ae5b7f..8499b1a06 100644 --- a/docs/dev/ADR-021-versioned-documents-and-upgrade-path.md +++ b/docs/dev/ADR-021-versioned-documents-and-upgrade-path.md @@ -15,7 +15,7 @@ is left alone. This is correct for protecting user content but wrong for everything else: -1. **No drift signal.** When SF adds a new template (e.g. `harness/AGENTS.md`, +1. **No drift signal.** When SF adds a new template (e.g. `.sf/harness/AGENTS.md`, `ARCHITECTURE.md`) or improves an existing one (e.g. tightens `RELIABILITY.md`), existing projects never notice. Only newly-bootstrapped projects benefit. @@ -95,7 +95,7 @@ Apply markers to every entry in `SCAFFOLD_FILES`. Categories: |----------|------------------|------------------| | Markdown docs | `AGENTS.md`, `ARCHITECTURE.md`, `docs/RELIABILITY.md`, `docs/SECURITY.md`, `docs/DESIGN.md`, `docs/QUALITY_SCORE.md`, `docs/RECORDS_KEEPER.md`, all `*/AGENTS.md` | HTML comment on line 1 | | Frontmatter docs | `.sf/PREFERENCES.md` | Frontmatter fields: `last_synced_with_sf`, `sf_template_state`, `sf_template_hash` (extends prior art in `preferences-template-upgrade.ts`) | -| Templates / specs | `docs/design-docs/ADR-TEMPLATE.md`, `harness/specs/bootstrap.md`, `harness/AGENTS.md`, `harness/specs/AGENTS.md`, `harness/evals/AGENTS.md`, `harness/graders/AGENTS.md` | HTML comment on line 1 | +| Templates / specs | `docs/design-docs/ADR-TEMPLATE.md`, `.sf/harness/specs/bootstrap.md`, `.sf/harness/AGENTS.md`, `.sf/harness/specs/AGENTS.md`, `.sf/harness/evals/AGENTS.md`, `.sf/harness/graders/AGENTS.md` | HTML comment on line 1 | | Reference slot text files | `docs/references/*-llms.txt` | HTML comment on line 1 (Markdown comments are valid in plain text consumed by LLMs) | | `.siftignore` and similar non-Markdown configs | `.siftignore` | Skip versioning. Sibling file `.sf/scaffold-manifest.json` records the applied version. (Rationale: hash-based legacy match is sufficient; markers in dotfiles fight tooling.) | diff --git a/harness/specs/bootstrap.md b/harness/specs/bootstrap.md deleted file mode 100644 index 4bf4c1a25..000000000 --- a/harness/specs/bootstrap.md +++ /dev/null @@ -1,20 +0,0 @@ -# Bootstrap Spec: Agent Legibility - -Verifies that the SF repo is minimally agent-legible. - -## Criteria - -- [ ] `AGENTS.md` exists at repo root and is non-empty. -- [ ] `ARCHITECTURE.md` exists at repo root and describes the system. -- [ ] `docs/exec-plans/active/index.md` exists. -- [ ] `docs/exec-plans/tech-debt-tracker.md` exists. -- [ ] `docs/design-docs/ADR-TEMPLATE.md` exists. -- [ ] `harness/specs/` exists with at least this file. - -## Verification command - -```bash -for f in AGENTS.md ARCHITECTURE.md docs/exec-plans/active/index.md docs/exec-plans/tech-debt-tracker.md docs/design-docs/ADR-TEMPLATE.md harness/specs/bootstrap.md; do [ -s "$f" ] && echo "OK: $f" || echo "MISSING: $f"; done -``` - -All lines should start with `OK:` for this spec to pass. diff --git a/justfile b/justfile index 91c9dddcd..ab13ceade 100644 --- a/justfile +++ b/justfile @@ -72,11 +72,12 @@ spec name: printf "# {{name}}\n\n## Job to be done\n\n## Workflow\n\n## Edge cases\n\n## Non-goals\n\n## Verification\n\n\`\`\`bash\n# command that proves this spec passes\n\`\`\`\n" > "${dest}" echo "Created: ${dest}" -# Create a new harness spec (usage: just harness-spec "behavior-name") +# Create a new SF-local harness spec (usage: just harness-spec "behavior-name") harness-spec name: #!/usr/bin/env bash set -euo pipefail - dest="harness/specs/{{name}}.md" + dest=".sf/harness/specs/{{name}}.md" if [ -f "${dest}" ]; then echo "Already exists: ${dest}"; exit 1; fi + mkdir -p "$(dirname "${dest}")" printf "# Harness Spec: {{name}}\n\n## Behavior\n\n## Verification command\n\n\`\`\`bash\n\n\`\`\`\n\n## Pass criteria\n\n" > "${dest}" echo "Created: ${dest}" diff --git a/package.json b/package.json index 4360b8620..9a394dc9c 100644 --- a/package.json +++ b/package.json @@ -98,8 +98,10 @@ "typecheck:extensions": "npm run check:versioned-json && tsc --noEmit --project tsconfig.extensions.json", "check:sf-inventory": "node scripts/check-sf-extension-inventory.mjs", "check:versioned-json": "node scripts/check-versioned-json.mjs && npm run check:sf-inventory", - "lint": "npm run check:versioned-json && biome lint src/", - "lint:fix": "npm run check:versioned-json && biome lint src/ --write", + "format": "biome format --write .", + "format:check": "biome format .", + "lint": "npm run check:versioned-json && biome check .", + "lint:fix": "npm run check:versioned-json && biome check --write .", "pipeline:version-stamp": "node scripts/version-stamp.mjs", "release:changelog": "node scripts/generate-changelog.mjs", "release:bump": "node scripts/bump-version.mjs", diff --git a/packages/daemon/src/channel-manager.ts b/packages/daemon/src/channel-manager.ts index 6f90b32a6..90f19b137 100644 --- a/packages/daemon/src/channel-manager.ts +++ b/packages/daemon/src/channel-manager.ts @@ -6,12 +6,12 @@ */ import { - ChannelType, - PermissionFlagsBits, - type Guild, type CategoryChannel, - type TextChannel, + ChannelType, + type Guild, type GuildBasedChannel, + PermissionFlagsBits, + type TextChannel, } from "discord.js"; import type { Logger } from "./logger.js"; diff --git a/packages/daemon/src/cli-main.ts b/packages/daemon/src/cli-main.ts index dd3b06242..40ee15b60 100644 --- a/packages/daemon/src/cli-main.ts +++ b/packages/daemon/src/cli-main.ts @@ -1,9 +1,9 @@ -import { parseArgs } from "node:util"; import { resolve } from "node:path"; -import { resolveConfigPath, loadConfig } from "./config.js"; -import { Logger } from "./logger.js"; +import { parseArgs } from "node:util"; +import { loadConfig, resolveConfigPath } from "./config.js"; import { Daemon } from "./daemon.js"; -import { install, uninstall, status } from "./launchd.js"; +import { install, status, uninstall } from "./launchd.js"; +import { Logger } from "./logger.js"; export const COMMAND_NAME = "sf-server"; diff --git a/packages/daemon/src/commands.ts b/packages/daemon/src/commands.ts index 51802c26e..312514661 100644 --- a/packages/daemon/src/commands.ts +++ b/packages/daemon/src/commands.ts @@ -6,13 +6,13 @@ */ import { - SlashCommandBuilder, - REST, - Routes, + type REST, type RESTPostAPIChatInputApplicationCommandsJSONBody, + Routes, + SlashCommandBuilder, } from "discord.js"; -import type { ManagedSession } from "./types.js"; import type { Logger } from "./logger.js"; +import type { ManagedSession } from "./types.js"; // --------------------------------------------------------------------------- // Command definitions diff --git a/packages/daemon/src/config.ts b/packages/daemon/src/config.ts index e1ec87d58..c257efcb5 100644 --- a/packages/daemon/src/config.ts +++ b/packages/daemon/src/config.ts @@ -1,4 +1,4 @@ -import { readFileSync, existsSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { resolve } from "node:path"; import { parse as parseYaml } from "yaml"; @@ -54,7 +54,7 @@ export function validateConfig(raw: unknown): DaemonConfig { const obj = raw as Record; // --- discord --- - let discord: DaemonConfig["discord"] = undefined; + let discord: DaemonConfig["discord"]; if (obj["discord"] != null && typeof obj["discord"] === "object") { const d = obj["discord"] as Record; discord = { diff --git a/packages/daemon/src/daemon.test.ts b/packages/daemon/src/daemon.test.ts index 74e7d9670..61c22edf2 100644 --- a/packages/daemon/src/daemon.test.ts +++ b/packages/daemon/src/daemon.test.ts @@ -1,22 +1,21 @@ -import { describe, it, afterEach, beforeAll, afterAll } from "vitest"; import assert from "node:assert/strict"; +import { execFileSync, spawn } from "node:child_process"; +import { randomUUID } from "node:crypto"; import { - mkdtempSync, - writeFileSync, - readFileSync, - rmSync, existsSync, mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, } from "node:fs"; -import { join } from "node:path"; -import { tmpdir, homedir } from "node:os"; -import { randomUUID } from "node:crypto"; -import { execFileSync, spawn } from "node:child_process"; +import { homedir, tmpdir } from "node:os"; +import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { dirname } from "node:path"; -import { resolveConfigPath, loadConfig, validateConfig } from "./config.js"; -import { Logger } from "./logger.js"; +import { afterAll, afterEach, beforeAll, describe, it } from "vitest"; +import { loadConfig, resolveConfigPath, validateConfig } from "./config.js"; import { Daemon } from "./daemon.js"; +import { Logger } from "./logger.js"; import { SessionManager } from "./session-manager.js"; import type { DaemonConfig, LogEntry } from "./types.js"; diff --git a/packages/daemon/src/daemon.ts b/packages/daemon/src/daemon.ts index 042b65656..ce47a3300 100644 --- a/packages/daemon/src/daemon.ts +++ b/packages/daemon/src/daemon.ts @@ -1,10 +1,10 @@ -import type { DaemonConfig, ProjectInfo } from "./types.js"; -import type { Logger } from "./logger.js"; -import { SessionManager } from "./session-manager.js"; -import { scanForProjects } from "./project-scanner.js"; import { DiscordBot, validateDiscordConfig } from "./discord-bot.js"; import { EventBridge } from "./event-bridge.js"; +import type { Logger } from "./logger.js"; import { Orchestrator } from "./orchestrator.js"; +import { scanForProjects } from "./project-scanner.js"; +import { SessionManager } from "./session-manager.js"; +import type { DaemonConfig, ProjectInfo } from "./types.js"; /** * Core daemon class — ties config + logger together with lifecycle management. diff --git a/packages/daemon/src/discord-bot.test.ts b/packages/daemon/src/discord-bot.test.ts index 66efde971..baac07af5 100644 --- a/packages/daemon/src/discord-bot.test.ts +++ b/packages/daemon/src/discord-bot.test.ts @@ -1,17 +1,17 @@ -import { describe, it, afterEach } from "vitest"; import assert from "node:assert/strict"; -import { mkdtempSync, readFileSync, rmSync, existsSync } from "node:fs"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; import { randomUUID } from "node:crypto"; +import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { ChannelType } from "discord.js"; -import { isAuthorized, validateDiscordConfig } from "./discord-bot.js"; -import { sanitizeChannelName, ChannelManager } from "./channel-manager.js"; +import { afterEach, describe, it } from "vitest"; +import { ChannelManager, sanitizeChannelName } from "./channel-manager.js"; import { buildCommands, formatSessionStatus } from "./commands.js"; -import { Daemon } from "./daemon.js"; -import { Logger } from "./logger.js"; import { validateConfig } from "./config.js"; -import type { DaemonConfig, LogEntry, ManagedSession } from "./types.js"; +import { Daemon } from "./daemon.js"; +import { isAuthorized, validateDiscordConfig } from "./discord-bot.js"; +import { Logger } from "./logger.js"; +import type { DaemonConfig, ManagedSession } from "./types.js"; // ---------- helpers ---------- @@ -315,7 +315,7 @@ describe("ChannelManager", () => { name: string; type: number; parentId: string | null; - edit?: Function; + edit?: (editOpts: { parent?: string }) => Promise; } >(); let createCounter = 0; @@ -592,7 +592,7 @@ describe("formatSessionStatus", () => { describe("command dispatch", () => { // Minimal mock of a ChatInputCommandInteraction - function mockInteraction(commandName: string, userId: string = "owner-1") { + function _mockInteraction(commandName: string, userId: string = "owner-1") { let replied = false; let replyContent = ""; @@ -611,8 +611,8 @@ describe("command dispatch", () => { } // Minimal mock of a non-command interaction - function mockNonCommandInteraction(userId: string = "owner-1") { - let replied = false; + function _mockNonCommandInteraction(userId: string = "owner-1") { + const replied = false; return { user: { id: userId }, type: 3, // InteractionType.MessageComponent diff --git a/packages/daemon/src/discord-bot.ts b/packages/daemon/src/discord-bot.ts index 9759df2d5..df4cc53e1 100644 --- a/packages/daemon/src/discord-bot.ts +++ b/packages/daemon/src/discord-bot.ts @@ -7,26 +7,25 @@ */ import { + ActionRowBuilder, Client, + ComponentType, GatewayIntentBits, + type Interaction, REST, StringSelectMenuBuilder, - ActionRowBuilder, - ComponentType, - type Interaction, - type Guild, type StringSelectMenuInteraction, } from "discord.js"; -import type { DaemonConfig, VerbosityLevel, ProjectInfo } from "./types.js"; -import type { Logger } from "./logger.js"; -import type { SessionManager } from "./session-manager.js"; import { ChannelManager } from "./channel-manager.js"; import { buildCommands, - registerGuildCommands, formatSessionStatus, + registerGuildCommands, } from "./commands.js"; import type { EventBridge } from "./event-bridge.js"; +import type { Logger } from "./logger.js"; +import type { SessionManager } from "./session-manager.js"; +import type { DaemonConfig, ProjectInfo, VerbosityLevel } from "./types.js"; // --------------------------------------------------------------------------- // Pure helpers — exported for testability diff --git a/packages/daemon/src/event-bridge.test.ts b/packages/daemon/src/event-bridge.test.ts index 07d292d2d..66eba120c 100644 --- a/packages/daemon/src/event-bridge.test.ts +++ b/packages/daemon/src/event-bridge.test.ts @@ -6,22 +6,22 @@ * blocker handling, conversation relay, and cleanup. */ -import { vi, describe, it } from "vitest"; import assert from "node:assert/strict"; import { EventEmitter } from "node:events"; -import { EventBridge } from "./event-bridge.js"; -import type { EventBridgeOptions, BridgeClient } from "./event-bridge.js"; import type { - PendingBlocker, - ManagedSession, - DaemonConfig, - SessionStatus, -} from "./types.js"; -import type { - SdkAgentEvent, RpcClient, RpcExtensionUIRequest, + SdkAgentEvent, } from "@singularity-forge/rpc-client"; +import { describe, it, vi } from "vitest"; +import type { BridgeClient, EventBridgeOptions } from "./event-bridge.js"; +import { EventBridge } from "./event-bridge.js"; +import type { + DaemonConfig, + ManagedSession, + PendingBlocker, + SessionStatus, +} from "./types.js"; // --------------------------------------------------------------------------- // Mock factories @@ -263,7 +263,7 @@ describe("EventBridge", () => { }); it("filters events based on verbosity", async () => { - const { bridge, sessionManager, channelManager, logger } = buildBridge(); + const { bridge, sessionManager, logger } = buildBridge(); bridge.start(); sessionManager.emit("session:started", { sessionId: "sess-1", @@ -470,7 +470,7 @@ describe("EventBridge", () => { }); await tick(); - const collectorCalls = mockFn( + const _collectorCalls = mockFn( channelManager._channel.createMessageComponentCollector, ).mock.calls; const collector = mockFn( @@ -518,7 +518,7 @@ describe("EventBridge", () => { }); await tick(); - const collectorCalls = mockFn( + const _collectorCalls = mockFn( channelManager._channel.createMessageComponentCollector, ).mock.calls; const collector = mockFn( diff --git a/packages/daemon/src/event-bridge.ts b/packages/daemon/src/event-bridge.ts index 79ad37a49..eb4d36de6 100644 --- a/packages/daemon/src/event-bridge.ts +++ b/packages/daemon/src/event-bridge.ts @@ -10,28 +10,27 @@ * - DM backup → owner gets DM on blocker when dm_on_blocker configured */ -import type { - Client, - Message, - TextChannel, - MessageComponentInteraction, -} from "discord.js"; -import { EmbedBuilder, ComponentType } from "discord.js"; import type { SdkAgentEvent } from "@singularity-forge/rpc-client"; -import type { Logger } from "./logger.js"; -import type { DaemonConfig, PendingBlocker } from "./types.js"; -import type { SessionManager } from "./session-manager.js"; +import type { + Message, + MessageComponentInteraction, + TextChannel, +} from "discord.js"; +import { ComponentType, EmbedBuilder } from "discord.js"; import type { ChannelManager } from "./channel-manager.js"; -import { MessageBatcher } from "./message-batcher.js"; -import { VerbosityManager } from "./verbosity.js"; -import { - formatEvent, - formatBlocker, - formatSessionStarted, - formatError, - formatCompletion, -} from "./event-formatter.js"; import { isAuthorized } from "./discord-bot.js"; +import { + formatBlocker, + formatCompletion, + formatError, + formatEvent, + formatSessionStarted, +} from "./event-formatter.js"; +import type { Logger } from "./logger.js"; +import { MessageBatcher } from "./message-batcher.js"; +import type { SessionManager } from "./session-manager.js"; +import type { DaemonConfig, PendingBlocker } from "./types.js"; +import { VerbosityManager } from "./verbosity.js"; // --------------------------------------------------------------------------- // Types diff --git a/packages/daemon/src/event-formatter.test.ts b/packages/daemon/src/event-formatter.test.ts index 92fd5ceed..f42a2731b 100644 --- a/packages/daemon/src/event-formatter.test.ts +++ b/packages/daemon/src/event-formatter.test.ts @@ -1,22 +1,20 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; -import { EmbedBuilder, ActionRowBuilder, ButtonBuilder } from "discord.js"; import type { SdkAgentEvent } from "@singularity-forge/rpc-client"; -import type { PendingBlocker, FormattedEvent } from "./types.js"; -import type { RpcExtensionUIRequest } from "@singularity-forge/rpc-client"; +import { describe, it } from "vitest"; import { - formatToolStart, - formatToolEnd, - formatMessage, formatBlocker, formatCompletion, - formatError, formatCostUpdate, + formatError, + formatEvent, + formatGenericEvent, + formatMessage, formatSessionStarted, formatTaskTransition, - formatGenericEvent, - formatEvent, + formatToolEnd, + formatToolStart, } from "./event-formatter.js"; +import type { FormattedEvent, PendingBlocker } from "./types.js"; // --------------------------------------------------------------------------- // Helpers diff --git a/packages/daemon/src/event-formatter.ts b/packages/daemon/src/event-formatter.ts index f8642497e..7ae244b03 100644 --- a/packages/daemon/src/event-formatter.ts +++ b/packages/daemon/src/event-formatter.ts @@ -10,14 +10,13 @@ * grey = tool / generic */ +import type { SdkAgentEvent } from "@singularity-forge/rpc-client"; import { - EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, + EmbedBuilder, } from "discord.js"; -import type { SdkAgentEvent } from "@singularity-forge/rpc-client"; -import type { RpcExtensionUIRequest } from "@singularity-forge/rpc-client"; import type { FormattedEvent, PendingBlocker } from "./types.js"; // --------------------------------------------------------------------------- @@ -190,7 +189,7 @@ export function formatBlocker( const chunks = chunkArray(options.slice(0, 25), 5); for (const chunk of chunks) { const row = new ActionRowBuilder(); - chunk.forEach((opt, i) => { + chunk.forEach((opt, _i) => { const globalIndex = options.indexOf(opt); row.addComponents( new ButtonBuilder() @@ -414,7 +413,7 @@ export function formatGenericEvent(event: SdkAgentEvent): FormattedEvent { */ export function formatEvent( event: SdkAgentEvent, - ownerId?: string, + _ownerId?: string, ): FormattedEvent { const type = str(event.type); diff --git a/packages/daemon/src/index.ts b/packages/daemon/src/index.ts index 6a3331ceb..00a7b87e9 100644 --- a/packages/daemon/src/index.ts +++ b/packages/daemon/src/index.ts @@ -1,72 +1,72 @@ -export type { - DaemonConfig, - LogLevel, - LogEntry, - SessionStatus, - ManagedSession, - PendingBlocker, - CostAccumulator, - ProjectInfo, - ProjectMarker, - StartSessionOptions, - FormattedEvent, - VerbosityLevel, -} from "./types.js"; -export { MAX_EVENTS, INIT_TIMEOUT_MS } from "./types.js"; -export { resolveConfigPath, loadConfig, validateConfig } from "./config.js"; -export { Logger } from "./logger.js"; -export type { LoggerOptions } from "./logger.js"; -export { Daemon } from "./daemon.js"; -export { scanForProjects } from "./project-scanner.js"; -export { SessionManager } from "./session-manager.js"; -export { - DiscordBot, - isAuthorized, - validateDiscordConfig, -} from "./discord-bot.js"; -export type { DiscordBotOptions } from "./discord-bot.js"; -export { ChannelManager, sanitizeChannelName } from "./channel-manager.js"; export type { ChannelManagerOptions } from "./channel-manager.js"; +export { ChannelManager, sanitizeChannelName } from "./channel-manager.js"; export { buildCommands, formatSessionStatus, registerGuildCommands, } from "./commands.js"; -export { EventBridge } from "./event-bridge.js"; -export type { BridgeClient, EventBridgeOptions } from "./event-bridge.js"; -export { Orchestrator } from "./orchestrator.js"; -export type { - OrchestratorConfig, - OrchestratorDeps, - DiscordMessageLike, -} from "./orchestrator.js"; -export { MessageBatcher } from "./message-batcher.js"; -export type { - SendPayload, - SendFn, - BatcherLogger, - BatcherOptions, -} from "./message-batcher.js"; -export { VerbosityManager, shouldShowAtLevel } from "./verbosity.js"; +export { loadConfig, resolveConfigPath, validateConfig } from "./config.js"; +export { Daemon } from "./daemon.js"; +export type { DiscordBotOptions } from "./discord-bot.js"; +export { + DiscordBot, + isAuthorized, + validateDiscordConfig, +} from "./discord-bot.js"; +export type { BridgeClient, EventBridgeOptions } from "./event-bridge.js"; +export { EventBridge } from "./event-bridge.js"; export { - formatToolStart, - formatToolEnd, - formatMessage, formatBlocker, formatCompletion, - formatError, formatCostUpdate, + formatError, + formatEvent, + formatGenericEvent, + formatMessage, formatSessionStarted, formatTaskTransition, - formatGenericEvent, - formatEvent, + formatToolEnd, + formatToolStart, } from "./event-formatter.js"; +export type { LaunchdStatus, PlistOptions, RunCommandFn } from "./launchd.js"; export { escapeXml, generatePlist, getPlistPath, install as installLaunchAgent, - uninstall as uninstallLaunchAgent, status as launchAgentStatus, + uninstall as uninstallLaunchAgent, } from "./launchd.js"; -export type { PlistOptions, LaunchdStatus, RunCommandFn } from "./launchd.js"; +export type { LoggerOptions } from "./logger.js"; +export { Logger } from "./logger.js"; +export type { + BatcherLogger, + BatcherOptions, + SendFn, + SendPayload, +} from "./message-batcher.js"; +export { MessageBatcher } from "./message-batcher.js"; +export type { + DiscordMessageLike, + OrchestratorConfig, + OrchestratorDeps, +} from "./orchestrator.js"; +export { Orchestrator } from "./orchestrator.js"; +export { scanForProjects } from "./project-scanner.js"; +export { SessionManager } from "./session-manager.js"; +export type { + CostAccumulator, + DaemonConfig, + FormattedEvent, + LogEntry, + LogLevel, + ManagedSession, + PendingBlocker, + ProjectInfo, + ProjectMarker, + SessionStatus, + StartSessionOptions, + VerbosityLevel, +} from "./types.js"; +export { INIT_TIMEOUT_MS, MAX_EVENTS } from "./types.js"; +export { shouldShowAtLevel, VerbosityManager } from "./verbosity.js"; diff --git a/packages/daemon/src/launchd.test.ts b/packages/daemon/src/launchd.test.ts index 7c4bc8d9e..12675175d 100644 --- a/packages/daemon/src/launchd.test.ts +++ b/packages/daemon/src/launchd.test.ts @@ -1,30 +1,22 @@ -import { describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; -import { - mkdtempSync, - existsSync, - readFileSync, - writeFileSync, - rmSync, - mkdirSync, - statSync, -} from "node:fs"; -import { join, dirname } from "node:path"; -import { tmpdir, homedir } from "node:os"; import { randomUUID } from "node:crypto"; +import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { homedir, tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, it } from "vitest"; +import type { PlistOptions, RunCommandFn } from "./launchd.js"; import { escapeXml, generatePlist, getPlistPath, install, - uninstall, status, + uninstall, } from "./launchd.js"; -import type { PlistOptions, RunCommandFn, LaunchdStatus } from "./launchd.js"; // ---------- helpers ---------- -function tmpDir(): string { +function _tmpDir(): string { return mkdtempSync( join(tmpdir(), `launchd-test-${randomUUID().slice(0, 8)}-`), ); @@ -184,8 +176,8 @@ describe("getPlistPath", () => { // ---------- install ---------- describe("install", () => { - let tmp: string; - let fakePlistPath: string; + let _tmp: string; + let _fakePlistPath: string; // We can't mock getPlistPath directly, but we can verify the commands // issued and the plist content by intercepting runCommand and filesystem ops. @@ -209,7 +201,7 @@ describe("install", () => { } const loadCalls = calls.filter((c) => c.startsWith("launchctl load")); - const listCalls = calls.filter((c) => c.startsWith("launchctl list")); + const _listCalls = calls.filter((c) => c.startsWith("launchctl list")); // Should have at least attempted launchctl load assert.ok( loadCalls.length > 0 || calls.length > 0, @@ -243,7 +235,7 @@ describe("install", () => { } // The second install should have tried to unload first - const unloadCalls = calls.filter((c) => c.startsWith("launchctl unload")); + const _unloadCalls = calls.filter((c) => c.startsWith("launchctl unload")); // If the plist path exists, we expect at least one unload attempt on second call // This is a command-level check; filesystem existence depends on environment }); diff --git a/packages/daemon/src/launchd.ts b/packages/daemon/src/launchd.ts index bc08eab69..53544e22b 100644 --- a/packages/daemon/src/launchd.ts +++ b/packages/daemon/src/launchd.ts @@ -1,14 +1,13 @@ -import { - writeFileSync, - unlinkSync, - existsSync, - chmodSync, - mkdirSync, -} from "node:fs"; -import { resolve } from "node:path"; -import { homedir } from "node:os"; import { execSync } from "node:child_process"; -import { dirname } from "node:path"; +import { + chmodSync, + existsSync, + mkdirSync, + unlinkSync, + writeFileSync, +} from "node:fs"; +import { homedir } from "node:os"; +import { dirname, resolve } from "node:path"; // --------------- types --------------- diff --git a/packages/daemon/src/logger.ts b/packages/daemon/src/logger.ts index c78e1521f..58145aa88 100644 --- a/packages/daemon/src/logger.ts +++ b/packages/daemon/src/logger.ts @@ -1,6 +1,6 @@ import { createWriteStream, mkdirSync, type WriteStream } from "node:fs"; import { dirname } from "node:path"; -import type { LogLevel, LogEntry } from "./types.js"; +import type { LogEntry, LogLevel } from "./types.js"; const LEVEL_ORDER: Record = { debug: 0, diff --git a/packages/daemon/src/message-batcher.test.ts b/packages/daemon/src/message-batcher.test.ts index ce2cf6793..40457fee4 100644 --- a/packages/daemon/src/message-batcher.test.ts +++ b/packages/daemon/src/message-batcher.test.ts @@ -1,7 +1,7 @@ -import { vi, describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; +import { describe, it, vi } from "vitest"; +import type { BatcherLogger, SendPayload } from "./message-batcher.js"; import { MessageBatcher } from "./message-batcher.js"; -import type { SendPayload, BatcherLogger } from "./message-batcher.js"; import type { FormattedEvent } from "./types.js"; // --------------------------------------------------------------------------- @@ -265,9 +265,9 @@ describe("MessageBatcher", () => { describe("error handling", () => { it("logs error and continues when send throws", async () => { - let attempt = 0; + let _attempt = 0; const sendFn = async () => { - attempt++; + _attempt++; throw new Error("Discord rate limit"); }; const { logger, errors, warns } = createLogger(); diff --git a/packages/daemon/src/orchestrator.test.ts b/packages/daemon/src/orchestrator.test.ts index f8b94d5a8..2f27f3d94 100644 --- a/packages/daemon/src/orchestrator.test.ts +++ b/packages/daemon/src/orchestrator.test.ts @@ -5,25 +5,20 @@ * allowing tool execution and conversation flow testing without real API calls. */ -import { describe, it, afterEach } from "vitest"; import assert from "node:assert/strict"; -import { mkdtempSync, rmSync, existsSync } from "node:fs"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; import { randomUUID } from "node:crypto"; +import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, it } from "vitest"; +import { Logger } from "./logger.js"; import { + type DiscordMessageLike, Orchestrator, type OrchestratorConfig, type OrchestratorDeps, - type DiscordMessageLike, } from "./orchestrator.js"; -import { Logger } from "./logger.js"; -import type { - ManagedSession, - ProjectInfo, - SessionStatus, - CostAccumulator, -} from "./types.js"; +import type { ManagedSession, ProjectInfo, SessionStatus } from "./types.js"; // --------------------------------------------------------------------------- // Helpers diff --git a/packages/daemon/src/orchestrator.ts b/packages/daemon/src/orchestrator.ts index 497791ce5..157189659 100644 --- a/packages/daemon/src/orchestrator.ts +++ b/packages/daemon/src/orchestrator.ts @@ -11,20 +11,20 @@ * at the tool execution layer. */ -import { z } from "zod"; import type Anthropic from "@anthropic-ai/sdk"; import type { - MessageParam, ContentBlockParam, + MessageParam, + TextBlock, Tool, ToolResultBlockParam, ToolUseBlock, - TextBlock, } from "@anthropic-ai/sdk/resources/messages/messages"; -import type { SessionManager } from "./session-manager.js"; +import { z } from "zod"; import type { ChannelManager } from "./channel-manager.js"; -import type { ProjectInfo, ManagedSession } from "./types.js"; import type { Logger } from "./logger.js"; +import type { SessionManager } from "./session-manager.js"; +import type { ManagedSession, ProjectInfo } from "./types.js"; // --------------------------------------------------------------------------- // API key resolution — requires ANTHROPIC_API_KEY env var diff --git a/packages/daemon/src/project-scanner.test.ts b/packages/daemon/src/project-scanner.test.ts index f98b3bd12..c1310ea31 100644 --- a/packages/daemon/src/project-scanner.test.ts +++ b/packages/daemon/src/project-scanner.test.ts @@ -2,19 +2,19 @@ * Tests for the project scanner module. */ -import { describe, it, afterEach } from "vitest"; import assert from "node:assert/strict"; -import { - mkdtempSync, - mkdirSync, - writeFileSync, - rmSync, - existsSync, - chmodSync, -} from "node:fs"; -import { join } from "node:path"; -import { tmpdir, platform } from "node:os"; import { randomUUID } from "node:crypto"; +import { + chmodSync, + existsSync, + mkdirSync, + mkdtempSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { platform, tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, it } from "vitest"; import { scanForProjects } from "./project-scanner.js"; // ---------- helpers ---------- diff --git a/packages/daemon/src/project-scanner.ts b/packages/daemon/src/project-scanner.ts index 8dac6d1f3..dee16600f 100644 --- a/packages/daemon/src/project-scanner.ts +++ b/packages/daemon/src/project-scanner.ts @@ -3,8 +3,8 @@ * marker files/directories. Reads one level deep (immediate children only). */ -import { readdir, stat, access } from "node:fs/promises"; -import { join, basename } from "node:path"; +import { readdir, stat } from "node:fs/promises"; +import { basename, join } from "node:path"; import type { ProjectInfo, ProjectMarker } from "./types.js"; // --------------------------------------------------------------------------- diff --git a/packages/daemon/src/session-manager.test.ts b/packages/daemon/src/session-manager.test.ts index 4a51c9179..da6af8774 100644 --- a/packages/daemon/src/session-manager.test.ts +++ b/packages/daemon/src/session-manager.test.ts @@ -6,17 +6,15 @@ * and cleanup without spawning real SF processes. */ -import { describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; -import { resolve, basename } from "node:path"; -import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs"; +import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; -import { join } from "node:path"; - -import { SessionManager } from "./session-manager.js"; -import { MAX_EVENTS } from "./types.js"; -import type { ManagedSession, PendingBlocker } from "./types.js"; +import { basename, join, resolve } from "node:path"; +import { afterEach, describe, it } from "vitest"; import { Logger } from "./logger.js"; +import { SessionManager } from "./session-manager.js"; +import type { ManagedSession, PendingBlocker } from "./types.js"; +import { MAX_EVENTS } from "./types.js"; // --------------------------------------------------------------------------- // Mock RpcClient (duck-typed to match RpcClient interface) @@ -843,7 +841,7 @@ describe("SessionManager", () => { it("logger receives structured calls during lifecycle", async () => { const { manager, spy } = createManager(); - const sessionId = await manager.startSession({ + const _sessionId = await manager.startSession({ projectDir: "/tmp/log-test", }); diff --git a/packages/daemon/src/session-manager.ts b/packages/daemon/src/session-manager.ts index 14f08cefd..f84bb9223 100644 --- a/packages/daemon/src/session-manager.ts +++ b/packages/daemon/src/session-manager.ts @@ -14,23 +14,23 @@ */ import { execSync } from "node:child_process"; -import { basename, resolve } from "node:path"; import { EventEmitter } from "node:events"; -import { RpcClient } from "@singularity-forge/rpc-client"; +import { basename, resolve } from "node:path"; import type { - SdkAgentEvent, - RpcInitResult, RpcCostUpdateEvent, RpcExtensionUIRequest, + RpcInitResult, + SdkAgentEvent, } from "@singularity-forge/rpc-client"; +import { RpcClient } from "@singularity-forge/rpc-client"; +import type { Logger } from "./logger.js"; import type { ManagedSession, - StartSessionOptions, PendingBlocker, RuntimeHeartbeat, + StartSessionOptions, } from "./types.js"; -import { MAX_EVENTS, INIT_TIMEOUT_MS } from "./types.js"; -import type { Logger } from "./logger.js"; +import { INIT_TIMEOUT_MS, MAX_EVENTS } from "./types.js"; // --------------------------------------------------------------------------- // Inlined detection logic (from headless-events.ts — no internal package imports) diff --git a/packages/daemon/src/types.ts b/packages/daemon/src/types.ts index 3590525ee..b9f8c638b 100644 --- a/packages/daemon/src/types.ts +++ b/packages/daemon/src/types.ts @@ -1,7 +1,7 @@ import type { RpcClient, - SdkAgentEvent, RpcExtensionUIRequest, + SdkAgentEvent, } from "@singularity-forge/rpc-client"; /** diff --git a/packages/daemon/src/verbosity.test.ts b/packages/daemon/src/verbosity.test.ts index 45b880030..8d9d2c1da 100644 --- a/packages/daemon/src/verbosity.test.ts +++ b/packages/daemon/src/verbosity.test.ts @@ -1,6 +1,6 @@ -import { describe, it, beforeEach } from "vitest"; import assert from "node:assert/strict"; -import { VerbosityManager, shouldShowAtLevel } from "./verbosity.js"; +import { beforeEach, describe, it } from "vitest"; +import { shouldShowAtLevel, VerbosityManager } from "./verbosity.js"; // --------------------------------------------------------------------------- // VerbosityManager diff --git a/packages/daemon/src/verbosity.ts b/packages/daemon/src/verbosity.ts index 7b2a852f4..8bc31b53c 100644 --- a/packages/daemon/src/verbosity.ts +++ b/packages/daemon/src/verbosity.ts @@ -34,7 +34,7 @@ const DEFAULT_SHOWN: ReadonlySet = new Set([ ]); /** Event types shown only at verbose level. */ -const VERBOSE_ONLY: ReadonlySet = new Set([ +const _VERBOSE_ONLY: ReadonlySet = new Set([ "cost_update", "state_update", "status", diff --git a/packages/mcp-server/src/cli.ts b/packages/mcp-server/src/cli.ts index 17e11ebf7..798fdd3b4 100644 --- a/packages/mcp-server/src/cli.ts +++ b/packages/mcp-server/src/cli.ts @@ -5,8 +5,8 @@ * Cursor, and other MCP-compatible clients. */ -import { SessionManager } from "./session-manager.js"; import { createMcpServer } from "./server.js"; +import { SessionManager } from "./session-manager.js"; import { loadStoredCredentialEnvKeys } from "./tool-credentials.js"; const MCP_PKG = "@modelcontextprotocol/sdk"; diff --git a/packages/mcp-server/src/coerce-string-arrays.test.ts b/packages/mcp-server/src/coerce-string-arrays.test.ts index 50bca996f..d3525855e 100644 --- a/packages/mcp-server/src/coerce-string-arrays.test.ts +++ b/packages/mcp-server/src/coerce-string-arrays.test.ts @@ -1,5 +1,5 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { z } from "zod"; import { validateToolArguments } from "../../pi-ai/src/utils/validation.ts"; diff --git a/packages/mcp-server/src/env-writer.test.ts b/packages/mcp-server/src/env-writer.test.ts index 067cd29be..a7a1d2af2 100644 --- a/packages/mcp-server/src/env-writer.test.ts +++ b/packages/mcp-server/src/env-writer.test.ts @@ -1,29 +1,29 @@ // @singularity-forge/mcp-server — Tests for env-writer utilities // Copyright (c) 2026 Jeremy McSpadden -import { describe, it, afterEach } from "vitest"; import assert from "node:assert/strict"; import { - mkdtempSync, mkdirSync, - rmSync, - writeFileSync, + mkdtempSync, readFileSync, realpathSync, + rmSync, symlinkSync, + writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { afterEach, describe, it } from "vitest"; import { + applySecrets, checkExistingEnvKeys, detectDestination, - writeEnvKey, - applySecrets, isSafeEnvVarKey, isSupportedDeploymentEnvironment, resolveProjectEnvFilePath, shellEscapeSingle, + writeEnvKey, } from "./env-writer.js"; function makeTempDir(prefix: string): string { diff --git a/packages/mcp-server/src/env-writer.ts b/packages/mcp-server/src/env-writer.ts index 73a52e550..ff3c296c2 100644 --- a/packages/mcp-server/src/env-writer.ts +++ b/packages/mcp-server/src/env-writer.ts @@ -5,7 +5,6 @@ // destinations, and checking existing keys. Used by secure_env_collect // MCP tool. No TUI dependencies — pure filesystem + process.env operations. -import { open, readFile, rename, rm } from "node:fs/promises"; import { constants, existsSync, @@ -13,6 +12,7 @@ import { realpathSync, statSync, } from "node:fs"; +import { open, readFile, rename, rm } from "node:fs/promises"; import { basename, dirname, diff --git a/packages/mcp-server/src/import-candidates.test.ts b/packages/mcp-server/src/import-candidates.test.ts index a6e3cd46d..81265d18a 100644 --- a/packages/mcp-server/src/import-candidates.test.ts +++ b/packages/mcp-server/src/import-candidates.test.ts @@ -1,6 +1,7 @@ // SF — Regression tests for importLocalModule candidate resolution (#3954) -import { describe, it } from "vitest"; + import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { _buildImportCandidates } from "./workflow-tools.js"; diff --git a/packages/mcp-server/src/mcp-server.test.ts b/packages/mcp-server/src/mcp-server.test.ts index 7ad9bee20..ace4f71b6 100644 --- a/packages/mcp-server/src/mcp-server.test.ts +++ b/packages/mcp-server/src/mcp-server.test.ts @@ -10,23 +10,17 @@ * 4. Testing CLI path resolution via static method */ -import { describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; import { resolve } from "node:path"; -import { EventEmitter } from "node:events"; - -import { SessionManager } from "./session-manager.js"; +import { afterEach, beforeEach, describe, it } from "vitest"; import { buildAskUserQuestionsElicitRequest, createMcpServer, formatAskUserQuestionsElicitResult, } from "./server.js"; +import { SessionManager } from "./session-manager.js"; +import type { ManagedSession } from "./types.js"; import { MAX_EVENTS } from "./types.js"; -import type { - ManagedSession, - CostAccumulator, - PendingBlocker, -} from "./types.js"; // --------------------------------------------------------------------------- // Mock RpcClient (duck-typed to match RpcClient interface) diff --git a/packages/mcp-server/src/readers/captures.ts b/packages/mcp-server/src/readers/captures.ts index 5d8ec066e..7c9b87e7b 100644 --- a/packages/mcp-server/src/readers/captures.ts +++ b/packages/mcp-server/src/readers/captures.ts @@ -1,8 +1,8 @@ // SF MCP Server — captures reader // Copyright (c) 2026 Jeremy McSpadden -import { readFileSync, existsSync } from "node:fs"; -import { resolveSFRoot, resolveRootFile } from "./paths.js"; +import { existsSync, readFileSync } from "node:fs"; +import { resolveRootFile, resolveSFRoot } from "./paths.js"; // --------------------------------------------------------------------------- // Types diff --git a/packages/mcp-server/src/readers/doctor-lite.ts b/packages/mcp-server/src/readers/doctor-lite.ts index 525fe1c80..8cf515950 100644 --- a/packages/mcp-server/src/readers/doctor-lite.ts +++ b/packages/mcp-server/src/readers/doctor-lite.ts @@ -1,16 +1,16 @@ // SF MCP Server — lightweight structural health checks // Copyright (c) 2026 Jeremy McSpadden -import { existsSync, readFileSync } from "node:fs"; +import { existsSync } from "node:fs"; import { - resolveSFRoot, - resolveRootFile, findMilestoneIds, - resolveMilestoneFile, - resolveMilestoneDir, findSliceIds, - resolveSliceFile, findTaskFiles, + resolveMilestoneDir, + resolveMilestoneFile, + resolveRootFile, + resolveSFRoot, + resolveSliceFile, } from "./paths.js"; // --------------------------------------------------------------------------- diff --git a/packages/mcp-server/src/readers/knowledge.ts b/packages/mcp-server/src/readers/knowledge.ts index 9a1405cab..94709beb2 100644 --- a/packages/mcp-server/src/readers/knowledge.ts +++ b/packages/mcp-server/src/readers/knowledge.ts @@ -1,8 +1,8 @@ // SF MCP Server — knowledge base reader // Copyright (c) 2026 Jeremy McSpadden -import { readFileSync, existsSync } from "node:fs"; -import { resolveSFRoot, resolveRootFile } from "./paths.js"; +import { existsSync, readFileSync } from "node:fs"; +import { resolveRootFile, resolveSFRoot } from "./paths.js"; // --------------------------------------------------------------------------- // Types diff --git a/packages/mcp-server/src/readers/metrics.ts b/packages/mcp-server/src/readers/metrics.ts index d7a521155..631b130e0 100644 --- a/packages/mcp-server/src/readers/metrics.ts +++ b/packages/mcp-server/src/readers/metrics.ts @@ -1,8 +1,8 @@ // SF MCP Server — metrics/history reader // Copyright (c) 2026 Jeremy McSpadden -import { readFileSync, existsSync } from "node:fs"; -import { resolveSFRoot, resolveRootFile } from "./paths.js"; +import { existsSync, readFileSync } from "node:fs"; +import { resolveRootFile, resolveSFRoot } from "./paths.js"; // --------------------------------------------------------------------------- // Types diff --git a/packages/mcp-server/src/readers/paths.ts b/packages/mcp-server/src/readers/paths.ts index 7cbf2e56e..e6a4bca00 100644 --- a/packages/mcp-server/src/readers/paths.ts +++ b/packages/mcp-server/src/readers/paths.ts @@ -1,9 +1,9 @@ // SF MCP Server — .sf/ directory resolution // Copyright (c) 2026 Jeremy McSpadden -import { existsSync, statSync, readdirSync } from "node:fs"; -import { join, resolve, dirname, basename } from "node:path"; import { execFileSync } from "node:child_process"; +import { existsSync, readdirSync, statSync } from "node:fs"; +import { basename, dirname, join, resolve } from "node:path"; /** * Resolve the .sf/ root directory for a project. diff --git a/packages/mcp-server/src/readers/readers.test.ts b/packages/mcp-server/src/readers/readers.test.ts index ca24ce1ca..ca3dd9157 100644 --- a/packages/mcp-server/src/readers/readers.test.ts +++ b/packages/mcp-server/src/readers/readers.test.ts @@ -1,19 +1,18 @@ // SF MCP Server — reader tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it, beforeAll, afterAll } from "vitest"; import assert from "node:assert/strict"; -import { mkdirSync, writeFileSync, rmSync } from "node:fs"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; import { randomBytes } from "node:crypto"; - -import { readProgress } from "./state.js"; -import { readRoadmap } from "./roadmap.js"; -import { readHistory } from "./metrics.js"; +import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, beforeAll, describe, it } from "vitest"; import { readCaptures } from "./captures.js"; -import { readKnowledge } from "./knowledge.js"; import { runDoctorLite } from "./doctor-lite.js"; +import { readKnowledge } from "./knowledge.js"; +import { readHistory } from "./metrics.js"; +import { readRoadmap } from "./roadmap.js"; +import { readProgress } from "./state.js"; // --------------------------------------------------------------------------- // Test fixture helpers diff --git a/packages/mcp-server/src/readers/roadmap.ts b/packages/mcp-server/src/readers/roadmap.ts index 860dbba88..fb4797027 100644 --- a/packages/mcp-server/src/readers/roadmap.ts +++ b/packages/mcp-server/src/readers/roadmap.ts @@ -1,14 +1,14 @@ // SF MCP Server — roadmap structure reader // Copyright (c) 2026 Jeremy McSpadden -import { readFileSync, existsSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { - resolveSFRoot, findMilestoneIds, - resolveMilestoneFile, findSliceIds, - resolveSliceFile, findTaskFiles, + resolveMilestoneFile, + resolveSFRoot, + resolveSliceFile, } from "./paths.js"; // --------------------------------------------------------------------------- diff --git a/packages/mcp-server/src/readers/state.ts b/packages/mcp-server/src/readers/state.ts index 7d1e2e893..47ebe5dae 100644 --- a/packages/mcp-server/src/readers/state.ts +++ b/packages/mcp-server/src/readers/state.ts @@ -1,15 +1,13 @@ // SF MCP Server — project state reader // Copyright (c) 2026 Jeremy McSpadden -import { readFileSync, existsSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { - resolveSFRoot, - resolveRootFile, findMilestoneIds, - resolveMilestoneDir, - resolveMilestoneFile, findSliceIds, findTaskFiles, + resolveRootFile, + resolveSFRoot, } from "./paths.js"; // --------------------------------------------------------------------------- diff --git a/packages/mcp-server/src/secure-env-collect.test.ts b/packages/mcp-server/src/secure-env-collect.test.ts index ad1cf29a0..10cd7a1e2 100644 --- a/packages/mcp-server/src/secure-env-collect.test.ts +++ b/packages/mcp-server/src/secure-env-collect.test.ts @@ -2,91 +2,18 @@ // Copyright (c) 2026 Jeremy McSpadden // // Tests the secure_env_collect tool registered in createMcpServer. -// Uses a mock MCP server to intercept tool registration and elicitInput calls. -import { describe, it, beforeEach } from "vitest"; import assert from "node:assert/strict"; -import { - mkdtempSync, - mkdirSync, - rmSync, - writeFileSync, - readFileSync, -} from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { describe, it } from "vitest"; import { createMcpServer } from "./server.js"; import { SessionManager } from "./session-manager.js"; // --------------------------------------------------------------------------- -// Mock infrastructure -// --------------------------------------------------------------------------- - -/** - * We intercept McpServer construction by monkey-patching the dynamic import. - * Instead, we'll test the tool handler indirectly through the exported - * createMcpServer function — capturing the registered tool handlers. - * - * Since createMcpServer dynamically imports McpServer, we need to test at - * a level that exercises the tool handler logic. We do this by extracting - * the tool handler through the server.tool() calls. - */ - -interface RegisteredTool { - name: string; - description: string; - params: Record; - handler: (args: Record) => Promise; -} - -interface ToolResult { - content?: Array<{ type: string; text: string }>; - isError?: boolean; -} - -/** - * Mock McpServer that captures tool registrations and provides - * a controllable elicitInput response. - */ -class MockMcpServer { - registeredTools: RegisteredTool[] = []; - elicitResponse: { action: string; content?: Record } = { - action: "accept", - content: {}, - }; - - server = { - elicitInput: async (_params: unknown) => { - return this.elicitResponse; - }, - }; - - tool( - name: string, - description: string, - params: Record, - handler: (args: Record) => Promise, - ) { - this.registeredTools.push({ name, description, params, handler }); - } - - async connect(_transport: unknown) { - /* no-op */ - } - async close() { - /* no-op */ - } - - getToolHandler( - name: string, - ): ((args: Record) => Promise) | undefined { - return this.registeredTools.find((t) => t.name === name)?.handler; - } -} - -// --------------------------------------------------------------------------- -// Helper to create a mock MCP server with secure_env_collect registered +// Test helpers // --------------------------------------------------------------------------- /** diff --git a/packages/mcp-server/src/session-manager.ts b/packages/mcp-server/src/session-manager.ts index 8c85716fb..f7223dc1a 100644 --- a/packages/mcp-server/src/session-manager.ts +++ b/packages/mcp-server/src/session-manager.ts @@ -8,21 +8,19 @@ import { execSync } from "node:child_process"; import { resolve } from "node:path"; -import { RpcClient } from "@singularity-forge/rpc-client"; import type { - SdkAgentEvent, - RpcInitResult, RpcCostUpdateEvent, RpcExtensionUIRequest, + RpcInitResult, + SdkAgentEvent, } from "@singularity-forge/rpc-client"; +import { RpcClient } from "@singularity-forge/rpc-client"; import type { - ManagedSession, ExecuteOptions, + ManagedSession, PendingBlocker, - CostAccumulator, - SessionStatus, } from "./types.js"; -import { MAX_EVENTS, INIT_TIMEOUT_MS } from "./types.js"; +import { INIT_TIMEOUT_MS, MAX_EVENTS } from "./types.js"; // --------------------------------------------------------------------------- // Inlined detection logic (from headless-events.ts — no internal package imports) diff --git a/packages/mcp-server/src/tool-credentials.test.ts b/packages/mcp-server/src/tool-credentials.test.ts index 6d2ad2693..7c8803dc0 100644 --- a/packages/mcp-server/src/tool-credentials.test.ts +++ b/packages/mcp-server/src/tool-credentials.test.ts @@ -1,8 +1,8 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; -import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { describe, it } from "vitest"; import { loadStoredCredentialEnvKeys, diff --git a/packages/mcp-server/src/types.ts b/packages/mcp-server/src/types.ts index af6a8b0ad..aadbc2984 100644 --- a/packages/mcp-server/src/types.ts +++ b/packages/mcp-server/src/types.ts @@ -4,9 +4,8 @@ import type { RpcClient, - SdkAgentEvent, - RpcCostUpdateEvent, RpcExtensionUIRequest, + SdkAgentEvent, } from "@singularity-forge/rpc-client"; // --------------------------------------------------------------------------- diff --git a/packages/native/src/__tests__/clipboard.test.mjs b/packages/native/src/__tests__/clipboard.test.mjs index 27ae8a758..6e63acb32 100644 --- a/packages/native/src/__tests__/clipboard.test.mjs +++ b/packages/native/src/__tests__/clipboard.test.mjs @@ -1,8 +1,8 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/diff.test.mjs b/packages/native/src/__tests__/diff.test.mjs index a95441606..f0e5b7c74 100644 --- a/packages/native/src/__tests__/diff.test.mjs +++ b/packages/native/src/__tests__/diff.test.mjs @@ -1,8 +1,8 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/edit.test.mjs b/packages/native/src/__tests__/edit.test.mjs index 5ef66a146..b421788dd 100644 --- a/packages/native/src/__tests__/edit.test.mjs +++ b/packages/native/src/__tests__/edit.test.mjs @@ -1,10 +1,10 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; +import * as fs from "node:fs"; import { createRequire } from "node:module"; +import * as os from "node:os"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; -import * as fs from "node:fs"; -import * as os from "node:os"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/fd.test.mjs b/packages/native/src/__tests__/fd.test.mjs index 8ab58e883..d63fe0b13 100644 --- a/packages/native/src/__tests__/fd.test.mjs +++ b/packages/native/src/__tests__/fd.test.mjs @@ -1,10 +1,10 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; +import * as fs from "node:fs"; import { createRequire } from "node:module"; +import * as os from "node:os"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; -import * as fs from "node:fs"; -import * as os from "node:os"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/glob.test.mjs b/packages/native/src/__tests__/glob.test.mjs index d4e8b9962..1a17ff1aa 100644 --- a/packages/native/src/__tests__/glob.test.mjs +++ b/packages/native/src/__tests__/glob.test.mjs @@ -1,10 +1,10 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; +import * as fs from "node:fs"; import { createRequire } from "node:module"; +import * as os from "node:os"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; -import * as fs from "node:fs"; -import * as os from "node:os"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/grep.test.mjs b/packages/native/src/__tests__/grep.test.mjs index ff2f6f828..ee7343a10 100644 --- a/packages/native/src/__tests__/grep.test.mjs +++ b/packages/native/src/__tests__/grep.test.mjs @@ -1,10 +1,10 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; +import * as fs from "node:fs"; import { createRequire } from "node:module"; +import * as os from "node:os"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; -import * as fs from "node:fs"; -import * as os from "node:os"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/highlight.test.mjs b/packages/native/src/__tests__/highlight.test.mjs index 007f9c375..69a336731 100644 --- a/packages/native/src/__tests__/highlight.test.mjs +++ b/packages/native/src/__tests__/highlight.test.mjs @@ -1,8 +1,8 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/html.test.mjs b/packages/native/src/__tests__/html.test.mjs index 86fa08db3..7ab8eabf2 100644 --- a/packages/native/src/__tests__/html.test.mjs +++ b/packages/native/src/__tests__/html.test.mjs @@ -1,8 +1,8 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/image.test.mjs b/packages/native/src/__tests__/image.test.mjs index 1de1d2a6b..10113be6a 100644 --- a/packages/native/src/__tests__/image.test.mjs +++ b/packages/native/src/__tests__/image.test.mjs @@ -1,9 +1,9 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; import { deflateSync } from "node:zlib"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/json-parse.test.mjs b/packages/native/src/__tests__/json-parse.test.mjs index adfb30a45..fbccdda8d 100644 --- a/packages/native/src/__tests__/json-parse.test.mjs +++ b/packages/native/src/__tests__/json-parse.test.mjs @@ -1,8 +1,8 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/module-compat.test.mjs b/packages/native/src/__tests__/module-compat.test.mjs index efb35765c..229bf50a6 100644 --- a/packages/native/src/__tests__/module-compat.test.mjs +++ b/packages/native/src/__tests__/module-compat.test.mjs @@ -7,11 +7,11 @@ * declared "type": "module" and strict ESM resolution was enforced. */ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const pkgPath = path.resolve(__dirname, "..", "..", "package.json"); diff --git a/packages/native/src/__tests__/ps.test.mjs b/packages/native/src/__tests__/ps.test.mjs index 50d3141c8..18e305065 100644 --- a/packages/native/src/__tests__/ps.test.mjs +++ b/packages/native/src/__tests__/ps.test.mjs @@ -1,9 +1,9 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; -import { spawn } from "node:child_process"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/stream-process.test.mjs b/packages/native/src/__tests__/stream-process.test.mjs index 55a0a4385..eadf1125c 100644 --- a/packages/native/src/__tests__/stream-process.test.mjs +++ b/packages/native/src/__tests__/stream-process.test.mjs @@ -1,5 +1,5 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; +import { describe, test } from "vitest"; import { processStreamChunk } from "../stream-process/index.ts"; describe("processStreamChunk", () => { diff --git a/packages/native/src/__tests__/symbol.test.mjs b/packages/native/src/__tests__/symbol.test.mjs index 82a540db2..0156ee6b8 100644 --- a/packages/native/src/__tests__/symbol.test.mjs +++ b/packages/native/src/__tests__/symbol.test.mjs @@ -10,13 +10,13 @@ * npx vitest run packages/native/src/__tests__/symbol.test.mjs --config vitest.config.ts */ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; +import * as fs from "node:fs"; import { createRequire } from "node:module"; +import * as os from "node:os"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; -import * as fs from "node:fs"; -import * as os from "node:os"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); @@ -147,14 +147,14 @@ describe("native symbol: replaceSymbol()", () => { test("replaces an arrow function declaration", ({ onTestFinished }) => { const { dir, file } = tempTsFile( - "const greet = (name: string) => { return `Hello ${name}`; }\n", + "const greet = (name: string) => { return `Hello " + "$" + "{name}`; }\n", ); onTestFinished(() => fs.rmSync(dir, { recursive: true, force: true })); const result = native.replaceSymbol( file, "greet", - "const greet = (name: string) => { return `Hi ${name}!`; }", + "const greet = (name: string) => { return `Hi " + "$" + "{name}!`; }", { fsync: false }, ); diff --git a/packages/native/src/__tests__/text.test.mjs b/packages/native/src/__tests__/text.test.mjs index 700a5aa75..29bb6b3f7 100644 --- a/packages/native/src/__tests__/text.test.mjs +++ b/packages/native/src/__tests__/text.test.mjs @@ -1,8 +1,8 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/truncate.test.mjs b/packages/native/src/__tests__/truncate.test.mjs index 0cfbf4de5..501750241 100644 --- a/packages/native/src/__tests__/truncate.test.mjs +++ b/packages/native/src/__tests__/truncate.test.mjs @@ -1,8 +1,8 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/ttsr.test.mjs b/packages/native/src/__tests__/ttsr.test.mjs index c3fcc64ee..7782e23c3 100644 --- a/packages/native/src/__tests__/ttsr.test.mjs +++ b/packages/native/src/__tests__/ttsr.test.mjs @@ -1,8 +1,8 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); diff --git a/packages/native/src/__tests__/watch.test.mjs b/packages/native/src/__tests__/watch.test.mjs index 532f6f787..56c2a69f5 100644 --- a/packages/native/src/__tests__/watch.test.mjs +++ b/packages/native/src/__tests__/watch.test.mjs @@ -1,9 +1,9 @@ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; -import * as path from "node:path"; -import { fileURLToPath } from "node:url"; import * as fs from "node:fs"; import * as os from "node:os"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const { watchTree } = await import( diff --git a/packages/native/src/__tests__/xxhash.test.mjs b/packages/native/src/__tests__/xxhash.test.mjs index 4d590c52d..f2ba447b8 100644 --- a/packages/native/src/__tests__/xxhash.test.mjs +++ b/packages/native/src/__tests__/xxhash.test.mjs @@ -1,6 +1,6 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; import { xxHash32, xxHash32Fallback } from "@singularity-forge/native/xxhash"; +import { describe, it } from "vitest"; /** * Reference values computed from the pure-JS xxHash32 implementation diff --git a/packages/native/src/ast/index.ts b/packages/native/src/ast/index.ts index 6a138c461..726c96d4a 100644 --- a/packages/native/src/ast/index.ts +++ b/packages/native/src/ast/index.ts @@ -1,12 +1,12 @@ import { native } from "../native.js"; import type { + AstFindMatch, AstFindOptions, AstFindResult, - AstReplaceOptions, - AstReplaceResult, - AstFindMatch, AstReplaceChange, AstReplaceFileChange, + AstReplaceOptions, + AstReplaceResult, } from "./types.js"; export type { @@ -20,11 +20,13 @@ export type { }; export function astGrep(options: AstFindOptions): AstFindResult { - return (native as Record).astGrep(options) as AstFindResult; + return (native as Record unknown>).astGrep( + options, + ) as AstFindResult; } export function astEdit(options: AstReplaceOptions): AstReplaceResult { - return (native as Record).astEdit( + return (native as Record unknown>).astEdit( options, ) as AstReplaceResult; } diff --git a/packages/native/src/diff/index.ts b/packages/native/src/diff/index.ts index f7b381ad9..516829f8c 100644 --- a/packages/native/src/diff/index.ts +++ b/packages/native/src/diff/index.ts @@ -18,9 +18,9 @@ export type { DiffResult, FuzzyMatchResult }; * - Special Unicode spaces to regular space */ export function normalizeForFuzzyMatch(text: string): string { - return (native as Record).normalizeForFuzzyMatch( - text, - ) as string; + return ( + native as Record unknown> + ).normalizeForFuzzyMatch(text) as string; } /** @@ -33,10 +33,9 @@ export function fuzzyFindText( content: string, oldText: string, ): FuzzyMatchResult { - return (native as Record).fuzzyFindText( - content, - oldText, - ) as FuzzyMatchResult; + return ( + native as Record unknown> + ).fuzzyFindText(content, oldText) as FuzzyMatchResult; } /** @@ -53,9 +52,7 @@ export function generateDiff( newContent: string, contextLines?: number, ): DiffResult { - return (native as Record).generateDiff( - oldContent, - newContent, - contextLines, - ) as DiffResult; + return ( + native as Record unknown> + ).generateDiff(oldContent, newContent, contextLines) as DiffResult; } diff --git a/packages/native/src/edit/index.ts b/packages/native/src/edit/index.ts index 167de990d..7df3978b8 100644 --- a/packages/native/src/edit/index.ts +++ b/packages/native/src/edit/index.ts @@ -8,8 +8,8 @@ * need to commit them to disk as one native operation. */ -import { native } from "../native.js"; import { EventEmitter } from "node:events"; +import { native } from "../native.js"; import type { ApplyEditsOptions, ApplyEditsResult, diff --git a/packages/native/src/forge-parser/index.ts b/packages/native/src/forge-parser/index.ts index ce4748702..37ee32ccf 100644 --- a/packages/native/src/forge-parser/index.ts +++ b/packages/native/src/forge-parser/index.ts @@ -43,9 +43,9 @@ export type { * of the parsed frontmatter key-value pairs. Parse it with `JSON.parse()`. */ export function parseFrontmatter(content: string): FrontmatterResult { - return (native as Record).parseFrontmatter( - content, - ) as FrontmatterResult; + return ( + native as Record unknown> + ).parseFrontmatter(content) as FrontmatterResult; } /** @@ -60,11 +60,9 @@ export function extractSection( heading: string, level?: number, ): SectionResult { - return (native as Record).extractSection( - content, - heading, - level, - ) as SectionResult; + return ( + native as Record unknown> + ).extractSection(content, heading, level) as SectionResult; } /** @@ -74,10 +72,9 @@ export function extractSection( * Parse with `JSON.parse()`. */ export function extractAllSections(content: string, level?: number): string { - return (native as Record).extractAllSections( - content, - level, - ) as string; + return ( + native as Record unknown> + ).extractAllSections(content, level) as string; } /** @@ -87,9 +84,9 @@ export function extractAllSections(content: string, level?: number): string { * Each file gets frontmatter parsing and section extraction. */ export function batchParseSfFiles(directory: string): BatchParseResult { - return (native as Record).batchParseSfFiles( - directory, - ) as BatchParseResult; + return ( + native as Record unknown> + ).batchParseSfFiles(directory) as BatchParseResult; } /** @@ -99,16 +96,16 @@ export function batchParseSfFiles(directory: string): BatchParseResult { * and boundary map entries. */ export function parseRoadmapFile(content: string): NativeRoadmap { - return (native as Record).parseRoadmapFile( - content, - ) as NativeRoadmap; + return ( + native as Record unknown> + ).parseRoadmapFile(content) as NativeRoadmap; } /** * Scan a `.sf/` directory tree. */ export function scanSfTree(directory: string): SfTreeEntry[] { - return (native as Record).scanSfTree( + return (native as Record unknown>).scanSfTree( directory, ) as SfTreeEntry[]; } @@ -121,27 +118,25 @@ export function parseJsonlTail( maxBytes?: number, maxEntries?: number, ): JsonlParseResult { - return (native as Record).parseJsonlTail( - filePath, - maxBytes, - maxEntries, - ) as JsonlParseResult; + return ( + native as Record unknown> + ).parseJsonlTail(filePath, maxBytes, maxEntries) as JsonlParseResult; } /** * Parse a task plan markdown file into structured data. */ export function parsePlanFile(content: string): NativePlan { - return (native as Record).parsePlanFile( - content, - ) as NativePlan; + return ( + native as Record unknown> + ).parsePlanFile(content) as NativePlan; } /** * Parse a summary markdown file into structured data. */ export function parseSummaryFile(content: string): NativeSummary { - return (native as Record).parseSummaryFile( - content, - ) as NativeSummary; + return ( + native as Record unknown> + ).parseSummaryFile(content) as NativeSummary; } diff --git a/packages/native/src/glob/types.ts b/packages/native/src/glob/types.ts index 32dde89f1..b690d0c33 100644 --- a/packages/native/src/glob/types.ts +++ b/packages/native/src/glob/types.ts @@ -1,5 +1,5 @@ /** File type classification for filesystem entries. */ -export const enum FileType { +export enum FileType { /** Regular file. */ File = 1, /** Directory. */ diff --git a/packages/native/src/image/index.ts b/packages/native/src/image/index.ts index 10434641d..4bce47248 100644 --- a/packages/native/src/image/index.ts +++ b/packages/native/src/image/index.ts @@ -8,8 +8,8 @@ import { native } from "../native.js"; import type { NativeImageHandle } from "./types.js"; import { ImageFormat, SamplingFilter } from "./types.js"; -export { ImageFormat, SamplingFilter }; export type { NativeImageHandle }; +export { ImageFormat, SamplingFilter }; const NativeImageClass = (native as Record) .NativeImage as NativeImageConstructor; diff --git a/packages/native/src/index.ts b/packages/native/src/index.ts index a2feab695..cb611eaab 100644 --- a/packages/native/src/index.ts +++ b/packages/native/src/index.ts @@ -14,47 +14,6 @@ for autocomplete and @-mention resolution */ -export { - copyToClipboard, - readTextFromClipboard, - readImageFromClipboard, -} from "./clipboard/index.js"; -export type { ClipboardImage } from "./clipboard/index.js"; - -export { - highlightCode, - supportsLanguage, - getSupportedLanguages, -} from "./highlight/index.js"; -export type { HighlightColors } from "./highlight/index.js"; - -export { searchContent, grep } from "./grep/index.js"; -export type { - ContextLine, - GrepMatch, - GrepOptions, - GrepResult, - SearchMatch, - SearchOptions, - SearchResult, -} from "./grep/index.js"; - -export { - killTree, - listDescendants, - processGroupId, - killProcessGroup, -} from "./ps/index.js"; - -export { glob, invalidateFsScanCache } from "./glob/index.js"; -export type { - FileType, - GlobMatch, - GlobOptions, - GlobResult, -} from "./glob/index.js"; - -export { astGrep, astEdit } from "./ast/index.js"; export type { AstFindMatch, AstFindOptions, @@ -64,35 +23,19 @@ export type { AstReplaceOptions, AstReplaceResult, } from "./ast/index.js"; - -export { htmlToMarkdown } from "./html/index.js"; -export type { HtmlToMarkdownOptions } from "./html/index.js"; - +export { astEdit, astGrep } from "./ast/index.js"; +export type { ClipboardImage } from "./clipboard/index.js"; export { - wrapTextWithAnsi, - truncateToWidth, - sliceWithWidth, - extractSegments, - sanitizeText, - visibleWidth, - EllipsisKind, -} from "./text/index.js"; -export type { SliceResult, ExtractSegmentsResult } from "./text/index.js"; - + copyToClipboard, + readImageFromClipboard, + readTextFromClipboard, +} from "./clipboard/index.js"; +export type { DiffResult, FuzzyMatchResult } from "./diff/index.js"; export { - normalizeForFuzzyMatch, fuzzyFindText, generateDiff, + normalizeForFuzzyMatch, } from "./diff/index.js"; -export type { FuzzyMatchResult, DiffResult } from "./diff/index.js"; - -export { - applyEdits, - applyWorkspaceEdit, - insertAroundSymbol, - replaceSymbol, - watchTree, -} from "./edit/index.js"; export type { ApplyEditsOptions, ApplyEditsResult, @@ -112,49 +55,19 @@ export type { WatchOptions, WorkspaceEditLike, } from "./edit/index.js"; - -export { fuzzyFind } from "./fd/index.js"; +export { + applyEdits, + applyWorkspaceEdit, + insertAroundSymbol, + replaceSymbol, + watchTree, +} from "./edit/index.js"; export type { FuzzyFindMatch, FuzzyFindOptions, FuzzyFindResult, } from "./fd/index.js"; - -export { parseImage, ImageFormat, SamplingFilter } from "./image/index.js"; -export type { NativeImageHandle } from "./image/index.js"; - -export { xxHash32, xxHash32Fallback } from "./xxhash/index.js"; - -export { - ttsrCompileRules, - ttsrCheckBuffer, - ttsrFreeRules, -} from "./ttsr/index.js"; -export type { TtsrHandle, TtsrRuleInput } from "./ttsr/index.js"; -export { - parseJson, - parsePartialJson, - parseStreamingJson, -} from "./json-parse/index.js"; -export { - processStreamChunk, - stripAnsiNative, - sanitizeBinaryOutputNative, -} from "./stream-process/index.js"; -export type { StreamState, StreamChunkResult } from "./stream-process/index.js"; - -export { - parseFrontmatter, - extractSection, - extractSection as nativeExtractSection, - extractAllSections, - batchParseSfFiles, - parseRoadmapFile, - scanSfTree, - parseJsonlTail, - parsePlanFile, - parseSummaryFile, -} from "./forge-parser/index.js"; +export { fuzzyFind } from "./fd/index.js"; export type { BatchParseResult, FrontmatterResult, @@ -172,10 +85,83 @@ export type { SectionResult, SfTreeEntry, } from "./forge-parser/index.js"; - export { - truncateTail, + batchParseSfFiles, + extractAllSections, + extractSection, + extractSection as nativeExtractSection, + parseFrontmatter, + parseJsonlTail, + parsePlanFile, + parseRoadmapFile, + parseSummaryFile, + scanSfTree, +} from "./forge-parser/index.js"; +export type { + FileType, + GlobMatch, + GlobOptions, + GlobResult, +} from "./glob/index.js"; +export { glob, invalidateFsScanCache } from "./glob/index.js"; +export type { + ContextLine, + GrepMatch, + GrepOptions, + GrepResult, + SearchMatch, + SearchOptions, + SearchResult, +} from "./grep/index.js"; +export { grep, searchContent } from "./grep/index.js"; +export type { HighlightColors } from "./highlight/index.js"; +export { + getSupportedLanguages, + highlightCode, + supportsLanguage, +} from "./highlight/index.js"; +export type { HtmlToMarkdownOptions } from "./html/index.js"; +export { htmlToMarkdown } from "./html/index.js"; +export type { NativeImageHandle } from "./image/index.js"; + +export { ImageFormat, parseImage, SamplingFilter } from "./image/index.js"; +export { + parseJson, + parsePartialJson, + parseStreamingJson, +} from "./json-parse/index.js"; +export { + killProcessGroup, + killTree, + listDescendants, + processGroupId, +} from "./ps/index.js"; +export type { StreamChunkResult, StreamState } from "./stream-process/index.js"; +export { + processStreamChunk, + sanitizeBinaryOutputNative, + stripAnsiNative, +} from "./stream-process/index.js"; +export type { ExtractSegmentsResult, SliceResult } from "./text/index.js"; +export { + EllipsisKind, + extractSegments, + sanitizeText, + sliceWithWidth, + truncateToWidth, + visibleWidth, + wrapTextWithAnsi, +} from "./text/index.js"; +export type { TruncateOutputResult, TruncateResult } from "./truncate/index.js"; +export { truncateHead, truncateOutput, + truncateTail, } from "./truncate/index.js"; -export type { TruncateResult, TruncateOutputResult } from "./truncate/index.js"; +export type { TtsrHandle, TtsrRuleInput } from "./ttsr/index.js"; +export { + ttsrCheckBuffer, + ttsrCompileRules, + ttsrFreeRules, +} from "./ttsr/index.js"; +export { xxHash32, xxHash32Fallback } from "./xxhash/index.js"; diff --git a/packages/native/src/native.ts b/packages/native/src/native.ts index d671075a6..b78a680b4 100644 --- a/packages/native/src/native.ts +++ b/packages/native/src/native.ts @@ -75,7 +75,7 @@ function loadNative(): Record { errors.push(`${devPath}: ${message}`); } - const details = errors.map((e) => ` - ${e}`).join("\n"); + const _details = errors.map((e) => ` - ${e}`).join("\n"); const supportedPlatforms = Object.keys(platformPackageMap); // Graceful fallback: on unsupported platforms (e.g., win32-arm64), return a diff --git a/packages/native/src/stream-process/index.ts b/packages/native/src/stream-process/index.ts index ec2008741..b81960fc4 100644 --- a/packages/native/src/stream-process/index.ts +++ b/packages/native/src/stream-process/index.ts @@ -38,10 +38,9 @@ export function processStreamChunk( } : undefined; - const result = (native as Record).processStreamChunk( - chunk, - napiState, - ) as { + const result = ( + native as Record unknown> + ).processStreamChunk(chunk, napiState) as { text: string; state: { utf8Pending: Buffer; ansiPending: Buffer }; }; @@ -59,7 +58,9 @@ export function processStreamChunk( * Strip ANSI escape sequences from a string. */ export function stripAnsiNative(text: string): string { - return (native as Record).stripAnsiNative(text) as string; + return ( + native as Record unknown> + ).stripAnsiNative(text) as string; } /** @@ -69,7 +70,7 @@ export function stripAnsiNative(text: string): string { * characters, Unicode format characters (U+FFF9-U+FFFB), and lone surrogates. */ export function sanitizeBinaryOutputNative(text: string): string { - return (native as Record).sanitizeBinaryOutputNative( - text, - ) as string; + return ( + native as Record unknown> + ).sanitizeBinaryOutputNative(text) as string; } diff --git a/packages/native/src/text/index.ts b/packages/native/src/text/index.ts index e803425e7..c0b4d510f 100644 --- a/packages/native/src/text/index.ts +++ b/packages/native/src/text/index.ts @@ -8,8 +8,8 @@ import { native } from "../native.js"; import type { ExtractSegmentsResult, SliceResult } from "./types.js"; -export type { ExtractSegmentsResult, SliceResult }; export { EllipsisKind } from "./types.js"; +export type { ExtractSegmentsResult, SliceResult }; /** * Word-wrap text to a visible width, preserving ANSI escape codes across @@ -24,11 +24,9 @@ export function wrapTextWithAnsi( width: number, tabWidth?: number, ): string[] { - return (native as Record).wrapTextWithAnsi( - text, - width, - tabWidth, - ) as string[]; + return ( + native as Record unknown> + ).wrapTextWithAnsi(text, width, tabWidth) as string[]; } /** @@ -47,13 +45,9 @@ export function truncateToWidth( pad: boolean, tabWidth?: number, ): string { - return (native as Record).truncateToWidth( - text, - maxWidth, - ellipsisKind, - pad, - tabWidth, - ) as string; + return ( + native as Record unknown> + ).truncateToWidth(text, maxWidth, ellipsisKind, pad, tabWidth) as string; } /** @@ -69,13 +63,9 @@ export function sliceWithWidth( strict: boolean, tabWidth?: number, ): SliceResult { - return (native as Record).sliceWithWidth( - line, - startCol, - length, - strict, - tabWidth, - ) as SliceResult; + return ( + native as Record unknown> + ).sliceWithWidth(line, startCol, length, strict, tabWidth) as SliceResult; } /** @@ -92,7 +82,9 @@ export function extractSegments( strictAfter: boolean, tabWidth?: number, ): ExtractSegmentsResult { - return (native as Record).extractSegments( + return ( + native as Record unknown> + ).extractSegments( line, beforeEnd, afterStart, @@ -109,7 +101,9 @@ export function extractSegments( * Returns the original string when no changes are needed (zero-copy). */ export function sanitizeText(text: string): string { - return (native as Record).sanitizeText(text) as string; + return ( + native as Record unknown> + ).sanitizeText(text) as string; } /** @@ -118,8 +112,7 @@ export function sanitizeText(text: string): string { * Tabs count as `tabWidth` cells (default 3). */ export function visibleWidth(text: string, tabWidth?: number): number { - return (native as Record).visibleWidth( - text, - tabWidth, - ) as number; + return ( + native as Record unknown> + ).visibleWidth(text, tabWidth) as number; } diff --git a/packages/native/src/truncate/index.ts b/packages/native/src/truncate/index.ts index a405e8db6..58bb4d992 100644 --- a/packages/native/src/truncate/index.ts +++ b/packages/native/src/truncate/index.ts @@ -24,20 +24,18 @@ export interface TruncateOutputResult { * Keep the first `maxBytes` worth of complete lines. */ export function truncateTail(text: string, maxBytes: number): TruncateResult { - return (native as Record).truncateTail( - text, - maxBytes, - ) as TruncateResult; + return ( + native as Record unknown> + ).truncateTail(text, maxBytes) as TruncateResult; } /** * Keep the last `maxBytes` worth of complete lines. */ export function truncateHead(text: string, maxBytes: number): TruncateResult { - return (native as Record).truncateHead( - text, - maxBytes, - ) as TruncateResult; + return ( + native as Record unknown> + ).truncateHead(text, maxBytes) as TruncateResult; } /** @@ -48,9 +46,7 @@ export function truncateOutput( maxBytes: number, mode?: string, ): TruncateOutputResult { - return (native as Record).truncateOutput( - text, - maxBytes, - mode, - ) as TruncateOutputResult; + return ( + native as Record unknown> + ).truncateOutput(text, maxBytes, mode) as TruncateOutputResult; } diff --git a/packages/pi-agent-core/src/agent.test.ts b/packages/pi-agent-core/src/agent.test.ts index f3a208e44..5c23480eb 100644 --- a/packages/pi-agent-core/src/agent.test.ts +++ b/packages/pi-agent-core/src/agent.test.ts @@ -3,16 +3,16 @@ // and that the footer reads activeInferenceModel instead of state.model. // Regression test for https://github.com/singularity-forge/sf-run/issues/1844 Bug 2 -import { describe, it } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; -import { join, dirname } from "node:path"; +import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { Agent } from "./agent.ts"; import { - getModel, type AssistantMessageEventStream, + getModel, } from "@singularity-forge/pi-ai"; +import { describe, it } from "vitest"; +import { Agent } from "./agent.ts"; const __dirname = dirname(fileURLToPath(import.meta.url)); diff --git a/packages/pi-agent-core/src/agent.ts b/packages/pi-agent-core/src/agent.ts index 957654e88..a262d5be9 100644 --- a/packages/pi-agent-core/src/agent.ts +++ b/packages/pi-agent-core/src/agent.ts @@ -22,10 +22,6 @@ import type { AgentMessage, AgentState, AgentTool, - BeforeToolCallContext, - BeforeToolCallResult, - AfterToolCallContext, - AfterToolCallResult, StreamFn, ThinkingLevel, } from "./types.js"; diff --git a/packages/pi-agent-core/src/proxy.ts b/packages/pi-agent-core/src/proxy.ts index e4f466713..3e09f507e 100644 --- a/packages/pi-agent-core/src/proxy.ts +++ b/packages/pi-agent-core/src/proxy.ts @@ -91,7 +91,7 @@ export interface ProxyStreamOptions extends SimpleStreamOptions { * }); * ``` */ -function streamProxy( +function _streamProxy( model: Model, context: Context, options: ProxyStreamOptions, diff --git a/packages/pi-agent-core/src/types.ts b/packages/pi-agent-core/src/types.ts index 8d1956a84..781f63bd2 100644 --- a/packages/pi-agent-core/src/types.ts +++ b/packages/pi-agent-core/src/types.ts @@ -1,3 +1,4 @@ +import type { Static, TSchema } from "@sinclair/typebox"; import type { AssistantMessage, AssistantMessageEvent, @@ -10,7 +11,6 @@ import type { Tool, ToolResultMessage, } from "@singularity-forge/pi-ai"; -import type { Static, TSchema } from "@sinclair/typebox"; /** Stream function - can return sync or Promise for async config lookup */ export type StreamFn = ( @@ -255,9 +255,8 @@ export type ThinkingLevel = * } * ``` */ -export interface CustomAgentMessages { - // Empty by default - apps extend via declaration merging -} +// biome-ignore lint/suspicious/noEmptyInterface: extension point for downstream declaration merging +export interface CustomAgentMessages {} /** * AgentMessage: Union of LLM messages + custom messages. diff --git a/packages/pi-ai/scripts/generate-models.ts b/packages/pi-ai/scripts/generate-models.ts index 22499003d..f59b98487 100644 --- a/packages/pi-ai/scripts/generate-models.ts +++ b/packages/pi-ai/scripts/generate-models.ts @@ -1,8 +1,8 @@ #!/usr/bin/env tsx -import { writeFileSync } from "fs"; -import { join } from "path"; -import { Api, KnownProvider, Model } from "../src/types.js"; +import { writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { Api, KnownProvider, Model } from "../src/types.js"; const packageRoot = join(import.meta.dirname, ".."); @@ -69,7 +69,7 @@ async function fetchOpenRouterModels(): Promise[]> { if (!model.supported_parameters?.includes("tools")) continue; // Parse provider from model ID - let provider: KnownProvider = "openrouter"; + const provider: KnownProvider = "openrouter"; let modelKey = model.id; modelKey = model.id; // Keep full ID for OpenRouter @@ -197,7 +197,7 @@ async function loadModelsDevData(): Promise[]> { const m = model as ModelsDevModel; if (m.tool_call !== true) continue; - let id = modelId; + const id = modelId; if (id.startsWith("ai21.jamba")) { // These models doesn't support tool use in streaming mode @@ -568,7 +568,7 @@ async function loadModelsDevData(): Promise[]> { if (m.status === "deprecated") continue; // Claude 4.x models route to Anthropic Messages API - const isCopilotClaude4 = /^claude-(haiku|sonnet|opus)-4([.\-]|$)/.test( + const isCopilotClaude4 = /^claude-(haiku|sonnet|opus)-4([.-]|$)/.test( modelId, ); // gpt-5 models require responses API, others use completions diff --git a/packages/pi-ai/src/cli.ts b/packages/pi-ai/src/cli.ts index 3ed6ce1b5..ec1ce8456 100644 --- a/packages/pi-ai/src/cli.ts +++ b/packages/pi-ai/src/cli.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node -import { existsSync, readFileSync, writeFileSync } from "fs"; -import { createInterface } from "readline"; +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { createInterface } from "node:readline"; import { getOAuthProvider, getOAuthProviders } from "./utils/oauth/index.js"; import type { OAuthCredentials, OAuthProviderId } from "./utils/oauth/types.js"; diff --git a/packages/pi-ai/src/index.ts b/packages/pi-ai/src/index.ts index ef755b82a..8a8409baa 100644 --- a/packages/pi-ai/src/index.ts +++ b/packages/pi-ai/src/index.ts @@ -37,6 +37,6 @@ export type { OAuthProviderInterface, } from "./utils/oauth/types.js"; export * from "./utils/overflow.js"; -export * from "./utils/typebox-helpers.js"; export * from "./utils/repair-tool-json.js"; +export * from "./utils/typebox-helpers.js"; export * from "./utils/validation.js"; diff --git a/packages/pi-ai/src/models.generated.test.ts b/packages/pi-ai/src/models.generated.test.ts index 3c8e9975a..b285a1369 100644 --- a/packages/pi-ai/src/models.generated.test.ts +++ b/packages/pi-ai/src/models.generated.test.ts @@ -1,5 +1,5 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { MODELS } from "./models.generated.js"; import { getModel, getModels, getProviders } from "./models.js"; diff --git a/packages/pi-ai/src/models.test.ts b/packages/pi-ai/src/models.test.ts index 44d8ec9b4..24556a1e8 100644 --- a/packages/pi-ai/src/models.test.ts +++ b/packages/pi-ai/src/models.test.ts @@ -1,11 +1,11 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { - getProviders, - getModels, - getModel, - supportsXhigh, applyCapabilityPatches, + getModel, + getModels, + getProviders, + supportsXhigh, } from "./models.js"; import type { Api, Model } from "./types.js"; @@ -178,7 +178,7 @@ describe("model registry — kimi-coding provider", () => { const providers = getProviders(); assert.ok( providers.includes("kimi-coding"), - `Expected \"kimi-coding\" in providers, got: ${providers.join(", ")}`, + `Expected "kimi-coding" in providers, got: ${providers.join(", ")}`, ); }); diff --git a/packages/pi-ai/src/models.ts b/packages/pi-ai/src/models.ts index 06e22887b..e50fd3b76 100644 --- a/packages/pi-ai/src/models.ts +++ b/packages/pi-ai/src/models.ts @@ -1,5 +1,5 @@ -import { MODELS } from "./models.generated.js"; import { CUSTOM_MODELS } from "./models.custom.js"; +import { MODELS } from "./models.generated.js"; import type { Api, KnownProvider, diff --git a/packages/pi-ai/src/providers/amazon-bedrock.test.ts b/packages/pi-ai/src/providers/amazon-bedrock.test.ts index 36c44f0c3..8d01f1c42 100644 --- a/packages/pi-ai/src/providers/amazon-bedrock.test.ts +++ b/packages/pi-ai/src/providers/amazon-bedrock.test.ts @@ -7,17 +7,16 @@ * Related: #4392 (opus-4-7 adaptive thinking not recognised on Bedrock) * #4352 (pre-existing: only opus-4-6 / sonnet-4-6 whitelisted) */ -import { describe, it } from "vitest"; + import assert from "node:assert/strict"; - -import { - supportsAdaptiveThinking, - mapThinkingLevelToEffort, - buildAdditionalModelRequestFields, - type BedrockOptions, -} from "./amazon-bedrock.js"; - +import { describe, it } from "vitest"; import type { Model } from "../types.js"; +import { + type BedrockOptions, + buildAdditionalModelRequestFields, + mapThinkingLevelToEffort, + supportsAdaptiveThinking, +} from "./amazon-bedrock.js"; // --------------------------------------------------------------------------- // Helpers diff --git a/packages/pi-ai/src/providers/anthropic-auth.test.ts b/packages/pi-ai/src/providers/anthropic-auth.test.ts index 73a792035..b58b44512 100644 --- a/packages/pi-ai/src/providers/anthropic-auth.test.ts +++ b/packages/pi-ai/src/providers/anthropic-auth.test.ts @@ -1,12 +1,12 @@ -import { test } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; import { - usesAnthropicBearerAuth, resolveAnthropicBaseUrl, + usesAnthropicBearerAuth, } from "./anthropic.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); diff --git a/packages/pi-ai/src/providers/anthropic-shared.test.ts b/packages/pi-ai/src/providers/anthropic-shared.test.ts index ca13a0df6..f7af46fd2 100644 --- a/packages/pi-ai/src/providers/anthropic-shared.test.ts +++ b/packages/pi-ai/src/providers/anthropic-shared.test.ts @@ -1,5 +1,5 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { convertTools, mapStopReason } from "./anthropic-shared.js"; const makeTool = (name: string) => diff --git a/packages/pi-ai/src/providers/anthropic-shared.ts b/packages/pi-ai/src/providers/anthropic-shared.ts index 9ec4a738b..1e496762e 100644 --- a/packages/pi-ai/src/providers/anthropic-shared.ts +++ b/packages/pi-ai/src/providers/anthropic-shared.ts @@ -33,6 +33,7 @@ import type { /** API types that use the Anthropic Messages protocol */ export type AnthropicApi = "anthropic-messages" | "anthropic-vertex"; + import type { AssistantMessageEventStream } from "../utils/event-stream.js"; import { parseAnthropicSSE } from "../utils/event-stream.js"; import { parseStreamingJson } from "../utils/json-parse.js"; @@ -233,7 +234,7 @@ export function isTransientNetworkError(error: unknown): boolean { export function extractRetryAfterMs( headers: Headers | { get(name: string): string | null }, - errorText = "", + _errorText = "", ): number | undefined { const normalizeDelay = (ms: number): number | undefined => ms > 0 ? Math.ceil(ms + 1000) : undefined; diff --git a/packages/pi-ai/src/providers/anthropic-vertex.ts b/packages/pi-ai/src/providers/anthropic-vertex.ts index 5737f88d3..5fbb8f431 100644 --- a/packages/pi-ai/src/providers/anthropic-vertex.ts +++ b/packages/pi-ai/src/providers/anthropic-vertex.ts @@ -10,18 +10,18 @@ import type { StreamFunction, } from "../types.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; -import { - adjustMaxTokensForThinking, - buildBaseOptions, - isAutoReasoning, - resolveReasoningLevel, -} from "./simple-options.js"; import { type AnthropicOptions, mapThinkingLevelToEffort, processAnthropicStream, supportsAdaptiveThinking, } from "./anthropic-shared.js"; +import { + adjustMaxTokensForThinking, + buildBaseOptions, + isAutoReasoning, + resolveReasoningLevel, +} from "./simple-options.js"; let _AnthropicVertexClass: typeof AnthropicVertex | undefined; let _AnthropicSdkClass: typeof Anthropic | undefined; diff --git a/packages/pi-ai/src/providers/anthropic.ts b/packages/pi-ai/src/providers/anthropic.ts index ea845f6f2..735176da3 100644 --- a/packages/pi-ai/src/providers/anthropic.ts +++ b/packages/pi-ai/src/providers/anthropic.ts @@ -9,7 +9,14 @@ import type { StreamFunction, } from "../types.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; - +import { + type AnthropicEffort, + type AnthropicOptions, + extractRetryAfterMs, + mapThinkingLevelToEffort, + processAnthropicStream, + supportsAdaptiveThinking, +} from "./anthropic-shared.js"; import { buildCopilotDynamicHeaders, hasCopilotVisionInput, @@ -20,14 +27,6 @@ import { isAutoReasoning, resolveReasoningLevel, } from "./simple-options.js"; -import { - type AnthropicEffort, - type AnthropicOptions, - extractRetryAfterMs, - mapThinkingLevelToEffort, - processAnthropicStream, - supportsAdaptiveThinking, -} from "./anthropic-shared.js"; // Re-export types used by other modules export type { AnthropicEffort, AnthropicOptions }; diff --git a/packages/pi-ai/src/providers/google-shared.test.ts b/packages/pi-ai/src/providers/google-shared.test.ts index 69473fd8e..7cd6a2d5d 100644 --- a/packages/pi-ai/src/providers/google-shared.test.ts +++ b/packages/pi-ai/src/providers/google-shared.test.ts @@ -1,5 +1,5 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { sanitizeSchemaForGoogle } from "./google-shared.js"; // ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/pi-ai/src/providers/google-vertex.ts b/packages/pi-ai/src/providers/google-vertex.ts index dbc6e1d75..db2642eee 100644 --- a/packages/pi-ai/src/providers/google-vertex.ts +++ b/packages/pi-ai/src/providers/google-vertex.ts @@ -1,9 +1,9 @@ // Lazy-loaded: Google GenAI SDK is imported on first use, not at startup. // This avoids penalizing users who don't use Google Vertex models. -import type { GoogleGenAI } from "@google/genai"; import type { GenerateContentConfig, GenerateContentParameters, + GoogleGenAI, ThinkingConfig, } from "@google/genai"; import { calculateCost } from "../models.js"; diff --git a/packages/pi-ai/src/providers/google.ts b/packages/pi-ai/src/providers/google.ts index 3f13f319b..4a36509dd 100644 --- a/packages/pi-ai/src/providers/google.ts +++ b/packages/pi-ai/src/providers/google.ts @@ -15,6 +15,7 @@ async function getGoogleGenAIClass(): Promise { } return _GoogleGenAIClass; } + import { getEnvApiKey } from "../env-api-keys.js"; import { calculateCost } from "../models.js"; import type { diff --git a/packages/pi-ai/src/providers/openai-codex-responses.ts b/packages/pi-ai/src/providers/openai-codex-responses.ts index 7e401db5f..4bc5df923 100644 --- a/packages/pi-ai/src/providers/openai-codex-responses.ts +++ b/packages/pi-ai/src/providers/openai-codex-responses.ts @@ -13,16 +13,16 @@ import type { } from "../types.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; import { parseStreamingJson } from "../utils/json-parse.js"; +import { + type CodexAppServerNotification, + getCodexAppServerClient, +} from "./codex-app-server-client.js"; import { convertResponsesMessages } from "./openai-responses-shared.js"; import { buildBaseOptions, clampReasoning, resolveReasoningLevel, } from "./simple-options.js"; -import { - getCodexAppServerClient, - type CodexAppServerNotification, -} from "./codex-app-server-client.js"; export interface OpenAICodexResponsesOptions extends StreamOptions { reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh"; @@ -657,7 +657,7 @@ function readString(value: unknown): string | undefined { function readErrorMessage(value: unknown): string | undefined { const object = asObject(value); - const error = asObject(object?.error); + const _error = asObject(object?.error); return readNestedCodexErrorMessage(object) ?? readString(object?.message); } diff --git a/packages/pi-ai/src/providers/openai-completions.ts b/packages/pi-ai/src/providers/openai-completions.ts index 41415b926..193d369a1 100644 --- a/packages/pi-ai/src/providers/openai-completions.ts +++ b/packages/pi-ai/src/providers/openai-completions.ts @@ -15,7 +15,6 @@ import type { FunctionParameters } from "openai/resources/shared.js"; import { getEnvApiKey } from "../env-api-keys.js"; import { calculateCost, supportsXhigh } from "../models.js"; import type { - AssistantMessage, Context, ImageContent, Message, @@ -34,12 +33,6 @@ import type { import { AssistantMessageEventStream } from "../utils/event-stream.js"; import { parseStreamingJson } from "../utils/json-parse.js"; import { sanitizeSurrogates } from "../utils/sanitize-unicode.js"; -import { sanitizeToolCallArgumentsForSerialization } from "./sanitize-tool-arguments.js"; -import { - buildBaseOptions, - clampReasoning, - resolveReasoningLevel, -} from "./simple-options.js"; import { assertStreamSuccess, buildInitialOutput, @@ -47,6 +40,12 @@ import { finalizeStream, handleStreamError, } from "./openai-shared.js"; +import { sanitizeToolCallArgumentsForSerialization } from "./sanitize-tool-arguments.js"; +import { + buildBaseOptions, + clampReasoning, + resolveReasoningLevel, +} from "./simple-options.js"; import { transformMessagesWithReport } from "./transform-messages.js"; /** diff --git a/packages/pi-ai/src/providers/provider-capabilities.ts b/packages/pi-ai/src/providers/provider-capabilities.ts index 3138894a5..29a1d4c6d 100644 --- a/packages/pi-ai/src/providers/provider-capabilities.ts +++ b/packages/pi-ai/src/providers/provider-capabilities.ts @@ -2,8 +2,6 @@ // Declarative registry of what each API provider supports, consolidating // scattered knowledge from *-shared.ts files into a queryable data structure. -import type { Api } from "../types.js"; - // ─── Types ────────────────────────────────────────────────────────────────── /** diff --git a/packages/pi-ai/src/providers/transform-messages-report.test.ts b/packages/pi-ai/src/providers/transform-messages-report.test.ts index 29b8ea4e9..41396d262 100644 --- a/packages/pi-ai/src/providers/transform-messages-report.test.ts +++ b/packages/pi-ai/src/providers/transform-messages-report.test.ts @@ -1,14 +1,13 @@ // SF — ProviderSwitchReport Tests (ADR-005 Phase 3) -import { describe, test } from "vitest"; -import assert from "node:assert/strict"; +import assert from "node:assert/strict"; +import { describe, test } from "vitest"; +import type { AssistantMessage, Message, Model, ToolCall } from "../types.js"; import { - transformMessages, createEmptyReport, hasTransformations, + transformMessages, } from "./transform-messages.js"; -import type { ProviderSwitchReport } from "./transform-messages.js"; -import type { Message, Model, AssistantMessage, ToolCall } from "../types.js"; // ─── Helpers ──────────────────────────────────────────────────────────────── diff --git a/packages/pi-ai/src/types.ts b/packages/pi-ai/src/types.ts index 3bc3ebc69..e07344bd3 100644 --- a/packages/pi-ai/src/types.ts +++ b/packages/pi-ai/src/types.ts @@ -373,9 +373,7 @@ export interface OpenAICompletionsCompat { } /** Compatibility settings for OpenAI Responses APIs. */ -export interface OpenAIResponsesCompat { - // Reserved for future use -} +export type OpenAIResponsesCompat = Record; /** * OpenRouter provider routing preferences. diff --git a/packages/pi-ai/src/utils/oauth/github-copilot.test.ts b/packages/pi-ai/src/utils/oauth/github-copilot.test.ts index eda779a5a..11ecbdeb0 100644 --- a/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +++ b/packages/pi-ai/src/utils/oauth/github-copilot.test.ts @@ -2,8 +2,8 @@ import assert from "node:assert/strict"; import { test } from "vitest"; import type { Api, Model } from "../../types.js"; -import type { OAuthCredentials } from "./index.js"; import { githubCopilotOAuthProvider } from "./github-copilot.js"; +import type { OAuthCredentials } from "./index.js"; function makeModel(provider: string, id: string): Model { return { diff --git a/packages/pi-ai/src/utils/validation.ts b/packages/pi-ai/src/utils/validation.ts index 090237a49..7bbfdbbf1 100644 --- a/packages/pi-ai/src/utils/validation.ts +++ b/packages/pi-ai/src/utils/validation.ts @@ -38,7 +38,7 @@ function coerceSchemaValue(schema: unknown, value: unknown): unknown { let next: JsonSchemaObject | null = null; for (const [key, propertySchema] of Object.entries(properties)) { - if (!Object.prototype.hasOwnProperty.call(value, key)) continue; + if (!Object.hasOwn(value, key)) continue; const coercedValue = coerceSchemaValue(propertySchema, value[key]); if (coercedValue !== value[key]) { next ??= { ...value }; diff --git a/packages/pi-coding-agent/scripts/copy-assets.cjs b/packages/pi-coding-agent/scripts/copy-assets.cjs index 2cf1bfded..78ad32e90 100644 --- a/packages/pi-coding-agent/scripts/copy-assets.cjs +++ b/packages/pi-coding-agent/scripts/copy-assets.cjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -const { mkdirSync, cpSync, copyFileSync, readdirSync } = require("fs"); -const { join } = require("path"); +const { mkdirSync, cpSync, copyFileSync, readdirSync } = require("node:fs"); +const { join } = require("node:path"); /** * Recursive directory copy using copyFileSync — workaround for cpSync failures diff --git a/packages/pi-coding-agent/src/cli/file-processor.ts b/packages/pi-coding-agent/src/cli/file-processor.ts index 7d1f87d6c..a217e8b9e 100644 --- a/packages/pi-coding-agent/src/cli/file-processor.ts +++ b/packages/pi-coding-agent/src/cli/file-processor.ts @@ -3,9 +3,9 @@ */ import { access, readFile, stat } from "node:fs/promises"; +import { resolve } from "node:path"; import type { ImageContent } from "@singularity-forge/pi-ai"; import chalk from "chalk"; -import { resolve } from "path"; import { resolveReadPath } from "../core/tools/path-utils.js"; import { formatDimensionNote, resizeImage } from "../utils/image-resize.js"; import { detectSupportedImageMimeTypeFromFile } from "../utils/mime.js"; diff --git a/packages/pi-coding-agent/src/cli/list-models.test.ts b/packages/pi-coding-agent/src/cli/list-models.test.ts index 70d3660d8..e56be64f2 100644 --- a/packages/pi-coding-agent/src/cli/list-models.test.ts +++ b/packages/pi-coding-agent/src/cli/list-models.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; -import { afterEach, beforeEach, describe, it } from "vitest"; import type { Model } from "@singularity-forge/pi-ai"; +import { afterEach, beforeEach, describe, it } from "vitest"; import type { ModelRegistry } from "../core/model-registry.js"; import { listModels } from "./list-models.js"; diff --git a/packages/pi-coding-agent/src/config.ts b/packages/pi-coding-agent/src/config.ts index 2c95ec5ef..5379c8176 100644 --- a/packages/pi-coding-agent/src/config.ts +++ b/packages/pi-coding-agent/src/config.ts @@ -1,6 +1,6 @@ -import { existsSync, readFileSync } from "fs"; -import { homedir } from "os"; -import { dirname, join, resolve } from "path"; +import { existsSync, readFileSync } from "node:fs"; +import { homedir } from "node:os"; +import { dirname, join, resolve } from "node:path"; // ============================================================================= // Package Detection diff --git a/packages/pi-coding-agent/src/core/agent-session-custom-message-queue.test.ts b/packages/pi-coding-agent/src/core/agent-session-custom-message-queue.test.ts index b761238e3..77a7e2ccc 100644 --- a/packages/pi-coding-agent/src/core/agent-session-custom-message-queue.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-custom-message-queue.test.ts @@ -2,9 +2,8 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, beforeEach, describe, it } from "vitest"; - import { Agent, type AgentMessage } from "@singularity-forge/pi-agent-core"; +import { afterEach, beforeEach, describe, it } from "vitest"; import { AgentSession } from "./agent-session.js"; import { AuthStorage } from "./auth-storage.js"; import { ModelRegistry } from "./model-registry.js"; diff --git a/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts b/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts index b81f8a77d..971547eac 100644 --- a/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts @@ -1,7 +1,7 @@ -import { test } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; +import { test } from "vitest"; const source = readFileSync( join(process.cwd(), "packages/pi-coding-agent/src/core/agent-session.ts"), diff --git a/packages/pi-coding-agent/src/core/agent-session-print-mode-persist.test.ts b/packages/pi-coding-agent/src/core/agent-session-print-mode-persist.test.ts index 10888b40e..2aa9de66f 100644 --- a/packages/pi-coding-agent/src/core/agent-session-print-mode-persist.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-print-mode-persist.test.ts @@ -1,7 +1,7 @@ -import { test } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; +import { test } from "vitest"; /** * Regression #4251: `sf -p --model / "msg"` must never mutate diff --git a/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts b/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts index 69013dc6d..e1fc97ecc 100644 --- a/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts @@ -2,13 +2,13 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, beforeEach, describe, it } from "vitest"; +import { Type } from "@sinclair/typebox"; import { Agent } from "@singularity-forge/pi-agent-core"; -import { Type } from "@sinclair/typebox"; -import type { ToolDefinition } from "./extensions/types.js"; +import { afterEach, beforeEach, describe, it } from "vitest"; import { AgentSession } from "./agent-session.js"; import { AuthStorage } from "./auth-storage.js"; +import type { ToolDefinition } from "./extensions/types.js"; import { ModelRegistry } from "./model-registry.js"; import { DefaultResourceLoader } from "./resource-loader.js"; import { SessionManager } from "./session-manager.js"; diff --git a/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts b/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts index ed589fd13..ceea78d63 100644 --- a/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts @@ -1,10 +1,10 @@ // SF — Regression tests for #3616: tool list persistence across newSession() calls // Copyright (c) 2026 Jeremy McSpadden -import { test, describe } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; +import { describe, test } from "vitest"; const source = readFileSync( join(process.cwd(), "packages/pi-coding-agent/src/core/agent-session.ts"), diff --git a/packages/pi-coding-agent/src/core/agent-session.ts b/packages/pi-coding-agent/src/core/agent-session.ts index 64209769c..b6e14bc17 100644 --- a/packages/pi-coding-agent/src/core/agent-session.ts +++ b/packages/pi-coding-agent/src/core/agent-session.ts @@ -15,6 +15,7 @@ import { readFileSync } from "node:fs"; import { basename, dirname, join } from "node:path"; +import { Type } from "@sinclair/typebox"; import type { Agent, AgentEvent, @@ -35,10 +36,9 @@ import { resetApiProviders, supportsXhigh, } from "@singularity-forge/pi-ai"; -import { Type } from "@sinclair/typebox"; import { getDocsPath } from "../config.js"; -import { getErrorMessage } from "../utils/error.js"; import { theme } from "../modes/interactive/theme/theme.js"; +import { getErrorMessage } from "../utils/error.js"; import { stripFrontmatter } from "../utils/frontmatter.js"; import { type BashResult, @@ -54,10 +54,7 @@ import { } from "./compaction/index.js"; import { CompactionOrchestrator } from "./compaction-orchestrator.js"; import { DEFAULT_THINKING_LEVEL } from "./defaults.js"; -import { - exportSessionToHtml, - type ToolHtmlRenderer, -} from "./export-html/index.js"; +import { exportSessionToHtml } from "./export-html/index.js"; import { createToolHtmlRenderer } from "./export-html/tool-renderer.js"; import { type ContextUsage, @@ -83,8 +80,12 @@ import { type TurnStartEvent, wrapRegisteredTools, } from "./extensions/index.js"; -import type { BashExecutionMessage, CustomMessage } from "./messages.js"; import { FallbackResolver } from "./fallback-resolver.js"; +import { + downsizeConversationImages, + isImageDimensionError, +} from "./image-overflow-recovery.js"; +import type { BashExecutionMessage, CustomMessage } from "./messages.js"; import type { ModelRegistry } from "./model-registry.js"; import { expandPromptTemplate, @@ -95,10 +96,6 @@ import type { ResourceLoader, } from "./resource-loader.js"; import { RetryHandler } from "./retry-handler.js"; -import { - isImageDimensionError, - downsizeConversationImages, -} from "./image-overflow-recovery.js"; import type { BranchSummaryEntry, SessionManager } from "./session-manager.js"; import { getLatestCompactionEntry } from "./session-manager.js"; import type { SettingsManager } from "./settings-manager.js"; diff --git a/packages/pi-coding-agent/src/core/artifact-manager.ts b/packages/pi-coding-agent/src/core/artifact-manager.ts index 9da71f720..22daca76c 100644 --- a/packages/pi-coding-agent/src/core/artifact-manager.ts +++ b/packages/pi-coding-agent/src/core/artifact-manager.ts @@ -4,7 +4,7 @@ * Artifacts are stored in a directory alongside the session file, * accessible via artifact:// URLs. */ -import { mkdirSync, readdirSync, writeFileSync, existsSync } from "node:fs"; +import { mkdirSync, readdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; /** diff --git a/packages/pi-coding-agent/src/core/auth-storage.test.ts b/packages/pi-coding-agent/src/core/auth-storage.test.ts index 87d7fa3b2..59d437634 100644 --- a/packages/pi-coding-agent/src/core/auth-storage.test.ts +++ b/packages/pi-coding-agent/src/core/auth-storage.test.ts @@ -1,5 +1,5 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { AuthStorage } from "./auth-storage.js"; // ─── helpers ────────────────────────────────────────────────────────────────── @@ -269,7 +269,7 @@ describe("AuthStorage — areAllCredentialsBackedOff", () => { // ─── mismatched oauth credential for non-OAuth provider (#2083) ─────────────── describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () => { - it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async (t) => { + it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async (_t) => { // Simulates the bug: OpenRouter credential stored as type:"oauth" // but OpenRouter is not a registered OAuth provider. const storage = inMemory({ diff --git a/packages/pi-coding-agent/src/core/auth-storage.ts b/packages/pi-coding-agent/src/core/auth-storage.ts index 2f3477436..1acc0bea3 100644 --- a/packages/pi-coding-agent/src/core/auth-storage.ts +++ b/packages/pi-coding-agent/src/core/auth-storage.ts @@ -9,6 +9,14 @@ * try to refresh tokens simultaneously. */ +import { + chmodSync, + existsSync, + mkdirSync, + readFileSync, + writeFileSync, +} from "node:fs"; +import { dirname, join } from "node:path"; import { getEnvApiKey, type OAuthCredentials, @@ -20,14 +28,6 @@ import { getOAuthProvider, getOAuthProviders, } from "@singularity-forge/pi-ai/oauth"; -import { - chmodSync, - existsSync, - mkdirSync, - readFileSync, - writeFileSync, -} from "fs"; -import { dirname, join } from "path"; import { getAgentDir } from "../config.js"; import { AUTH_LOCK_STALE_MS } from "./constants.js"; import { acquireLockAsync, acquireLockSyncWithRetry } from "./lock-utils.js"; diff --git a/packages/pi-coding-agent/src/core/bash-executor.ts b/packages/pi-coding-agent/src/core/bash-executor.ts index d73c86b40..3c680f9f2 100644 --- a/packages/pi-coding-agent/src/core/bash-executor.ts +++ b/packages/pi-coding-agent/src/core/bash-executor.ts @@ -6,11 +6,11 @@ * - Direct calls from modes that need bash execution */ +import { type ChildProcess, spawn } from "node:child_process"; import { randomBytes } from "node:crypto"; import { createWriteStream, unlinkSync, type WriteStream } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { type ChildProcess, spawn } from "child_process"; /** Track temp files created by bash execution for cleanup on exit. */ const bashTempFiles = new Set(); @@ -29,6 +29,7 @@ function registerTempCleanup(): void { } }); } + import { processStreamChunk, type StreamState, diff --git a/packages/pi-coding-agent/src/core/blob-store.ts b/packages/pi-coding-agent/src/core/blob-store.ts index 0258cb995..7295711ab 100644 --- a/packages/pi-coding-agent/src/core/blob-store.ts +++ b/packages/pi-coding-agent/src/core/blob-store.ts @@ -7,13 +7,13 @@ */ import { createHash } from "node:crypto"; import { + accessSync, mkdirSync, readdirSync, readFileSync, - writeFileSync, - accessSync, - unlinkSync, statSync, + unlinkSync, + writeFileSync, } from "node:fs"; import { join } from "node:path"; diff --git a/packages/pi-coding-agent/src/core/compaction-orchestrator.ts b/packages/pi-coding-agent/src/core/compaction-orchestrator.ts index 10b93ecbb..1209ded6c 100644 --- a/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +++ b/packages/pi-coding-agent/src/core/compaction-orchestrator.ts @@ -12,6 +12,8 @@ import type { Agent } from "@singularity-forge/pi-agent-core"; import type { AssistantMessage, Model } from "@singularity-forge/pi-ai"; import { isContextOverflow } from "@singularity-forge/pi-ai"; +import { getErrorMessage } from "../utils/error.js"; +import type { AgentSessionEvent } from "./agent-session.js"; import { type CompactionResult, calculateContextTokens, @@ -25,11 +27,9 @@ import type { SessionBeforeCompactResult, } from "./extensions/index.js"; import type { ModelRegistry } from "./model-registry.js"; -import { getLatestCompactionEntry } from "./session-manager.js"; import type { CompactionEntry, SessionManager } from "./session-manager.js"; +import { getLatestCompactionEntry } from "./session-manager.js"; import type { SettingsManager } from "./settings-manager.js"; -import type { AgentSessionEvent } from "./agent-session.js"; -import { getErrorMessage } from "../utils/error.js"; /** Dependencies injected from AgentSession into CompactionOrchestrator */ export interface CompactionOrchestratorDeps { diff --git a/packages/pi-coding-agent/src/core/compaction-utils.test.ts b/packages/pi-coding-agent/src/core/compaction-utils.test.ts index c00f65efe..bfa066a9a 100644 --- a/packages/pi-coding-agent/src/core/compaction-utils.test.ts +++ b/packages/pi-coding-agent/src/core/compaction-utils.test.ts @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; -import { test } from "vitest"; - import type { Message } from "@singularity-forge/pi-ai"; +import { test } from "vitest"; import { serializeConversation } from "./compaction/index.js"; diff --git a/packages/pi-coding-agent/src/core/compaction/compaction.test.ts b/packages/pi-coding-agent/src/core/compaction/compaction.test.ts index b830edf66..d1bd85355 100644 --- a/packages/pi-coding-agent/src/core/compaction/compaction.test.ts +++ b/packages/pi-coding-agent/src/core/compaction/compaction.test.ts @@ -4,15 +4,14 @@ */ import assert from "node:assert/strict"; -import { vi, describe, it } from "vitest"; - import type { AgentMessage } from "@singularity-forge/pi-agent-core"; -import type { Model, AssistantMessage } from "@singularity-forge/pi-ai"; +import type { AssistantMessage, Model } from "@singularity-forge/pi-ai"; +import { describe, it, vi } from "vitest"; import { - generateSummary, - estimateTokens, chunkMessages, + estimateTokens, + generateSummary, } from "./compaction.js"; // --------------------------------------------------------------------------- diff --git a/packages/pi-coding-agent/src/core/compaction/compaction.ts b/packages/pi-coding-agent/src/core/compaction/compaction.ts index 958ffc53f..e2e1dcbb8 100644 --- a/packages/pi-coding-agent/src/core/compaction/compaction.ts +++ b/packages/pi-coding-agent/src/core/compaction/compaction.ts @@ -608,7 +608,7 @@ export async function generateSummary( // Overhead for the prompt framing, system prompt, and response budget const promptOverhead = 4_000; - const maxTokens = Math.floor(0.8 * reserveTokens); + const _maxTokens = Math.floor(0.8 * reserveTokens); const maxInputTokens = (model.contextWindow || 200_000) - reserveTokens - promptOverhead; diff --git a/packages/pi-coding-agent/src/core/contextual-tips.test.ts b/packages/pi-coding-agent/src/core/contextual-tips.test.ts index 9bfc83716..144672d20 100644 --- a/packages/pi-coding-agent/src/core/contextual-tips.test.ts +++ b/packages/pi-coding-agent/src/core/contextual-tips.test.ts @@ -1,5 +1,5 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { ContextualTips } from "./contextual-tips.js"; const baseCtx = { diff --git a/packages/pi-coding-agent/src/core/discovery-cache.ts b/packages/pi-coding-agent/src/core/discovery-cache.ts index a8255cb5f..c09da669f 100644 --- a/packages/pi-coding-agent/src/core/discovery-cache.ts +++ b/packages/pi-coding-agent/src/core/discovery-cache.ts @@ -9,8 +9,8 @@ import { readFileSync, renameSync, writeFileSync, -} from "fs"; -import { dirname, join } from "path"; +} from "node:fs"; +import { dirname, join } from "node:path"; import { getAgentDir } from "../config.js"; import { type DiscoveredModel, getDefaultTTL } from "./model-discovery.js"; diff --git a/packages/pi-coding-agent/src/core/export-html/index.ts b/packages/pi-coding-agent/src/core/export-html/index.ts index 7477d8db1..59df59bd6 100644 --- a/packages/pi-coding-agent/src/core/export-html/index.ts +++ b/packages/pi-coding-agent/src/core/export-html/index.ts @@ -1,6 +1,6 @@ +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { basename, join } from "node:path"; import type { AgentState } from "@singularity-forge/pi-agent-core"; -import { existsSync, readFileSync, writeFileSync } from "fs"; -import { basename, join } from "path"; import { APP_NAME, getExportTemplateDir } from "../../config.js"; import { getResolvedThemeColors, @@ -191,11 +191,11 @@ function generateHtml(sessionData: SessionData, themeName?: string): string { .replace("{{INFO_BG}}", infoBg); return template - .replace("{{CSS}}", css) - .replace("{{JS}}", templateJs) - .replace("{{SESSION_DATA}}", sessionDataBase64) - .replace("{{MARKED_JS}}", markedJs) - .replace("{{HIGHLIGHT_JS}}", hljsJs); + .replace("/*__SF_EXPORT_CSS__*/", css) + .replace("/*__SF_EXPORT_JS__*/", templateJs) + .replace('"__SF_SESSION_DATA__"', sessionDataBase64) + .replace("/*__SF_EXPORT_MARKED_JS__*/", markedJs) + .replace("/*__SF_EXPORT_HIGHLIGHT_JS__*/", hljsJs); } /** Built-in tool names that have custom rendering in template.js */ diff --git a/packages/pi-coding-agent/src/core/export-html/template.html b/packages/pi-coding-agent/src/core/export-html/template.html index 42f2a45b0..e17d37e8c 100644 --- a/packages/pi-coding-agent/src/core/export-html/template.html +++ b/packages/pi-coding-agent/src/core/export-html/template.html @@ -5,11 +5,11 @@ Session Export - +
@@ -38,17 +38,16 @@ - + - + - + - diff --git a/packages/pi-coding-agent/src/core/export-html/template.js b/packages/pi-coding-agent/src/core/export-html/template.js index d0d2e122e..419a0f2a5 100644 --- a/packages/pi-coding-agent/src/core/export-html/template.js +++ b/packages/pi-coding-agent/src/core/export-html/template.js @@ -1,6 +1,4 @@ -(function () { - "use strict"; - +(() => { // ============================================================ // DATA LOADING // ============================================================ @@ -1208,7 +1206,7 @@ * Download the session data as a JSONL file. * Reconstructs the original format: header line + entry lines. */ - window.downloadSessionJson = function () { + window.downloadSessionJson = () => { // Build JSONL content: header first, then all entries const lines = []; if (header) { @@ -1278,7 +1276,7 @@ await navigator.clipboard.writeText(text); success = true; } - } catch (err) { + } catch (_err) { // Clipboard API failed, try fallback } @@ -1699,7 +1697,7 @@ // Escape HTML tags in text (but not code blocks) function escapeHtmlTags(text) { - return text.replace(/<(?=[a-zA-Z\/])/g, "<"); + return text.replace(/<(?=[a-zA-Z/])/g, "<"); } // Configure marked with syntax highlighting and HTML escaping for text diff --git a/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts b/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts index f82f7d8cc..bface0ff2 100644 --- a/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts @@ -1,11 +1,11 @@ // SF — Extension Manifest Tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it } from "vitest"; import assert from "node:assert/strict"; -import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; +import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, it } from "vitest"; import { readManifest, readManifestFromEntryPath, diff --git a/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts b/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts index e81438924..ecb7cd0b4 100644 --- a/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts @@ -1,11 +1,11 @@ // SF — Extension Sort Tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it } from "vitest"; import assert from "node:assert/strict"; -import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; +import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, it } from "vitest"; import { sortExtensionPaths } from "./extension-sort.js"; function createExtDir(base: string, id: string, deps?: string[]): string { diff --git a/packages/pi-coding-agent/src/core/extensions/index.ts b/packages/pi-coding-agent/src/core/extensions/index.ts index 1cddfb313..64025fc41 100644 --- a/packages/pi-coding-agent/src/core/extensions/index.ts +++ b/packages/pi-coding-agent/src/core/extensions/index.ts @@ -2,6 +2,11 @@ * Extension system for lifecycle events and custom tools. */ +export type { + SlashCommandInfo, + SlashCommandLocation, + SlashCommandSource, +} from "../slash-commands.js"; export type { ExtensionManifest } from "./extension-manifest.js"; export { readManifest, @@ -9,11 +14,6 @@ export { } from "./extension-manifest.js"; export type { SortResult, SortWarning } from "./extension-sort.js"; export { sortExtensionPaths } from "./extension-sort.js"; -export type { - SlashCommandInfo, - SlashCommandLocation, - SlashCommandSource, -} from "../slash-commands.js"; export { createExtensionRuntime, discoverAndLoadExtensions, @@ -34,6 +34,9 @@ export type { } from "./runner.js"; export { ExtensionRunner } from "./runner.js"; export type { + // Events - Adjust Tool Set (ADR-005) + AdjustToolSetEvent, + AdjustToolSetResult, AgentEndEvent, AgentStartEvent, // Re-exports @@ -44,15 +47,14 @@ export type { // Events - Tool (ToolCallEvent types) BashToolCallEvent, BashToolResultEvent, + BashTransformEvent, + BashTransformEventResult, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, // Context CompactOptions, - // Events - Adjust Tool Set (ADR-005) - AdjustToolSetEvent, - AdjustToolSetResult, // Events - Agent ContextEvent, // Event Results @@ -94,6 +96,11 @@ export type { InputEventResult, InputSource, KeybindingsManager, + LifecycleHookContext, + LifecycleHookHandler, + LifecycleHookMap, + LifecycleHookPhase, + LifecycleHookScope, LoadExtensionsResult, LsToolCallEvent, LsToolResultEvent, @@ -109,11 +116,6 @@ export type { // Provider Registration ProviderConfig, ProviderModelConfig, - LifecycleHookContext, - LifecycleHookHandler, - LifecycleHookMap, - LifecycleHookPhase, - LifecycleHookScope, ReadToolCallEvent, ReadToolResultEvent, // Commands @@ -162,8 +164,6 @@ export type { // Events - User Bash UserBashEvent, UserBashEventResult, - BashTransformEvent, - BashTransformEventResult, WidgetPlacement, WriteToolCallEvent, WriteToolResultEvent, diff --git a/packages/pi-coding-agent/src/core/extensions/loader.test.ts b/packages/pi-coding-agent/src/core/extensions/loader.test.ts index b2e953ad3..f5561588d 100644 --- a/packages/pi-coding-agent/src/core/extensions/loader.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/loader.test.ts @@ -1,20 +1,20 @@ -import { describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { pathToFileURL } from "node:url"; -import { - isProjectTrusted, - trustProject, - getUntrustedExtensionPaths, -} from "./project-trust.js"; +import { afterEach, beforeEach, describe, it } from "vitest"; import { containsTypeScriptSyntax, importExtensionModule, loadExtensions, resetExtensionLoaderCache, } from "./loader.js"; +import { + getUntrustedExtensionPaths, + isProjectTrusted, + trustProject, +} from "./project-trust.js"; // ─── helpers ────────────────────────────────────────────────────────────────── diff --git a/packages/pi-coding-agent/src/core/extensions/loader.ts b/packages/pi-coding-agent/src/core/extensions/loader.ts index efaf4250e..7ca5299bd 100644 --- a/packages/pi-coding-agent/src/core/extensions/loader.ts +++ b/packages/pi-coding-agent/src/core/extensions/loader.ts @@ -10,25 +10,25 @@ import * as os from "node:os"; import * as path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { createJiti } from "@mariozechner/jiti"; +import * as _bundledMcpClient from "@modelcontextprotocol/sdk/client"; +import * as _bundledMcpSse from "@modelcontextprotocol/sdk/client/sse.js"; +import * as _bundledMcpStdio from "@modelcontextprotocol/sdk/client/stdio.js"; +import * as _bundledMcpStreamableHttp from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import * as _bundledMcpServer from "@modelcontextprotocol/sdk/server"; +import * as _bundledMcpServerSse from "@modelcontextprotocol/sdk/server/sse.js"; +import * as _bundledMcpServerStdio from "@modelcontextprotocol/sdk/server/stdio.js"; +import * as _bundledMcpServerStreamableHttp from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import * as _bundledMcpTypes from "@modelcontextprotocol/sdk/types.js"; +// Static imports of packages that extensions may use. +// These MUST be static so Bun bundles them into the compiled binary. +// The virtualModules option then makes them available to extensions. +import * as _bundledTypebox from "@sinclair/typebox"; import * as _bundledPiAgentCore from "@singularity-forge/pi-agent-core"; import * as _bundledPiAi from "@singularity-forge/pi-ai"; import * as _bundledPiAiOauth from "@singularity-forge/pi-ai/oauth"; import type { KeyId } from "@singularity-forge/pi-tui"; import * as _bundledPiTui from "@singularity-forge/pi-tui"; -// Static imports of packages that extensions may use. -// These MUST be static so Bun bundles them into the compiled binary. -// The virtualModules option then makes them available to extensions. -import * as _bundledTypebox from "@sinclair/typebox"; import * as _bundledYaml from "yaml"; -import * as _bundledMcpClient from "@modelcontextprotocol/sdk/client"; -import * as _bundledMcpStdio from "@modelcontextprotocol/sdk/client/stdio.js"; -import * as _bundledMcpStreamableHttp from "@modelcontextprotocol/sdk/client/streamableHttp.js"; -import * as _bundledMcpSse from "@modelcontextprotocol/sdk/client/sse.js"; -import * as _bundledMcpServer from "@modelcontextprotocol/sdk/server"; -import * as _bundledMcpServerStdio from "@modelcontextprotocol/sdk/server/stdio.js"; -import * as _bundledMcpServerSse from "@modelcontextprotocol/sdk/server/sse.js"; -import * as _bundledMcpServerStreamableHttp from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import * as _bundledMcpTypes from "@modelcontextprotocol/sdk/types.js"; import { getAgentDir, isBunBinary } from "../../config.js"; // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts, // avoiding a circular dependency. Extensions can import from "@singularity-forge/pi-coding-agent. @@ -37,18 +37,20 @@ import { createEventBus, type EventBus } from "../event-bus.js"; import type { ExecOptions } from "../exec.js"; import { execCommand } from "../exec.js"; import { getUntrustedExtensionPaths } from "./project-trust.js"; + export { + getUntrustedExtensionPaths, isProjectTrusted, trustProject, - getUntrustedExtensionPaths, } from "./project-trust.js"; + import { registerToolCompatibility } from "../tools/tool-compatibility-registry.js"; import type { Extension, ExtensionAPI, ExtensionFactory, - LifecycleHookHandler, ExtensionRuntime, + LifecycleHookHandler, LoadExtensionsResult, MessageRenderer, ProviderConfig, diff --git a/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts b/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts index d3f5787f9..b9503e765 100644 --- a/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts @@ -1,8 +1,8 @@ // sf — Regression test: pendingProviderRegistrations must be flushed exactly once (#3576) // Copyright (c) 2026 Jeremy McSpadden -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; /** * This test validates that the provider preflush pattern in sdk.ts clears diff --git a/packages/pi-coding-agent/src/core/extensions/runner.test.ts b/packages/pi-coding-agent/src/core/extensions/runner.test.ts index 439cd1b44..e50b34c4e 100644 --- a/packages/pi-coding-agent/src/core/extensions/runner.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/runner.test.ts @@ -1,13 +1,13 @@ import assert from "node:assert/strict"; -import { describe, it } from "vitest"; -import { ExtensionRunner } from "./runner.js"; -import type { Extension, ExtensionRuntime, ToolCallEvent } from "./index.js"; -import { SessionManager } from "../session-manager.js"; -import { ModelRegistry } from "../model-registry.js"; import { mkdtempSync, rmSync } from "node:fs"; -import { join } from "node:path"; import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, it } from "vitest"; import { AuthStorage } from "../auth-storage.js"; +import { ModelRegistry } from "../model-registry.js"; +import { SessionManager } from "../session-manager.js"; +import type { Extension, ExtensionRuntime, ToolCallEvent } from "./index.js"; +import { ExtensionRunner } from "./runner.js"; function makeMinimalRuntime(): ExtensionRuntime { return { diff --git a/packages/pi-coding-agent/src/core/extensions/types.ts b/packages/pi-coding-agent/src/core/extensions/types.ts index 398082183..c7543c2c5 100644 --- a/packages/pi-coding-agent/src/core/extensions/types.ts +++ b/packages/pi-coding-agent/src/core/extensions/types.ts @@ -10,6 +10,7 @@ * @remarks Stale-dist verification comment — will be reverted. */ +import type { Static, TSchema } from "@sinclair/typebox"; import type { AgentMessage, AgentToolResult, @@ -39,7 +40,6 @@ import type { OverlayOptions, TUI, } from "@singularity-forge/pi-tui"; -import type { Static, TSchema } from "@sinclair/typebox"; import type { Theme } from "../../modes/interactive/theme/theme.js"; import type { AuthStorage } from "../auth-storage.js"; import type { BashResult } from "../bash-executor.js"; @@ -79,8 +79,8 @@ import type { } from "../tools/index.js"; export type { ExecOptions, ExecResult } from "../exec.js"; -export type { AgentToolResult, AgentToolUpdateCallback }; export type { AppAction, KeybindingsManager } from "../keybindings.js"; +export type { AgentToolResult, AgentToolUpdateCallback }; // ============================================================================ // UI Context @@ -1197,11 +1197,10 @@ export type LifecycleHookMap = Record< // ============================================================================ /** Handler function type for events */ -// biome-ignore lint/suspicious/noConfusingVoidType: void allows bare return statements export type ExtensionHandler = ( event: E, ctx: ExtensionContext, -) => Promise | R | void; +) => Promise | R | undefined; /** * ExtensionAPI passed to extension factory functions. diff --git a/packages/pi-coding-agent/src/core/fallback-resolver.test.ts b/packages/pi-coding-agent/src/core/fallback-resolver.test.ts index f7e5a4d89..c0499381c 100644 --- a/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +++ b/packages/pi-coding-agent/src/core/fallback-resolver.test.ts @@ -1,11 +1,11 @@ // SF Provider Fallback Resolver Tests // Copyright (c) 2026 Jeremy McSpadden -import { vi, describe, it, beforeEach } from "vitest"; import assert from "node:assert/strict"; -import { FallbackResolver } from "./fallback-resolver.js"; import type { Api, Model } from "@singularity-forge/pi-ai"; +import { describe, it, vi } from "vitest"; import type { AuthStorage } from "./auth-storage.js"; +import { FallbackResolver } from "./fallback-resolver.js"; import type { ModelRegistry } from "./model-registry.js"; import type { FallbackChainEntry, diff --git a/packages/pi-coding-agent/src/core/footer-data-provider.ts b/packages/pi-coding-agent/src/core/footer-data-provider.ts index d9252b7b6..436aa5b41 100644 --- a/packages/pi-coding-agent/src/core/footer-data-provider.ts +++ b/packages/pi-coding-agent/src/core/footer-data-provider.ts @@ -1,5 +1,11 @@ -import { existsSync, type FSWatcher, readFileSync, statSync, watch } from "fs"; -import { dirname, join, resolve } from "path"; +import { + existsSync, + type FSWatcher, + readFileSync, + statSync, + watch, +} from "node:fs"; +import { dirname, join, resolve } from "node:path"; /** * Find the git HEAD path by walking up from cwd. diff --git a/packages/pi-coding-agent/src/core/fs-utils.test.ts b/packages/pi-coding-agent/src/core/fs-utils.test.ts index 9126423c0..3f9961e0f 100644 --- a/packages/pi-coding-agent/src/core/fs-utils.test.ts +++ b/packages/pi-coding-agent/src/core/fs-utils.test.ts @@ -1,8 +1,8 @@ import assert from "node:assert/strict"; -import { describe, it, afterEach } from "vitest"; -import { mkdtempSync, readFileSync, rmSync, existsSync } from "node:fs"; -import { join } from "node:path"; +import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, it } from "vitest"; import { atomicWriteFileSync } from "./fs-utils.js"; describe("atomicWriteFileSync", () => { diff --git a/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts b/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts index 5b41364b4..52c93b335 100644 --- a/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts +++ b/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts @@ -1,11 +1,11 @@ import assert from "node:assert/strict"; +import type { Message } from "@singularity-forge/pi-ai"; import { describe, it } from "vitest"; import { + downsizeConversationImages, isImageDimensionError, MANY_IMAGE_MAX_DIMENSION, - downsizeConversationImages, } from "./image-overflow-recovery.js"; -import type { Message } from "@singularity-forge/pi-ai"; // ─── isImageDimensionError ──────────────────────────────────────────────────── diff --git a/packages/pi-coding-agent/src/core/image-overflow-recovery.ts b/packages/pi-coding-agent/src/core/image-overflow-recovery.ts index 7d7523e21..bf6a841ce 100644 --- a/packages/pi-coding-agent/src/core/image-overflow-recovery.ts +++ b/packages/pi-coding-agent/src/core/image-overflow-recovery.ts @@ -11,8 +11,8 @@ */ import type { - Message, ImageContent, + Message, TextContent, } from "@singularity-forge/pi-ai"; diff --git a/packages/pi-coding-agent/src/core/index.ts b/packages/pi-coding-agent/src/core/index.ts index e0cb27d8c..a67e76f3a 100644 --- a/packages/pi-coding-agent/src/core/index.ts +++ b/packages/pi-coding-agent/src/core/index.ts @@ -17,8 +17,8 @@ export { executeBash, executeBashWithOperations, } from "./bash-executor.js"; -export { FallbackResolver, type FallbackResult } from "./fallback-resolver.js"; export type { CompactionResult } from "./compaction/index.js"; +export { ContextualTips, type TipContext } from "./contextual-tips.js"; export { createEventBus, type EventBus, @@ -38,7 +38,6 @@ export { type ExecResult, type Extension, type ExtensionAPI, - type ExtensionManifest, type ExtensionCommandContext, type ExtensionContext, type ExtensionError, @@ -46,12 +45,15 @@ export { type ExtensionFactory, type ExtensionFlag, type ExtensionHandler, + type ExtensionManifest, ExtensionRunner, type ExtensionShortcut, type ExtensionUIContext, type LoadExtensionsResult, type MessageRenderer, type RegisteredCommand, + readManifest, + readManifestFromEntryPath, type SessionBeforeCompactEvent, type SessionBeforeForkEvent, type SessionBeforeSwitchEvent, @@ -62,12 +64,10 @@ export { type SessionStartEvent, type SessionSwitchEvent, type SessionTreeEvent, - type ToolCallEvent, - readManifest, - readManifestFromEntryPath, type SortResult, type SortWarning, sortExtensionPaths, + type ToolCallEvent, type ToolDefinition, type ToolRenderResultOptions, type ToolResultEvent, @@ -75,5 +75,4 @@ export { type TurnStartEvent, wrapToolsWithExtensions, } from "./extensions/index.js"; - -export { ContextualTips, type TipContext } from "./contextual-tips.js"; +export { FallbackResolver, type FallbackResult } from "./fallback-resolver.js"; diff --git a/packages/pi-coding-agent/src/core/keybindings.ts b/packages/pi-coding-agent/src/core/keybindings.ts index 5ba03c63c..5367146be 100644 --- a/packages/pi-coding-agent/src/core/keybindings.ts +++ b/packages/pi-coding-agent/src/core/keybindings.ts @@ -1,3 +1,5 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; import { DEFAULT_EDITOR_KEYBINDINGS, type EditorAction, @@ -7,8 +9,6 @@ import { matchesKey, setEditorKeybindings, } from "@singularity-forge/pi-tui"; -import { existsSync, readFileSync } from "fs"; -import { join } from "path"; import { getAgentDir } from "../config.js"; /** diff --git a/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts b/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts index 57526131b..3d537b611 100644 --- a/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +++ b/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts @@ -1,19 +1,19 @@ import assert from "node:assert/strict"; import { - mkdtempSync, + existsSync, mkdirSync, + mkdtempSync, rmSync, writeFileSync, - existsSync, } from "node:fs"; import { homedir, tmpdir } from "node:os"; import { join, resolve } from "node:path"; -import { describe, it, afterEach } from "vitest"; +import { afterEach, describe, it } from "vitest"; import { - readManifestRuntimeDeps, collectRuntimeDependencies, - verifyRuntimeDependencies, + readManifestRuntimeDeps, resolveLocalSourcePath, + verifyRuntimeDependencies, } from "./lifecycle-hooks.js"; const _tmpDirs: string[] = []; diff --git a/packages/pi-coding-agent/src/core/lifecycle-hooks.ts b/packages/pi-coding-agent/src/core/lifecycle-hooks.ts index 1e4401e47..54e988a40 100644 --- a/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +++ b/packages/pi-coding-agent/src/core/lifecycle-hooks.ts @@ -6,12 +6,12 @@ import { pathToFileURL } from "node:url"; import { parseGitUrl } from "../utils/git.js"; import { importExtensionModule, - loadExtensions, type LifecycleHookContext, - type LifecycleHookMap, type LifecycleHookHandler, + type LifecycleHookMap, type LifecycleHookPhase, type LifecycleHookScope, + loadExtensions, } from "./extensions/index.js"; import type { DefaultPackageManager } from "./package-manager.js"; @@ -244,7 +244,7 @@ const _legacyModuleCache = new Map>(); async function runLegacyExportHook( entryPath: string, phase: LifecycleHookPhase, - context: LifecycleHookContext, + _context: LifecycleHookContext, ): Promise { try { let module = _legacyModuleCache.get(entryPath); diff --git a/packages/pi-coding-agent/src/core/lsp/client.ts b/packages/pi-coding-agent/src/core/lsp/client.ts index 01868af47..d6b710e81 100644 --- a/packages/pi-coding-agent/src/core/lsp/client.ts +++ b/packages/pi-coding-agent/src/core/lsp/client.ts @@ -2,13 +2,13 @@ import { spawn } from "node:child_process"; import * as fsPromises from "node:fs/promises"; import type { Writable } from "node:stream"; import { killProcessTree } from "../../utils/shell.js"; +import { applyWorkspaceEdit } from "./edits.js"; import { - ToolAbortError, isEnoent, + ToolAbortError, throwIfAborted, untilAborted, } from "./helpers.js"; -import { applyWorkspaceEdit } from "./edits.js"; import { getLspmuxCommand, isLspmuxSupported } from "./lspmux.js"; import type { Diagnostic, diff --git a/packages/pi-coding-agent/src/core/lsp/config.ts b/packages/pi-coding-agent/src/core/lsp/config.ts index 180eef22c..fadfeeff3 100644 --- a/packages/pi-coding-agent/src/core/lsp/config.ts +++ b/packages/pi-coding-agent/src/core/lsp/config.ts @@ -1,10 +1,10 @@ +import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; +import { globSync } from "node:fs"; import { createRequire } from "node:module"; import * as os from "node:os"; import * as path from "node:path"; -import { spawnSync } from "node:child_process"; import YAML from "yaml"; -import { globSync } from "node:fs"; import { CONFIG_DIR_NAME } from "../../config.js"; import { isRecord } from "./helpers.js"; import type { ServerConfig } from "./types.js"; @@ -75,7 +75,7 @@ function normalizeStringArray(value: unknown): string[] | null { } function normalizeServerConfig( - name: string, + _name: string, config: Partial, ): ServerConfig | null { const command = diff --git a/packages/pi-coding-agent/src/core/lsp/index.ts b/packages/pi-coding-agent/src/core/lsp/index.ts index 8986723c5..e17913d03 100644 --- a/packages/pi-coding-agent/src/core/lsp/index.ts +++ b/packages/pi-coding-agent/src/core/lsp/index.ts @@ -1,7 +1,7 @@ +import { spawn } from "node:child_process"; import * as fs from "node:fs"; import * as fsSync from "node:fs"; import * as path from "node:path"; -import { spawn } from "node:child_process"; import type { AgentTool, AgentToolResult, @@ -20,13 +20,13 @@ import { import { getServerForFile, getServersForFile, + hasRootMarkers, type LspConfig, loadConfig, - hasRootMarkers, resolveCommand, } from "./config.js"; import { applyTextEdits, applyWorkspaceEdit } from "./edits.js"; -import { ToolAbortError, clampTimeout, throwIfAborted } from "./helpers.js"; +import { clampTimeout, ToolAbortError, throwIfAborted } from "./helpers.js"; import { detectLspmux } from "./lspmux.js"; import { type CallHierarchyIncomingCall, @@ -166,7 +166,7 @@ const BATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS = 400; const MAX_GLOB_DIAGNOSTIC_TARGETS = 20; const WORKSPACE_SYMBOL_LIMIT = 200; -function limitDiagnosticMessages(messages: string[]): string[] { +function _limitDiagnosticMessages(messages: string[]): string[] { if (messages.length <= DIAGNOSTIC_MESSAGE_LIMIT) { return messages; } @@ -431,7 +431,7 @@ async function runWorkspaceDiagnostics( proc.stdout?.on("data", (chunk: Buffer) => stdoutChunks.push(chunk)); proc.stderr?.on("data", (chunk: Buffer) => stderrChunks.push(chunk)); - const exitCode = await new Promise((resolve) => { + const _exitCode = await new Promise((resolve) => { proc.on("exit", (code: number | null) => resolve(code ?? 1)); }); @@ -541,7 +541,7 @@ export function createLspTool( "No language servers configured for this project.", ]; const matchedButMissing: string[] = []; - const noMarkers: string[] = []; + const _noMarkers: string[] = []; for (const [name, def] of Object.entries(DEFAULTS)) { if (hasRootMarkers(cwd, def.rootMarkers)) { diff --git a/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts b/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts index 5a09eb61f..93fa56e90 100644 --- a/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +++ b/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts @@ -9,11 +9,11 @@ * containing an lsp.json that uses the legacy key. */ -import { describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; import * as fs from "node:fs"; -import * as path from "node:path"; import * as os from "node:os"; +import * as path from "node:path"; +import { afterEach, beforeEach, describe, it } from "vitest"; import { loadConfig } from "./config.js"; describe("LSP legacy server key aliases", () => { diff --git a/packages/pi-coding-agent/src/core/lsp/types.ts b/packages/pi-coding-agent/src/core/lsp/types.ts index 7a0e0a9e0..5fd22bb2f 100644 --- a/packages/pi-coding-agent/src/core/lsp/types.ts +++ b/packages/pi-coding-agent/src/core/lsp/types.ts @@ -1,5 +1,5 @@ -import { type Static, type TUnsafe, Type } from "@sinclair/typebox"; import type { ChildProcess } from "node:child_process"; +import { type Static, type TUnsafe, Type } from "@sinclair/typebox"; function StringEnum( values: T, diff --git a/packages/pi-coding-agent/src/core/lsp/utils.ts b/packages/pi-coding-agent/src/core/lsp/utils.ts index 450ba2416..bbe600024 100644 --- a/packages/pi-coding-agent/src/core/lsp/utils.ts +++ b/packages/pi-coding-agent/src/core/lsp/utils.ts @@ -1,6 +1,6 @@ import * as fsPromises from "node:fs/promises"; -import path from "node:path"; import { glob } from "node:fs/promises"; +import path from "node:path"; import { isEnoent } from "./helpers.js"; import type { CallHierarchyItem, @@ -14,7 +14,6 @@ import type { SignatureHelp, SymbolInformation, SymbolKind, - TextEdit, WorkspaceEdit, } from "./types.js"; diff --git a/packages/pi-coding-agent/src/core/messages.test.ts b/packages/pi-coding-agent/src/core/messages.test.ts index 079decef7..a55bb89db 100644 --- a/packages/pi-coding-agent/src/core/messages.test.ts +++ b/packages/pi-coding-agent/src/core/messages.test.ts @@ -6,9 +6,9 @@ * user-typed input when converted to LLM messages. */ -import { test } from "vitest"; import assert from "node:assert/strict"; -import { convertToLlm, type CustomMessage } from "./messages.js"; +import { test } from "vitest"; +import { type CustomMessage, convertToLlm } from "./messages.js"; /** Extract the first content block from a message, asserting array content. */ function firstTextBlock(msg: ReturnType[number]) { diff --git a/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts b/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts index 584180787..d0e6f5e25 100644 --- a/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts +++ b/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts @@ -1,5 +1,4 @@ import assert from "node:assert/strict"; -import { describe, it } from "vitest"; import type { Api, AssistantMessageEventStream, @@ -7,6 +6,7 @@ import type { Model, SimpleStreamOptions, } from "@singularity-forge/pi-ai"; +import { describe, it } from "vitest"; import type { AuthStorage } from "./auth-storage.js"; import { ModelRegistry } from "./model-registry.js"; diff --git a/packages/pi-coding-agent/src/core/model-registry.ts b/packages/pi-coding-agent/src/core/model-registry.ts index 4cf28af85..b2e92c8f5 100644 --- a/packages/pi-coding-agent/src/core/model-registry.ts +++ b/packages/pi-coding-agent/src/core/model-registry.ts @@ -2,6 +2,8 @@ * Model registry - manages built-in and custom models, provides API key resolution. */ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; import { type Static, Type } from "@sinclair/typebox"; import { type Api, @@ -26,8 +28,6 @@ import { resetOAuthProviders, } from "@singularity-forge/pi-ai/oauth"; import AjvModule from "ajv"; -import { existsSync, readFileSync } from "fs"; -import { join } from "path"; import { getAgentDir } from "../config.js"; import type { AuthStorage } from "./auth-storage.js"; import { ModelDiscoveryCache } from "./discovery-cache.js"; diff --git a/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts b/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts index bc0ee1d25..dfd5057cc 100644 --- a/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +++ b/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; -import { describe, it } from "vitest"; import type { Api, Model } from "@singularity-forge/pi-ai"; +import { describe, it } from "vitest"; import type { ModelRegistry } from "./model-registry.js"; import { findInitialModel } from "./model-resolver.js"; diff --git a/packages/pi-coding-agent/src/core/model-resolver.test.ts b/packages/pi-coding-agent/src/core/model-resolver.test.ts index f3055fb73..4fe7bbf19 100644 --- a/packages/pi-coding-agent/src/core/model-resolver.test.ts +++ b/packages/pi-coding-agent/src/core/model-resolver.test.ts @@ -5,8 +5,8 @@ * "current". */ -import { test } from "vitest"; import assert from "node:assert/strict"; +import { test } from "vitest"; import { findInitialModel } from "./model-resolver.js"; function fakeRegistry(options: { diff --git a/packages/pi-coding-agent/src/core/models-json-writer.test.ts b/packages/pi-coding-agent/src/core/models-json-writer.test.ts index 14f6d3dca..271737a00 100644 --- a/packages/pi-coding-agent/src/core/models-json-writer.test.ts +++ b/packages/pi-coding-agent/src/core/models-json-writer.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs"; +import { mkdirSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, it } from "vitest"; diff --git a/packages/pi-coding-agent/src/core/models-json-writer.ts b/packages/pi-coding-agent/src/core/models-json-writer.ts index 030ac7b27..0a73aadf9 100644 --- a/packages/pi-coding-agent/src/core/models-json-writer.ts +++ b/packages/pi-coding-agent/src/core/models-json-writer.ts @@ -3,8 +3,8 @@ * Prevents concurrent writes from corrupting the config file. */ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; -import { dirname, join } from "path"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; import lockfile from "proper-lockfile"; import { getAgentDir } from "../config.js"; diff --git a/packages/pi-coding-agent/src/core/package-commands.test.ts b/packages/pi-coding-agent/src/core/package-commands.test.ts index 3b948e981..87f424810 100644 --- a/packages/pi-coding-agent/src/core/package-commands.test.ts +++ b/packages/pi-coding-agent/src/core/package-commands.test.ts @@ -1,8 +1,8 @@ import assert from "node:assert/strict"; import { existsSync, - mkdtempSync, mkdirSync, + mkdtempSync, readFileSync, rmSync, writeFileSync, diff --git a/packages/pi-coding-agent/src/core/package-commands.ts b/packages/pi-coding-agent/src/core/package-commands.ts index 0cb14568a..c217bb8e6 100644 --- a/packages/pi-coding-agent/src/core/package-commands.ts +++ b/packages/pi-coding-agent/src/core/package-commands.ts @@ -1,6 +1,6 @@ import chalk from "chalk"; -import { DefaultPackageManager } from "./package-manager.js"; import { prepareLifecycleHooks, runLifecycleHooks } from "./lifecycle-hooks.js"; +import { DefaultPackageManager } from "./package-manager.js"; import { SettingsManager } from "./settings-manager.js"; export type PackageCommand = "install" | "remove" | "update" | "list"; diff --git a/packages/pi-coding-agent/src/core/package-manager.ts b/packages/pi-coding-agent/src/core/package-manager.ts index 770b5ba86..7908c8a23 100644 --- a/packages/pi-coding-agent/src/core/package-manager.ts +++ b/packages/pi-coding-agent/src/core/package-manager.ts @@ -10,7 +10,7 @@ import { writeFileSync, } from "node:fs"; import { homedir, tmpdir } from "node:os"; -import { basename, dirname, join, relative, resolve, sep } from "node:path"; +import { basename, dirname, join, relative, resolve } from "node:path"; import ignore from "ignore"; import { minimatch } from "minimatch"; import { CONFIG_DIR_NAME } from "../config.js"; diff --git a/packages/pi-coding-agent/src/core/prompt-templates.ts b/packages/pi-coding-agent/src/core/prompt-templates.ts index 9dbbd38e6..52a8359d4 100644 --- a/packages/pi-coding-agent/src/core/prompt-templates.ts +++ b/packages/pi-coding-agent/src/core/prompt-templates.ts @@ -1,6 +1,6 @@ -import { existsSync, readdirSync, readFileSync, statSync } from "fs"; -import { homedir } from "os"; -import { basename, isAbsolute, join, resolve, sep } from "path"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { homedir } from "node:os"; +import { basename, isAbsolute, join, resolve, sep } from "node:path"; import { CONFIG_DIR_NAME, getPromptsDir } from "../config.js"; import { parseFrontmatter } from "../utils/frontmatter.js"; import { canonicalizePath } from "./tools/path-utils.js"; diff --git a/packages/pi-coding-agent/src/core/resolve-config-value.test.ts b/packages/pi-coding-agent/src/core/resolve-config-value.test.ts index e88d3ddbb..4028d63ce 100644 --- a/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +++ b/packages/pi-coding-agent/src/core/resolve-config-value.test.ts @@ -1,11 +1,11 @@ -import { describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; +import { afterEach, beforeEach, describe, it } from "vitest"; import { - resolveConfigValue, clearConfigValueCache, + getAllowedCommandPrefixes, + resolveConfigValue, SAFE_COMMAND_PREFIXES, setAllowedCommandPrefixes, - getAllowedCommandPrefixes, } from "./resolve-config-value.js"; beforeEach(() => { @@ -43,7 +43,10 @@ describe("resolveConfigValue — command allowlist enforcement", () => { it("blocks a disallowed command and returns undefined", () => { const stderrChunks: string[] = []; const originalWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + process.stderr.write = ( + chunk: string | Uint8Array, + ..._args: unknown[] + ) => { stderrChunks.push(chunk.toString()); return true; }; @@ -73,7 +76,10 @@ describe("resolveConfigValue — command allowlist enforcement", () => { // We confirm by checking no "Blocked" message appears on stderr. const stderrChunks: string[] = []; const originalWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + process.stderr.write = ( + chunk: string | Uint8Array, + ..._args: unknown[] + ) => { stderrChunks.push(chunk.toString()); return true; }; @@ -137,7 +143,10 @@ describe("resolveConfigValue — shell operator bypass prevention", () => { it("writes stderr warning when shell operators detected", () => { const stderrChunks: string[] = []; const originalWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + process.stderr.write = ( + chunk: string | Uint8Array, + ..._args: unknown[] + ) => { stderrChunks.push(chunk.toString()); return true; }; @@ -154,7 +163,10 @@ describe("resolveConfigValue — caching", () => { it("caches the result of a blocked command", () => { const callCount = { n: 0 }; const originalWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + process.stderr.write = ( + _chunk: string | Uint8Array, + ..._args: unknown[] + ) => { callCount.n++; return true; }; @@ -172,7 +184,10 @@ describe("resolveConfigValue — caching", () => { it("clearConfigValueCache resets cached entries", () => { const stderrChunks: string[] = []; const originalWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + process.stderr.write = ( + chunk: string | Uint8Array, + ..._args: unknown[] + ) => { stderrChunks.push(chunk.toString()); return true; }; @@ -199,7 +214,10 @@ describe("REGRESSION #666: non-default credential tool blocked with no override" it("sops is blocked by default, then unblocked by setAllowedCommandPrefixes", () => { const stderrChunks: string[] = []; const originalWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + process.stderr.write = ( + chunk: string | Uint8Array, + ..._args: unknown[] + ) => { stderrChunks.push(chunk.toString()); return true; }; @@ -255,7 +273,10 @@ describe("setAllowedCommandPrefixes — user override", () => { it("custom prefix is allowed through to execution", () => { const stderrChunks: string[] = []; const originalWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + process.stderr.write = ( + chunk: string | Uint8Array, + ..._args: unknown[] + ) => { stderrChunks.push(chunk.toString()); return true; }; @@ -278,7 +299,10 @@ describe("setAllowedCommandPrefixes — user override", () => { it("previously-allowed prefix is blocked after override", () => { const stderrChunks: string[] = []; const originalWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + process.stderr.write = ( + chunk: string | Uint8Array, + ..._args: unknown[] + ) => { stderrChunks.push(chunk.toString()); return true; }; @@ -302,7 +326,10 @@ describe("setAllowedCommandPrefixes — user override", () => { it("clears cache when overriding prefixes", () => { const stderrChunks: string[] = []; const originalWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + process.stderr.write = ( + chunk: string | Uint8Array, + ..._args: unknown[] + ) => { stderrChunks.push(chunk.toString()); return true; }; diff --git a/packages/pi-coding-agent/src/core/resolve-config-value.ts b/packages/pi-coding-agent/src/core/resolve-config-value.ts index c3b51384c..94f4ddca4 100644 --- a/packages/pi-coding-agent/src/core/resolve-config-value.ts +++ b/packages/pi-coding-agent/src/core/resolve-config-value.ts @@ -3,7 +3,7 @@ * Used by auth-storage.ts and model-registry.ts. */ -import { execFileSync } from "child_process"; +import { execFileSync } from "node:child_process"; import { COMMAND_EXECUTION_TIMEOUT_MS } from "./constants.js"; const SHELL_OPERATORS = /[;|&`$><]/; diff --git a/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts b/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts index 215d3005e..89011c263 100644 --- a/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +++ b/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts @@ -1,10 +1,10 @@ // SF — Regression test for #3616: reload() must reset jiti extension loader cache // Copyright (c) 2026 Jeremy McSpadden -import { test, describe } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; +import { describe, test } from "vitest"; const source = readFileSync( join(process.cwd(), "packages/pi-coding-agent/src/core/resource-loader.ts"), diff --git a/packages/pi-coding-agent/src/core/resource-loader.ts b/packages/pi-coding-agent/src/core/resource-loader.ts index 731210bea..332a5ef4f 100644 --- a/packages/pi-coding-agent/src/core/resource-loader.ts +++ b/packages/pi-coding-agent/src/core/resource-loader.ts @@ -1,6 +1,6 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { homedir } from "node:os"; -import { basename, dirname, join, relative, resolve, sep } from "node:path"; +import { join, relative, resolve, sep } from "node:path"; import chalk from "chalk"; import { CONFIG_DIR_NAME, getAgentDir } from "../config.js"; import { @@ -1015,19 +1015,6 @@ export class DefaultResourceLoader implements ResourceLoader { return target.startsWith(prefix); } - /** - * Extract the extension name from its path. - * For root-level files: basename without extension (e.g. "search-the-web.ts" → "search-the-web") - * For subdirectory extensions: the directory name (e.g. "/path/to/sf/index.ts" → "sf") - */ - private getExtensionNameFromPath(extPath: string): string { - const base = basename(extPath); - if (base === "index.js" || base === "index.ts") { - return basename(dirname(extPath)); - } - return base.replace(/\.(?:ts|js)$/, ""); - } - private detectExtensionConflicts( extensions: Extension[], ): Array<{ path: string; message: string }> { diff --git a/packages/pi-coding-agent/src/core/retry-handler.test.ts b/packages/pi-coding-agent/src/core/retry-handler.test.ts index 6914ceab1..5e8afa302 100644 --- a/packages/pi-coding-agent/src/core/retry-handler.test.ts +++ b/packages/pi-coding-agent/src/core/retry-handler.test.ts @@ -8,7 +8,7 @@ import assert from "node:assert/strict"; import type { Api, AssistantMessage, Model } from "@singularity-forge/pi-ai"; -import { beforeEach, describe, it, type Mock, vi } from "vitest"; +import { describe, it, type Mock, vi } from "vitest"; import type { FallbackResolver } from "./fallback-resolver.js"; import type { ModelRegistry } from "./model-registry.js"; import { RetryHandler, type RetryHandlerDeps } from "./retry-handler.js"; @@ -150,7 +150,7 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => { // When the error is classified as quota_exhausted AND no alternate credentials // AND no fallback, the handler should emit fallback_chain_exhausted and stop. // If misclassified as rate_limit, it would enter the backoff loop instead. - const { deps, emittedEvents, findModel } = createMockDeps({ + const { deps, emittedEvents } = createMockDeps({ model: createMockModel("anthropic", "claude-opus-4-6[1m]"), markUsageLimitReachedResult: false, // no alternate credentials fallbackResult: null, // no cross-provider fallback @@ -279,17 +279,16 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => { describe("long-context model downgrade", () => { it("downgrades from [1m] to base model when entitlement error and no fallback", async () => { const baseModel = createMockModel("anthropic", "claude-opus-4-6"); - const { deps, emittedEvents, onModelChangeFn, continueFn } = - createMockDeps({ - model: createMockModel("anthropic", "claude-opus-4-6[1m]"), - markUsageLimitReachedResult: false, - fallbackResult: null, - findModelResult: (provider: string, modelId: string) => { - if (provider === "anthropic" && modelId === "claude-opus-4-6") - return baseModel; - return undefined; - }, - }); + const { deps, emittedEvents, onModelChangeFn } = createMockDeps({ + model: createMockModel("anthropic", "claude-opus-4-6[1m]"), + markUsageLimitReachedResult: false, + fallbackResult: null, + findModelResult: (provider: string, modelId: string) => { + if (provider === "anthropic" && modelId === "claude-opus-4-6") + return baseModel; + return undefined; + }, + }); const handler = new RetryHandler(deps); const msg = errorMessage( @@ -418,7 +417,7 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => { const expensiveModel = createMockModel("openrouter", "openai/gpt-5-pro"); expensiveModel.maxTokens = 128000; - const { deps, emittedEvents, onModelChangeFn } = createMockDeps({ + const { deps, emittedEvents } = createMockDeps({ model: expensiveModel, markUsageLimitReachedResult: false, fallbackResult: null, @@ -531,7 +530,7 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => { describe("third-party block claude-code fallback (#3772)", () => { it("switches to claude-code provider when current provider is anthropic", async () => { const ccModel = createMockModel("claude-code", "claude-opus-4-6"); - const { deps, emittedEvents, onModelChangeFn } = createMockDeps({ + const { deps, emittedEvents } = createMockDeps({ model: createMockModel("anthropic", "claude-opus-4-6"), findModelResult: (provider: string, modelId: string) => { if (provider === "claude-code" && modelId === "claude-opus-4-6") @@ -604,7 +603,7 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => { "third-party apps are not supported for this plan", ); - const result = await handler.handleRetryableError(msg); + const _result = await handler.handleRetryableError(msg); // Should NOT have triggered the claude-code fallback const switchEvent = emittedEvents.find( @@ -622,7 +621,7 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => { describe("quota wait before fallback", () => { it("waits for short retryAfterMs before retrying same provider on quota error", async () => { - const { deps, emittedEvents, continueFn } = createMockDeps({ + const { deps, emittedEvents } = createMockDeps({ model: createMockModel("google-gemini-cli", "gemini-2.5-pro"), fallbackResult: null, retrySettings: { maxRetries: 5, baseDelayMs: 1000, maxDelayMs: 60000 }, @@ -816,7 +815,7 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => { it("still tries cross-provider fallback for quota_exhausted without credential backoff", async () => { const fallbackModel = createMockModel("openai", "gpt-4o"); - const { deps, markUsageLimitReached, continueFn } = createMockDeps({ + const { deps, markUsageLimitReached } = createMockDeps({ model: createMockModel("anthropic", "claude-opus-4-6[1m]"), markUsageLimitReachedResult: false, fallbackResult: { diff --git a/packages/pi-coding-agent/src/core/session-manager.test.ts b/packages/pi-coding-agent/src/core/session-manager.test.ts index 8a59f18bf..61bade776 100644 --- a/packages/pi-coding-agent/src/core/session-manager.test.ts +++ b/packages/pi-coding-agent/src/core/session-manager.test.ts @@ -1,8 +1,8 @@ import assert from "node:assert/strict"; -import { describe, it, afterEach } from "vitest"; import { mkdtempSync, rmSync } from "node:fs"; -import { join } from "node:path"; import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, it } from "vitest"; import { SessionManager } from "./session-manager.js"; diff --git a/packages/pi-coding-agent/src/core/session-manager.ts b/packages/pi-coding-agent/src/core/session-manager.ts index a658cfd35..6450961bb 100644 --- a/packages/pi-coding-agent/src/core/session-manager.ts +++ b/packages/pi-coding-agent/src/core/session-manager.ts @@ -1,10 +1,4 @@ -import type { AgentMessage } from "@singularity-forge/pi-agent-core"; -import type { - ImageContent, - Message, - TextContent, -} from "@singularity-forge/pi-ai"; -import { randomUUID } from "crypto"; +import { randomUUID } from "node:crypto"; import { appendFileSync, closeSync, @@ -15,16 +9,27 @@ import { readFileSync, readSync, statSync, - writeFileSync, -} from "fs"; -import { atomicWriteFileSync } from "./fs-utils.js"; -import { readdir, readFile, stat } from "fs/promises"; -import { join, resolve } from "path"; +} from "node:fs"; +import { readdir, readFile, stat } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import type { AgentMessage } from "@singularity-forge/pi-agent-core"; +import type { + ImageContent, + Message, + TextContent, +} from "@singularity-forge/pi-ai"; import { - getAgentDir as getDefaultAgentDir, getBlobsDir, + getAgentDir as getDefaultAgentDir, getSessionsDir, } from "../config.js"; +import { + BlobStore, + externalizeImageData, + isBlobRef, + resolveImageData, +} from "./blob-store.js"; +import { atomicWriteFileSync } from "./fs-utils.js"; import { tryAcquireLockSync } from "./lock-utils.js"; import { type BashExecutionMessage, @@ -33,12 +38,6 @@ import { createCompactionSummaryMessage, createCustomMessage, } from "./messages.js"; -import { - BlobStore, - externalizeImageData, - isBlobRef, - resolveImageData, -} from "./blob-store.js"; /** Inline concurrency limiter to cap parallel async operations. */ function pLimit(concurrency: number) { diff --git a/packages/pi-coding-agent/src/core/settings-manager-security.test.ts b/packages/pi-coding-agent/src/core/settings-manager-security.test.ts index e9f9e1868..3767c565d 100644 --- a/packages/pi-coding-agent/src/core/settings-manager-security.test.ts +++ b/packages/pi-coding-agent/src/core/settings-manager-security.test.ts @@ -1,10 +1,10 @@ -import { describe, it, afterEach } from "vitest"; import assert from "node:assert/strict"; -import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs"; -import { join } from "node:path"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; -import { SettingsManager } from "./settings-manager.js"; +import { join } from "node:path"; +import { afterEach, describe, it } from "vitest"; import { CONFIG_DIR_NAME } from "../config.js"; +import { SettingsManager } from "./settings-manager.js"; function makeTempDirs() { const base = mkdtempSync(join(tmpdir(), "settings-security-test-")); diff --git a/packages/pi-coding-agent/src/core/settings-manager.ts b/packages/pi-coding-agent/src/core/settings-manager.ts index d30f2f30e..60c69d36b 100644 --- a/packages/pi-coding-agent/src/core/settings-manager.ts +++ b/packages/pi-coding-agent/src/core/settings-manager.ts @@ -1,6 +1,6 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; import type { Transport } from "@singularity-forge/pi-ai"; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; -import { dirname, join } from "path"; import lockfile from "proper-lockfile"; import { CONFIG_DIR_NAME, getAgentDir } from "../config.js"; import { diff --git a/packages/pi-coding-agent/src/core/skill-tool.test.ts b/packages/pi-coding-agent/src/core/skill-tool.test.ts index 7bca7254c..b89716122 100644 --- a/packages/pi-coding-agent/src/core/skill-tool.test.ts +++ b/packages/pi-coding-agent/src/core/skill-tool.test.ts @@ -2,11 +2,10 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, beforeEach, describe, it } from "vitest"; - import { Agent } from "@singularity-forge/pi-agent-core"; -import { AuthStorage } from "./auth-storage.js"; +import { afterEach, beforeEach, describe, it } from "vitest"; import { AgentSession } from "./agent-session.js"; +import { AuthStorage } from "./auth-storage.js"; import { ModelRegistry } from "./model-registry.js"; import { DefaultResourceLoader } from "./resource-loader.js"; import { SessionManager } from "./session-manager.js"; diff --git a/packages/pi-coding-agent/src/core/skills.ts b/packages/pi-coding-agent/src/core/skills.ts index 0544c246b..52cc1381c 100644 --- a/packages/pi-coding-agent/src/core/skills.ts +++ b/packages/pi-coding-agent/src/core/skills.ts @@ -1,6 +1,5 @@ -import { existsSync, readdirSync, readFileSync, statSync } from "fs"; -import ignore from "ignore"; -import { homedir } from "os"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { homedir } from "node:os"; import { basename, dirname, @@ -9,12 +8,13 @@ import { relative, resolve, sep, -} from "path"; +} from "node:path"; +import ignore from "ignore"; +import { CONFIG_DIR_NAME } from "../config.js"; import { parseFrontmatter } from "../utils/frontmatter.js"; import { toPosixPath } from "../utils/path-display.js"; -import { canonicalizePath } from "./tools/path-utils.js"; import type { ResourceDiagnostic } from "./diagnostics.js"; -import { CONFIG_DIR_NAME } from "../config.js"; +import { canonicalizePath } from "./tools/path-utils.js"; /** * The standard ecosystem skills directory used by skills.sh and the diff --git a/packages/pi-coding-agent/src/core/tools/bash-background.test.ts b/packages/pi-coding-agent/src/core/tools/bash-background.test.ts index fee65bd49..c591fd762 100644 --- a/packages/pi-coding-agent/src/core/tools/bash-background.test.ts +++ b/packages/pi-coding-agent/src/core/tools/bash-background.test.ts @@ -7,8 +7,8 @@ * the command does not already redirect stdout. */ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { rewriteBackgroundCommand } from "./bash.js"; describe("rewriteBackgroundCommand", () => { diff --git a/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts b/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts index 3a0ceacec..f74186ea5 100644 --- a/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts +++ b/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts @@ -1,10 +1,10 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { + type BashInterceptorRule, checkBashInterception, compileInterceptor, DEFAULT_BASH_INTERCEPTOR_RULES, - type BashInterceptorRule, } from "./bash-interceptor.js"; const ALL_TOOLS = ["read", "grep", "find", "edit", "write"]; diff --git a/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts b/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts index 664274eec..52b95f980 100644 --- a/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +++ b/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts @@ -16,16 +16,15 @@ * See: singularity-forge/sf-run#XXXX */ -import { test } from "vitest"; import assert from "node:assert/strict"; import { spawn } from "node:child_process"; - // Verify the spawn option pattern used across the codebase. // This is a static/structural test — it reads the source files and asserts // they use the platform-guarded detached flag. import { readFileSync } from "node:fs"; -import { join, dirname } from "node:path"; +import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); diff --git a/packages/pi-coding-agent/src/core/tools/bash.ts b/packages/pi-coding-agent/src/core/tools/bash.ts index ca6d7b831..cb7bdeae7 100644 --- a/packages/pi-coding-agent/src/core/tools/bash.ts +++ b/packages/pi-coding-agent/src/core/tools/bash.ts @@ -1,11 +1,11 @@ +import { spawn } from "node:child_process"; import { randomBytes } from "node:crypto"; import { createWriteStream, existsSync } from "node:fs"; import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { AgentTool } from "@singularity-forge/pi-agent-core"; import { type Static, Type } from "@sinclair/typebox"; -import { spawn } from "child_process"; +import type { AgentTool } from "@singularity-forge/pi-agent-core"; import { getShellConfig, getShellEnv, @@ -14,6 +14,7 @@ import { trackDetachedChildPid, untrackDetachedChildPid, } from "../../utils/shell.js"; +import type { ArtifactManager } from "../artifact-manager.js"; import { type BashInterceptorRule, compileInterceptor, @@ -26,7 +27,6 @@ import { type TruncationResult, truncateTail, } from "./truncate.js"; -import type { ArtifactManager } from "../artifact-manager.js"; // Cached Win32 FFI handles for restoring VT input after child processes let _vtHandles: { diff --git a/packages/pi-coding-agent/src/core/tools/edit-diff.ts b/packages/pi-coding-agent/src/core/tools/edit-diff.ts index dbbf7e5fb..b9362d684 100644 --- a/packages/pi-coding-agent/src/core/tools/edit-diff.ts +++ b/packages/pi-coding-agent/src/core/tools/edit-diff.ts @@ -7,8 +7,8 @@ * hang there can wedge the entire interactive session after a successful tool run. */ -import { constants } from "fs"; -import { access, readFile } from "fs/promises"; +import { constants } from "node:fs"; +import { access, readFile } from "node:fs/promises"; import { resolveToCwd, UNICODE_SPACES } from "./path-utils.js"; export function detectLineEnding(content: string): "\r\n" | "\n" { diff --git a/packages/pi-coding-agent/src/core/tools/edit.ts b/packages/pi-coding-agent/src/core/tools/edit.ts index 975ac972b..6778771b1 100644 --- a/packages/pi-coding-agent/src/core/tools/edit.ts +++ b/packages/pi-coding-agent/src/core/tools/edit.ts @@ -1,11 +1,12 @@ -import type { AgentTool } from "@singularity-forge/pi-agent-core"; -import { type Static, Type } from "@sinclair/typebox"; -import { constants } from "fs"; +import { constants } from "node:fs"; import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile, -} from "fs/promises"; +} from "node:fs/promises"; +import { type Static, Type } from "@sinclair/typebox"; +import type { AgentTool } from "@singularity-forge/pi-agent-core"; +import { notifyFileChanged } from "../lsp/client.js"; import { applyEditsToNormalizedContent, detectLineEnding, @@ -17,7 +18,6 @@ import { restoreLineEndings, stripBom, } from "./edit-diff.js"; -import { notifyFileChanged } from "../lsp/client.js"; import { resolveToCwd } from "./path-utils.js"; const editSchema = Type.Object({ diff --git a/packages/pi-coding-agent/src/core/tools/find.ts b/packages/pi-coding-agent/src/core/tools/find.ts index 214f55fd3..bc2f53546 100644 --- a/packages/pi-coding-agent/src/core/tools/find.ts +++ b/packages/pi-coding-agent/src/core/tools/find.ts @@ -1,8 +1,8 @@ -import type { AgentTool } from "@singularity-forge/pi-agent-core"; -import { glob as nativeGlob } from "@singularity-forge/native/glob"; +import { existsSync } from "node:fs"; +import path from "node:path"; import { type Static, Type } from "@sinclair/typebox"; -import { existsSync } from "fs"; -import path from "path"; +import { glob as nativeGlob } from "@singularity-forge/native/glob"; +import type { AgentTool } from "@singularity-forge/pi-agent-core"; import { FIND_DEFAULT_LIMIT } from "../constants.js"; import { resolveToCwd } from "./path-utils.js"; import { diff --git a/packages/pi-coding-agent/src/core/tools/grep.ts b/packages/pi-coding-agent/src/core/tools/grep.ts index 2ec71d79a..34b380e8b 100644 --- a/packages/pi-coding-agent/src/core/tools/grep.ts +++ b/packages/pi-coding-agent/src/core/tools/grep.ts @@ -1,9 +1,9 @@ +import { spawn } from "node:child_process"; +import { readFileSync, statSync } from "node:fs"; +import path from "node:path"; import { createInterface } from "node:readline"; -import type { AgentTool } from "@singularity-forge/pi-agent-core"; import { type Static, Type } from "@sinclair/typebox"; -import { spawn } from "child_process"; -import { readFileSync, statSync } from "fs"; -import path from "path"; +import type { AgentTool } from "@singularity-forge/pi-agent-core"; import { ensureTool } from "../../utils/tools-manager.js"; import { resolveToCwd } from "./path-utils.js"; import { diff --git a/packages/pi-coding-agent/src/core/tools/hashline-edit.ts b/packages/pi-coding-agent/src/core/tools/hashline-edit.ts index 0d0a925b8..9d00139b8 100644 --- a/packages/pi-coding-agent/src/core/tools/hashline-edit.ts +++ b/packages/pi-coding-agent/src/core/tools/hashline-edit.ts @@ -4,15 +4,16 @@ * The model references lines by `LINE#ID` tags from read output. * Each tag uniquely identifies a line, so edits remain stable even when lines shift. */ -import type { AgentTool } from "@singularity-forge/pi-agent-core"; -import { type Static, Type } from "@sinclair/typebox"; -import { constants } from "fs"; + +import { constants } from "node:fs"; import { access as fsAccess, readFile as fsReadFile, unlink as fsUnlink, writeFile as fsWriteFile, -} from "fs/promises"; +} from "node:fs/promises"; +import { type Static, Type } from "@sinclair/typebox"; +import type { AgentTool } from "@singularity-forge/pi-agent-core"; import { detectLineEnding, generateDiffString, @@ -23,7 +24,6 @@ import { import { type Anchor, applyHashlineEdits, - computeLineHash, type HashlineEdit, parseHashlineText, parseTag, diff --git a/packages/pi-coding-agent/src/core/tools/hashline-read.ts b/packages/pi-coding-agent/src/core/tools/hashline-read.ts index d058584e3..7dbcf803b 100644 --- a/packages/pi-coding-agent/src/core/tools/hashline-read.ts +++ b/packages/pi-coding-agent/src/core/tools/hashline-read.ts @@ -8,11 +8,12 @@ * * These tags are used by the hashline_edit tool to address lines precisely. */ + +import { constants } from "node:fs"; +import { access as fsAccess, readFile as fsReadFile } from "node:fs/promises"; +import { type Static, Type } from "@sinclair/typebox"; import type { AgentTool } from "@singularity-forge/pi-agent-core"; import type { ImageContent, TextContent } from "@singularity-forge/pi-ai"; -import { type Static, Type } from "@sinclair/typebox"; -import { constants } from "fs"; -import { access as fsAccess, readFile as fsReadFile } from "fs/promises"; import { formatDimensionNote, resizeImage } from "../../utils/image-resize.js"; import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js"; import { formatHashLines } from "./hashline.js"; diff --git a/packages/pi-coding-agent/src/core/tools/hashline.test.ts b/packages/pi-coding-agent/src/core/tools/hashline.test.ts index 827b84e4b..78dc63bd8 100644 --- a/packages/pi-coding-agent/src/core/tools/hashline.test.ts +++ b/packages/pi-coding-agent/src/core/tools/hashline.test.ts @@ -1,17 +1,17 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { + type Anchor, + applyHashlineEdits, computeLineHash, formatHashLines, formatLineTag, - parseTag, - validateLineRef, - applyHashlineEdits, + type HashlineEdit, HashlineMismatchError, parseHashlineText, + parseTag, stripNewLinePrefixes, - type HashlineEdit, - type Anchor, + validateLineRef, } from "./hashline.js"; function makeTag(line: number, content: string): Anchor { diff --git a/packages/pi-coding-agent/src/core/tools/index.ts b/packages/pi-coding-agent/src/core/tools/index.ts index 959615f88..802f4d1df 100644 --- a/packages/pi-coding-agent/src/core/tools/index.ts +++ b/packages/pi-coding-agent/src/core/tools/index.ts @@ -1,3 +1,10 @@ +export type { LspServerStatus } from "../lsp/client.js"; +export { + createLspTool, + type LspToolDetails, + lspSchema, + lspTool, +} from "../lsp/index.js"; export { type BashOperations, type BashSpawnContext, @@ -11,8 +18,8 @@ export { } from "./bash.js"; export { type BashInterceptorRule, - checkBashInterception, type CompiledInterceptor, + checkBashInterception, compileInterceptor, DEFAULT_BASH_INTERCEPTOR_RULES, type InterceptionResult, @@ -42,38 +49,19 @@ export { grepTool, } from "./grep.js"; export { - createLsTool, - type LsOperations, - type LsToolDetails, - type LsToolInput, - type LsToolOptions, - lsTool, -} from "./ls.js"; -export { - createReadTool, - type ReadOperations, - type ReadToolDetails, - type ReadToolInput, - type ReadToolOptions, - readTool, -} from "./read.js"; -export { - DEFAULT_MAX_BYTES, - DEFAULT_MAX_LINES, - formatSize, - type TruncationOptions, - type TruncationResult, - truncateHead, - truncateLine, - truncateTail, -} from "./truncate.js"; -export { - createWriteTool, - type WriteOperations, - type WriteToolInput, - type WriteToolOptions, - writeTool, -} from "./write.js"; + type Anchor, + applyHashlineEdits, + computeLineHash, + formatHashLines, + formatLineTag, + type HashlineEdit, + HashlineMismatchError, + type HashMismatch, + parseHashlineText, + parseTag, + stripNewLinePrefixes, + validateLineRef, +} from "./hashline.js"; export { createHashlineEditTool, type HashlineEditInput, @@ -92,35 +80,48 @@ export { hashlineReadTool, } from "./hashline-read.js"; export { - type Anchor, - applyHashlineEdits, - computeLineHash, - formatHashLines, - formatLineTag, - type HashlineEdit, - HashlineMismatchError, - parseHashlineText, - type HashMismatch, - parseTag, - stripNewLinePrefixes, - validateLineRef, -} from "./hashline.js"; + createLsTool, + type LsOperations, + type LsToolDetails, + type LsToolInput, + type LsToolOptions, + lsTool, +} from "./ls.js"; export { - createLspTool, - type LspToolDetails, - lspSchema, - lspTool, -} from "../lsp/index.js"; -export type { LspServerStatus } from "../lsp/client.js"; + createReadTool, + type ReadOperations, + type ReadToolDetails, + type ReadToolInput, + type ReadToolOptions, + readTool, +} from "./read.js"; export { - registerToolCompatibility, - getToolCompatibility, getAllToolCompatibility, + getToolCompatibility, registerMcpToolCompatibility, + registerToolCompatibility, resetToolCompatibilityRegistry, } from "./tool-compatibility-registry.js"; +export { + DEFAULT_MAX_BYTES, + DEFAULT_MAX_LINES, + formatSize, + type TruncationOptions, + type TruncationResult, + truncateHead, + truncateLine, + truncateTail, +} from "./truncate.js"; +export { + createWriteTool, + type WriteOperations, + type WriteToolInput, + type WriteToolOptions, + writeTool, +} from "./write.js"; import type { AgentTool } from "@singularity-forge/pi-agent-core"; +import { createLspTool, lspTool } from "../lsp/index.js"; import { type BashToolOptions, bashTool, createBashTool } from "./bash.js"; import { createEditTool, editTool } from "./edit.js"; import { createFindTool, findTool } from "./find.js"; @@ -130,7 +131,6 @@ import { createHashlineReadTool, hashlineReadTool } from "./hashline-read.js"; import { createLsTool, lsTool } from "./ls.js"; import { createReadTool, type ReadToolOptions, readTool } from "./read.js"; import { createWriteTool, writeTool } from "./write.js"; -import { createLspTool, lspTool } from "../lsp/index.js"; /** Tool type (AgentTool from pi-ai) */ export type Tool = AgentTool; diff --git a/packages/pi-coding-agent/src/core/tools/ls.ts b/packages/pi-coding-agent/src/core/tools/ls.ts index 9ff7d9135..a0110b32e 100644 --- a/packages/pi-coding-agent/src/core/tools/ls.ts +++ b/packages/pi-coding-agent/src/core/tools/ls.ts @@ -1,7 +1,7 @@ -import type { AgentTool } from "@singularity-forge/pi-agent-core"; +import { existsSync, readdirSync, statSync } from "node:fs"; +import nodePath from "node:path"; import { type Static, Type } from "@sinclair/typebox"; -import { existsSync, readdirSync, statSync } from "fs"; -import nodePath from "path"; +import type { AgentTool } from "@singularity-forge/pi-agent-core"; import { resolveToCwd } from "./path-utils.js"; import { DEFAULT_MAX_BYTES, diff --git a/packages/pi-coding-agent/src/core/tools/path-utils.test.ts b/packages/pi-coding-agent/src/core/tools/path-utils.test.ts index cc0c4143b..1a950c21a 100644 --- a/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +++ b/packages/pi-coding-agent/src/core/tools/path-utils.test.ts @@ -1,15 +1,15 @@ -import { describe, it, afterEach } from "vitest"; import assert from "node:assert/strict"; import { mkdtempSync, - writeFileSync, + rmdirSync, symlinkSync, unlinkSync, - rmdirSync, + writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { resolveToCwd, expandPath, canonicalizePath } from "./path-utils.js"; +import { afterEach, describe, it } from "vitest"; +import { canonicalizePath, resolveToCwd } from "./path-utils.js"; describe("canonicalizePath", () => { it("returns realpath for existing files", () => { @@ -95,7 +95,7 @@ describe("normalizeMsysPath (via resolveToCwd on win32)", () => { }); it("does not convert regular Unix paths", () => { - const regularPath = "/usr/local/bin"; + const _regularPath = "/usr/local/bin"; const msysRegex = /^\/[a-zA-Z]\//; // /u/local/bin would match, but /usr/local/bin has 3+ chars before / // Actually /u/ would match — but /usr/ won't because 'us' is 2 chars. diff --git a/packages/pi-coding-agent/src/core/tools/read.ts b/packages/pi-coding-agent/src/core/tools/read.ts index d6cd2db58..99af9b6d6 100644 --- a/packages/pi-coding-agent/src/core/tools/read.ts +++ b/packages/pi-coding-agent/src/core/tools/read.ts @@ -1,10 +1,9 @@ +import { constants, createReadStream } from "node:fs"; +import { access as fsAccess, readFile as fsReadFile } from "node:fs/promises"; +import { createInterface } from "node:readline"; +import { type Static, Type } from "@sinclair/typebox"; import type { AgentTool } from "@singularity-forge/pi-agent-core"; import type { ImageContent, TextContent } from "@singularity-forge/pi-ai"; -import { type Static, Type } from "@sinclair/typebox"; -import { createReadStream } from "fs"; -import { constants } from "fs"; -import { access as fsAccess, readFile as fsReadFile } from "fs/promises"; -import { createInterface } from "readline"; import { formatDimensionNote, resizeImage } from "../../utils/image-resize.js"; import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js"; import { resolveReadPath } from "./path-utils.js"; @@ -258,7 +257,7 @@ export function createReadTool( } else { // Read as text let outputText: string; - let details: ReadToolDetails | undefined; + let _details: ReadToolDetails | undefined; // Use streaming when offset or limit are specified (memory-efficient for large files) if (offset !== undefined || limit !== undefined) { @@ -293,7 +292,7 @@ export function createReadTool( Buffer.byteLength(lines[0] ?? "", "utf-8"), ); outputText = `[Line ${effectiveStartLine} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${effectiveStartLine}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`; - details = { truncation }; + _details = { truncation }; } else if (truncation.truncated) { const endLineDisplay = effectiveStartLine + truncation.outputLines - 1; @@ -306,7 +305,7 @@ export function createReadTool( } else { outputText += `\n\n[Showing lines ${effectiveStartLine}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`; } - details = { truncation }; + _details = { truncation }; } else if ( lines.length >= lineCount && effectiveStartLine + lines.length - 1 < totalFileLines @@ -336,7 +335,7 @@ export function createReadTool( if (truncation.firstLineExceedsLimit) { outputText = `[File exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: head -c ${DEFAULT_MAX_BYTES} ${path}]`; - details = { truncation }; + _details = { truncation }; } else if (truncation.truncated) { const endLineDisplay = truncation.outputLines; const nextOffset = endLineDisplay + 1; @@ -348,7 +347,7 @@ export function createReadTool( } else { outputText += `\n\n[Showing lines 1-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`; } - details = { truncation }; + _details = { truncation }; } else { outputText = truncation.content; } diff --git a/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts b/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts index 8814f9da8..4704cc449 100644 --- a/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts +++ b/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts @@ -13,11 +13,11 @@ * Fixes: singularity-forge/sf-run#2854 */ -import { test } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; -import { join, dirname, relative } from "node:path"; +import { dirname, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); const coreDir = join(__dirname, ".."); diff --git a/packages/pi-coding-agent/src/core/tools/write.ts b/packages/pi-coding-agent/src/core/tools/write.ts index b4f47518c..143dcc86f 100644 --- a/packages/pi-coding-agent/src/core/tools/write.ts +++ b/packages/pi-coding-agent/src/core/tools/write.ts @@ -1,7 +1,7 @@ -import type { AgentTool } from "@singularity-forge/pi-agent-core"; +import { mkdir as fsMkdir, writeFile as fsWriteFile } from "node:fs/promises"; +import { dirname } from "node:path"; import { type Static, Type } from "@sinclair/typebox"; -import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises"; -import { dirname } from "path"; +import type { AgentTool } from "@singularity-forge/pi-agent-core"; import { notifyFileChanged } from "../lsp/client.js"; import { resolveToCwd } from "./path-utils.js"; diff --git a/packages/pi-coding-agent/src/index.ts b/packages/pi-coding-agent/src/index.ts index 320be2ac5..9d2dbed55 100644 --- a/packages/pi-coding-agent/src/index.ts +++ b/packages/pi-coding-agent/src/index.ts @@ -1,5 +1,6 @@ // Core session management +export { discoverAndPrintModels, listModels } from "./cli/list-models.js"; // Config paths export { getAgentDir, VERSION } from "./config.js"; export { @@ -13,6 +14,7 @@ export { parseSkillBlock, type SessionStats, } from "./core/agent-session.js"; +export { ArtifactManager } from "./core/artifact-manager.js"; // Auth and model registry export { type ApiKeyCredential, @@ -23,6 +25,14 @@ export { InMemoryAuthStorageBackend, type OAuthCredential, } from "./core/auth-storage.js"; +// Blob and artifact storage +export { + BlobStore, + externalizeImageData, + isBlobRef, + parseBlobRef, + resolveImageData, +} from "./core/blob-store.js"; // Compaction export { type BranchPreparation, @@ -46,6 +56,7 @@ export { serializeConversation, shouldCompact, } from "./core/compaction/index.js"; +export { ModelDiscoveryCache } from "./core/discovery-cache.js"; export { createEventBus, type EventBus, @@ -61,6 +72,8 @@ export type { AgentToolUpdateCallback, AppAction, BashToolCallEvent, + BashTransformEvent, + BashTransformEventResult, BeforeAgentStartEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, @@ -74,7 +87,6 @@ export type { Extension, ExtensionActions, ExtensionAPI, - ExtensionManifest, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, @@ -84,6 +96,7 @@ export type { ExtensionFactory, ExtensionFlag, ExtensionHandler, + ExtensionManifest, ExtensionRuntime, ExtensionShortcut, ExtensionStartupContext, @@ -96,17 +109,17 @@ export type { InputEventResult, InputSource, KeybindingsManager, + LifecycleHookContext, + LifecycleHookHandler, + LifecycleHookMap, + LifecycleHookPhase, + LifecycleHookScope, LoadExtensionsResult, LsToolCallEvent, MessageRenderer, MessageRenderOptions, ProviderConfig, ProviderModelConfig, - LifecycleHookContext, - LifecycleHookHandler, - LifecycleHookMap, - LifecycleHookPhase, - LifecycleHookScope, ReadToolCallEvent, RegisteredCommand, RegisteredTool, @@ -123,21 +136,19 @@ export type { SlashCommandInfo, SlashCommandLocation, SlashCommandSource, + SortResult, + SortWarning, TerminalInputHandler, ToolCallEvent, ToolCompatibility, ToolDefinition, ToolInfo, - SortResult, - SortWarning, ToolRenderResultOptions, ToolResultEvent, TurnEndEvent, TurnStartEvent, UserBashEvent, UserBashEventResult, - BashTransformEvent, - BashTransformEventResult, WidgetPlacement, WriteToolCallEvent, } from "./core/extensions/index.js"; @@ -159,7 +170,6 @@ export { // Footer data provider (git branch + extension statuses - data not otherwise available to extensions) export type { ReadonlyFooterDataProvider } from "./core/footer-data-provider.js"; export { convertToLlm } from "./core/messages.js"; -export { ModelDiscoveryCache } from "./core/discovery-cache.js"; export type { DiscoveredModel, DiscoveryResult, @@ -173,16 +183,6 @@ export { } from "./core/model-discovery.js"; export { ModelRegistry } from "./core/model-registry.js"; export { ModelsJsonWriter } from "./core/models-json-writer.js"; -export { discoverAndPrintModels, listModels } from "./cli/list-models.js"; -export type { - PackageManager, - PathMetadata, - ProgressCallback, - ProgressEvent, - ResolvedPaths, - ResolvedResource, -} from "./core/package-manager.js"; -export { DefaultPackageManager } from "./core/package-manager.js"; export type { PackageCommand, PackageCommandOptions, @@ -194,6 +194,20 @@ export { parsePackageCommand, runPackageCommand, } from "./core/package-commands.js"; +export type { + PackageManager, + PathMetadata, + ProgressCallback, + ProgressEvent, + ResolvedPaths, + ResolvedResource, +} from "./core/package-manager.js"; +export { DefaultPackageManager } from "./core/package-manager.js"; +export { + getAllowedCommandPrefixes, + SAFE_COMMAND_PREFIXES, + setAllowedCommandPrefixes, +} from "./core/resolve-config-value.js"; export type { ResourceCollision, ResourceDiagnostic, @@ -244,15 +258,6 @@ export { type SessionMessageEntry, type ThinkingLevelChangeEntry, } from "./core/session-manager.js"; -// Blob and artifact storage -export { - BlobStore, - isBlobRef, - parseBlobRef, - externalizeImageData, - resolveImageData, -} from "./core/blob-store.js"; -export { ArtifactManager } from "./core/artifact-manager.js"; export { type AsyncSettings, type CompactionSettings, @@ -263,15 +268,10 @@ export { SettingsManager, type TaskIsolationSettings, } from "./core/settings-manager.js"; -export { - SAFE_COMMAND_PREFIXES, - setAllowedCommandPrefixes, - getAllowedCommandPrefixes, -} from "./core/resolve-config-value.js"; // Skills export { - ECOSYSTEM_SKILLS_DIR, ECOSYSTEM_PROJECT_SKILLS_DIR, + ECOSYSTEM_SKILLS_DIR, formatSkillsForPrompt, getLoadedSkills, type LoadSkillsFromDirOptions, @@ -291,12 +291,14 @@ export { type BashToolInput, type BashToolOptions, bashTool, - rewriteBackgroundCommand, - checkBashInterception, type CompiledInterceptor, - compileInterceptor, - DEFAULT_BASH_INTERCEPTOR_RULES, + checkBashInterception, codingTools, + compileInterceptor, + createHashlineCodingTools, + createHashlineEditTool, + createHashlineReadTool, + DEFAULT_BASH_INTERCEPTOR_RULES, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, type EditOperations, @@ -314,7 +316,19 @@ export { type GrepToolDetails, type GrepToolInput, type GrepToolOptions, + getAllToolCompatibility, + getToolCompatibility, grepTool, + type HashlineEditInput, + type HashlineEditToolDetails, + type HashlineEditToolOptions, + type HashlineReadToolDetails, + type HashlineReadToolInput, + type HashlineReadToolOptions, + hashlineCodingTools, + // Hashline edit mode tools + hashlineEditTool, + hashlineReadTool, type LsOperations, type LsToolDetails, type LsToolInput, @@ -325,6 +339,11 @@ export { type ReadToolInput, type ReadToolOptions, readTool, + registerMcpToolCompatibility, + // Tool compatibility registry (ADR-005) + registerToolCompatibility, + resetToolCompatibilityRegistry, + rewriteBackgroundCommand, type ToolsOptions, type TruncationOptions, type TruncationResult, @@ -335,25 +354,6 @@ export { type WriteToolInput, type WriteToolOptions, writeTool, - // Hashline edit mode tools - hashlineEditTool, - hashlineReadTool, - hashlineCodingTools, - createHashlineEditTool, - createHashlineReadTool, - createHashlineCodingTools, - type HashlineEditInput, - type HashlineEditToolDetails, - type HashlineEditToolOptions, - type HashlineReadToolDetails, - type HashlineReadToolInput, - type HashlineReadToolOptions, - // Tool compatibility registry (ADR-005) - registerToolCompatibility, - getToolCompatibility, - getAllToolCompatibility, - registerMcpToolCompatibility, - resetToolCompatibilityRegistry, } from "./core/tools/index.js"; // Main entry point export { main } from "./main.js"; @@ -361,22 +361,20 @@ export { main } from "./main.js"; export { InteractiveMode, type InteractiveModeOptions, - type PrintModeOptions, - runPrintMode, - runRpcMode, type ModelInfo, + type PrintModeOptions, RpcClient, type RpcClientOptions, - type RpcEventListener, type RpcCommand, + type RpcEventListener, type RpcInitResult, type RpcProtocolVersion, type RpcResponse, type RpcSessionState, type RpcV2Event, + runPrintMode, + runRpcMode, } from "./modes/index.js"; -// RPC JSONL utilities -export { attachJsonlLineReader, serializeJsonLine } from "./modes/rpc/jsonl.js"; // UI components for extensions export { ArminComponent, @@ -430,10 +428,12 @@ export { Theme, type ThemeColor, } from "./modes/interactive/theme/theme.js"; +// RPC JSONL utilities +export { attachJsonlLineReader, serializeJsonLine } from "./modes/rpc/jsonl.js"; // Clipboard utilities export { copyToClipboard } from "./utils/clipboard.js"; export { parseFrontmatter, stripFrontmatter } from "./utils/frontmatter.js"; -// Shell utilities -export { getShellConfig, sanitizeCommand } from "./utils/shell.js"; // Cross-platform path display export { toPosixPath } from "./utils/path-display.js"; +// Shell utilities +export { getShellConfig, sanitizeCommand } from "./utils/shell.js"; diff --git a/packages/pi-coding-agent/src/main.ts b/packages/pi-coding-agent/src/main.ts index e14e572ee..fd9842987 100644 --- a/packages/pi-coding-agent/src/main.ts +++ b/packages/pi-coding-agent/src/main.ts @@ -5,13 +5,13 @@ * createAgentSession() options. The SDK does the heavy lifting. */ +import { createInterface } from "node:readline"; import { type ImageContent, modelsAreEqual, supportsXhigh, } from "@singularity-forge/pi-ai"; import chalk from "chalk"; -import { createInterface } from "readline"; import { type Args, type ExtensionFlagParseOptions, diff --git a/packages/pi-coding-agent/src/migrations.ts b/packages/pi-coding-agent/src/migrations.ts index 879e0a117..9402b34e1 100644 --- a/packages/pi-coding-agent/src/migrations.ts +++ b/packages/pi-coding-agent/src/migrations.ts @@ -2,7 +2,6 @@ * One-time migrations that run on startup. */ -import chalk from "chalk"; import { existsSync, mkdirSync, @@ -11,8 +10,9 @@ import { renameSync, rmSync, writeFileSync, -} from "fs"; -import { dirname, join } from "path"; +} from "node:fs"; +import { dirname, join } from "node:path"; +import chalk from "chalk"; import { CONFIG_DIR_NAME, getAgentDir, getBinDir } from "./config.js"; const MIGRATION_GUIDE_URL = diff --git a/packages/pi-coding-agent/src/modes/interactive/components/custom-editor.ts b/packages/pi-coding-agent/src/modes/interactive/components/custom-editor.ts index da175a465..0cf96c72a 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/custom-editor.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/custom-editor.ts @@ -2,8 +2,8 @@ import { Editor, type EditorOptions, type EditorTheme, - type TUI, isKittyProtocolActive, + type TUI, } from "@singularity-forge/pi-tui"; import type { AppAction, diff --git a/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts b/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts index e9ce3616d..029e2e229 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts @@ -1,5 +1,7 @@ // SF Login Dialog Component — OAuth login flow UI // Copyright (c) 2026 Jeremy McSpadden + +import { execFile } from "node:child_process"; import { getOAuthProviders } from "@singularity-forge/pi-ai/oauth"; import { Container, @@ -8,10 +10,9 @@ import { Input, Spacer, Text, - truncateToWidth, type TUI, + truncateToWidth, } from "@singularity-forge/pi-tui"; -import { execFile } from "child_process"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; import { keyHint } from "./keybinding-hints.js"; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts b/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts index e9400f0af..cd7338511 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts @@ -13,11 +13,11 @@ import { } from "@singularity-forge/pi-tui"; import type { AuthStorage } from "../../../core/auth-storage.js"; import { getDiscoverableProviders } from "../../../core/model-discovery.js"; -import { providerDisplayName } from "./model-selector.js"; import type { ModelRegistry } from "../../../core/model-registry.js"; import { ModelsJsonWriter } from "../../../core/models-json-writer.js"; import { theme } from "../theme/theme.js"; import { rawKeyHint } from "./keybinding-hints.js"; +import { providerDisplayName } from "./model-selector.js"; interface ProviderInfo { name: string; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts b/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts index 29bd39d39..2c8d7a2a7 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts @@ -1,5 +1,4 @@ import type { Model } from "@singularity-forge/pi-ai"; -import { providerDisplayName } from "./model-selector.js"; import { Container, type Focusable, @@ -13,6 +12,7 @@ import { } from "@singularity-forge/pi-tui"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; +import { providerDisplayName } from "./model-selector.js"; // EnabledIds: null = all enabled (no filter), string[] = explicit ordered list type EnabledIds = string[] | null; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts b/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts index 21dca012a..2bd069745 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts @@ -1,6 +1,5 @@ import type { ThinkingLevel } from "@singularity-forge/pi-agent-core"; import type { Transport } from "@singularity-forge/pi-ai"; -import { PROXY_FAMILY_PRIORITY } from "../../../core/model-registry.js"; import { Container, getCapabilities, @@ -11,6 +10,7 @@ import { Spacer, Text, } from "@singularity-forge/pi-tui"; +import { PROXY_FAMILY_PRIORITY } from "../../../core/model-registry.js"; import { getSelectListTheme, getSettingsListTheme, @@ -170,7 +170,7 @@ export class ProxyPrioritySubmenu extends Container { constructor( private proxyFamilyProviders: Record, private onChange: (familyPrefix: string, provider: string) => void, - private onDone: () => void, + _onDone: () => void, private onCancel: () => void, ) { super(); diff --git a/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts b/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts index f6a634174..c009256de 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts @@ -3,7 +3,6 @@ import { Markdown, type MarkdownTheme, Spacer, - Text, } from "@singularity-forge/pi-tui"; import { getMarkdownTheme, theme } from "../theme/theme.js"; import { formatTimestamp, type TimestampFormat } from "./timestamp.js"; diff --git a/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts b/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts index b2727dbf6..06d6efe0f 100644 --- a/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +++ b/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts @@ -1,14 +1,13 @@ import { Loader, Markdown, Spacer, Text } from "@singularity-forge/pi-tui"; - +import { AssistantMessageComponent } from "../components/assistant-message.js"; +import { DynamicBorder } from "../components/dynamic-border.js"; +import { appKey } from "../components/keybinding-hints.js"; +import { ToolExecutionComponent } from "../components/tool-execution.js"; import type { InteractiveModeEvent, InteractiveModeStateHost, } from "../interactive-mode-state.js"; import { theme } from "../theme/theme.js"; -import { AssistantMessageComponent } from "../components/assistant-message.js"; -import { ToolExecutionComponent } from "../components/tool-execution.js"; -import { DynamicBorder } from "../components/dynamic-border.js"; -import { appKey } from "../components/keybinding-hints.js"; // Tracks the last processed content index to avoid re-scanning all blocks on every message_update let lastProcessedContentIndex = 0; diff --git a/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts b/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts index 33159df7e..0e8f489f2 100644 --- a/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +++ b/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts @@ -1,14 +1,13 @@ import type { ExtensionUIContext } from "../../../core/extensions/index.js"; - +import { appKey } from "../components/keybinding-hints.js"; import { - Theme, getAvailableThemesWithPaths, getThemeByName, setTheme, setThemeInstance, + Theme, theme, } from "../theme/theme.js"; -import { appKey } from "../components/keybinding-hints.js"; export function createExtensionUIContext(host: any): ExtensionUIContext { return { diff --git a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts index 35eb49c11..4ae75f29a 100644 --- a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts @@ -3,6 +3,7 @@ * Handles TUI rendering and user interaction, delegating business logic to AgentSession. */ +import { spawn, spawnSync } from "node:child_process"; import * as crypto from "node:crypto"; import * as fs from "node:fs"; import * as os from "node:os"; @@ -13,8 +14,6 @@ import type { AssistantMessage, ImageContent, Message, - Model, - OAuthProviderId, } from "@singularity-forge/pi-ai"; import type { AutocompleteItem, @@ -22,8 +21,6 @@ import type { EditorTheme, KeyId, MarkdownTheme, - OverlayHandle, - OverlayOptions, SlashCommand, } from "@singularity-forge/pi-tui"; import { @@ -36,16 +33,14 @@ import { matchesKey, ProcessTerminal, Spacer, - type Terminal as TuiTerminal, Text, TruncatedText, TUI, + type Terminal as TuiTerminal, visibleWidth, } from "@singularity-forge/pi-tui"; -import { spawn, spawnSync } from "child_process"; import { APP_NAME, - getAuthPath, getDebugLogPath, getUpdateInstruction, VERSION, @@ -56,12 +51,12 @@ import { parseSkillBlock, } from "../../core/agent-session.js"; import type { CompactionResult } from "../../core/compaction/index.js"; +import { ContextualTips } from "../../core/contextual-tips.js"; import type { ExtensionContext, ExtensionRunner, ExtensionUIContext, ExtensionUIDialogOptions, - ExtensionWidgetOptions, } from "../../core/extensions/index.js"; import { FooterDataProvider, @@ -69,8 +64,6 @@ import { } from "../../core/footer-data-provider.js"; import { type AppAction, KeybindingsManager } from "../../core/keybindings.js"; import { createCompactionSummaryMessage } from "../../core/messages.js"; -import { resolveModelScope } from "../../core/model-resolver.js"; -import { PROXY_FAMILY_PRIORITY } from "../../core/model-registry.js"; import type { ResourceDiagnostic } from "../../core/resource-loader.js"; import { type SessionContext, @@ -87,6 +80,7 @@ import { extensionForImageMimeType, readClipboardImage, } from "../../utils/clipboard-image.js"; +import { killTrackedDetachedChildren } from "../../utils/shell.js"; import { ensureTool } from "../../utils/tools-manager.js"; import { AssistantMessageComponent } from "./components/assistant-message.js"; import { BashExecutionComponent } from "./components/bash-execution.js"; @@ -98,61 +92,39 @@ import { CustomMessageComponent } from "./components/custom-message.js"; import { DaxnutsComponent } from "./components/daxnuts.js"; import { DynamicBorder } from "./components/dynamic-border.js"; import { ExtensionEditorComponent } from "./components/extension-editor.js"; -import { ExtensionInputComponent } from "./components/extension-input.js"; +import type { ExtensionInputComponent } from "./components/extension-input.js"; import { ExtensionSelectorComponent } from "./components/extension-selector.js"; import { FooterComponent } from "./components/footer.js"; import { appKey, appKeyHint, - editorKey, - formatKeyForDisplay, keyHint, rawKeyHint, } from "./components/keybinding-hints.js"; -import { LoginDialogComponent } from "./components/login-dialog.js"; import { ModelSelectorComponent, providerDisplayName, } from "./components/model-selector.js"; -import { OAuthSelectorComponent } from "./components/oauth-selector.js"; -import { ProviderManagerComponent } from "./components/provider-manager.js"; -import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js"; import { SessionSelectorComponent } from "./components/session-selector.js"; -import { SettingsSelectorComponent } from "./components/settings-selector.js"; import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js"; import { ToolExecutionComponent } from "./components/tool-execution.js"; import { TreeSelectorComponent } from "./components/tree-selector.js"; import { UserMessageComponent } from "./components/user-message.js"; import { UserMessageSelectorComponent } from "./components/user-message-selector.js"; -import { ContextualTips } from "../../core/contextual-tips.js"; -import { - type SlashCommandContext, - dispatchSlashCommand, - getAppKeyDisplay, -} from "./slash-command-handlers.js"; import { handleAgentEvent } from "./controllers/chat-controller.js"; import { createExtensionUIContext as buildExtensionUIContext } from "./controllers/extension-ui-controller.js"; import { setupEditorSubmitHandler as setupEditorSubmitHandlerController } from "./controllers/input-controller.js"; +import { updateAvailableProviderCount as updateAvailableProviderCountController } from "./controllers/model-controller.js"; +import { getAppKeyDisplay } from "./slash-command-handlers.js"; import { - findExactModelMatch as findExactModelMatchController, - getModelCandidates as getModelCandidatesController, - handleModelCommand as handleModelCommandController, - updateAvailableProviderCount as updateAvailableProviderCountController, -} from "./controllers/model-controller.js"; -import { killTrackedDetachedChildren } from "../../utils/shell.js"; -import { - getAvailableThemes, - getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, - getThemeByName, initTheme, onThemeChange, - stopThemeWatcher, setRegisteredThemes, setTheme, - setThemeInstance, - Theme, + stopThemeWatcher, + type Theme, type ThemeColor, theme, } from "./theme/theme.js"; @@ -224,7 +196,6 @@ export class InteractiveMode { private isInitialized = false; private onInputCallback?: (text: string) => void; private loadingAnimation: Loader | undefined = undefined; - private pendingWorkingMessage: string | undefined = undefined; private readonly defaultWorkingMessage = "Working..."; private lastSigintTime = 0; @@ -265,26 +236,9 @@ export class InteractiveMode { // Contextual tips — session-scoped, non-intrusive hints private contextualTips = new ContextualTips(); - // Track current bash execution component - private bashComponent: BashExecutionComponent | undefined = undefined; - - // Track pending bash components (shown in pending area, moved to chat on submit) - private pendingBashComponents: BashExecutionComponent[] = []; - - // Auto-compaction state - private autoCompactionLoader: Loader | undefined = undefined; - private autoCompactionEscapeHandler?: () => void; - - // Auto-retry state - private retryLoader: Loader | undefined = undefined; - private retryEscapeHandler?: () => void; - // Messages queued while compaction is running private compactionQueuedMessages: CompactionQueuedMessage[] = []; - // Shutdown state - private shutdownRequested = false; - // Extension UI state private extensionSelector: ExtensionSelectorComponent | undefined = undefined; private extensionInput: ExtensionInputComponent | undefined = undefined; @@ -1588,61 +1542,6 @@ export class InteractiveMode { this.ui.requestRender(); } - /** - * Set an extension widget (string array or custom component). - */ - private setExtensionWidget( - key: string, - content: - | string[] - | ((tui: TUI, thm: Theme) => Component & { dispose?(): void }) - | undefined, - options?: ExtensionWidgetOptions, - ): void { - const placement = options?.placement ?? "aboveEditor"; - const removeExisting = ( - map: Map, - ) => { - const existing = map.get(key); - if (existing?.dispose) existing.dispose(); - map.delete(key); - }; - - removeExisting(this.extensionWidgetsAbove); - removeExisting(this.extensionWidgetsBelow); - - if (content === undefined) { - this.renderWidgets(); - return; - } - - let component: Component & { dispose?(): void }; - - if (Array.isArray(content)) { - // Wrap string array in a Container with Text components - const container = new Container(); - for (const line of content.slice(0, InteractiveMode.MAX_WIDGET_LINES)) { - container.addChild(new Text(line, 1, 0)); - } - if (content.length > InteractiveMode.MAX_WIDGET_LINES) { - container.addChild( - new Text(theme.fg("muted", "... (widget truncated)"), 1, 0), - ); - } - component = container; - } else { - // Factory function - create component - component = content(this.ui, theme); - } - - const targetMap = - placement === "belowEditor" - ? this.extensionWidgetsBelow - : this.extensionWidgetsAbove; - targetMap.set(key, component); - this.renderWidgets(); - } - private clearExtensionWidgets(): void { for (const widget of this.extensionWidgetsAbove.values()) { widget.dispose?.(); @@ -1682,9 +1581,6 @@ export class InteractiveMode { } } - // Maximum total widget lines to prevent viewport overflow - private static readonly MAX_WIDGET_LINES = 10; - /** * Render all extension widgets to the widget container. */ @@ -1818,17 +1714,6 @@ export class InteractiveMode { this.ui.requestRender(); } - private addExtensionTerminalInputListener( - handler: (data: string) => { consume?: boolean; data?: string } | undefined, - ): () => void { - const unsubscribe = this.ui.addInputListener(handler); - this.extensionTerminalInputUnsubscribers.add(unsubscribe); - return () => { - unsubscribe(); - this.extensionTerminalInputUnsubscribers.delete(unsubscribe); - }; - } - private clearExtensionTerminalInputListeners(): void { for (const unsubscribe of this.extensionTerminalInputUnsubscribers) { unsubscribe(); @@ -1909,65 +1794,6 @@ export class InteractiveMode { this.ui.requestRender(); } - /** - * Show a confirmation dialog for extensions. - */ - private async showExtensionConfirm( - title: string, - message: string, - opts?: ExtensionUIDialogOptions, - ): Promise { - const result = await this.showExtensionSelector( - `${title}\n${message}`, - ["Yes", "No"], - opts, - ); - return result === "Yes"; - } - - /** - * Show a text input for extensions. - */ - private showExtensionInput( - title: string, - placeholder?: string, - opts?: ExtensionUIDialogOptions, - ): Promise { - return new Promise((resolve) => { - if (opts?.signal?.aborted) { - resolve(undefined); - return; - } - - const onAbort = () => { - this.hideExtensionInput(); - resolve(undefined); - }; - opts?.signal?.addEventListener("abort", onAbort, { once: true }); - - this.extensionInput = new ExtensionInputComponent( - title, - placeholder, - (value) => { - opts?.signal?.removeEventListener("abort", onAbort); - this.hideExtensionInput(); - resolve(value); - }, - () => { - opts?.signal?.removeEventListener("abort", onAbort); - this.hideExtensionInput(); - resolve(undefined); - }, - { tui: this.ui, timeout: opts?.timeout, secure: opts?.secure }, - ); - - this.editorContainer.clear(); - this.editorContainer.addChild(this.extensionInput); - this.ui.setFocus(this.extensionInput); - this.ui.requestRender(); - }); - } - /** * Hide the extension input. */ @@ -2104,103 +1930,6 @@ export class InteractiveMode { this.ui.requestRender(); } - /** - * Show a notification for extensions. - */ - private showExtensionNotify( - message: string, - type?: "info" | "warning" | "error" | "success", - ): void { - if (type === "error") { - this.showError(message); - } else if (type === "warning") { - this.showWarning(message); - } else { - this.showStatus(message, { append: true }); - } - } - - /** Show a custom component with keyboard focus. Overlay mode renders on top of existing content. */ - private async showExtensionCustom( - factory: ( - tui: TUI, - theme: Theme, - keybindings: KeybindingsManager, - done: (result: T) => void, - ) => - | (Component & { dispose?(): void }) - | Promise, - options?: { - overlay?: boolean; - overlayOptions?: OverlayOptions | (() => OverlayOptions); - onHandle?: (handle: OverlayHandle) => void; - }, - ): Promise { - const savedText = this.editor.getText(); - const isOverlay = options?.overlay ?? false; - - const restoreEditor = () => { - this.editorContainer.clear(); - this.editorContainer.addChild(this.editor); - this.editor.setText(savedText); - this.ui.setFocus(this.editor); - this.ui.requestRender(); - }; - - return new Promise((resolve, reject) => { - let component: Component & { dispose?(): void }; - let closed = false; - - const close = (result: T) => { - if (closed) return; - closed = true; - if (isOverlay) this.ui.hideOverlay(); - else restoreEditor(); - // Note: both branches above already call requestRender - resolve(result); - try { - component?.dispose?.(); - } catch { - /* ignore dispose errors */ - } - }; - - Promise.resolve(factory(this.ui, theme, this.keybindings, close)) - .then((c) => { - if (closed) return; - component = c; - if (isOverlay) { - // Resolve overlay options - can be static or dynamic function - const resolveOptions = (): OverlayOptions | undefined => { - if (options?.overlayOptions) { - const opts = - typeof options.overlayOptions === "function" - ? options.overlayOptions() - : options.overlayOptions; - return opts; - } - // Fallback: use component's width property if available - const w = (component as { width?: number }).width; - return w ? { width: w } : undefined; - }; - const handle = this.ui.showOverlay(component, resolveOptions()); - // Expose handle to caller for visibility control - options?.onHandle?.(handle); - } else { - this.editorContainer.clear(); - this.editorContainer.addChild(component); - this.ui.setFocus(component); - this.ui.requestRender(); - } - }) - .catch((err) => { - if (closed) return; - if (!isOverlay) restoreEditor(); - reject(err); - }); - }); - } - /** * Show an extension error in the UI. */ @@ -2330,53 +2059,6 @@ export class InteractiveMode { } } - private getSlashCommandContext(): SlashCommandContext { - return { - session: this.session, - ui: this.ui, - keybindings: this.keybindings, - chatContainer: this.chatContainer, - statusContainer: this.statusContainer, - editorContainer: this.editorContainer, - headerContainer: this.headerContainer, - pendingMessagesContainer: this.pendingMessagesContainer, - editor: this.editor, - defaultEditor: this.defaultEditor, - sessionManager: this.sessionManager, - settingsManager: this.settingsManager, - invalidateFooter: () => this.footer.invalidate(), - showStatus: (msg) => this.showStatus(msg), - showError: (msg) => this.showError(msg), - showWarning: (msg) => this.showWarning(msg), - showSelector: (create) => this.showSelector(create), - updateEditorBorderColor: () => this.updateEditorBorderColor(), - getMarkdownThemeWithSettings: () => this.getMarkdownThemeWithSettings(), - requestRender: () => this.ui.requestRender(), - updateTerminalTitle: () => this.updateTerminalTitle(), - showSettingsSelector: () => this.showSettingsSelector(), - showModelsSelector: () => this.showModelsSelector(), - handleModelCommand: (searchTerm) => this.handleModelCommand(searchTerm), - showUserMessageSelector: () => this.showUserMessageSelector(), - showTreeSelector: () => this.showTreeSelector(), - showProviderManager: () => this.showProviderManager(), - showOAuthSelector: (mode) => this.showOAuthSelector(mode), - showSessionSelector: () => this.showSessionSelector(), - handleClearCommand: () => this.handleClearCommand(), - handleReloadCommand: () => this.handleReloadCommand(), - handleDebugCommand: () => this.handleDebugCommand(), - shutdown: () => this.shutdown(), - executeCompaction: (instructions, isAuto) => - this.executeCompaction(instructions, isAuto), - handleBashCommand: (command, options) => - this.handleBashCommand( - command, - options?.excludeFromContext, - options?.displayCommand, - options?.loginShell, - ), - }; - } - private setupEditorSubmitHandler(): void { setupEditorSubmitHandlerController(this as any); } @@ -2872,14 +2554,6 @@ export class InteractiveMode { process.exit(0); } - /** - * Check if shutdown was requested and perform shutdown if so. - */ - private async checkShutdownRequested(): Promise { - if (!this.shutdownRequested) return; - await this.shutdown(); - } - private handleCtrlZ(): void { // On Windows, SIGTSTP doesn't exist - Ctrl+Z is not supported if (process.platform === "win32") { @@ -3408,15 +3082,6 @@ export class InteractiveMode { } } - /** Move pending bash components from pending area to chat */ - private flushPendingBashComponents(): void { - for (const component of this.pendingBashComponents) { - this.pendingMessagesContainer.removeChild(component); - this.chatContainer.addChild(component); - } - this.pendingBashComponents = []; - } - // ========================================================================= // Selectors // ========================================================================= @@ -3440,194 +3105,6 @@ export class InteractiveMode { this.ui.requestRender(); } - private showSettingsSelector(): void { - this.showSelector((done) => { - const selector = new SettingsSelectorComponent( - { - autoCompact: this.session.autoCompactionEnabled, - showImages: this.settingsManager.getShowImages(), - autoResizeImages: this.settingsManager.getImageAutoResize(), - blockImages: this.settingsManager.getBlockImages(), - enableSkillCommands: this.settingsManager.getEnableSkillCommands(), - steeringMode: this.session.steeringMode, - followUpMode: this.session.followUpMode, - transport: this.settingsManager.getTransport(), - thinkingLevel: this.session.thinkingLevel, - availableThinkingLevels: this.session.getAvailableThinkingLevels(), - currentTheme: this.settingsManager.getTheme() || "dark", - availableThemes: getAvailableThemes(), - hideThinkingBlock: this.hideThinkingBlock, - collapseChangelog: this.settingsManager.getCollapseChangelog(), - doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(), - treeFilterMode: this.settingsManager.getTreeFilterMode(), - showHardwareCursor: this.settingsManager.getShowHardwareCursor(), - editorPaddingX: this.settingsManager.getEditorPaddingX(), - autocompleteMaxVisible: - this.settingsManager.getAutocompleteMaxVisible(), - respectGitignoreInPicker: - this.settingsManager.getRespectGitignoreInPicker(), - quietStartup: this.settingsManager.getQuietStartup(), - clearOnShrink: this.settingsManager.getClearOnShrink(), - timestampFormat: this.settingsManager.getTimestampFormat(), - proxyFamilyProviders: (() => { - const overrides = this.settingsManager.getProxyProviderPriority(); - const result: Record = {}; - for (const rule of PROXY_FAMILY_PRIORITY) { - const top = overrides[rule.prefix]?.[0] ?? rule.providers[0]; - if (top) result[rule.prefix] = top; - } - return result; - })(), - }, - { - onAutoCompactChange: (enabled) => { - this.session.setAutoCompactionEnabled(enabled); - this.footer.setAutoCompactEnabled(enabled); - }, - onShowImagesChange: (enabled) => { - this.settingsManager.setShowImages(enabled); - for (const child of this.chatContainer.children) { - if (child instanceof ToolExecutionComponent) { - child.setShowImages(enabled); - } - } - }, - onAutoResizeImagesChange: (enabled) => { - this.settingsManager.setImageAutoResize(enabled); - }, - onBlockImagesChange: (blocked) => { - this.settingsManager.setBlockImages(blocked); - }, - onEnableSkillCommandsChange: (enabled) => { - this.settingsManager.setEnableSkillCommands(enabled); - this.setupAutocomplete(); - }, - onSteeringModeChange: (mode) => { - this.session.setSteeringMode(mode); - }, - onFollowUpModeChange: (mode) => { - this.session.setFollowUpMode(mode); - }, - onTransportChange: (transport) => { - this.settingsManager.setTransport(transport); - this.session.agent.setTransport(transport); - }, - onThinkingLevelChange: (level) => { - this.session.setThinkingLevel(level); - this.footer.invalidate(); - this.updateEditorBorderColor(); - }, - onThemeChange: (themeName) => { - const result = setTheme(themeName, true); - this.settingsManager.setTheme(themeName); - this.ui.invalidate(); - if (!result.success) { - this.showError( - `Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`, - ); - } - }, - onThemePreview: (themeName) => { - const result = setTheme(themeName, true); - if (result.success) { - this.ui.invalidate(); - this.ui.requestRender(); - } - }, - onHideThinkingBlockChange: (hidden) => { - this.hideThinkingBlock = hidden; - this.settingsManager.setHideThinkingBlock(hidden); - for (const child of this.chatContainer.children) { - if (child instanceof AssistantMessageComponent) { - child.setHideThinkingBlock(hidden); - } - } - this.chatContainer.clear(); - this.rebuildChatFromMessages(); - }, - onCollapseChangelogChange: (collapsed) => { - this.settingsManager.setCollapseChangelog(collapsed); - }, - onQuietStartupChange: (enabled) => { - this.settingsManager.setQuietStartup(enabled); - }, - onDoubleEscapeActionChange: (action) => { - this.settingsManager.setDoubleEscapeAction(action); - }, - onTreeFilterModeChange: (mode) => { - this.settingsManager.setTreeFilterMode(mode); - }, - onShowHardwareCursorChange: (enabled) => { - this.settingsManager.setShowHardwareCursor(enabled); - this.ui.setShowHardwareCursor(enabled); - }, - onEditorPaddingXChange: (padding) => { - this.settingsManager.setEditorPaddingX(padding); - this.defaultEditor.setPaddingX(padding); - if ( - this.editor !== this.defaultEditor && - this.editor.setPaddingX !== undefined - ) { - this.editor.setPaddingX(padding); - } - }, - onAutocompleteMaxVisibleChange: (maxVisible) => { - this.settingsManager.setAutocompleteMaxVisible(maxVisible); - this.defaultEditor.setAutocompleteMaxVisible(maxVisible); - if ( - this.editor !== this.defaultEditor && - this.editor.setAutocompleteMaxVisible !== undefined - ) { - this.editor.setAutocompleteMaxVisible(maxVisible); - } - }, - onClearOnShrinkChange: (enabled) => { - this.settingsManager.setClearOnShrink(enabled); - this.ui.setClearOnShrink(enabled); - }, - onRespectGitignoreInPickerChange: (enabled) => { - this.settingsManager.setRespectGitignoreInPicker(enabled); - this.autocompleteProvider?.setRespectGitignore(enabled); - }, - onTimestampFormatChange: (format) => { - this.settingsManager.setTimestampFormat(format); - }, - onProxyFamilyProviderChange: (familyPrefix, preferredProvider) => { - const rule = PROXY_FAMILY_PRIORITY.find( - (r) => r.prefix === familyPrefix, - ); - if (!rule) return; - // Move selected provider to front, keep others in original order - const rest = rule.providers.filter((p) => p !== preferredProvider); - this.settingsManager.setProxyFamilyProvider(familyPrefix, [ - preferredProvider, - ...rest, - ]); - }, - onCancel: () => { - done(); - this.ui.requestRender(); - }, - }, - ); - return { component: selector, focus: selector.getSettingsList() }; - }); - } - - private async handleModelCommand(searchTerm?: string): Promise { - await handleModelCommandController(this, searchTerm); - } - - private async findExactModelMatch( - searchTerm: string, - ): Promise | undefined> { - return findExactModelMatchController(this, searchTerm); - } - - private async getModelCandidates(): Promise[]> { - return getModelCandidatesController(this); - } - /** Update the footer's available provider count from current model candidates */ private async updateAvailableProviderCount(): Promise { await updateAvailableProviderCountController(this); @@ -3666,130 +3143,6 @@ export class InteractiveMode { }); } - private async showModelsSelector(): Promise { - // Get all available models - this.session.modelRegistry.refresh(); - const allModels = this.session.modelRegistry.getAvailable(); - - if (allModels.length === 0) { - this.showStatus("No models available"); - return; - } - - // Check if session has scoped models (from previous session-only changes or CLI --models) - const sessionScopedModels = this.session.scopedModels; - const hasSessionScope = sessionScopedModels.length > 0; - - // Build enabled model IDs from session state or settings - const enabledModelIds = new Set(); - let hasFilter = false; - - if (hasSessionScope) { - // Use current session's scoped models - for (const sm of sessionScopedModels) { - enabledModelIds.add(`${sm.model.provider}/${sm.model.id}`); - } - hasFilter = true; - } else { - // Fall back to settings - const patterns = this.settingsManager.getEnabledModels(); - if (patterns !== undefined && patterns.length > 0) { - hasFilter = true; - const scopedModels = await resolveModelScope( - patterns, - this.session.modelRegistry, - ); - for (const sm of scopedModels) { - enabledModelIds.add(`${sm.model.provider}/${sm.model.id}`); - } - } - } - - // Track current enabled state (session-only until persisted) - const currentEnabledIds = new Set(enabledModelIds); - let currentHasFilter = hasFilter; - - // Helper to update session's scoped models (session-only, no persist) - const updateSessionModels = async (enabledIds: Set) => { - if (enabledIds.size > 0 && enabledIds.size < allModels.length) { - const newScopedModels = await resolveModelScope( - Array.from(enabledIds), - this.session.modelRegistry, - ); - this.session.setScopedModels( - newScopedModels.map((sm) => ({ - model: sm.model, - thinkingLevel: sm.thinkingLevel, - })), - ); - } else { - // All enabled or none enabled = no filter - this.session.setScopedModels([]); - } - await this.updateAvailableProviderCount(); - this.ui.requestRender(); - }; - - this.showSelector((done) => { - const selector = new ScopedModelsSelectorComponent( - { - allModels, - enabledModelIds: currentEnabledIds, - hasEnabledModelsFilter: currentHasFilter, - }, - { - onModelToggle: async (modelId, enabled) => { - if (enabled) { - currentEnabledIds.add(modelId); - } else { - currentEnabledIds.delete(modelId); - } - currentHasFilter = true; - await updateSessionModels(currentEnabledIds); - }, - onEnableAll: async (allModelIds) => { - currentEnabledIds.clear(); - for (const id of allModelIds) { - currentEnabledIds.add(id); - } - currentHasFilter = false; - await updateSessionModels(currentEnabledIds); - }, - onClearAll: async () => { - currentEnabledIds.clear(); - currentHasFilter = true; - await updateSessionModels(currentEnabledIds); - }, - onToggleProvider: async (_provider, modelIds, enabled) => { - for (const id of modelIds) { - if (enabled) { - currentEnabledIds.add(id); - } else { - currentEnabledIds.delete(id); - } - } - currentHasFilter = true; - await updateSessionModels(currentEnabledIds); - }, - onPersist: (enabledIds) => { - // Persist to settings - const newPatterns = - enabledIds.length === allModels.length - ? undefined // All enabled = clear filter - : enabledIds; - this.settingsManager.setEnabledModels(newPatterns); - this.showStatus("Model selection saved to settings"); - }, - onCancel: () => { - done(); - this.ui.requestRender(); - }, - }, - ); - return { component: selector, focus: selector }; - }); - } - private showUserMessageSelector(): void { const userMessages = this.session.getUserMessagesForForking(); @@ -4029,239 +3382,6 @@ export class InteractiveMode { } } - private showProviderManager(): void { - this.showSelector((done) => { - const component = new ProviderManagerComponent( - this.ui, - this.session.modelRegistry.authStorage, - this.session.modelRegistry, - () => { - done(); - this.ui.requestRender(); - }, - async (provider: string) => { - this.showStatus(`Discovering models for ${provider}...`); - try { - const results = await this.session.modelRegistry.discoverModels([ - provider, - ]); - const result = results[0]; - if (result?.error) { - this.showError(`Discovery failed: ${result.error}`); - } else { - this.showStatus( - `Discovered ${result?.models.length ?? 0} models from ${provider}`, - ); - } - } catch (error) { - this.showError( - error instanceof Error ? error.message : String(error), - ); - } - done(); - this.ui.requestRender(); - }, - async (provider: string) => { - // Enter key → auth setup for selected provider (#3579) - done(); - await this.showLoginDialog(provider); - }, - ); - return { component, focus: component }; - }); - } - - private async showOAuthSelector(mode: "login" | "logout"): Promise { - if (mode === "logout") { - const providers = this.session.modelRegistry.authStorage.list(); - const loggedInProviders = providers.filter( - (p) => this.session.modelRegistry.authStorage.get(p)?.type === "oauth", - ); - if (loggedInProviders.length === 0) { - this.showStatus("No OAuth providers logged in. Use /login first."); - return; - } - } - - this.showSelector((done) => { - const selector = new OAuthSelectorComponent( - mode, - this.session.modelRegistry.authStorage, - (providerId: string) => { - done(); - - // OAuthSelectorComponent calls this synchronously (no await), - // so we must catch async errors here to prevent unhandled rejections - // when the user cancels the login dialog (#821). - const handleAsync = async () => { - if (mode === "login") { - await this.showLoginDialog(providerId); - } else { - // Logout flow - const providerInfo = this.session.modelRegistry.authStorage - .getOAuthProviders() - .find((p) => p.id === providerId); - const providerName = providerInfo?.name || providerId; - - try { - this.session.modelRegistry.authStorage.logout(providerId); - this.session.modelRegistry.refresh(); - await this.updateAvailableProviderCount(); - - // Auto-switch model if current model belongs to the logged-out provider - const currentModel = this.session.model; - if (currentModel?.provider === providerId) { - try { - const available = this.session.modelRegistry.getAvailable(); - const fallback = available.find( - (m) => m.provider !== providerId, - ); - if (fallback) { - await this.session.setModel(fallback); - } - } catch { - // Model switch failed — user can manually switch via /model - } - } - - this.showStatus(`Logged out of ${providerName}`); - } catch (error: unknown) { - this.showError( - `Logout failed: ${error instanceof Error ? error.message : String(error)}`, - ); - } - } - }; - handleAsync().catch(() => { - // Swallow — showLoginDialog already handles its own errors. - // This prevents unhandled rejections when login is cancelled. - }); - }, - () => { - done(); - this.ui.requestRender(); - }, - ); - return { component: selector, focus: selector }; - }); - } - - private async showLoginDialog(providerId: string): Promise { - const providerInfo = this.session.modelRegistry.authStorage - .getOAuthProviders() - .find((p) => p.id === providerId); - const providerName = providerInfo?.name || providerId; - - // Providers that use callback servers (can paste redirect URL) - const usesCallbackServer = providerInfo?.usesCallbackServer ?? false; - - // Create login dialog component - const dialog = new LoginDialogComponent( - this.ui, - providerId, - (_success, _message) => { - // Completion handled below - }, - ); - - // Show dialog in editor container - this.editorContainer.clear(); - this.editorContainer.addChild(dialog); - this.ui.setFocus(dialog); - this.ui.requestRender(); - - // Restore editor helper — also disposes the dialog to reject any - // dangling promises and prevent the UI from getting stuck. - const restoreEditor = () => { - dialog.dispose(); - this.editorContainer.clear(); - this.editorContainer.addChild(this.editor); - this.ui.setFocus(this.editor); - this.ui.requestRender(); - }; - - try { - await this.session.modelRegistry.authStorage.login( - providerId as OAuthProviderId, - { - onAuth: (info: { url: string; instructions?: string }) => { - dialog.showAuth(info.url, info.instructions); - - if (!usesCallbackServer && providerId === "github-copilot") { - // GitHub Copilot polls after onAuth - dialog.showWaiting("Waiting for browser authentication..."); - } - // For Anthropic: onPrompt is called immediately after - }, - - onPrompt: async (prompt: { - message: string; - placeholder?: string; - }) => { - return dialog.showPrompt(prompt.message, prompt.placeholder); - }, - - onProgress: (message: string) => { - dialog.showProgress(message); - }, - - // Callback-server providers race browser callback with pasted redirect URL. - // Keep manual-input promise ownership inside provider flow to avoid - // orphaned rejections when the callback is not consumed. - onManualCodeInput: usesCallbackServer - ? () => - dialog.showManualInput( - "Paste redirect URL below, or complete login in browser:", - ) - : undefined, - - signal: dialog.signal, - }, - ); - - // Success - restoreEditor(); - this.session.modelRegistry.refresh(); - await this.updateAvailableProviderCount(); - - // Auto-switch model if current model has no valid API key - try { - const currentModel = this.session.model; - if (currentModel) { - const currentKey = - await this.session.modelRegistry.getApiKey(currentModel); - if (!currentKey) { - const available = this.session.modelRegistry.getAvailable(); - const newProviderModel = available.find( - (m) => m.provider === providerId, - ); - if (newProviderModel) { - await this.session.setModel(newProviderModel); - } else if (available.length > 0) { - await this.session.setModel(available[0]); - } - } - } - } catch (error: unknown) { - // Model switch failed — user can manually switch via /model - } - - this.showStatus( - `Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`, - ); - } catch (error: unknown) { - restoreEditor(); - const errorMsg = error instanceof Error ? error.message : String(error); - if ( - errorMsg !== "Login cancelled" && - !errorMsg.includes("Superseded") && - !errorMsg.includes("disposed") - ) { - this.showError(`Failed to login to ${providerName}: ${errorMsg}`); - } - } - } - // ========================================================================= // Command handlers // ========================================================================= @@ -4435,115 +3555,6 @@ export class InteractiveMode { } } - private async handleBashCommand( - command: string, - excludeFromContext = false, - displayCommand?: string, - loginShell?: boolean, - ): Promise { - const extensionRunner = this.session.extensionRunner; - const label = displayCommand || command; - - // Emit user_bash event to let extensions intercept - const eventResult = extensionRunner - ? await extensionRunner.emitUserBash({ - type: "user_bash", - command, - excludeFromContext, - cwd: process.cwd(), - }) - : undefined; - - // If extension returned a full result, use it directly - if (eventResult?.result) { - const result = eventResult.result; - - // Create UI component for display - this.bashComponent = new BashExecutionComponent( - label, - this.ui, - excludeFromContext, - ); - if (this.session.isStreaming) { - this.pendingMessagesContainer.addChild(this.bashComponent); - this.pendingBashComponents.push(this.bashComponent); - } else { - this.chatContainer.addChild(this.bashComponent); - } - - // Show output and complete - if (result.output) { - this.bashComponent.appendOutput(result.output); - } - this.bashComponent.setComplete( - result.exitCode, - result.cancelled, - result.truncated - ? ({ truncated: true, content: result.output } as TruncationResult) - : undefined, - result.fullOutputPath, - ); - - // Record the result in session - this.session.recordBashResult(command, result, { excludeFromContext }); - this.bashComponent = undefined; - this.ui.requestRender(); - return; - } - - // Normal execution path (possibly with custom operations) - const isDeferred = this.session.isStreaming; - this.bashComponent = new BashExecutionComponent( - label, - this.ui, - excludeFromContext, - ); - - if (isDeferred) { - // Show in pending area when agent is streaming - this.pendingMessagesContainer.addChild(this.bashComponent); - this.pendingBashComponents.push(this.bashComponent); - } else { - // Show in chat immediately when agent is idle - this.chatContainer.addChild(this.bashComponent); - } - this.ui.requestRender(); - - try { - const result = await this.session.executeBash( - command, - (chunk) => { - if (this.bashComponent) { - this.bashComponent.appendOutput(chunk); - this.ui.requestRender(); - } - }, - { excludeFromContext, operations: eventResult?.operations, loginShell }, - ); - - if (this.bashComponent) { - this.bashComponent.setComplete( - result.exitCode, - result.cancelled, - result.truncated - ? ({ truncated: true, content: result.output } as TruncationResult) - : undefined, - result.fullOutputPath, - ); - } - } catch (error) { - if (this.bashComponent) { - this.bashComponent.setComplete(undefined, false); - } - this.showError( - `Bash command failed: ${error instanceof Error ? error.message : "Unknown error"}`, - ); - } - - this.bashComponent = undefined; - this.ui.requestRender(); - } - private async executeCompaction( customInstructions?: string, isAuto = false, diff --git a/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts b/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts index 0b9b1ec6e..3d88665bd 100644 --- a/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +++ b/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts @@ -8,6 +8,7 @@ * InteractiveMode and are called through the `SlashCommandContext` interface. */ +import { spawn, spawnSync } from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; @@ -16,22 +17,22 @@ import type { EditorAction, EditorComponent, MarkdownTheme, + TUI, } from "@singularity-forge/pi-tui"; import { type Component, - Container, + type Container, Markdown, Spacer, Text, } from "@singularity-forge/pi-tui"; -import { spawn, spawnSync } from "child_process"; import { getShareViewerUrl } from "../../config.js"; import type { AgentSession } from "../../core/agent-session.js"; import type { AppAction, KeybindingsManager } from "../../core/keybindings.js"; import type { SessionManager } from "../../core/session-manager.js"; import type { SettingsManager } from "../../core/settings-manager.js"; -import { copyToClipboard } from "../../utils/clipboard.js"; import { getChangelogPath, parseChangelog } from "../../utils/changelog.js"; +import { copyToClipboard } from "../../utils/clipboard.js"; import { ArminComponent } from "./components/armin.js"; import { BorderedLoader } from "./components/bordered-loader.js"; import { DynamicBorder } from "./components/dynamic-border.js"; @@ -46,8 +47,6 @@ import { } from "./components/settings-selector.js"; import { theme } from "./theme/theme.js"; -import type { TUI } from "@singularity-forge/pi-tui"; - // --------------------------------------------------------------------------- // Context interface — the subset of InteractiveMode needed by slash commands // --------------------------------------------------------------------------- diff --git a/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts b/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts index 7c8f58895..d6cae5635 100644 --- a/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +++ b/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts @@ -1,18 +1,18 @@ import * as fs from "node:fs"; import * as path from "node:path"; +import { type Static, Type } from "@sinclair/typebox"; +import { TypeCompiler } from "@sinclair/typebox/compiler"; +import { + type HighlightColors, + highlightCode as nativeHighlightCode, + supportsLanguage, +} from "@singularity-forge/native"; import type { EditorTheme, MarkdownTheme, SelectListTheme, } from "@singularity-forge/pi-tui"; -import { type Static, Type } from "@sinclair/typebox"; -import { TypeCompiler } from "@sinclair/typebox/compiler"; import chalk from "chalk"; -import { - highlightCode as nativeHighlightCode, - supportsLanguage, - type HighlightColors, -} from "@singularity-forge/native"; import { getCustomThemesDir } from "../../../config.js"; import { builtinThemes } from "./themes.js"; diff --git a/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts b/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts index 171862dbd..41076ce43 100644 --- a/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +++ b/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts @@ -12,15 +12,15 @@ */ import * as crypto from "node:crypto"; -import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import type { AgentSession } from "../../core/agent-session.js"; -import { killTrackedDetachedChildren } from "../../utils/shell.js"; import type { ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, } from "../../core/extensions/index.js"; +import { killTrackedDetachedChildren } from "../../utils/shell.js"; import { InteractiveMode } from "../interactive/interactive-mode.js"; import { type Theme, theme } from "../interactive/theme/theme.js"; import { createDefaultCommandContextActions } from "../shared/command-context-actions.js"; diff --git a/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts b/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts index 58dba0072..27f359caf 100644 --- a/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +++ b/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts @@ -6,20 +6,20 @@ * mock child processes using PassThrough streams. */ -import { describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; import { PassThrough } from "node:stream"; +import { afterEach, beforeEach, describe, it } from "vitest"; import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js"; import { buildRpcLaunchSpec } from "./rpc-client.js"; import type { RpcCommand, - RpcResponse, - RpcInitResult, - RpcExecutionCompleteEvent, RpcCostUpdateEvent, - RpcV2Event, + RpcExecutionCompleteEvent, + RpcInitResult, RpcProtocolVersion, + RpcResponse, RpcSessionState, + RpcV2Event, } from "./rpc-types.js"; // ============================================================================ @@ -874,7 +874,7 @@ describe("Protocol version detection logic", () => { }); it("rejects re-init after v2 lock", () => { - let protocolLocked = true; // already locked from first init + const protocolLocked = true; // already locked from first init let errorMessage: string | null = null; const command: RpcCommand = { type: "init", protocolVersion: 2 }; @@ -889,8 +889,8 @@ describe("Protocol version detection logic", () => { }); it("rejects init after v1 lock", () => { - let protocolLocked = true; // already locked from first non-init command - let protocolVersion: 1 | 2 = 1; + const protocolLocked = true; // already locked from first non-init command + const protocolVersion: 1 | 2 = 1; let errorMessage: string | null = null; const command: RpcCommand = { type: "init", protocolVersion: 2 }; diff --git a/packages/pi-coding-agent/src/resources/extensions/memory/index.ts b/packages/pi-coding-agent/src/resources/extensions/memory/index.ts index 353a9db6c..43c55383a 100644 --- a/packages/pi-coding-agent/src/resources/extensions/memory/index.ts +++ b/packages/pi-coding-agent/src/resources/extensions/memory/index.ts @@ -11,15 +11,15 @@ * - /memory command: view, clear, rebuild, stats */ +import { createHash } from "node:crypto"; +import { existsSync, mkdirSync, rmSync } from "node:fs"; +import { join } from "node:path"; +import { completeSimple } from "@singularity-forge/pi-ai"; import type { ExtensionAPI } from "@singularity-forge/pi-coding-agent"; import { getAgentDir, SettingsManager, } from "@singularity-forge/pi-coding-agent"; -import { completeSimple } from "@singularity-forge/pi-ai"; -import { createHash } from "crypto"; -import { existsSync, mkdirSync, rmSync } from "fs"; -import { join } from "path"; import { getFullMemory, getMemorySummary, runStartup } from "./pipeline.js"; import { MemoryStorage } from "./storage.js"; diff --git a/packages/pi-coding-agent/src/resources/extensions/memory/pipeline.ts b/packages/pi-coding-agent/src/resources/extensions/memory/pipeline.ts index f4257e0d4..777a61323 100644 --- a/packages/pi-coding-agent/src/resources/extensions/memory/pipeline.ts +++ b/packages/pi-coding-agent/src/resources/extensions/memory/pipeline.ts @@ -10,13 +10,13 @@ import { createReadStream, existsSync, mkdirSync, - readFileSync, readdirSync, + readFileSync, statSync, writeFileSync, -} from "fs"; -import { join } from "path"; -import { createInterface } from "readline"; +} from "node:fs"; +import { join } from "node:path"; +import { createInterface } from "node:readline"; import type { MemoryStorage } from "./storage.js"; /** Inline concurrency limiter to cap parallel async operations. */ @@ -493,7 +493,7 @@ async function runPhase2( storage.completePhase2Job(phase2.jobId); return true; - } catch (err) { + } catch (_err) { // Phase 2 failed - job will expire and can be retried return false; } diff --git a/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts b/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts index d837d5e85..3f419b548 100644 --- a/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +++ b/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts @@ -1,8 +1,8 @@ import assert from "node:assert/strict"; -import { describe, it, afterEach } from "vitest"; -import { mkdtempSync, rmSync, readFileSync, existsSync } from "node:fs"; -import { join } from "node:path"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, it } from "vitest"; import { MemoryStorage } from "./storage.js"; @@ -29,7 +29,7 @@ describe("MemoryStorage debounced persistence", () => { const storage = await MemoryStorage.create(dbPath); const initialStat = readFileSync(dbPath); - const initialMtime = initialStat.length; + const _initialMtime = initialStat.length; storage.upsertThreads([ { diff --git a/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts b/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts index c4edf0d4c..0885f16c0 100644 --- a/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +++ b/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts @@ -7,10 +7,10 @@ * - jobs: lease-based job queue for pipeline phases */ +import { randomUUID } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname } from "node:path"; import initSqlJs, { type Database as SqlJsDatabase } from "sql.js"; -import { randomUUID } from "crypto"; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; -import { dirname } from "path"; export interface ThreadRow { thread_id: string; diff --git a/packages/pi-coding-agent/src/tests/path-display.test.ts b/packages/pi-coding-agent/src/tests/path-display.test.ts index 3b9f9ab46..0bb76f211 100644 --- a/packages/pi-coding-agent/src/tests/path-display.test.ts +++ b/packages/pi-coding-agent/src/tests/path-display.test.ts @@ -5,10 +5,10 @@ * the system prompt builder produces forward-slash paths for LLM consumption. */ -import { test } from "vitest"; import assert from "node:assert/strict"; -import { toPosixPath } from "../utils/path-display.js"; +import { test } from "vitest"; import { buildSystemPrompt } from "../core/system-prompt.js"; +import { toPosixPath } from "../utils/path-display.js"; // ─── toPosixPath ──────────────────────────────────────────────────────────── diff --git a/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts b/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts index 62df3e125..723de194a 100644 --- a/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +++ b/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts @@ -3,11 +3,10 @@ // filter lets consumers narrow the catalog rendered into // the cached system prompt without touching skill loading or invocation. -import { test } from "vitest"; import assert from "node:assert/strict"; - -import { buildSystemPrompt } from "../core/system-prompt.js"; +import { test } from "vitest"; import type { Skill } from "../core/skills.js"; +import { buildSystemPrompt } from "../core/system-prompt.js"; function makeSkill( name: string, diff --git a/packages/pi-coding-agent/src/utils/changelog.ts b/packages/pi-coding-agent/src/utils/changelog.ts index 068f3a355..c05fd0294 100644 --- a/packages/pi-coding-agent/src/utils/changelog.ts +++ b/packages/pi-coding-agent/src/utils/changelog.ts @@ -1,4 +1,4 @@ -import { existsSync, readFileSync } from "fs"; +import { existsSync, readFileSync } from "node:fs"; export interface ChangelogEntry { major: number; diff --git a/packages/pi-coding-agent/src/utils/clipboard-image.ts b/packages/pi-coding-agent/src/utils/clipboard-image.ts index 51af98226..29ddf9273 100644 --- a/packages/pi-coding-agent/src/utils/clipboard-image.ts +++ b/packages/pi-coding-agent/src/utils/clipboard-image.ts @@ -1,5 +1,4 @@ -import { spawnSync } from "child_process"; - +import { spawnSync } from "node:child_process"; import { readImageFromClipboard as nativeReadImage } from "@singularity-forge/native/clipboard"; import { ImageFormat, parseImage } from "@singularity-forge/native/image"; diff --git a/packages/pi-coding-agent/src/utils/clipboard-native.ts b/packages/pi-coding-agent/src/utils/clipboard-native.ts index 64b7e9ca1..9f1643057 100644 --- a/packages/pi-coding-agent/src/utils/clipboard-native.ts +++ b/packages/pi-coding-agent/src/utils/clipboard-native.ts @@ -6,6 +6,6 @@ */ export { copyToClipboard, - readTextFromClipboard, readImageFromClipboard, + readTextFromClipboard, } from "@singularity-forge/native/clipboard"; diff --git a/packages/pi-coding-agent/src/utils/image-resize.ts b/packages/pi-coding-agent/src/utils/image-resize.ts index f71b09f7b..e4b1b23f7 100644 --- a/packages/pi-coding-agent/src/utils/image-resize.ts +++ b/packages/pi-coding-agent/src/utils/image-resize.ts @@ -1,10 +1,10 @@ -import type { ImageContent } from "@singularity-forge/pi-ai"; +import type { NativeImageHandle } from "@singularity-forge/native/image"; import { ImageFormat, parseImage, SamplingFilter, } from "@singularity-forge/native/image"; -import type { NativeImageHandle } from "@singularity-forge/native/image"; +import type { ImageContent } from "@singularity-forge/pi-ai"; export interface ImageResizeOptions { maxWidth?: number; // Default: 2000 diff --git a/packages/pi-coding-agent/src/utils/proxy-server.ts b/packages/pi-coding-agent/src/utils/proxy-server.ts index 1f3d40913..754c3f225 100644 --- a/packages/pi-coding-agent/src/utils/proxy-server.ts +++ b/packages/pi-coding-agent/src/utils/proxy-server.ts @@ -1,15 +1,13 @@ -import express from "express"; -import type { Server } from "http"; +import type { Server } from "node:http"; import { - getModels, - stream, type Context, - type Message, - type Model, + getModels, type StreamOptions, + stream, } from "@singularity-forge/pi-ai"; -import { AuthStorage } from "../core/auth-storage.js"; -import { ModelRegistry } from "../core/model-registry.js"; +import express from "express"; +import type { AuthStorage } from "../core/auth-storage.js"; +import type { ModelRegistry } from "../core/model-registry.js"; export type ProxyServerOptions = { port: number; @@ -53,7 +51,7 @@ const PROXY_FAMILY_PRIORITY: Array<{ match: RegExp; providers: string[] }> = [ }, ]; -function sortByFamilyPriority( +function _sortByFamilyPriority( models: T[], ): T[] { if (models.length <= 1) return models; diff --git a/packages/pi-coding-agent/src/utils/tools-manager.ts b/packages/pi-coding-agent/src/utils/tools-manager.ts index 0b35c783f..aa29a0635 100644 --- a/packages/pi-coding-agent/src/utils/tools-manager.ts +++ b/packages/pi-coding-agent/src/utils/tools-manager.ts @@ -1,6 +1,4 @@ -import chalk from "chalk"; -import { spawnSync } from "child_process"; -import extractZip from "extract-zip"; +import { spawnSync } from "node:child_process"; import { chmodSync, createWriteStream, @@ -9,11 +7,13 @@ import { readdirSync, renameSync, rmSync, -} from "fs"; -import { arch, platform } from "os"; -import { join } from "path"; -import { Readable } from "stream"; -import { finished } from "stream/promises"; +} from "node:fs"; +import { arch, platform } from "node:os"; +import { join } from "node:path"; +import { Readable } from "node:stream"; +import { finished } from "node:stream/promises"; +import chalk from "chalk"; +import extractZip from "extract-zip"; import { APP_NAME, getBinDir } from "../config.js"; const TOOLS_DIR = getBinDir(); diff --git a/packages/pi-tui/src/__tests__/autocomplete.test.ts b/packages/pi-tui/src/__tests__/autocomplete.test.ts index 6a22f3729..070b48dbd 100644 --- a/packages/pi-tui/src/__tests__/autocomplete.test.ts +++ b/packages/pi-tui/src/__tests__/autocomplete.test.ts @@ -1,10 +1,10 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { CombinedAutocompleteProvider } from "../autocomplete.js"; +import { describe, it } from "vitest"; import type { SlashCommand } from "../autocomplete.js"; +import { CombinedAutocompleteProvider } from "../autocomplete.js"; function makeProvider( commands: SlashCommand[] = [], diff --git a/packages/pi-tui/src/__tests__/fuzzy.test.ts b/packages/pi-tui/src/__tests__/fuzzy.test.ts index 4c6faceb3..41e6fec80 100644 --- a/packages/pi-tui/src/__tests__/fuzzy.test.ts +++ b/packages/pi-tui/src/__tests__/fuzzy.test.ts @@ -1,6 +1,6 @@ -import { describe, it } from "vitest"; import assert from "node:assert/strict"; -import { fuzzyMatch, fuzzyFilter } from "../fuzzy.js"; +import { describe, it } from "vitest"; +import { fuzzyFilter, fuzzyMatch } from "../fuzzy.js"; describe("fuzzyMatch", () => { it("matches exact string", () => { diff --git a/packages/pi-tui/src/__tests__/overlay-layout.test.ts b/packages/pi-tui/src/__tests__/overlay-layout.test.ts index ad72325b3..1bee756ef 100644 --- a/packages/pi-tui/src/__tests__/overlay-layout.test.ts +++ b/packages/pi-tui/src/__tests__/overlay-layout.test.ts @@ -1,7 +1,7 @@ // pi-tui — Overlay Layout Tests (backdrop dimming) -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { compositeOverlays, type OverlayEntry } from "../overlay-layout.js"; function makeEntry( diff --git a/packages/pi-tui/src/__tests__/stdin-buffer.test.ts b/packages/pi-tui/src/__tests__/stdin-buffer.test.ts index 2253daa8d..38637d09a 100644 --- a/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +++ b/packages/pi-tui/src/__tests__/stdin-buffer.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; -import { describe, it } from "vitest"; import { setTimeout as delay } from "node:timers/promises"; +import { describe, it } from "vitest"; import { StdinBuffer } from "../stdin-buffer.js"; diff --git a/packages/pi-tui/src/__tests__/tui.test.ts b/packages/pi-tui/src/__tests__/tui.test.ts index 09c764dab..805a28923 100644 --- a/packages/pi-tui/src/__tests__/tui.test.ts +++ b/packages/pi-tui/src/__tests__/tui.test.ts @@ -1,9 +1,8 @@ import assert from "node:assert/strict"; import { describe, it } from "vitest"; - -import { Container, TUI } from "../tui.js"; -import type { Component } from "../tui.js"; import type { Terminal } from "../terminal.js"; +import type { Component } from "../tui.js"; +import { Container, TUI } from "../tui.js"; function makeTerminal(): Terminal { return { diff --git a/packages/pi-tui/src/autocomplete.ts b/packages/pi-tui/src/autocomplete.ts index ba008af93..c41a4e94d 100644 --- a/packages/pi-tui/src/autocomplete.ts +++ b/packages/pi-tui/src/autocomplete.ts @@ -1,6 +1,6 @@ -import { readdirSync, statSync } from "fs"; -import { homedir } from "os"; -import { basename, dirname, join } from "path"; +import { readdirSync, statSync } from "node:fs"; +import { homedir } from "node:os"; +import { basename, dirname, join } from "node:path"; import { fuzzyFind } from "@singularity-forge/native/fd"; import { fuzzyFilter } from "./fuzzy.js"; diff --git a/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts b/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts index 633560852..e8a9803d4 100644 --- a/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts +++ b/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts @@ -1,8 +1,8 @@ // pi-tui CancellableLoader component regression tests // Copyright (c) 2026 Jeremy McSpadden -import { vi, describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; +import { afterEach, beforeEach, describe, it, vi } from "vitest"; import { CancellableLoader } from "../cancellable-loader.js"; function makeMockTUI() { diff --git a/packages/pi-tui/src/components/__tests__/editor.test.ts b/packages/pi-tui/src/components/__tests__/editor.test.ts index 49a33f741..850ddda45 100644 --- a/packages/pi-tui/src/components/__tests__/editor.test.ts +++ b/packages/pi-tui/src/components/__tests__/editor.test.ts @@ -1,9 +1,8 @@ import assert from "node:assert/strict"; import { describe, it } from "vitest"; - -import { Editor, type EditorTheme } from "../editor.js"; -import { CURSOR_MARKER, TUI } from "../../tui.js"; import type { Terminal } from "../../terminal.js"; +import { CURSOR_MARKER, TUI } from "../../tui.js"; +import { Editor, type EditorTheme } from "../editor.js"; function makeTerminal(): Terminal { return { diff --git a/packages/pi-tui/src/components/__tests__/input.test.ts b/packages/pi-tui/src/components/__tests__/input.test.ts index dd426ffbf..284861e7d 100644 --- a/packages/pi-tui/src/components/__tests__/input.test.ts +++ b/packages/pi-tui/src/components/__tests__/input.test.ts @@ -1,8 +1,8 @@ // pi-tui Input component regression tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it } from "vitest"; import assert from "node:assert/strict"; +import { describe, it } from "vitest"; import { Input } from "../input.js"; describe("Input", () => { diff --git a/packages/pi-tui/src/components/__tests__/loader.test.ts b/packages/pi-tui/src/components/__tests__/loader.test.ts index 974870acb..5602a232c 100644 --- a/packages/pi-tui/src/components/__tests__/loader.test.ts +++ b/packages/pi-tui/src/components/__tests__/loader.test.ts @@ -1,8 +1,8 @@ // pi-tui Loader component regression tests // Copyright (c) 2026 Jeremy McSpadden -import { vi, describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; +import { afterEach, beforeEach, describe, it, vi } from "vitest"; import { Loader } from "../loader.js"; function makeMockTUI() { diff --git a/packages/pi-tui/src/components/image.test.ts b/packages/pi-tui/src/components/image.test.ts index 00e8041d2..f8afd7dd0 100644 --- a/packages/pi-tui/src/components/image.test.ts +++ b/packages/pi-tui/src/components/image.test.ts @@ -3,8 +3,8 @@ * re-render loop when dimensions resolve in cmux sessions. */ -import { describe, test } from "vitest"; import assert from "node:assert/strict"; +import { describe, test } from "vitest"; import { Image } from "./image.js"; describe("Image component (#3455)", () => { diff --git a/packages/pi-tui/src/overlay-layout.ts b/packages/pi-tui/src/overlay-layout.ts index 2e16104b6..0c93df961 100644 --- a/packages/pi-tui/src/overlay-layout.ts +++ b/packages/pi-tui/src/overlay-layout.ts @@ -5,17 +5,16 @@ * positions and composite overlay content onto base terminal lines. */ +import { isImageLine } from "./terminal-image.js"; import type { OverlayAnchor, OverlayOptions, SizeValue } from "./tui.js"; +import { CURSOR_MARKER } from "./tui.js"; import { applyBackgroundToLine, extractSegments, sliceByColumn, sliceWithWidth, - truncateToWidth, visibleWidth, } from "./utils.js"; -import { isImageLine } from "./terminal-image.js"; -import { CURSOR_MARKER } from "./tui.js"; // ─── Size parsing ─────────────────────────────────────────────────────────── diff --git a/packages/pi-tui/src/stdin-buffer.ts b/packages/pi-tui/src/stdin-buffer.ts index 83585f2a1..f7c1f75d7 100644 --- a/packages/pi-tui/src/stdin-buffer.ts +++ b/packages/pi-tui/src/stdin-buffer.ts @@ -17,7 +17,7 @@ * MIT License - Copyright (c) 2025 opentui */ -import { EventEmitter } from "events"; +import { EventEmitter } from "node:events"; const ESC = "\x1b"; const BRACKETED_PASTE_START = "\x1b[200~"; diff --git a/packages/pi-tui/src/utils.ts b/packages/pi-tui/src/utils.ts index 3f34e0c92..36a1ef0a8 100644 --- a/packages/pi-tui/src/utils.ts +++ b/packages/pi-tui/src/utils.ts @@ -1,10 +1,10 @@ import { + EllipsisKind, + extractSegments as nativeExtractSegments, + sliceWithWidth as nativeSliceWithWidth, + truncateToWidth as nativeTruncateToWidth, visibleWidth as nativeVisibleWidth, wrapTextWithAnsi as nativeWrapTextWithAnsi, - truncateToWidth as nativeTruncateToWidth, - sliceWithWidth as nativeSliceWithWidth, - extractSegments as nativeExtractSegments, - EllipsisKind, } from "@singularity-forge/native/text"; // Grapheme segmenter (shared instance) diff --git a/packages/rpc-client/src/index.ts b/packages/rpc-client/src/index.ts index 5aad13419..176f560cc 100644 --- a/packages/rpc-client/src/index.ts +++ b/packages/rpc-client/src/index.ts @@ -4,11 +4,11 @@ * Re-exports all types, JSONL utilities, and the RpcClient class. */ -export * from "./rpc-types.js"; -export { serializeJsonLine, attachJsonlLineReader } from "./jsonl.js"; -export { RpcClient } from "./rpc-client.js"; +export { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js"; export type { RpcClientOptions, RpcEventListener, SdkAgentEvent, } from "./rpc-client.js"; +export { RpcClient } from "./rpc-client.js"; +export * from "./rpc-types.js"; diff --git a/packages/rpc-client/src/rpc-client.test.ts b/packages/rpc-client/src/rpc-client.test.ts index 9a93ace59..57fdcc3a2 100644 --- a/packages/rpc-client/src/rpc-client.test.ts +++ b/packages/rpc-client/src/rpc-client.test.ts @@ -1,17 +1,17 @@ -import { describe, it, beforeEach, afterEach } from "vitest"; import assert from "node:assert/strict"; import { PassThrough } from "node:stream"; -import { serializeJsonLine, attachJsonlLineReader } from "./jsonl.js"; -import type { - RpcInitResult, - RpcExecutionCompleteEvent, - RpcCostUpdateEvent, - RpcProtocolVersion, - SessionStats, - RpcV2Event, -} from "./rpc-types.js"; -import { buildRpcLaunchSpec, RpcClient } from "./rpc-client.js"; +import { describe, it } from "vitest"; +import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js"; import type { SdkAgentEvent } from "./rpc-client.js"; +import { buildRpcLaunchSpec, RpcClient } from "./rpc-client.js"; +import type { + RpcCostUpdateEvent, + RpcExecutionCompleteEvent, + RpcInitResult, + RpcProtocolVersion, + RpcV2Event, + SessionStats, +} from "./rpc-types.js"; // ============================================================================ // JSONL Tests @@ -407,7 +407,7 @@ describe("events() async generator", () => { on: (event: string, handler: () => void) => { if (event === "exit") exitHandlers.push(handler); }, - removeListener: (event: string, handler: () => void) => { + removeListener: (_event: string, handler: () => void) => { const idx = exitHandlers.indexOf(handler); if (idx !== -1) exitHandlers.splice(idx, 1); }, diff --git a/packages/rpc-client/src/rpc-client.ts b/packages/rpc-client/src/rpc-client.ts index 8742c2480..f9661e986 100644 --- a/packages/rpc-client/src/rpc-client.ts +++ b/packages/rpc-client/src/rpc-client.ts @@ -20,8 +20,8 @@ import type { RpcResponse, RpcSessionState, RpcSlashCommand, - ThinkingLevel, SessionStats, + ThinkingLevel, } from "./rpc-types.js"; // ============================================================================ @@ -285,7 +285,7 @@ export class RpcClient { } const buffer: SdkAgentEvent[] = []; - let resolve: ((value: void) => void) | null = null; + let resolve: ((value: undefined) => void) | null = null; let done = false; // When a new event arrives, either push to buffer or wake up the awaiting generator diff --git a/rust-engine/scripts/sync-platform-versions.cjs b/rust-engine/scripts/sync-platform-versions.cjs index 2c06de240..3317ce7f1 100644 --- a/rust-engine/scripts/sync-platform-versions.cjs +++ b/rust-engine/scripts/sync-platform-versions.cjs @@ -7,8 +7,8 @@ * package.json files and updates optionalDependencies in root package.json. */ -const fs = require("fs"); -const path = require("path"); +const fs = require("node:fs"); +const path = require("node:path"); const rootDir = path.resolve(__dirname, "..", ".."); const npmDir = path.resolve(__dirname, "..", "npm"); diff --git a/scripts/build-web-if-stale.cjs b/scripts/build-web-if-stale.cjs index 137f65acf..a3baf6782 100644 --- a/scripts/build-web-if-stale.cjs +++ b/scripts/build-web-if-stale.cjs @@ -46,7 +46,7 @@ const IGNORED_DIRS = new Set([ */ function newestMtime(dir) { let max = 0; - let stack = [dir]; + const stack = [dir]; while (stack.length > 0) { const current = stack.pop(); let entries; diff --git a/scripts/bump-version.mjs b/scripts/bump-version.mjs index 24817a81c..462033ffb 100644 --- a/scripts/bump-version.mjs +++ b/scripts/bump-version.mjs @@ -1,11 +1,11 @@ #!/usr/bin/env node +import { execSync } from "node:child_process"; /** * Bump version in package.json, then sync platform packages and pkg/package.json. * Usage: node scripts/bump-version.mjs */ -import { readFileSync, writeFileSync, existsSync } from "fs"; -import { resolve } from "path"; -import { execSync } from "child_process"; +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { resolve } from "node:path"; const __dirname = import.meta.dirname; const root = resolve(__dirname, ".."); diff --git a/scripts/check-sf-extension-inventory.mjs b/scripts/check-sf-extension-inventory.mjs index 7d7164f02..82584714c 100644 --- a/scripts/check-sf-extension-inventory.mjs +++ b/scripts/check-sf-extension-inventory.mjs @@ -1,5 +1,5 @@ import { execFileSync } from "node:child_process"; -import { existsSync, readFileSync, readdirSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; import { join, resolve } from "node:path"; const repoRoot = resolve(import.meta.dirname, ".."); @@ -18,7 +18,7 @@ const HIDDEN_OR_ALIAS_SUBCOMMANDS = new Set([ "wt", ]); -function rel(path) { +function _rel(path) { return path.replace(`${repoRoot}/`, ""); } diff --git a/scripts/check-skill-references.mjs b/scripts/check-skill-references.mjs index b21ea47f9..ee082a845 100644 --- a/scripts/check-skill-references.mjs +++ b/scripts/check-skill-references.mjs @@ -22,8 +22,8 @@ * Exit 0 if all references resolve. Exit 1 if any are broken. */ -import { readFileSync, readdirSync, existsSync } from "node:fs"; -import { join, resolve, dirname, extname } from "node:path"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { dirname, extname, join, resolve } from "node:path"; const SKILLS_DIR = resolve("src/resources/skills"); diff --git a/scripts/check-versioned-json.mjs b/scripts/check-versioned-json.mjs index 2d173ad12..7e21f9cbc 100644 --- a/scripts/check-versioned-json.mjs +++ b/scripts/check-versioned-json.mjs @@ -42,7 +42,7 @@ export function isSfOwnedJsonContract(path) { } export function hasOwn(object, key) { - return Object.prototype.hasOwnProperty.call(object, key); + return Object.hasOwn(object, key); } export function getSchemaVersion(parsed) { diff --git a/scripts/ci_monitor.cjs b/scripts/ci_monitor.cjs index 0d073bcfa..4f0c1c2b7 100644 --- a/scripts/ci_monitor.cjs +++ b/scripts/ci_monitor.cjs @@ -2,9 +2,9 @@ /** * GitHub Actions CI/CD Workflow Monitor - Pure Node.js implementation */ -const { spawnSync } = require("child_process"); -const fs = require("fs"); -const path = require("path"); +const { spawnSync } = require("node:child_process"); +const fs = require("node:fs"); +const path = require("node:path"); const EMOJI = { success: "✅", @@ -88,7 +88,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // Commands const cmd = { runs: (opts = {}) => { - const list = runList({ ...opts, limit: parseInt(opts.limit) || 15 }); + const list = runList({ ...opts, limit: parseInt(opts.limit, 10) || 15 }); console.log( `\n📋 Recent runs${opts.branch ? ` for "${opts.branch}"` : ""}:\n`, ); @@ -102,7 +102,7 @@ const cmd = { return list; }, watch: async (id, opts = {}) => { - const int = parseInt(opts.interval) || INTERVAL; + const int = parseInt(opts.interval, 10) || INTERVAL; console.log(`👁️ Watching run ${id}...\n`); const last = new Map(); while (true) { @@ -133,7 +133,7 @@ const cmd = { } }, "fail-fast": async (id, opts = {}) => { - const int = parseInt(opts.interval) || INTERVAL; + const int = parseInt(opts.interval, 10) || INTERVAL; console.log(`🔍 Watching run ${id} (fail-fast)...\n`); const seen = new Set(); while (true) { @@ -186,7 +186,7 @@ const cmd = { maxBuffer: MAXBUF, }) .split(/\r?\n/) - .slice(-(parseInt(opts.lines) || 200)) + .slice(-(parseInt(opts.lines, 10) || 200)) .join("\n"), ); } catch (e) { @@ -205,7 +205,7 @@ const cmd = { lines = lines.filter((l) => re.test(l)); console.log(`🔍 Filtered (${lines.length} lines):\n`); } - console.log(lines.slice(-(parseInt(opts.lines) || 500)).join("\n")); + console.log(lines.slice(-(parseInt(opts.lines, 10) || 500)).join("\n")); } catch (e) { console.error(`Could not fetch logs: ${e.message}`); } @@ -230,7 +230,7 @@ const cmd = { return; } console.log(`Found ${matches.length} matches:\n`); - const ctx = parseInt(opts.context) || 3; + const ctx = parseInt(opts.context, 10) || 3; for (const m of matches.slice(0, 20)) { console.log(`--- Line ${m.i} ---`); for ( @@ -246,7 +246,7 @@ const cmd = { console.error(`Could not fetch logs: ${e.message}`); } }, - "test-summary": (id, opts = {}) => { + "test-summary": (id, _opts = {}) => { console.log(`\n📊 Test summary for run ${id}:\n`); try { const logs = gh( @@ -274,7 +274,7 @@ const cmd = { console.log( getLogs(id, findJob(id, job).id) .split(/\r?\n/) - .slice(-(parseInt(opts.lines) || 100)) + .slice(-(parseInt(opts.lines, 10) || 100)) .join("\n"), ), "wait-for": async (id, jobName, opts = {}) => { @@ -282,8 +282,8 @@ const cmd = { console.error("❌ --keyword required"); process.exit(1); } - const to = (parseInt(opts.timeout) || TIMEOUT) * 1000, - int = (parseInt(opts.interval) || 5) * 1000; + const to = (parseInt(opts.timeout, 10) || TIMEOUT) * 1000, + int = (parseInt(opts.interval, 10) || 5) * 1000; console.log(`🔍 Waiting for "${opts.keyword}" in "${jobName}"...\n`); const start = Date.now(); let job = null; @@ -315,7 +315,7 @@ const cmd = { console.log( `📝 Log: ${logs.length} chars (${Math.floor((Date.now() - start) / 1000)}s)`, ); - } catch (e) { + } catch (_e) { /* ignore */ } await sleep(int); @@ -362,14 +362,14 @@ const cmd = { } }, "branch-runs": (branch, opts = {}) => { - const list = runList({ branch, limit: parseInt(opts.limit) || 10 }); + const list = runList({ branch, limit: parseInt(opts.limit, 10) || 10 }); console.log(`\n📋 Runs for "${branch}":\n`); for (const r of list) console.log( `${emoji(r.status, r.conclusion)} ${String(r.databaseId).padEnd(10)} ${new Date(r.createdAt).toLocaleDateString()} ${r.displayTitle?.substring(0, 40) || ""}`, ); }, - "list-workflows": (opts = {}) => { + "list-workflows": (_opts = {}) => { const dir = path.join(".github", "workflows"); if (!fs.existsSync(dir)) { console.error("❌ No .github/workflows directory"); @@ -400,7 +400,7 @@ const cmd = { } return files; }, - "check-actions": (wf, opts = {}) => { + "check-actions": (wf, _opts = {}) => { const fp = wf || path.join(".github", "workflows", "ci.yml"); if (!fs.existsSync(fp)) { console.error(`❌ File not found: ${fp}`); diff --git a/scripts/compile-tests.mjs b/scripts/compile-tests.mjs index cf5a39765..edf19f833 100644 --- a/scripts/compile-tests.mjs +++ b/scripts/compile-tests.mjs @@ -1,4 +1,5 @@ #!/usr/bin/env node + /** * Compile all TypeScript source + test files to dist-test/ using esbuild. * Run compiled JS directly with node --test (no per-file TS overhead). @@ -6,8 +7,8 @@ * Usage: node scripts/compile-tests.mjs */ +import { symlinkSync } from "node:fs"; import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises"; -import { existsSync, symlinkSync } from "node:fs"; import { createRequire } from "node:module"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; diff --git a/scripts/copy-export-html.cjs b/scripts/copy-export-html.cjs index 20ad7abd3..74306c2ee 100644 --- a/scripts/copy-export-html.cjs +++ b/scripts/copy-export-html.cjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -const { mkdirSync, cpSync } = require("fs"); -const { resolve } = require("path"); +const { mkdirSync, cpSync } = require("node:fs"); +const { resolve } = require("node:path"); const src = resolve( __dirname, "..", diff --git a/scripts/copy-themes.cjs b/scripts/copy-themes.cjs index 52d414828..4cc33ecb8 100644 --- a/scripts/copy-themes.cjs +++ b/scripts/copy-themes.cjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -const { mkdirSync, cpSync } = require("fs"); -const { resolve } = require("path"); +const { mkdirSync, cpSync } = require("node:fs"); +const { resolve } = require("node:path"); const src = resolve( __dirname, "..", diff --git a/scripts/dev.js b/scripts/dev.js index cf38b7b72..84aaa4c47 100644 --- a/scripts/dev.js +++ b/scripts/dev.js @@ -9,8 +9,8 @@ */ import { spawn } from "node:child_process"; -import { resolve } from "node:path"; import { createRequire } from "node:module"; +import { resolve } from "node:path"; const __dirname = import.meta.dirname; const root = resolve(__dirname, ".."); diff --git a/scripts/dist-test-resolve.mjs b/scripts/dist-test-resolve.mjs index bed7a1374..bc59bea52 100644 --- a/scripts/dist-test-resolve.mjs +++ b/scripts/dist-test-resolve.mjs @@ -8,10 +8,6 @@ * Also redirects @sf bare imports to their compiled counterparts in dist-test. */ -import { fileURLToPath, pathToFileURL } from "node:url"; -import { existsSync } from "node:fs"; -import { join } from "node:path"; - // dist-test root — everything compiled lands here const DIST_TEST = new URL("../dist-test/", import.meta.url).href; diff --git a/scripts/ensure-workspace-builds.cjs b/scripts/ensure-workspace-builds.cjs index 9eb0184b9..ac9493610 100644 --- a/scripts/ensure-workspace-builds.cjs +++ b/scripts/ensure-workspace-builds.cjs @@ -14,9 +14,9 @@ * Skipped in CI (where the full build pipeline handles this) and when * installing as an end-user dependency (no packages/ directory). */ -const { existsSync, statSync, readdirSync } = require("fs"); -const { resolve, join } = require("path"); -const { execSync } = require("child_process"); +const { existsSync, statSync, readdirSync } = require("node:fs"); +const { resolve, join } = require("node:path"); +const { execSync } = require("node:child_process"); /** * Returns the most recent mtime (ms) of any .ts file under dir, recursively. diff --git a/scripts/fix-vitest-api.mjs b/scripts/fix-vitest-api.mjs index e41dec00a..3d56b1b72 100644 --- a/scripts/fix-vitest-api.mjs +++ b/scripts/fix-vitest-api.mjs @@ -1,4 +1,5 @@ #!/usr/bin/env node + /** * Fix remaining node:test API calls that the initial migration missed. * @@ -9,8 +10,8 @@ * where t.skip() should become a return or conditional. */ -import { readFileSync, writeFileSync } from "node:fs"; import { execSync } from "node:child_process"; +import { readFileSync, writeFileSync } from "node:fs"; const files = execSync( 'grep -rl "t\\.after\\b\\|t\\.before\\b\\|t\\.test(\\|t\\.skip(" src/tests/ src/resources/extensions/ --include="*.test.ts" --include="*.test.mjs"', diff --git a/scripts/generate-changelog.mjs b/scripts/generate-changelog.mjs index d075889e6..033471d99 100644 --- a/scripts/generate-changelog.mjs +++ b/scripts/generate-changelog.mjs @@ -3,9 +3,9 @@ * Parse conventional commits since the last stable tag. * Outputs JSON: { bumpType, newVersion, changelogEntry, releaseNotes } */ -import { execSync } from "child_process"; -import { readFileSync } from "fs"; -import { resolve } from "path"; +import { execSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; const __dirname = import.meta.dirname; const root = resolve(__dirname, ".."); diff --git a/scripts/generate-features-inventory.mjs b/scripts/generate-features-inventory.mjs index 8a63393ae..485e4bce8 100644 --- a/scripts/generate-features-inventory.mjs +++ b/scripts/generate-features-inventory.mjs @@ -1,4 +1,4 @@ -import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { join, relative, resolve } from "node:path"; const __filename = import.meta.filename; diff --git a/scripts/link-workspace-packages.cjs b/scripts/link-workspace-packages.cjs index a37826959..ebcf26dcc 100644 --- a/scripts/link-workspace-packages.cjs +++ b/scripts/link-workspace-packages.cjs @@ -24,8 +24,8 @@ const { lstatSync, readlinkSync, unlinkSync, -} = require("fs"); -const { resolve, join } = require("path"); +} = require("node:fs"); +const { resolve, join } = require("node:path"); const root = resolve(__dirname, ".."); const packagesDir = join(root, "packages"); diff --git a/scripts/migrate-to-vitest-all.mjs b/scripts/migrate-to-vitest-all.mjs index 0a921ce26..96c7e2da6 100644 --- a/scripts/migrate-to-vitest-all.mjs +++ b/scripts/migrate-to-vitest-all.mjs @@ -9,7 +9,7 @@ * 3. Files using mock.timers: migrate to vi fake timers API */ -import { readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { readdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; const ROOTS = [ @@ -54,7 +54,7 @@ function migrateImport(content, { hasMockFn, hasMockTimers }) { // Case 2: import { ... } from "node:test" (and variants with default import) content = content.replace( /import\s+(test,)?\s*\{\s*([^}]+)\}\s+from\s+["']node:test["'];?/g, - (match, hasDefault, named) => { + (_match, hasDefault, named) => { const namedList = named .split(",") .map((s) => s.trim()) diff --git a/scripts/migrate-to-vitest.mjs b/scripts/migrate-to-vitest.mjs index c3eb5ecce..a70b672e7 100644 --- a/scripts/migrate-to-vitest.mjs +++ b/scripts/migrate-to-vitest.mjs @@ -8,7 +8,7 @@ * 3. auto-loop.test.ts: replace mock.timers with vi fake timers API */ -import { readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { readdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; const ROOT = join(process.cwd(), "src"); @@ -60,7 +60,7 @@ function migrateImport(content, { isAutoLoop, isMockFn }) { // → import { test, vi } from 'vitest' return content.replace( /import\s+(test,)?\s*\{\s*([^}]+)\}\s+from\s+"node:test";?/, - (match, hasDefault, named) => { + (_match, hasDefault, named) => { const namedList = named .split(",") .map((s) => s.trim()) @@ -78,7 +78,7 @@ function migrateImport(content, { isAutoLoop, isMockFn }) { // → import { ..., vi, ... } from 'vitest' return content.replace( /import\s+(test,)?\s*\{\s*([^}]+)\}\s+from\s+"node:test";?/, - (match, hasDefault, named) => { + (_match, hasDefault, named) => { const namedList = named .split(",") .map((s) => s.trim()) @@ -95,7 +95,7 @@ function migrateImport(content, { isAutoLoop, isMockFn }) { // Simple case: just swap the source return content.replace( /import\s+(test,)?\s*\{\s*([^}]+)\}\s+from\s+"node:test";?/, - (match, hasDefault, named) => { + (_match, hasDefault, named) => { const defaultImport = hasDefault ? "test, " : ""; return `import { ${defaultImport}${named.trim()} } from 'vitest';`; }, diff --git a/scripts/model-smoke-benchmark.mjs b/scripts/model-smoke-benchmark.mjs index 9f43d5749..6b4114597 100644 --- a/scripts/model-smoke-benchmark.mjs +++ b/scripts/model-smoke-benchmark.mjs @@ -1,9 +1,9 @@ #!/usr/bin/env node -import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; import { dirname, resolve } from "node:path"; -import { spawnSync } from "node:child_process"; import { performance } from "node:perf_hooks"; const repoRoot = resolve(import.meta.dirname, ".."); diff --git a/scripts/parallel-monitor.mjs b/scripts/parallel-monitor.mjs index 57ef16a90..17dd42c30 100755 --- a/scripts/parallel-monitor.mjs +++ b/scripts/parallel-monitor.mjs @@ -1,4 +1,5 @@ #!/usr/bin/env node + /** * SF Parallel Worker Monitor * @@ -40,9 +41,9 @@ * failures. Resets retry count when a worker comes back alive. */ +import { execSync, spawn, spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; -import { execSync, spawn, spawnSync } from "node:child_process"; // ─── Configuration ─────────────────────────────────────────────────────────── @@ -69,7 +70,7 @@ const ESC = "\x1b["; const RESET = `${ESC}0m`; const BOLD = `${ESC}1m`; const DIM = `${ESC}2m`; -const ITALIC = `${ESC}3m`; +const _ITALIC = `${ESC}3m`; const FG = { black: `${ESC}30m`, @@ -96,10 +97,10 @@ const BG = { const CLEAR_SCREEN = `${ESC}2J${ESC}H`; const HIDE_CURSOR = `${ESC}?25l`; const SHOW_CURSOR = `${ESC}?25h`; -const SAVE_POS = `${ESC}s`; -const RESTORE_POS = `${ESC}u`; +const _SAVE_POS = `${ESC}s`; +const _RESTORE_POS = `${ESC}u`; -function moveTo(row, col) { +function _moveTo(row, col) { return `${ESC}${row};${col}H`; } @@ -425,9 +426,6 @@ function respawnWorker(mid) { SF_MILESTONE_LOCK: mid, SF_PROJECT_ROOT: PROJECT_ROOT, SF_PARALLEL_WORKER: "1", - SF_MILESTONE_LOCK: mid, - SF_PROJECT_ROOT: PROJECT_ROOT, - SF_PARALLEL_WORKER: "1", }, stdio: ["ignore", stdoutFd, stderrFd], windowsHide: true, @@ -436,7 +434,7 @@ function respawnWorker(mid) { child.unref(); return child.pid ?? null; - } catch (err) { + } catch (_err) { return null; } finally { if (stdoutFd !== undefined) { @@ -494,7 +492,7 @@ function healWorkers(workers) { // Cooldown — don't respawn too quickly const elapsed = now - hs.lastAttempt; if (elapsed < HEAL_COOLDOWN_SEC * 1000) { - const remaining = Math.ceil((HEAL_COOLDOWN_SEC * 1000 - elapsed) / 1000); + const _remaining = Math.ceil((HEAL_COOLDOWN_SEC * 1000 - elapsed) / 1000); // Don't spam the feed — only note on first cooldown tick continue; } @@ -605,14 +603,14 @@ function progressBar(done, total, width = 20) { return `${"█".repeat(filled)}${"░".repeat(empty)}`; } -function pad(str, width) { +function _pad(str, width) { const s = String(str); return s.length >= width ? s.slice(0, width) : s + " ".repeat(width - s.length); } -function rpad(str, width) { +function _rpad(str, width) { const s = String(str); return s.length >= width ? s.slice(0, width) @@ -655,7 +653,7 @@ function queryRecentCompletions(mid) { // ─── Rendering ─────────────────────────────────────────────────────────────── const COLS = Math.max(process.stdout.columns || 100, 80); -const ROWS = Math.max(process.stdout.rows || 40, 20); +const _ROWS = Math.max(process.stdout.rows || 40, 20); let lastEventFeed = []; // Persisted across renders const stderrBaselines = {}; // mid → file size at monitor startup (skip pre-existing errors) @@ -725,7 +723,7 @@ function collectWorkerData() { : null; // If no lock and worker is dead, show nothing (not a misleading "START" label) - const showUnit = currentUnit || (alive ? null : null); + const _showUnit = currentUnit || (alive ? null : null); const elapsed = status?.startedAt ? Date.now() - status.startedAt diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 0ebbdbd15..abb9dab0a 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -1,21 +1,21 @@ #!/usr/bin/env node -import { exec as execCb, spawnSync } from "child_process"; -import { createHash, randomUUID } from "crypto"; +import { exec as execCb, spawnSync } from "node:child_process"; +import { createHash, randomUUID } from "node:crypto"; import { chmodSync, copyFileSync, createWriteStream, existsSync, mkdirSync, - readFileSync, readdirSync, + readFileSync, rmSync, -} from "fs"; -import { arch, homedir, platform } from "os"; -import { resolve, join } from "path"; -import { Readable } from "stream"; -import { finished } from "stream/promises"; +} from "node:fs"; +import { arch, homedir, platform } from "node:os"; +import { join, resolve } from "node:path"; +import { Readable } from "node:stream"; +import { finished } from "node:stream/promises"; import extractZip from "extract-zip"; const __dirname = import.meta.dirname; diff --git a/scripts/pr-risk-check.mjs b/scripts/pr-risk-check.mjs index e13755b89..8bf7ce011 100644 --- a/scripts/pr-risk-check.mjs +++ b/scripts/pr-risk-check.mjs @@ -12,10 +12,10 @@ * node scripts/pr-risk-check.mjs --github # GitHub Actions summary output */ -import { readFileSync, existsSync } from "fs"; -import { execSync } from "child_process"; -import { resolve } from "path"; -import { createInterface } from "readline"; +import { execSync } from "node:child_process"; +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { createInterface } from "node:readline"; const __dirname = import.meta.dirname; const REPO_ROOT = resolve(__dirname, ".."); @@ -480,7 +480,7 @@ async function main() { const summary = renderGitHubSummary(report); // Write to GitHub step summary if available if (process.env.GITHUB_STEP_SUMMARY) { - const { appendFileSync } = await import("fs"); + const { appendFileSync } = await import("node:fs"); appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary + "\n"); } // Also output the summary markdown for use in PR comments diff --git a/scripts/preview-dashboard.ts b/scripts/preview-dashboard.ts index a4b3e087b..213edcde5 100644 --- a/scripts/preview-dashboard.ts +++ b/scripts/preview-dashboard.ts @@ -15,9 +15,9 @@ import { truncateToWidth, visibleWidth } from "@singularity-forge/pi-tui"; import { - makeUI, GLYPH, INDENT, + makeUI, } from "../src/resources/extensions/shared/mod.js"; // ── Minimal ANSI color theme (no Theme class dependency) ──────────────── diff --git a/scripts/rtk-benchmark.mjs b/scripts/rtk-benchmark.mjs index eb03a015b..7bf3cea77 100644 --- a/scripts/rtk-benchmark.mjs +++ b/scripts/rtk-benchmark.mjs @@ -1,9 +1,9 @@ #!/usr/bin/env node import { spawnSync } from "node:child_process"; -import { homedir, tmpdir } from "node:os"; -import { join, dirname } from "node:path"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { homedir, tmpdir } from "node:os"; +import { dirname, join } from "node:path"; function getManagedRtkPath() { return join( diff --git a/scripts/sync-pkg-version.cjs b/scripts/sync-pkg-version.cjs index f035a1b45..74a547b9d 100644 --- a/scripts/sync-pkg-version.cjs +++ b/scripts/sync-pkg-version.cjs @@ -11,8 +11,8 @@ * This script reads the actual installed pi-coding-agent version and writes it * into pkg/package.json so VERSION is always correct at publish time. */ -const { readFileSync, writeFileSync } = require("fs"); -const { resolve, join } = require("path"); +const { readFileSync, writeFileSync } = require("node:fs"); +const { resolve, join } = require("node:path"); const root = resolve(__dirname, ".."); const piPkgPath = join(root, "packages", "pi-coding-agent", "package.json"); diff --git a/scripts/uninstall-pi-global.js b/scripts/uninstall-pi-global.js index bc4a1f0e1..26377389a 100644 --- a/scripts/uninstall-pi-global.js +++ b/scripts/uninstall-pi-global.js @@ -1,10 +1,10 @@ #!/usr/bin/env node import { existsSync, - readFileSync, readdirSync, - rmSync, + readFileSync, rmdirSync, + rmSync, } from "node:fs"; import os from "node:os"; import { join, resolve } from "node:path"; diff --git a/scripts/update-changelog.mjs b/scripts/update-changelog.mjs index 837ec15ed..413761660 100644 --- a/scripts/update-changelog.mjs +++ b/scripts/update-changelog.mjs @@ -5,8 +5,8 @@ * * Usage: node scripts/update-changelog.mjs */ -import { readFileSync, writeFileSync } from "fs"; -import { resolve } from "path"; +import { readFileSync, writeFileSync } from "node:fs"; +import { resolve } from "node:path"; const __dirname = import.meta.dirname; const root = resolve(__dirname, ".."); diff --git a/scripts/version-stamp.mjs b/scripts/version-stamp.mjs index 36e5cf04f..777754765 100644 --- a/scripts/version-stamp.mjs +++ b/scripts/version-stamp.mjs @@ -1,6 +1,6 @@ -import { readFileSync, writeFileSync } from "fs"; -import { execFileSync, execSync } from "child_process"; -import { resolve } from "path"; +import { execFileSync, execSync } from "node:child_process"; +import { readFileSync, writeFileSync } from "node:fs"; +import { resolve } from "node:path"; const __dirname = import.meta.dirname; const root = resolve(__dirname, ".."); diff --git a/scripts/watch-resources.js b/scripts/watch-resources.js index 64b91a68a..6855cbe93 100644 --- a/scripts/watch-resources.js +++ b/scripts/watch-resources.js @@ -12,8 +12,7 @@ * projects using sf. */ -import { watch } from "node:fs"; -import { cpSync, mkdirSync, rmSync } from "node:fs"; +import { cpSync, mkdirSync, rmSync, watch } from "node:fs"; import { resolve } from "node:path"; const __dirname = import.meta.dirname; diff --git a/src/headless-context.ts b/src/headless-context.ts index 18c738c92..e9870c6ca 100644 --- a/src/headless-context.ts +++ b/src/headless-context.ts @@ -16,17 +16,17 @@ import { writeFileSync, } from "node:fs"; import { join, relative, resolve } from "node:path"; +import { ensureAgenticDocsScaffold } from "./resources/extensions/sf/agentic-docs-scaffold.js"; +import { ensureSiftIndexWarmup } from "./resources/extensions/sf/code-intelligence.js"; +import { + checkDocsScaffold, + formatDocCheckReport, +} from "./resources/extensions/sf/doc-checker.js"; import { ensureGitignore, ensurePreferences, untrackRuntimeFiles, } from "./resources/extensions/sf/gitignore.js"; -import { ensureAgenticDocsScaffold } from "./resources/extensions/sf/agentic-docs-scaffold.js"; -import { - checkDocsScaffold, - formatDocCheckReport, -} from "./resources/extensions/sf/doc-checker.js"; -import { ensureSiftIndexWarmup } from "./resources/extensions/sf/code-intelligence.js"; import { nativeInit, nativeIsRepo, @@ -333,7 +333,7 @@ function ensureSerenaMcp(basePath: string): void { const content = readFileSync(serenaConfigPath, "utf-8"); const lines = content.split("\n"); // Check if project already registered - const projectRe = /^(\s*)-\s*(.+)$/; + const _projectRe = /^(\s*)-\s*(.+)$/; let inProjects = false; let alreadyListed = false; for (const line of lines) { diff --git a/src/headless-query.ts b/src/headless-query.ts index b013e33bf..1f4f37219 100644 --- a/src/headless-query.ts +++ b/src/headless-query.ts @@ -21,7 +21,6 @@ import { dirname, join } from "node:path"; import { createJiti } from "@mariozechner/jiti"; import { resolveBundledSourceResource } from "./bundled-resource-path.js"; import type { SFState } from "./resources/extensions/sf/types.js"; -import { createScheduleStore } from "./resources/extensions/sf/schedule/schedule-store.js"; const jiti = createJiti(import.meta.filename, { interopDefault: true, diff --git a/src/resources/extensions/ask-user-questions.js b/src/resources/extensions/ask-user-questions.js index 5a7d43cb3..0a21c9310 100644 --- a/src/resources/extensions/ask-user-questions.js +++ b/src/resources/extensions/ask-user-questions.js @@ -13,6 +13,7 @@ import { formatRoundResultForTool } from "@singularity-forge/pi-agent-core"; import { Text } from "@singularity-forge/pi-tui"; import { sanitizeError } from "./shared/sanitize.js"; import { showInterviewRound } from "./shared/tui.js"; + // ─── Schema ─────────────────────────────────────────────────────────────────── const OptionSchema = Type.Object({ label: Type.String({ description: "User-facing label (1-5 words)" }), @@ -46,6 +47,7 @@ const AskUserQuestionsParams = Type.Object({ description: "Questions to show the user. Prefer 1 and do not exceed 3.", }), }); + // ─── Per-turn deduplication ────────────────────────────────────────────────── // Prevents duplicate question dispatches (especially to remote channels like // Discord) when the LLM calls ask_user_questions multiple times with the same @@ -53,6 +55,7 @@ const AskUserQuestionsParams = Type.Object({ // question, options, allowMultiple) — not just IDs — so that calls with the // same IDs but different text/options are treated as distinct. import { createHash } from "node:crypto"; + const turnCache = new Map(); /** @internal Exported for testing only. */ export function questionSignature(questions) { diff --git a/src/resources/extensions/async-jobs/async-bash-tool.js b/src/resources/extensions/async-jobs/async-bash-tool.js index be9029f51..86f9e328e 100644 --- a/src/resources/extensions/async-jobs/async-bash-tool.js +++ b/src/resources/extensions/async-jobs/async-bash-tool.js @@ -18,6 +18,7 @@ import { sanitizeCommand, } from "@singularity-forge/pi-coding-agent"; import { rewriteCommandWithRtk } from "../shared/rtk.js"; + const schema = Type.Object({ command: Type.String({ description: "Bash command to execute in the background", diff --git a/src/resources/extensions/async-jobs/await-tool.js b/src/resources/extensions/async-jobs/await-tool.js index 82c21d9c0..db78b07c5 100644 --- a/src/resources/extensions/async-jobs/await-tool.js +++ b/src/resources/extensions/async-jobs/await-tool.js @@ -5,6 +5,7 @@ * If omitted, waits for any running job to complete. */ import { Type } from "@sinclair/typebox"; + const DEFAULT_TIMEOUT_SECONDS = 120; const schema = Type.Object({ jobs: Type.Optional( diff --git a/src/resources/extensions/async-jobs/cancel-job-tool.js b/src/resources/extensions/async-jobs/cancel-job-tool.js index f1a706792..821c07705 100644 --- a/src/resources/extensions/async-jobs/cancel-job-tool.js +++ b/src/resources/extensions/async-jobs/cancel-job-tool.js @@ -2,6 +2,7 @@ * cancel_job tool — cancel a running background job. */ import { Type } from "@sinclair/typebox"; + const schema = Type.Object({ job_id: Type.String({ description: "The background job ID to cancel (e.g. bg_a1b2c3d4)", diff --git a/src/resources/extensions/aws-auth/index.js b/src/resources/extensions/aws-auth/index.js index 0f9966f65..72937c0a3 100644 --- a/src/resources/extensions/aws-auth/index.js +++ b/src/resources/extensions/aws-auth/index.js @@ -46,6 +46,7 @@ import { exec } from "node:child_process"; import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; + /** Matches AWS SDK / Bedrock / SSO credential and token errors. */ const AWS_AUTH_ERROR_RE = /ExpiredToken|security token.*expired|unable to locate credentials|SSO.*(?:session|token).*(?:expired|not found|invalid)|UnrecognizedClient|Could not load credentials|Invalid identity token|token is expired|credentials.*(?:could not|cannot|failed to).*(?:load|resolve|find)|The.*token.*is.*not.*valid|token has expired|SSOTokenProviderFailure|Error loading SSO Token|Token.*does not exist/i; diff --git a/src/resources/extensions/bg-shell/index.js b/src/resources/extensions/bg-shell/index.js index a249ca172..3c7bad4e1 100644 --- a/src/resources/extensions/bg-shell/index.js +++ b/src/resources/extensions/bg-shell/index.js @@ -6,6 +6,7 @@ */ import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; import { registerBgShellLifecycle } from "./bg-shell-lifecycle.js"; + let featuresPromise = null; async function registerBgShellFeatures(pi, state) { if (!featuresPromise) { diff --git a/src/resources/extensions/bg-shell/output-formatter.js b/src/resources/extensions/bg-shell/output-formatter.js index 5c18af087..0fff96636 100644 --- a/src/resources/extensions/bg-shell/output-formatter.js +++ b/src/resources/extensions/bg-shell/output-formatter.js @@ -54,7 +54,6 @@ export function analyzeLine(bg, line, _stream) { // Use PORT_PATTERN_SOURCE (string) to avoid re-parsing the literal each time const portRe = new RegExp(PORT_PATTERN_SOURCE, "gi"); let portMatch; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((portMatch = portRe.exec(line)) !== null) { const port = parseInt(portMatch[1], 10); if (port > 0 && port <= 65535 && !bg.ports.includes(port)) { diff --git a/src/resources/extensions/bg-shell/utilities.js b/src/resources/extensions/bg-shell/utilities.js index 80cd14fc1..79873f4a4 100644 --- a/src/resources/extensions/bg-shell/utilities.js +++ b/src/resources/extensions/bg-shell/utilities.js @@ -3,6 +3,7 @@ */ import { existsSync } from "node:fs"; import { createRequire } from "node:module"; + // ── Windows VT Input Restoration ──────────────────────────────────────────── // Child processes (esp. Git Bash / MSYS2) can strip the ENABLE_VIRTUAL_TERMINAL_INPUT // flag from the shared stdin console handle. Re-enable it after each child exits. @@ -37,6 +38,7 @@ export function restoreWindowsVTInput() { /* koffi not available on non-Windows */ } } + // ── Time Formatting ──────────────────────────────────────────────────────── import { formatDuration } from "../shared/mod.js"; export const formatUptime = formatDuration; diff --git a/src/resources/extensions/browser-tools/capture.js b/src/resources/extensions/browser-tools/capture.js index 3d421f107..8dfb2b6e4 100644 --- a/src/resources/extensions/browser-tools/capture.js +++ b/src/resources/extensions/browser-tools/capture.js @@ -19,7 +19,9 @@ async function getSharp() { } return _sharp; } + import { formatCompactStateSummary } from "./utils.js"; + // Anthropic vision: 1568px is the recommended optimal width. Height is capped // generously at 8000px so tall full-page screenshots remain readable rather // than being squished into a square constraint. diff --git a/src/resources/extensions/browser-tools/index.js b/src/resources/extensions/browser-tools/index.js index c91a8270d..dccf75691 100644 --- a/src/resources/extensions/browser-tools/index.js +++ b/src/resources/extensions/browser-tools/index.js @@ -1,5 +1,6 @@ /** browser-tools — pi extension: full browser interaction via Playwright. */ import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; + let registrationPromise = null; async function registerBrowserTools(pi) { if (!registrationPromise) { diff --git a/src/resources/extensions/browser-tools/tools/action-cache.js b/src/resources/extensions/browser-tools/tools/action-cache.js index f662e81bc..c1258528f 100644 --- a/src/resources/extensions/browser-tools/tools/action-cache.js +++ b/src/resources/extensions/browser-tools/tools/action-cache.js @@ -1,4 +1,5 @@ import { Type } from "@sinclair/typebox"; + const cache = new Map(); const MAX_CACHE_SIZE = 200; export function registerActionCacheTools(pi, deps) { diff --git a/src/resources/extensions/browser-tools/tools/codegen.js b/src/resources/extensions/browser-tools/tools/codegen.js index fc6e48a13..57447c39a 100644 --- a/src/resources/extensions/browser-tools/tools/codegen.js +++ b/src/resources/extensions/browser-tools/tools/codegen.js @@ -278,7 +278,6 @@ function parseParamsSummary(summary) { if (!summary) return result; const regex = /(\w+)=(?:"([^"]*(?:\\"[^"]*)*)"|([^,\s]+))/g; let match; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = regex.exec(summary)) !== null) { const key = match[1]; const value = match[2] ?? match[3]; diff --git a/src/resources/extensions/browser-tools/tools/forms.js b/src/resources/extensions/browser-tools/tools/forms.js index 7f7095dc5..9a2583a84 100644 --- a/src/resources/extensions/browser-tools/tools/forms.js +++ b/src/resources/extensions/browser-tools/tools/forms.js @@ -1,5 +1,6 @@ import { Type } from "@sinclair/typebox"; import { setLastActionAfterState, setLastActionBeforeState } from "../state.js"; + /** * Runs inside page.evaluate(). Finds the target form, inventories all fields * with full label resolution, and returns a structured result. diff --git a/src/resources/extensions/browser-tools/tools/injection-detect.js b/src/resources/extensions/browser-tools/tools/injection-detect.js index 71b491e4b..6d89e5e94 100644 --- a/src/resources/extensions/browser-tools/tools/injection-detect.js +++ b/src/resources/extensions/browser-tools/tools/injection-detect.js @@ -1,4 +1,5 @@ import { Type } from "@sinclair/typebox"; + /** * Prompt injection detection — scan page content for text attempting to hijack the agent. */ @@ -179,7 +180,6 @@ export function registerInjectionDetectionTools(pi, deps) { NodeFilter.SHOW_COMMENT, ); let node; - // biome-ignore lint/suspicious/noAssignInExpressions: read-loop pattern while ((node = walker.nextNode())) { const text = node.textContent?.trim() ?? ""; if (text.length > 10) { diff --git a/src/resources/extensions/browser-tools/tools/intent.js b/src/resources/extensions/browser-tools/tools/intent.js index 8a1293270..15850de48 100644 --- a/src/resources/extensions/browser-tools/tools/intent.js +++ b/src/resources/extensions/browser-tools/tools/intent.js @@ -2,6 +2,7 @@ import { Type } from "@sinclair/typebox"; import { StringEnum } from "@singularity-forge/pi-ai"; import { diffCompactStates } from "../core.js"; import { setLastActionAfterState, setLastActionBeforeState } from "../state.js"; + // --------------------------------------------------------------------------- // Intent definitions // --------------------------------------------------------------------------- diff --git a/src/resources/extensions/browser-tools/tools/network-mock.js b/src/resources/extensions/browser-tools/tools/network-mock.js index c0d4d7939..75566756f 100644 --- a/src/resources/extensions/browser-tools/tools/network-mock.js +++ b/src/resources/extensions/browser-tools/tools/network-mock.js @@ -1,4 +1,5 @@ import { Type } from "@sinclair/typebox"; + let nextRouteId = 1; const activeRoutes = []; const routeCleanups = new Map(); diff --git a/src/resources/extensions/browser-tools/tools/state-persistence.js b/src/resources/extensions/browser-tools/tools/state-persistence.js index 7fcfc3502..e57001a02 100644 --- a/src/resources/extensions/browser-tools/tools/state-persistence.js +++ b/src/resources/extensions/browser-tools/tools/state-persistence.js @@ -1,4 +1,5 @@ import { Type } from "@sinclair/typebox"; + /** * State persistence tools — save/restore cookies, localStorage, sessionStorage. */ diff --git a/src/resources/extensions/browser-tools/tools/visual-diff.js b/src/resources/extensions/browser-tools/tools/visual-diff.js index 56bd4bb74..87c75e4f7 100644 --- a/src/resources/extensions/browser-tools/tools/visual-diff.js +++ b/src/resources/extensions/browser-tools/tools/visual-diff.js @@ -1,4 +1,5 @@ import { Type } from "@sinclair/typebox"; + /** * Visual regression diffing — compare current page screenshot against a stored baseline. */ diff --git a/src/resources/extensions/claude-code-cli/readiness.js b/src/resources/extensions/claude-code-cli/readiness.js index 2c58fb3f0..299b1da8e 100644 --- a/src/resources/extensions/claude-code-cli/readiness.js +++ b/src/resources/extensions/claude-code-cli/readiness.js @@ -9,6 +9,7 @@ * and check the exit code + output for an authenticated session. */ import { execFileSync } from "node:child_process"; + let cachedBinaryPresent = null; let cachedAuthed = null; let lastCheckMs = 0; diff --git a/src/resources/extensions/claude-code-cli/stream-adapter.js b/src/resources/extensions/claude-code-cli/stream-adapter.js index 6f9be4f65..e7b5499e7 100644 --- a/src/resources/extensions/claude-code-cli/stream-adapter.js +++ b/src/resources/extensions/claude-code-cli/stream-adapter.js @@ -18,6 +18,7 @@ import { PartialMessageBuilder, ZERO_USAGE, } from "./partial-builder.js"; + const OTHER_OPTION_LABEL = "None of the above"; const SENSITIVE_FIELD_PATTERN = /(password|passphrase|secret|token|api[_\s-]*key|private[_\s-]*key|credential)/i; diff --git a/src/resources/extensions/cmux/index.js b/src/resources/extensions/cmux/index.js index b3c5d105a..4e9725e74 100644 --- a/src/resources/extensions/cmux/index.js +++ b/src/resources/extensions/cmux/index.js @@ -1,5 +1,6 @@ import { execFileSync, spawn } from "node:child_process"; import { existsSync } from "node:fs"; + const DEFAULT_SOCKET_PATH = "/tmp/cmux.sock"; const STATUS_KEY = "sf"; const lastSidebarSnapshots = new Map(); diff --git a/src/resources/extensions/context7/index.js b/src/resources/extensions/context7/index.js index f0b99e942..2aea4b03c 100644 --- a/src/resources/extensions/context7/index.js +++ b/src/resources/extensions/context7/index.js @@ -29,6 +29,7 @@ import { truncateHead, } from "@singularity-forge/pi-coding-agent"; import { Text } from "@singularity-forge/pi-tui"; + // ─── In-session cache ───────────────────────────────────────────────────────── // Keyed by lowercased query string const searchCache = new Map(); diff --git a/src/resources/extensions/genai-proxy/index.js b/src/resources/extensions/genai-proxy/index.js index 8bc57392f..4ee9acf40 100644 --- a/src/resources/extensions/genai-proxy/index.js +++ b/src/resources/extensions/genai-proxy/index.js @@ -1,4 +1,5 @@ import { installGenaiProxyExtension } from "./proxy-command.js"; + export { installGenaiProxyExtension, resolveProxyPort, diff --git a/src/resources/extensions/genai-proxy/proxy-command.js b/src/resources/extensions/genai-proxy/proxy-command.js index e1369e14e..fe86499b8 100644 --- a/src/resources/extensions/genai-proxy/proxy-command.js +++ b/src/resources/extensions/genai-proxy/proxy-command.js @@ -1,4 +1,5 @@ import { createProxyServer } from "./proxy-server.js"; + const PROXY_COMMAND_NAME = "genai-proxy"; const PROXY_FLAG_NAME = "gemini-cli-proxy"; const DEFAULT_PROXY_PORT = 3000; diff --git a/src/resources/extensions/genai-proxy/proxy-server.js b/src/resources/extensions/genai-proxy/proxy-server.js index 6c6fae18b..acc347259 100644 --- a/src/resources/extensions/genai-proxy/proxy-server.js +++ b/src/resources/extensions/genai-proxy/proxy-server.js @@ -1,5 +1,6 @@ import { stream } from "@singularity-forge/pi-ai"; import express from "express"; + const LISTEN_ADDRESS = "127.0.0.1"; const OPENAI_CREATED_TIMESTAMP = 1_677_610_602; const SSE_CONTENT_TYPE = "text/event-stream"; diff --git a/src/resources/extensions/get-secrets-from-user.js b/src/resources/extensions/get-secrets-from-user.js index 43ebd8da5..11ad67a76 100644 --- a/src/resources/extensions/get-secrets-from-user.js +++ b/src/resources/extensions/get-secrets-from-user.js @@ -21,6 +21,7 @@ import { formatSecretsManifest, parseSecretsManifest } from "./sf/files.js"; import { resolveMilestoneFile } from "./sf/paths.js"; import { maskEditorLine } from "./shared/mod.js"; import { makeUI } from "./shared/tui.js"; + // ─── Helpers ────────────────────────────────────────────────────────────────── function maskPreview(value) { if (!value) return ""; @@ -70,11 +71,13 @@ async function writeEnvKey(filePath, key, value) { } await writeFile(filePath, content, "utf8"); } + // ─── Exported utilities ─────────────────────────────────────────────────────── // Re-export from env-utils.ts so existing consumers still work. // The implementation lives in env-utils.ts to avoid pulling @singularity-forge/pi-tui // into modules that only need env-checking (e.g. files.ts during reports). import { checkExistingEnvKeys } from "./sf/env-utils.js"; + export { checkExistingEnvKeys }; /** * Detect the write destination based on project files in basePath. diff --git a/src/resources/extensions/github-sync/cli.js b/src/resources/extensions/github-sync/cli.js index 8bb2fd4eb..1861fecb0 100644 --- a/src/resources/extensions/github-sync/cli.js +++ b/src/resources/extensions/github-sync/cli.js @@ -5,6 +5,7 @@ * Uses `execFileSync` (not `execSync`) for safety. */ import { execFileSync } from "node:child_process"; + function ok(data) { return { ok: true, data }; } diff --git a/src/resources/extensions/github-sync/mapping.js b/src/resources/extensions/github-sync/mapping.js index be19b010f..b656a44b3 100644 --- a/src/resources/extensions/github-sync/mapping.js +++ b/src/resources/extensions/github-sync/mapping.js @@ -8,6 +8,7 @@ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { atomicWriteSync } from "../sf/atomic-write.js"; + const MAPPING_FILENAME = "github-sync.json"; function mappingPath(basePath) { return join(basePath, ".sf", MAPPING_FILENAME); diff --git a/src/resources/extensions/google-search/index.js b/src/resources/extensions/google-search/index.js index bdb0c913c..1b5f065bc 100644 --- a/src/resources/extensions/google-search/index.js +++ b/src/resources/extensions/google-search/index.js @@ -18,9 +18,9 @@ import { } from "@singularity-forge/pi-coding-agent"; import { Text } from "@singularity-forge/pi-tui"; import { - resolveSearchProvider, - getTavilyApiKey, getBraveApiKey, + getTavilyApiKey, + resolveSearchProvider, } from "../search-the-web/provider.js"; let client = null; diff --git a/src/resources/extensions/guardrails/index.js b/src/resources/extensions/guardrails/index.js index 4736b75cc..6b3b8f38a 100644 --- a/src/resources/extensions/guardrails/index.js +++ b/src/resources/extensions/guardrails/index.js @@ -9,6 +9,7 @@ * - Blocks writes to protected paths (.env, .git, .ssh, etc.) */ import * as path from "node:path"; + const SENSITIVE_PATTERNS = [ { pattern: /\b(sk-[a-zA-Z0-9]{20,})\b/g, diff --git a/src/resources/extensions/mac-tools/index.js b/src/resources/extensions/mac-tools/index.js index f52067808..39b68db03 100644 --- a/src/resources/extensions/mac-tools/index.js +++ b/src/resources/extensions/mac-tools/index.js @@ -16,6 +16,7 @@ import { readdirSync, statSync } from "node:fs"; import path from "node:path"; import { Type } from "@sinclair/typebox"; import { StringEnum } from "@singularity-forge/pi-ai"; + // --------------------------------------------------------------------------- // Paths // --------------------------------------------------------------------------- diff --git a/src/resources/extensions/mcp-client/index.js b/src/resources/extensions/mcp-client/index.js index 9762d6ca8..3dab8a5a7 100644 --- a/src/resources/extensions/mcp-client/index.js +++ b/src/resources/extensions/mcp-client/index.js @@ -25,6 +25,7 @@ import { } from "@singularity-forge/pi-coding-agent"; import { Text } from "@singularity-forge/pi-tui"; import { buildHttpTransportOpts } from "./auth.js"; + // ─── Connection Manager ─────────────────────────────────────────────────────── const connections = new Map(); let configCache = null; diff --git a/src/resources/extensions/ollama/index.js b/src/resources/extensions/ollama/index.js index 1f5f27f1a..775ceefa4 100644 --- a/src/resources/extensions/ollama/index.js +++ b/src/resources/extensions/ollama/index.js @@ -20,6 +20,7 @@ import { streamOllamaChat } from "./ollama-chat-provider.js"; import * as client from "./ollama-client.js"; import { registerOllamaCommands } from "./ollama-commands.js"; import { discoverModels } from "./ollama-discovery.js"; + let toolsPromise = null; async function registerOllamaTools(pi) { if (!toolsPromise) { diff --git a/src/resources/extensions/ollama/ollama-chat-provider.js b/src/resources/extensions/ollama/ollama-chat-provider.js index 2b04ca829..3eedacbab 100644 --- a/src/resources/extensions/ollama/ollama-chat-provider.js +++ b/src/resources/extensions/ollama/ollama-chat-provider.js @@ -8,6 +8,7 @@ import { EventStream } from "@singularity-forge/pi-ai"; import { chat } from "./ollama-client.js"; import { ThinkingTagParser } from "./thinking-parser.js"; + /** Create an AssistantMessageEventStream using the base EventStream class. */ function createStream() { return new EventStream( diff --git a/src/resources/extensions/ollama/ollama-client.js b/src/resources/extensions/ollama/ollama-client.js index 3f24fe993..7dc1bc09a 100644 --- a/src/resources/extensions/ollama/ollama-client.js +++ b/src/resources/extensions/ollama/ollama-client.js @@ -6,6 +6,7 @@ * Reference: https://github.com/ollama/ollama/blob/main/docs/api.md */ import { parseNDJsonStream } from "./ndjson-stream.js"; + const DEFAULT_HOST = "http://localhost:11434"; const PROBE_TIMEOUT_MS = 1500; const REQUEST_TIMEOUT_MS = 10000; diff --git a/src/resources/extensions/ollama/ollama-discovery.js b/src/resources/extensions/ollama/ollama-discovery.js index 9d87a8403..f5998fdd2 100644 --- a/src/resources/extensions/ollama/ollama-discovery.js +++ b/src/resources/extensions/ollama/ollama-discovery.js @@ -13,6 +13,7 @@ import { humanizeModelName, } from "./model-capabilities.js"; import { listModels, showModel } from "./ollama-client.js"; + /** * Extract context window from /api/show model_info. * Keys follow the pattern "{architecture}.context_length" (e.g. "llama.context_length"). diff --git a/src/resources/extensions/remote-questions/config.js b/src/resources/extensions/remote-questions/config.js index 7bd46e093..6fdffd595 100644 --- a/src/resources/extensions/remote-questions/config.js +++ b/src/resources/extensions/remote-questions/config.js @@ -3,6 +3,7 @@ */ import { AuthStorage } from "@singularity-forge/pi-coding-agent"; import { loadEffectiveSFPreferences } from "../sf/preferences.js"; + const ENV_KEYS = { slack: "SLACK_BOT_TOKEN", discord: "DISCORD_BOT_TOKEN", diff --git a/src/resources/extensions/remote-questions/discord-adapter.js b/src/resources/extensions/remote-questions/discord-adapter.js index a6d72c97f..77f378493 100644 --- a/src/resources/extensions/remote-questions/discord-adapter.js +++ b/src/resources/extensions/remote-questions/discord-adapter.js @@ -7,6 +7,7 @@ import { parseDiscordResponse, } from "./format.js"; import { apiRequest } from "./http-client.js"; + const DISCORD_API = "https://discord.com/api/v10"; export class DiscordAdapter { name = "discord"; diff --git a/src/resources/extensions/remote-questions/slack-adapter.js b/src/resources/extensions/remote-questions/slack-adapter.js index 64102b0fb..f044c97a1 100644 --- a/src/resources/extensions/remote-questions/slack-adapter.js +++ b/src/resources/extensions/remote-questions/slack-adapter.js @@ -8,6 +8,7 @@ import { SLACK_NUMBER_REACTION_NAMES, } from "./format.js"; import { apiRequest } from "./http-client.js"; + const SLACK_API = "https://slack.com/api"; const SLACK_ACK_REACTION = "white_check_mark"; export class SlackAdapter { diff --git a/src/resources/extensions/remote-questions/status.js b/src/resources/extensions/remote-questions/status.js index 7e7cb004a..a548196a5 100644 --- a/src/resources/extensions/remote-questions/status.js +++ b/src/resources/extensions/remote-questions/status.js @@ -5,6 +5,7 @@ import { existsSync, readdirSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; import { readPromptRecord } from "./store.js"; + function getSfHome() { return process.env.SF_HOME || join(homedir(), ".sf"); } diff --git a/src/resources/extensions/remote-questions/store.js b/src/resources/extensions/remote-questions/store.js index 18ce52f64..1fd0ba662 100644 --- a/src/resources/extensions/remote-questions/store.js +++ b/src/resources/extensions/remote-questions/store.js @@ -4,6 +4,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; + function getSfHome() { return process.env.SF_HOME || join(homedir(), ".sf"); } diff --git a/src/resources/extensions/remote-questions/telegram-adapter.js b/src/resources/extensions/remote-questions/telegram-adapter.js index 5d1afa9c8..2f68bc823 100644 --- a/src/resources/extensions/remote-questions/telegram-adapter.js +++ b/src/resources/extensions/remote-questions/telegram-adapter.js @@ -3,6 +3,7 @@ */ import { formatForTelegram, parseTelegramResponse } from "./format.js"; import { apiRequest } from "./http-client.js"; + const TELEGRAM_API = "https://api.telegram.org"; export class TelegramAdapter { name = "telegram"; diff --git a/src/resources/extensions/search-the-web/command-search-provider.js b/src/resources/extensions/search-the-web/command-search-provider.js index 777e48d9a..ec888b16e 100644 --- a/src/resources/extensions/search-the-web/command-search-provider.js +++ b/src/resources/extensions/search-the-web/command-search-provider.js @@ -18,6 +18,7 @@ import { resolveSearchProvider, setSearchProviderPreference, } from "./provider.js"; + const VALID_PREFERENCES = [ "tavily", "minimax", diff --git a/src/resources/extensions/search-the-web/format.js b/src/resources/extensions/search-the-web/format.js index 8485e2c09..366abc870 100644 --- a/src/resources/extensions/search-the-web/format.js +++ b/src/resources/extensions/search-the-web/format.js @@ -3,6 +3,7 @@ * and LLM context responses. */ import { extractDomain } from "./url-utils.js"; + // ============================================================================= // Adaptive Snippet Budget // ============================================================================= diff --git a/src/resources/extensions/search-the-web/index.js b/src/resources/extensions/search-the-web/index.js index 0987c9993..2b823d6c8 100644 --- a/src/resources/extensions/search-the-web/index.js +++ b/src/resources/extensions/search-the-web/index.js @@ -7,6 +7,7 @@ import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; import { registerSearchProviderCommand } from "./command-search-provider.js"; import { registerNativeSearchHooks } from "./native-search.js"; + let toolsPromise = null; let resetSearchLoopGuardStateRef = null; async function registerSearchTools(pi) { diff --git a/src/resources/extensions/search-the-web/provider.js b/src/resources/extensions/search-the-web/provider.js index db45fc958..28185517d 100644 --- a/src/resources/extensions/search-the-web/provider.js +++ b/src/resources/extensions/search-the-web/provider.js @@ -13,6 +13,7 @@ import { homedir } from "node:os"; import { join } from "node:path"; import { AuthStorage } from "@singularity-forge/pi-coding-agent"; import { resolveSearchProviderFromPreferences } from "../sf/preferences.js"; + // Compute authFilePath locally instead of importing from app-paths.ts, // because extensions are copied to ~/.sf/agent/extensions/ at runtime // where the relative import '../../../app-paths.ts' doesn't resolve. diff --git a/src/resources/extensions/search-the-web/tool-fetch-page.js b/src/resources/extensions/search-the-web/tool-fetch-page.js index 2cc02b730..102833fc7 100644 --- a/src/resources/extensions/search-the-web/tool-fetch-page.js +++ b/src/resources/extensions/search-the-web/tool-fetch-page.js @@ -19,6 +19,7 @@ import { formatPageContent } from "./format.js"; import { fetchSimple, HttpError } from "./http.js"; import { getOllamaApiKey } from "./provider.js"; import { extractDomain, isBlockedUrl } from "./url-utils.js"; + // Page content cache: max 30 entries, 15-minute TTL const pageCache = new LRUTTLCache({ max: 30, ttlMs: 900_000 }); pageCache.startPurgeInterval(120_000); diff --git a/src/resources/extensions/search-the-web/tool-llm-context.js b/src/resources/extensions/search-the-web/tool-llm-context.js index bab5611bb..ea9bf7609 100644 --- a/src/resources/extensions/search-the-web/tool-llm-context.js +++ b/src/resources/extensions/search-the-web/tool-llm-context.js @@ -41,6 +41,7 @@ import { } from "./provider.js"; import { publishedDateToAge } from "./tavily.js"; import { extractDomain, normalizeQuery } from "./url-utils.js"; + // ============================================================================= // Cache // ============================================================================= diff --git a/src/resources/extensions/search-the-web/tool-search.js b/src/resources/extensions/search-the-web/tool-search.js index be473bd70..db736e85d 100644 --- a/src/resources/extensions/search-the-web/tool-search.js +++ b/src/resources/extensions/search-the-web/tool-search.js @@ -33,6 +33,7 @@ import { } from "./provider.js"; import { mapFreshnessToTavily, normalizeTavilyResult } from "./tavily.js"; import { detectFreshness, normalizeQuery, toDedupeKey } from "./url-utils.js"; + // ============================================================================= // Caches // ============================================================================= diff --git a/src/resources/extensions/sf-notify/index.js b/src/resources/extensions/sf-notify/index.js index fe80abb52..e636baaa4 100644 --- a/src/resources/extensions/sf-notify/index.js +++ b/src/resources/extensions/sf-notify/index.js @@ -21,6 +21,7 @@ import { SAY_MESSAGES, speakMessage, } from "../shared/notify.js"; + const DEFAULT_CONFIG = { thresholdMs: 2000, beep: true, diff --git a/src/resources/extensions/sf-permissions/index.js b/src/resources/extensions/sf-permissions/index.js index e6ed8fcc5..36ac9fb28 100644 --- a/src/resources/extensions/sf-permissions/index.js +++ b/src/resources/extensions/sf-permissions/index.js @@ -56,8 +56,10 @@ import { saveGlobalPermissionMode, savePermissionConfig, } from "./permission-core.js"; + // Re-export types and constants needed by the hook export { LEVEL_INFO, LEVELS, PERMISSION_MODE_INFO, PERMISSION_MODES }; + // ============================================================================ // SOUND NOTIFICATION // ============================================================================ diff --git a/src/resources/extensions/sf-tui/color-band.js b/src/resources/extensions/sf-tui/color-band.js index e088932fb..ec23bd9e5 100644 --- a/src/resources/extensions/sf-tui/color-band.js +++ b/src/resources/extensions/sf-tui/color-band.js @@ -6,6 +6,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; + const DEFAULT_CONFIG = { enabledByDefault: true, blockChar: "▁", diff --git a/src/resources/extensions/sf-tui/emoji.js b/src/resources/extensions/sf-tui/emoji.js index aecabc339..8f608b12b 100644 --- a/src/resources/extensions/sf-tui/emoji.js +++ b/src/resources/extensions/sf-tui/emoji.js @@ -5,6 +5,7 @@ * AI-powered selection based on conversation, or random assignment. */ import { complete } from "@singularity-forge/pi-ai"; + const DEFAULT_CONFIG = { enabledByDefault: true, autoAssignMode: "ai", diff --git a/src/resources/extensions/sf-tui/footer.js b/src/resources/extensions/sf-tui/footer.js index bacd08c68..466a8b3ae 100644 --- a/src/resources/extensions/sf-tui/footer.js +++ b/src/resources/extensions/sf-tui/footer.js @@ -1,5 +1,6 @@ import { truncateToWidth, visibleWidth } from "@singularity-forge/pi-tui"; import { refreshGitStatus } from "./git.js"; + const RESET = "\x1b[0m"; const BOLD = "\x1b[1m"; const SE = { diff --git a/src/resources/extensions/sf-tui/git.js b/src/resources/extensions/sf-tui/git.js index 2982777cf..0e48f51b0 100644 --- a/src/resources/extensions/sf-tui/git.js +++ b/src/resources/extensions/sf-tui/git.js @@ -1,5 +1,6 @@ import { execFileSync } from "node:child_process"; import { basename } from "node:path"; + let cache = null; let lastFetch = 0; function getRepoName(cwd) { diff --git a/src/resources/extensions/sf-tui/header.js b/src/resources/extensions/sf-tui/header.js index 0ececab72..54160648e 100644 --- a/src/resources/extensions/sf-tui/header.js +++ b/src/resources/extensions/sf-tui/header.js @@ -1,6 +1,7 @@ import { basename } from "node:path"; import { truncateToWidth, visibleWidth } from "@singularity-forge/pi-tui"; import { refreshGitStatus } from "./git.js"; + function align(left, right, width, ellipsis) { const gap = Math.max(1, width - visibleWidth(left) - visibleWidth(right)); return truncateToWidth(left + " ".repeat(gap) + right, width, ellipsis); diff --git a/src/resources/extensions/sf-tui/index.js b/src/resources/extensions/sf-tui/index.js index c6e89ef14..bfc2afcd9 100644 --- a/src/resources/extensions/sf-tui/index.js +++ b/src/resources/extensions/sf-tui/index.js @@ -15,6 +15,7 @@ import { invalidateGitStatus } from "./git.js"; import { renderHeader } from "./header.js"; import { openMarketplaceOverlay } from "./marketplace.js"; import { openStashOverlay, pushStash, readStash, writeStash } from "./stash.js"; + function installHeader(ctx) { if (!ctx.hasUI) return; ctx.ui.setHeader((_tui, theme) => { diff --git a/src/resources/extensions/sf-tui/marketplace.js b/src/resources/extensions/sf-tui/marketplace.js index 24276de08..7fa5f7549 100644 --- a/src/resources/extensions/sf-tui/marketplace.js +++ b/src/resources/extensions/sf-tui/marketplace.js @@ -7,6 +7,7 @@ import { truncateToWidth, visibleWidth, } from "@singularity-forge/pi-tui"; + const CATEGORIES = ["all", "extension", "skill", "theme"]; const FEATURED = [ { diff --git a/src/resources/extensions/sf-tui/powerline.js b/src/resources/extensions/sf-tui/powerline.js index ae94028c5..527dc1be5 100644 --- a/src/resources/extensions/sf-tui/powerline.js +++ b/src/resources/extensions/sf-tui/powerline.js @@ -1,4 +1,5 @@ import { truncateToWidth, visibleWidth } from "@singularity-forge/pi-tui"; + const RESET = "\x1b[0m"; function fgCode(color) { switch (color) { diff --git a/src/resources/extensions/sf-tui/stash.js b/src/resources/extensions/sf-tui/stash.js index f6c3e4323..65be82358 100644 --- a/src/resources/extensions/sf-tui/stash.js +++ b/src/resources/extensions/sf-tui/stash.js @@ -7,6 +7,7 @@ import { truncateToWidth, visibleWidth, } from "@singularity-forge/pi-tui"; + const LIMIT = 20; function stashPath() { return join(homedir(), ".sf", "agent", "prompt-history.json"); diff --git a/src/resources/extensions/sf-usage-bar/index.js b/src/resources/extensions/sf-usage-bar/index.js index eac2694af..89e995dd8 100644 --- a/src/resources/extensions/sf-usage-bar/index.js +++ b/src/resources/extensions/sf-usage-bar/index.js @@ -19,6 +19,7 @@ import { setupUser, } from "@google/gemini-cli-core"; import { visibleWidth } from "@singularity-forge/pi-tui"; + // ============================================================================ // Auth helper // ============================================================================ @@ -492,7 +493,6 @@ async function fetchCodexUsage(modelRegistry) { // Kiro (AWS) // ============================================================================ function stripAnsi(text) { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences return text.replace(/\x1B\[[0-9;?]*[A-Za-z]|\x1B\].*?\x07/g, ""); } function whichSync(cmd) { diff --git a/src/resources/extensions/sf/activity-log.js b/src/resources/extensions/sf/activity-log.js index 01d0c1966..4117ee5e2 100644 --- a/src/resources/extensions/sf/activity-log.js +++ b/src/resources/extensions/sf/activity-log.js @@ -20,10 +20,13 @@ import { } from "node:fs"; import { join } from "node:path"; import { SF_IO_ERROR, SFError } from "./errors.js"; + const SEQ_PREFIX_RE = /^(\d+)-/; + import { sfRuntimeRoot } from "./paths.js"; import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js"; import { isAuditEnvelopeEnabled } from "./uok/audit-toggle.js"; + const activityLogState = new Map(); /** * Clear accumulated activity log state (#611). diff --git a/src/resources/extensions/sf/agentic-docs-scaffold.js b/src/resources/extensions/sf/agentic-docs-scaffold.js index 21c37e69d..e649075d6 100644 --- a/src/resources/extensions/sf/agentic-docs-scaffold.js +++ b/src/resources/extensions/sf/agentic-docs-scaffold.js @@ -1,19 +1,34 @@ -import { existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { + existsSync, + mkdirSync, + readdirSync, + rmdirSync, + rmSync, + writeFileSync, +} from "node:fs"; import { dirname, join } from "node:path"; +import { migrateLegacyScaffold } from "./scaffold-drift.js"; import { bodyHash, extractMarker, recordScaffoldApply, stampScaffoldFile, } from "./scaffold-versioning.js"; -import { migrateLegacyScaffold } from "./scaffold-drift.js"; import { logWarning } from "./workflow-logger.js"; + /** * Files in SCAFFOLD_FILES that intentionally do not carry an inline * version marker (per ADR-021 §2). The manifest still records that SF * wrote them, so legacy-hash migration in Phase C can identify them. */ const NO_MARKER_PATHS = new Set([".siftignore"]); +const LEGACY_ROOT_HARNESS_PATHS = [ + "harness/AGENTS.md", + "harness/specs/AGENTS.md", + "harness/specs/bootstrap.md", + "harness/evals/AGENTS.md", + "harness/graders/AGENTS.md", +]; /** * Canonical scaffold file templates SF manages for agent legibility. * @@ -358,10 +373,10 @@ What other options were evaluated and why were they not chosen? `, }, { - path: "harness/AGENTS.md", + path: ".sf/harness/AGENTS.md", content: `# Harness Agent Notes -The harness is a collection of contracts the agent can read and verify against. +The harness is SF-local operational scaffolding the agent can read and verify against. - \`specs/\`: behavior contracts. Each spec states what "done" looks like and the command that proves it. - \`evals/\`: task definitions for behaviors tests cannot cover — model output quality, multi-turn flows, agent decisions. @@ -371,7 +386,7 @@ The harness is a collection of contracts the agent can read and verify against. `, }, { - path: "harness/specs/AGENTS.md", + path: ".sf/harness/specs/AGENTS.md", content: `# Harness Specs Agent Notes Each spec file in this directory: @@ -384,7 +399,7 @@ Write the spec before implementation. Run it after. Record the result. `, }, { - path: "harness/specs/bootstrap.md", + path: ".sf/harness/specs/bootstrap.md", content: `# Bootstrap Spec: Agent Legibility Verifies that this repo is minimally agent-legible. @@ -400,14 +415,14 @@ Verifies that this repo is minimally agent-legible. ## Verification command \`\`\`bash -for f in AGENTS.md ARCHITECTURE.md docs/exec-plans/active/index.md docs/exec-plans/tech-debt-tracker.md docs/design-docs/ADR-TEMPLATE.md; do [ -s "$f" ] && echo "OK: $f" || echo "MISSING: $f"; done +for f in AGENTS.md ARCHITECTURE.md docs/exec-plans/active/index.md docs/exec-plans/tech-debt-tracker.md docs/design-docs/ADR-TEMPLATE.md .sf/harness/specs/bootstrap.md; do [ -s "$f" ] && echo "OK: $f" || echo "MISSING: $f"; done \`\`\` All lines should start with \`OK:\` for the bootstrap spec to pass. `, }, { - path: "harness/evals/AGENTS.md", + path: ".sf/harness/evals/AGENTS.md", content: `# Harness Evals Agent Notes Evals verify behavior that unit tests cannot cover — model output quality, agent decisions, multi-turn flows. @@ -421,7 +436,7 @@ Keep evals deterministic where possible. Log results to \`docs/records/\` at mil `, }, { - path: "harness/graders/AGENTS.md", + path: ".sf/harness/graders/AGENTS.md", content: `# Harness Graders Agent Notes Graders are reusable scripts or prompts that score eval outputs. @@ -472,6 +487,44 @@ This is gold — most wrong agent calls come from not knowing what to avoid. Eac `, }, ]; + +function pruneEmptyDir(path) { + try { + if (existsSync(path) && readdirSync(path).length === 0) { + rmdirSync(path); + } + } catch (err) { + logWarning("scaffold", "failed to prune empty legacy harness dir", { + dir: path, + error: err.message, + }); + } +} + +function removeLegacyRootHarnessScaffold(basePath) { + for (const relPath of LEGACY_ROOT_HARNESS_PATHS) { + const target = join(basePath, relPath); + try { + if (!existsSync(target)) continue; + const { marker, body } = extractMarker(target); + if (!marker) continue; + if (marker.template !== relPath) continue; + if (marker.state !== "pending") continue; + if (bodyHash(body) !== marker.hash) continue; + rmSync(target); + } catch (err) { + logWarning("scaffold", "failed to remove legacy root harness file", { + file: relPath, + error: err.message, + }); + } + } + pruneEmptyDir(join(basePath, "harness/specs")); + pruneEmptyDir(join(basePath, "harness/evals")); + pruneEmptyDir(join(basePath, "harness/graders")); + pruneEmptyDir(join(basePath, "harness")); +} + /** * Drift-aware scaffold sync (ADR-021 Phase C). * @@ -501,6 +554,7 @@ export function ensureAgenticDocsScaffold(basePath) { error: err.message, }); } + removeLegacyRootHarnessScaffold(basePath); // Step 2: missing-file creation + pending-state silent upgrade. for (const file of SCAFFOLD_FILES) { const target = join(basePath, file.path); diff --git a/src/resources/extensions/sf/atomic-write.js b/src/resources/extensions/sf/atomic-write.js index 67db51e98..7c8a37392 100644 --- a/src/resources/extensions/sf/atomic-write.js +++ b/src/resources/extensions/sf/atomic-write.js @@ -8,6 +8,7 @@ import { } from "node:fs"; import { dirname } from "node:path"; import { isMainThread } from "node:worker_threads"; + const TRANSIENT_LOCK_ERROR_CODES = new Set(["EBUSY", "EPERM", "EACCES"]); const MAX_RENAME_ATTEMPTS = 5; function defaultTempPath(filePath) { diff --git a/src/resources/extensions/sf/auto-bootstrap-context.js b/src/resources/extensions/sf/auto-bootstrap-context.js index b1fbef55d..e077526a7 100644 --- a/src/resources/extensions/sf/auto-bootstrap-context.js +++ b/src/resources/extensions/sf/auto-bootstrap-context.js @@ -1,5 +1,6 @@ import { readdirSync, readFileSync, statSync } from "node:fs"; import { join, relative } from "node:path"; + const AUTO_BOOTSTRAP_MAX_BYTES = readPositiveIntEnv( "SF_AUTO_BOOTSTRAP_MAX_BYTES", 48_000, diff --git a/src/resources/extensions/sf/auto-dashboard.js b/src/resources/extensions/sf/auto-dashboard.js index ccb20645f..7275453c2 100644 --- a/src/resources/extensions/sf/auto-dashboard.js +++ b/src/resources/extensions/sf/auto-dashboard.js @@ -33,6 +33,7 @@ import { parseUnitId } from "./unit-id.js"; import { logWarning } from "./workflow-logger.js"; import { getCurrentBranch } from "./worktree.js"; import { getActiveWorktreeName } from "./worktree-command.js"; + const ACTIVITY_FRAMES = ["|", "/", "-", "\\"]; // ─── UAT Slice Extraction ───────────────────────────────────────────────────── /** diff --git a/src/resources/extensions/sf/auto-dispatch.js b/src/resources/extensions/sf/auto-dispatch.js index 1c515bb17..4db4ff5b1 100644 --- a/src/resources/extensions/sf/auto-dispatch.js +++ b/src/resources/extensions/sf/auto-dispatch.js @@ -8,7 +8,7 @@ * data structure that is inspectable, testable per-rule, and extensible * without modifying orchestration code. */ -import { createScheduleStore } from "./schedule/schedule-store.js"; + import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { @@ -26,8 +26,8 @@ import { buildReassessRoadmapPrompt, buildRefineSlicePrompt, buildReplanSlicePrompt, - buildResearchProjectPrompt, buildResearchMilestonePrompt, + buildResearchProjectPrompt, buildResearchSlicePrompt, buildRewriteDocsPrompt, buildRunUatPrompt, @@ -62,6 +62,7 @@ import { sfRoot, } from "./paths.js"; import { resolveModelWithFallbacksForUnit } from "./preferences-models.js"; +import { createScheduleStore } from "./schedule/schedule-store.js"; import { getMilestone, getMilestoneSlices, @@ -83,6 +84,7 @@ import { UokGateRunner } from "./uok/gate-runner.js"; import { hasFinalizedMilestoneContext } from "./uok/plan-v2.js"; import { extractVerdict, isAcceptableUatVerdict } from "./verdict-parser.js"; import { logError, logWarning } from "./workflow-logger.js"; + const MAX_PARALLEL_RESEARCH_SLICES = 8; const PARALLEL_RESEARCH_BLOCKING_PHASES = new Set([ "blocked", @@ -1585,7 +1587,9 @@ export const DISPATCH_RULES = [ }, }, ]; + import { getRegistry, hasRegistry } from "./rule-registry.js"; + // ─── Dispatch Envelope Emission ─────────────────────────────────────────── /** * Emit a UokDispatchEnvelope as an audit event when audit is enabled. diff --git a/src/resources/extensions/sf/auto-post-unit.js b/src/resources/extensions/sf/auto-post-unit.js index f2d1fab12..6ca4b1c38 100644 --- a/src/resources/extensions/sf/auto-post-unit.js +++ b/src/resources/extensions/sf/auto-post-unit.js @@ -96,6 +96,7 @@ import { isAwaitingUserInput } from "./user-input-boundary.js"; import { writePreExecutionEvidence } from "./verification-evidence.js"; import { logError, logWarning } from "./workflow-logger.js"; import { regenerateIfMissing } from "./workflow-projections.js"; + /** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */ const MAX_VERIFICATION_RETRIES = 3; function isCompletedTaskStatus(status) { @@ -156,11 +157,13 @@ const LIFECYCLE_ONLY_UNITS = new Set([ "reassess-roadmap", "rewrite-docs", ]); + import { existsSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { describeNextUnit } from "./auto-dashboard.js"; import { _resetHasChangesCache } from "./native-git-bridge.js"; import { autoCommitCurrentBranch } from "./worktree.js"; + /** * Detect summary files written directly to disk without the LLM calling * the completion tool. A "rogue" file is one that exists on disk but has diff --git a/src/resources/extensions/sf/auto-prompts.js b/src/resources/extensions/sf/auto-prompts.js index 61b3c81d8..30d305432 100644 --- a/src/resources/extensions/sf/auto-prompts.js +++ b/src/resources/extensions/sf/auto-prompts.js @@ -66,6 +66,7 @@ import { import { composeInlinedContext } from "./unit-context-composer.js"; import { getUatType, hasVerdict } from "./verdict-parser.js"; import { logWarning } from "./workflow-logger.js"; + // ─── Preamble Cap ───────────────────────────────────────────────────────────── /** * Historical static ceiling for the preamble cap. Kept as an upper bound even diff --git a/src/resources/extensions/sf/auto-recovery.js b/src/resources/extensions/sf/auto-recovery.js index 8f132d441..7e8f75101 100644 --- a/src/resources/extensions/sf/auto-recovery.js +++ b/src/resources/extensions/sf/auto-recovery.js @@ -56,6 +56,7 @@ import { isValidationTerminal } from "./state.js"; import { parseUnitId } from "./unit-id.js"; import { appendEvent } from "./workflow-events.js"; import { logError, logWarning } from "./workflow-logger.js"; + // Re-export so existing consumers of auto-recovery.ts keep working. export { diagnoseExpectedArtifact, resolveExpectedArtifactPath }; // ─── Artifact Resolution & Verification ─────────────────────────────────────── diff --git a/src/resources/extensions/sf/auto-start.js b/src/resources/extensions/sf/auto-start.js index 85eb9e3de..9c2a86fb8 100644 --- a/src/resources/extensions/sf/auto-start.js +++ b/src/resources/extensions/sf/auto-start.js @@ -18,17 +18,16 @@ import { } from "node:fs"; import { join, sep as pathSep } from "node:path"; import { collectSecretsFromManifest } from "../get-secrets-from-user.js"; -import { hideFooter } from "./auto-dashboard.js"; import { ensureAgenticDocsScaffold } from "./agentic-docs-scaffold.js"; -import { ensureSiftIndexWarmup } from "./code-intelligence.js"; +import { hideFooter } from "./auto-dashboard.js"; import { cleanStaleRuntimeUnits, getAutoWorktreePath, readResourceVersion, } from "./auto-worktree.js"; import { resolveProjectRootDbPath } from "./bootstrap/dynamic-tools.js"; -import { reconcileStaleCompleteSliceRecords } from "./unit-runtime.js"; import { invalidateAllCaches } from "./cache.js"; +import { ensureSiftIndexWarmup } from "./code-intelligence.js"; import { clearLock, writeLock } from "./crash-recovery.js"; import { debugLog, @@ -91,6 +90,7 @@ import { getMilestone, isDbAvailable, openDatabase } from "./sf-db.js"; import { snapshotSkills } from "./skill-discovery.js"; import { deriveState, isGhostMilestone } from "./state.js"; import { isClosedStatus } from "./status-guards.js"; +import { reconcileStaleCompleteSliceRecords } from "./unit-runtime.js"; import { logError, logWarning } from "./workflow-logger.js"; import { captureIntegrationBranch, @@ -102,6 +102,7 @@ import { isInsideWorktreesDir, } from "./worktree-manager.js"; import { emitWorktreeOrphaned } from "./worktree-telemetry.js"; + /** * Bootstrap a fresh auto-mode session. Handles everything from git init * through secrets collection, returning when ready for the first diff --git a/src/resources/extensions/sf/auto-supervisor.js b/src/resources/extensions/sf/auto-supervisor.js index bd756644b..5305e190c 100644 --- a/src/resources/extensions/sf/auto-supervisor.js +++ b/src/resources/extensions/sf/auto-supervisor.js @@ -7,6 +7,7 @@ import { clearLock } from "./crash-recovery.js"; import { nativeHasChanges } from "./native-git-bridge.js"; import { releaseSessionLock } from "./session-lock.js"; import { logWarning } from "./workflow-logger.js"; + // ─── Signal Handling ───────────────────────────────────────────────────────── /** Signals that should trigger lock cleanup on process termination. */ const CLEANUP_SIGNALS = ["SIGTERM", "SIGHUP", "SIGINT"]; diff --git a/src/resources/extensions/sf/auto-timeout-recovery.js b/src/resources/extensions/sf/auto-timeout-recovery.js index 8d70213db..4a6d9d3be 100644 --- a/src/resources/extensions/sf/auto-timeout-recovery.js +++ b/src/resources/extensions/sf/auto-timeout-recovery.js @@ -27,6 +27,7 @@ import { readUnitRuntimeRecord, writeUnitRuntimeRecord, } from "./unit-runtime.js"; + function relToBase(basePath, path) { const rel = relative(basePath, path); return rel && !rel.startsWith("..") ? rel : path; diff --git a/src/resources/extensions/sf/auto-verification.js b/src/resources/extensions/sf/auto-verification.js index 2e386ef5c..d0d31015f 100644 --- a/src/resources/extensions/sf/auto-verification.js +++ b/src/resources/extensions/sf/auto-verification.js @@ -36,6 +36,7 @@ import { runVerificationGate, } from "./verification-gate.js"; import { logError, logWarning } from "./workflow-logger.js"; + function isInfraVerificationFailure(stderr) { return /\b(ENOENT|ENOTFOUND|ETIMEDOUT|ECONNRESET|EAI_AGAIN|spawn\s+\S+\s+ENOENT|command not found)\b/i.test( stderr, diff --git a/src/resources/extensions/sf/auto-worktree.js b/src/resources/extensions/sf/auto-worktree.js index 72bdb4371..d007a45dc 100644 --- a/src/resources/extensions/sf/auto-worktree.js +++ b/src/resources/extensions/sf/auto-worktree.js @@ -29,6 +29,7 @@ import { RUNTIME_EXCLUSION_PATHS, readIntegrationBranch, } from "./git-service.js"; +import { emitJournalEvent } from "./journal.js"; import { nativeAddAllWithExclusions, nativeAddPaths, @@ -50,6 +51,7 @@ import { } from "./native-git-bridge.js"; import { sfRoot } from "./paths.js"; import { loadEffectiveSFPreferences } from "./preferences.js"; +import { isInsideWorktree } from "./repo-identity.js"; import { safeCopy, safeCopyRecursive } from "./safe-fs.js"; import { getMilestone, @@ -57,7 +59,6 @@ import { isDbAvailable, reconcileWorktreeDb, } from "./sf-db.js"; -import { emitJournalEvent } from "./journal.js"; import { logError, logWarning } from "./workflow-logger.js"; import { detectWorktreeName, nudgeGitBranchCache } from "./worktree.js"; import { @@ -67,7 +68,7 @@ import { resolveGitDir, worktreePath, } from "./worktree-manager.js"; -import { isInsideWorktree } from "./repo-identity.js"; + const sfHome = process.env.SF_HOME || join(homedir(), ".sf"); const PROJECT_PREFERENCES_FILE = "PREFERENCES.md"; const LEGACY_PROJECT_PREFERENCES_FILE = "preferences.md"; @@ -1490,7 +1491,6 @@ export function mergeMilestoneToMain( if (completedSlices.length === 0 && roadmapContent) { const sliceRe = /- \[x\] \*\*(\w+):\s*(.+?)\*\*/gi; let m; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((m = sliceRe.exec(roadmapContent)) !== null) { completedSlices.push({ id: m[1], title: m[2] }); } diff --git a/src/resources/extensions/sf/auto/auto-post-unit-staging.js b/src/resources/extensions/sf/auto/auto-post-unit-staging.js index 96b87a736..8a9cee7b7 100644 --- a/src/resources/extensions/sf/auto/auto-post-unit-staging.js +++ b/src/resources/extensions/sf/auto/auto-post-unit-staging.js @@ -9,9 +9,9 @@ */ import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { getDocSyncProposal, formatDocSyncProposal } from "../doc-sync.js"; -import { runGit } from "../git-service.js"; import { debugLog } from "../debug-logger.js"; +import { formatDocSyncProposal, getDocSyncProposal } from "../doc-sync.js"; +import { runGit } from "../git-service.js"; /** Unit types that mutate code — doc-sync only runs after these. */ const CODE_MUTATING_UNITS = new Set(["execute-task", "complete-slice"]); diff --git a/src/resources/extensions/sf/auto/detect-stuck.js b/src/resources/extensions/sf/auto/detect-stuck.js index def9d7b86..c350ad188 100644 --- a/src/resources/extensions/sf/auto/detect-stuck.js +++ b/src/resources/extensions/sf/auto/detect-stuck.js @@ -4,6 +4,7 @@ * Leaf node in the import DAG. */ import { summarizeLogs } from "../workflow-logger.js"; + /** * Pattern matching ENOENT errors with a file path. * Matches: "ENOENT: no such file or directory, access '/path/to/file'" diff --git a/src/resources/extensions/sf/auto/loop.js b/src/resources/extensions/sf/auto/loop.js index 0c6ec1d1a..d59db2727 100644 --- a/src/resources/extensions/sf/auto/loop.js +++ b/src/resources/extensions/sf/auto/loop.js @@ -36,6 +36,7 @@ import { } from "./phases.js"; import { _clearCurrentResolve } from "./resolve.js"; import { MAX_LOOP_ITERATIONS } from "./types.js"; + // ── Stuck detection persistence (#3704) ────────────────────────────────── // Persist stuck detection state to disk so it survives session restarts. // Without this, restarting auto-mode resets all counters, allowing the diff --git a/src/resources/extensions/sf/auto/phases.js b/src/resources/extensions/sf/auto/phases.js index e7e07c2bd..2d83b4874 100644 --- a/src/resources/extensions/sf/auto/phases.js +++ b/src/resources/extensions/sf/auto/phases.js @@ -105,6 +105,7 @@ import { MAX_FINALIZE_TIMEOUTS, MAX_RECOVERY_CHARS, } from "./types.js"; + // ─── Session timeout auto-resume state ──────────────────────────────────────── let consecutiveSessionTimeouts = 0; const MAX_SESSION_TIMEOUT_AUTO_RESUMES = 3; diff --git a/src/resources/extensions/sf/auto/resolve.js b/src/resources/extensions/sf/auto/resolve.js index ceb8bd621..ceff3d0f3 100644 --- a/src/resources/extensions/sf/auto/resolve.js +++ b/src/resources/extensions/sf/auto/resolve.js @@ -8,6 +8,7 @@ * Imports from: auto/types */ import { debugLog } from "../debug-logger.js"; + // ─── Per-unit one-shot promise state ──────────────────────────────────────── // // A single module-level resolve function scoped to the current unit execution. diff --git a/src/resources/extensions/sf/auto/run-unit.js b/src/resources/extensions/sf/auto/run-unit.js index a81f24803..246cbf0b0 100644 --- a/src/resources/extensions/sf/auto/run-unit.js +++ b/src/resources/extensions/sf/auto/run-unit.js @@ -26,6 +26,7 @@ import { getCurrentTurnGeneration, runWithTurnGeneration, } from "./turn-epoch.js"; + // Tracks the latest session-switch attempt so a late timeout settlement from an // older runUnit() call cannot clear the guard for a newer one. let sessionSwitchGeneration = 0; diff --git a/src/resources/extensions/sf/auto/turn-epoch.js b/src/resources/extensions/sf/auto/turn-epoch.js index 0ccf47d6d..e98c55403 100644 --- a/src/resources/extensions/sf/auto/turn-epoch.js +++ b/src/resources/extensions/sf/auto/turn-epoch.js @@ -25,6 +25,7 @@ */ import { AsyncLocalStorage } from "node:async_hooks"; import { debugLog } from "../debug-logger.js"; + let _currentGeneration = 0; const turnContext = new AsyncLocalStorage(); /** Current turn generation. Mutated only by bumpTurnGeneration. */ diff --git a/src/resources/extensions/sf/benchmark-selector.js b/src/resources/extensions/sf/benchmark-selector.js index 685d99574..37e34401a 100644 --- a/src/resources/extensions/sf/benchmark-selector.js +++ b/src/resources/extensions/sf/benchmark-selector.js @@ -24,6 +24,7 @@ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { tierOrdinal } from "./complexity-classifier.js"; import { getModelTier } from "./model-router.js"; + // ─── Benchmark File Loader ─────────────────────────────────────────────────── let _benchmarksCache = null; function loadBenchmarks() { diff --git a/src/resources/extensions/sf/blocked-models.js b/src/resources/extensions/sf/blocked-models.js index 0d03706c3..746eec220 100644 --- a/src/resources/extensions/sf/blocked-models.js +++ b/src/resources/extensions/sf/blocked-models.js @@ -9,6 +9,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { withFileLockSync } from "./file-lock.js"; import { sfRoot } from "./paths.js"; + function blockedModelsPath(basePath) { return join(sfRoot(basePath), "runtime", "blocked-models.json"); } diff --git a/src/resources/extensions/sf/bootstrap/agent-end-recovery.js b/src/resources/extensions/sf/bootstrap/agent-end-recovery.js index 1e2b17464..5b6a68c6c 100644 --- a/src/resources/extensions/sf/bootstrap/agent-end-recovery.js +++ b/src/resources/extensions/sf/bootstrap/agent-end-recovery.js @@ -23,6 +23,7 @@ import { import { pauseAutoForProviderError } from "../provider-error-pause.js"; import { logWarning } from "../workflow-logger.js"; import { clearDiscussionFlowState } from "./write-gate.js"; + const retryState = createRetryState(); /** * Reset the module-level retry state so a resumed auto-session starts fresh. diff --git a/src/resources/extensions/sf/bootstrap/exec-tools.js b/src/resources/extensions/sf/bootstrap/exec-tools.js index 008de025a..4dfe249ca 100644 --- a/src/resources/extensions/sf/bootstrap/exec-tools.js +++ b/src/resources/extensions/sf/bootstrap/exec-tools.js @@ -7,9 +7,11 @@ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { Type } from "@sinclair/typebox"; import { loadEffectiveSFPreferences } from "../preferences.js"; + // Headless exit code for "reload with session resume". Correlates with // EXIT_RELOAD in src/headless-events.ts — kept in sync manually. const EXIT_RELOAD = 12; + import { executeExecSearch } from "../tools/exec-search-tool.js"; import { executeSfExec } from "../tools/exec-tool.js"; import { executeResume } from "../tools/resume-tool.js"; diff --git a/src/resources/extensions/sf/bootstrap/notify-interceptor.js b/src/resources/extensions/sf/bootstrap/notify-interceptor.js index 0bb0e439d..61c120e45 100644 --- a/src/resources/extensions/sf/bootstrap/notify-interceptor.js +++ b/src/resources/extensions/sf/bootstrap/notify-interceptor.js @@ -2,8 +2,10 @@ // Wraps ctx.ui.notify() in-place to persist every notification through the // notification store. Uses a WeakSet to prevent double-wrapping and handle // UI context replacement on /reload gracefully. -import { logWarning } from "../workflow-logger.js"; + import { appendNotification } from "../notification-store.js"; +import { logWarning } from "../workflow-logger.js"; + // Track which ui context objects have been wrapped to prevent double-install. // WeakSet allows GC to collect replaced uiContext instances after /reload. const _wrappedContexts = new WeakSet(); diff --git a/src/resources/extensions/sf/bootstrap/provider-error-resume.js b/src/resources/extensions/sf/bootstrap/provider-error-resume.js index 700feb7e8..1a46e5ace 100644 --- a/src/resources/extensions/sf/bootstrap/provider-error-resume.js +++ b/src/resources/extensions/sf/bootstrap/provider-error-resume.js @@ -4,6 +4,7 @@ import { startAuto, } from "../auto.js"; import { resetTransientRetryState } from "./agent-end-recovery.js"; + const defaultDeps = { getSnapshot: () => getAutoDashboardData(), resetTransientRetryState, diff --git a/src/resources/extensions/sf/bootstrap/register-extension.js b/src/resources/extensions/sf/bootstrap/register-extension.js index d4be52c80..ff8b601eb 100644 --- a/src/resources/extensions/sf/bootstrap/register-extension.js +++ b/src/resources/extensions/sf/bootstrap/register-extension.js @@ -1,6 +1,7 @@ // SF2 — Extension registration: wires all SF tools, commands, and hooks into pi import { loadEcosystemExtensions } from "../ecosystem/loader.js"; import { registerExitCommand } from "../exit-command.js"; +import { registerSiftSearchTool } from "../tools/sift-search-tool.js"; import { logWarning } from "../workflow-logger.js"; import { registerWorktreeCommand } from "../worktree-command.js"; import { writeCrashLog } from "./crash-log.js"; @@ -12,9 +13,9 @@ import { registerJudgmentTools } from "./judgment-tools.js"; import { registerMemoryTools } from "./memory-tools.js"; import { registerProductAuditTool } from "./product-audit-tool.js"; import { registerQueryTools } from "./query-tools.js"; -import { registerSiftSearchTool } from "../tools/sift-search-tool.js"; import { registerHooks } from "./register-hooks.js"; import { registerShortcuts } from "./register-shortcuts.js"; + export { writeCrashLog } from "./crash-log.js"; export function handleRecoverableExtensionProcessError(err) { if (err.code === "EPIPE") { diff --git a/src/resources/extensions/sf/bootstrap/register-hooks.js b/src/resources/extensions/sf/bootstrap/register-hooks.js index 2cf490a7c..aa6b19cfb 100644 --- a/src/resources/extensions/sf/bootstrap/register-hooks.js +++ b/src/resources/extensions/sf/bootstrap/register-hooks.js @@ -86,6 +86,7 @@ import { shouldBlockPendingGateBash, shouldBlockQueueExecution, } from "./write-gate.js"; + // Skip the welcome screen on the very first session_start — cli.ts already // printed it before the TUI launched. Only re-print on /clear (subsequent sessions). let isFirstSession = true; diff --git a/src/resources/extensions/sf/bootstrap/system-context.js b/src/resources/extensions/sf/bootstrap/system-context.js index f5934336e..8db07f016 100644 --- a/src/resources/extensions/sf/bootstrap/system-context.js +++ b/src/resources/extensions/sf/bootstrap/system-context.js @@ -56,6 +56,7 @@ import { getActiveWorktreeName, getWorktreeOriginalCwd, } from "../worktree-command.js"; + const sfHome = process.env.SF_HOME || join(homedir(), ".sf"); const _fileReadCache = new Map(); /** diff --git a/src/resources/extensions/sf/bootstrap/tool-call-loop-guard.js b/src/resources/extensions/sf/bootstrap/tool-call-loop-guard.js index 6e025d541..0f90c533a 100644 --- a/src/resources/extensions/sf/bootstrap/tool-call-loop-guard.js +++ b/src/resources/extensions/sf/bootstrap/tool-call-loop-guard.js @@ -12,6 +12,7 @@ * and when a different tool call breaks the streak. */ import { createHash } from "node:crypto"; + const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4; /** Interactive/user-facing tools where even 1 duplicate is confusing. */ const STRICT_LOOP_TOOLS = new Set(["ask_user_questions"]); diff --git a/src/resources/extensions/sf/captures.js b/src/resources/extensions/sf/captures.js index 813c8356a..1fedc4fa3 100644 --- a/src/resources/extensions/sf/captures.js +++ b/src/resources/extensions/sf/captures.js @@ -11,6 +11,7 @@ import { randomUUID } from "node:crypto"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join, resolve, sep } from "node:path"; import { sfRoot } from "./paths.js"; + // ─── Constants ──────────────────────────────────────────────────────────────── const CAPTURES_FILENAME = "CAPTURES.md"; const VALID_CLASSIFICATIONS = [ diff --git a/src/resources/extensions/sf/claude-import.js b/src/resources/extensions/sf/claude-import.js index 001d54588..ca1e0cec1 100644 --- a/src/resources/extensions/sf/claude-import.js +++ b/src/resources/extensions/sf/claude-import.js @@ -6,6 +6,7 @@ import { SettingsManager, } from "@singularity-forge/pi-coding-agent"; import { PluginImporter } from "./plugin-importer.js"; + const SKIP_DIRS = new Set([ ".git", "node_modules", diff --git a/src/resources/extensions/sf/clean-root-preflight.js b/src/resources/extensions/sf/clean-root-preflight.js index c8122d988..b4a7cde41 100644 --- a/src/resources/extensions/sf/clean-root-preflight.js +++ b/src/resources/extensions/sf/clean-root-preflight.js @@ -14,8 +14,8 @@ */ import { execFileSync } from "node:child_process"; import { GIT_NO_PROMPT_ENV } from "./git-constants.js"; -import { logWarning } from "./workflow-logger.js"; import { nativeHasChanges } from "./native-git-bridge.js"; +import { logWarning } from "./workflow-logger.js"; /** * Check the working tree for dirty files before a milestone merge. * diff --git a/src/resources/extensions/sf/codebase-generator.js b/src/resources/extensions/sf/codebase-generator.js index 999d52f87..bbf0f8d06 100644 --- a/src/resources/extensions/sf/codebase-generator.js +++ b/src/resources/extensions/sf/codebase-generator.js @@ -12,6 +12,7 @@ import { createHash } from "node:crypto"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, extname, join } from "node:path"; import { sfRoot } from "./paths.js"; + // ─── Defaults ──────────────────────────────────────────────────────────────── const DEFAULT_EXCLUDES = [ // ── AI / tooling meta ── diff --git a/src/resources/extensions/sf/commands-add-tests.js b/src/resources/extensions/sf/commands-add-tests.js index 0432109fd..838a7f5e0 100644 --- a/src/resources/extensions/sf/commands-add-tests.js +++ b/src/resources/extensions/sf/commands-add-tests.js @@ -9,6 +9,7 @@ import { join } from "node:path"; import { resolveSliceFile, sfRoot } from "./paths.js"; import { loadPrompt } from "./prompt-loader.js"; import { deriveState } from "./state.js"; + function findLastCompletedSlice(basePath, milestoneId) { // Scan disk for slices that have a SUMMARY.md (indicating completion) const slicesDir = join(sfRoot(basePath), "milestones", milestoneId, "slices"); diff --git a/src/resources/extensions/sf/commands-backlog.js b/src/resources/extensions/sf/commands-backlog.js index acc369c95..49b2766e7 100644 --- a/src/resources/extensions/sf/commands-backlog.js +++ b/src/resources/extensions/sf/commands-backlog.js @@ -8,6 +8,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { sfRoot } from "./paths.js"; + function backlogPath(basePath) { return join(sfRoot(basePath), "WORK-QUEUE.md"); } diff --git a/src/resources/extensions/sf/commands-debug.js b/src/resources/extensions/sf/commands-debug.js index 4698d3d82..8dbbb6133 100644 --- a/src/resources/extensions/sf/commands-debug.js +++ b/src/resources/extensions/sf/commands-debug.js @@ -6,6 +6,7 @@ import { updateDebugSession, } from "./debug-session-store.js"; import { loadPrompt } from "./prompt-loader.js"; + const SUBCOMMANDS = new Set(["list", "status", "continue", "--diagnose"]); function isValidSlugCandidate(input) { try { diff --git a/src/resources/extensions/sf/commands-do.js b/src/resources/extensions/sf/commands-do.js index 8237ca345..83f18ad8d 100644 --- a/src/resources/extensions/sf/commands-do.js +++ b/src/resources/extensions/sf/commands-do.js @@ -5,6 +5,7 @@ * using keyword matching. Falls back to /sf quick for task-like input. */ import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; + const ROUTES = [ { keywords: ["progress", "status", "dashboard", "how far", "where are we"], diff --git a/src/resources/extensions/sf/commands-escalate.js b/src/resources/extensions/sf/commands-escalate.js index 23b4f053f..d5b2ba0a8 100644 --- a/src/resources/extensions/sf/commands-escalate.js +++ b/src/resources/extensions/sf/commands-escalate.js @@ -14,6 +14,7 @@ import { isDbAvailable, listEscalationArtifacts, } from "./sf-db.js"; + function usage() { return [ "Usage: /sf escalate ", diff --git a/src/resources/extensions/sf/commands-eval-review.js b/src/resources/extensions/sf/commands-eval-review.js index b2d779bf2..b66c64cd0 100644 --- a/src/resources/extensions/sf/commands-eval-review.js +++ b/src/resources/extensions/sf/commands-eval-review.js @@ -26,14 +26,7 @@ import { existsSync } from "node:fs"; import { open, readFile } from "node:fs/promises"; import { join, relative } from "node:path"; -import { - buildSliceFileName, - resolveMilestonePath, - resolveSliceFile, - resolveSlicePath, -} from "./paths.js"; import { projectRoot } from "./commands/context.js"; -import { deriveState } from "./state.js"; import { COVERAGE_WEIGHT, DIMENSION_VALUES, @@ -44,6 +37,13 @@ import { SEVERITY_VALUES, VERDICT_VALUES, } from "./eval-review-schema.js"; +import { + buildSliceFileName, + resolveMilestonePath, + resolveSliceFile, + resolveSlicePath, +} from "./paths.js"; +import { deriveState } from "./state.js"; // ─── Constants ──────────────────────────────────────────────────────────────── /** * Slice-ID format. Must match the canonical `/^S\d+$/` used elsewhere in the diff --git a/src/resources/extensions/sf/commands-extensions.js b/src/resources/extensions/sf/commands-extensions.js index f4b08edd4..2ed890617 100644 --- a/src/resources/extensions/sf/commands-extensions.js +++ b/src/resources/extensions/sf/commands-extensions.js @@ -15,6 +15,7 @@ import { } from "node:fs"; import { homedir } from "node:os"; import { dirname, join } from "node:path"; + const sfHome = process.env.SF_HOME || join(homedir(), ".sf"); // ─── Registry I/O ─────────────────────────────────────────────────────────── /** diff --git a/src/resources/extensions/sf/commands-handlers.js b/src/resources/extensions/sf/commands-handlers.js index da3aa20f8..70bb66779 100644 --- a/src/resources/extensions/sf/commands-handlers.js +++ b/src/resources/extensions/sf/commands-handlers.js @@ -13,8 +13,8 @@ import { hasPendingCaptures, loadPendingCaptures, } from "./captures.js"; -import { buildTodoTriageLLMCall, triageTodoDump } from "./commands-todo.js"; import { projectRoot } from "./commands/context.js"; +import { buildTodoTriageLLMCall, triageTodoDump } from "./commands-todo.js"; import { filterDoctorIssues, formatDoctorIssuesForPrompt, @@ -28,6 +28,7 @@ import { appendKnowledge, appendOverride } from "./files.js"; import { sfRoot } from "./paths.js"; import { loadPrompt } from "./prompt-loader.js"; import { deriveState } from "./state.js"; + const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/sf-run/latest"; const UPDATE_FETCH_TIMEOUT_MS = 5000; function resolveInstallCommand(pkg) { diff --git a/src/resources/extensions/sf/commands-harness.js b/src/resources/extensions/sf/commands-harness.js index 50b10c44a..5398b7767 100644 --- a/src/resources/extensions/sf/commands-harness.js +++ b/src/resources/extensions/sf/commands-harness.js @@ -12,6 +12,7 @@ import { ensureDbOpen } from "./bootstrap/dynamic-tools.js"; import { projectRoot } from "./commands/context.js"; import { profileRepository } from "./repo-profiler.js"; import { getLatestRepoProfile, recordRepoProfile } from "./sf-db.js"; + const HARNESS_PROMOTION_REPO_DIR = "docs/exec-plans/active"; /** * Format a repo profile summary for user notification. diff --git a/src/resources/extensions/sf/commands-logs.js b/src/resources/extensions/sf/commands-logs.js index 6588f593c..54a3f611c 100644 --- a/src/resources/extensions/sf/commands-logs.js +++ b/src/resources/extensions/sf/commands-logs.js @@ -18,8 +18,9 @@ import { } from "node:fs"; import { join } from "node:path"; import { loadJsonFileOrNull } from "./json-persistence.js"; -import { readSessionLockData } from "./session-lock.js"; import { sfRoot } from "./paths.js"; +import { readSessionLockData } from "./session-lock.js"; + // ─── Helpers ──────────────────────────────────────────────────────────────── /** * Get the activity logs directory path. diff --git a/src/resources/extensions/sf/commands-memory.js b/src/resources/extensions/sf/commands-memory.js index eee7f5b60..11ee3f402 100644 --- a/src/resources/extensions/sf/commands-memory.js +++ b/src/resources/extensions/sf/commands-memory.js @@ -20,6 +20,7 @@ import { ingestUrl, summarizeIngest, } from "./memory-ingest.js"; +import { createMemoryRelation, listRelationsFor } from "./memory-relations.js"; import { getMemorySource, listMemorySources } from "./memory-source-store.js"; import { createMemory, @@ -31,7 +32,7 @@ import { supersedeMemory, } from "./memory-store.js"; import { _getAdapter, isDbAvailable } from "./sf-db.js"; -import { createMemoryRelation, listRelationsFor } from "./memory-relations.js"; + function parseArgs(raw) { const tokens = splitArgs(raw); const sub = (tokens.shift() ?? "list").toLowerCase(); diff --git a/src/resources/extensions/sf/commands-plan.js b/src/resources/extensions/sf/commands-plan.js index 261fb6d64..0e56da453 100644 --- a/src/resources/extensions/sf/commands-plan.js +++ b/src/resources/extensions/sf/commands-plan.js @@ -7,14 +7,15 @@ * * Consumer: SF ops handler (commands/handlers/ops.js) via `/sf plan `. */ + +import { spawnSync } from "node:child_process"; import { createHash } from "node:crypto"; import { copyFileSync, existsSync, - lstatSync, mkdirSync, - readFileSync, readdirSync, + readFileSync, statSync, } from "node:fs"; import { homedir } from "node:os"; @@ -27,7 +28,6 @@ import { relative, resolve, } from "node:path"; -import { spawnSync } from "node:child_process"; import { projectRoot } from "./commands/context.js"; import { repoIdentity } from "./repo-identity.js"; @@ -63,7 +63,7 @@ function sha256File(path) { return createHash("sha256").update(data).digest("hex"); } -function sha256String(data) { +function _sha256String(data) { return createHash("sha256").update(data).digest("hex"); } @@ -150,7 +150,7 @@ function nextAdrNumber() { return max + 1; } -function formatDiffLine(line) { +function _formatDiffLine(line) { if (line.startsWith("+")) return line; // keep color codes if present if (line.startsWith("-")) return line; if (line.startsWith("@@")) return line; @@ -270,7 +270,7 @@ export async function handlePlanList(_args, ctx) { } // Sort by mtime desc - rows.sort((a, b) => { + rows.sort((_a, _b) => { // We don't have mtime in the row object, re-stat or use original order // For simplicity, keep walk order (not guaranteed sorted) return 0; @@ -348,7 +348,7 @@ export async function handlePlanDiff(args, ctx) { // Strip the "diff --git" prefix lines that include absolute paths const lines = output.split("\n"); const filtered = []; - let skipNext = false; + const _skipNext = false; for (const line of lines) { if (line.startsWith("diff --git")) { filtered.push(`--- a/${relative(process.cwd(), sourcePath)}`); diff --git a/src/resources/extensions/sf/commands-pr-branch.js b/src/resources/extensions/sf/commands-pr-branch.js index 02c389531..08f32aaf7 100644 --- a/src/resources/extensions/sf/commands-pr-branch.js +++ b/src/resources/extensions/sf/commands-pr-branch.js @@ -11,6 +11,7 @@ import { nativeDetectMainBranch, nativeGetCurrentBranch, } from "./native-git-bridge.js"; + const EXCLUDED_PATHS = [".sf", ".planning", "PLAN.md"]; function git(basePath, args) { return execFileSync("git", args, { cwd: basePath, encoding: "utf-8" }).trim(); diff --git a/src/resources/extensions/sf/commands-prefs-wizard.js b/src/resources/extensions/sf/commands-prefs-wizard.js index fd76b1f21..183831ad9 100644 --- a/src/resources/extensions/sf/commands-prefs-wizard.js +++ b/src/resources/extensions/sf/commands-prefs-wizard.js @@ -23,6 +23,7 @@ import { loadProjectSFPreferences, resolveAllSkillReferences, } from "./preferences.js"; + /** Extract body content after frontmatter closing delimiter, or null if none. */ function extractBodyAfterFrontmatter(content) { const closingIdx = content.indexOf("\n---", content.indexOf("---")); diff --git a/src/resources/extensions/sf/commands-rate.js b/src/resources/extensions/sf/commands-rate.js index 8742118e0..b511fcdb8 100644 --- a/src/resources/extensions/sf/commands-rate.js +++ b/src/resources/extensions/sf/commands-rate.js @@ -4,6 +4,7 @@ */ import { loadLedgerFromDisk } from "./metrics.js"; import { initRoutingHistory, recordFeedback } from "./routing-history.js"; + const VALID_RATINGS = new Set(["over", "under", "ok"]); export async function handleRate(args, ctx, basePath) { const rating = args.trim().toLowerCase(); diff --git a/src/resources/extensions/sf/commands-schedule.js b/src/resources/extensions/sf/commands-schedule.js index cbc8c78c3..9e280048e 100644 --- a/src/resources/extensions/sf/commands-schedule.js +++ b/src/resources/extensions/sf/commands-schedule.js @@ -5,10 +5,11 @@ * Entries stored as append-only JSONL in .sf/schedule.jsonl (project) * or ~/.sf/schedule.jsonl (global). */ -import { createScheduleStore } from "./schedule/schedule-store.js"; -import { generateULID } from "./schedule/schedule-ulid.js"; -import { isValidKind } from "./schedule/schedule-types.js"; + import { execSync } from "node:child_process"; +import { createScheduleStore } from "./schedule/schedule-store.js"; +import { isValidKind } from "./schedule/schedule-types.js"; +import { generateULID } from "./schedule/schedule-ulid.js"; // ─── Duration parser ──────────────────────────────────────────────────────── @@ -101,7 +102,7 @@ async function addItem(args, ctx) { let kind = "reminder"; let scope = "project"; let dueAt = null; - let titleParts = []; + const titleParts = []; for (let i = 0; i < parts.length; i++) { const p = parts[i]; @@ -201,7 +202,6 @@ async function listItems(args, ctx) { } if (p === "--json" || p === "-j") { json = true; - continue; } } diff --git a/src/resources/extensions/sf/commands-session-report.js b/src/resources/extensions/sf/commands-session-report.js index 5d6b88775..89d2bfcc4 100644 --- a/src/resources/extensions/sf/commands-session-report.js +++ b/src/resources/extensions/sf/commands-session-report.js @@ -16,6 +16,7 @@ import { loadLedgerFromDisk, } from "./metrics.js"; import { sfRoot } from "./paths.js"; + function formatSessionReport(units) { const totals = getProjectTotals(units); const byModel = aggregateByModel(units); diff --git a/src/resources/extensions/sf/commands-ship.js b/src/resources/extensions/sf/commands-ship.js index d0f5a5b77..7dc6ae600 100644 --- a/src/resources/extensions/sf/commands-ship.js +++ b/src/resources/extensions/sf/commands-ship.js @@ -25,6 +25,7 @@ import { resolveSlicePath, } from "./paths.js"; import { deriveState } from "./state.js"; + function git(basePath, args) { return execFileSync("git", args, { cwd: basePath, encoding: "utf-8" }).trim(); } diff --git a/src/resources/extensions/sf/commands-todo.js b/src/resources/extensions/sf/commands-todo.js index 7591a1433..532f1abaf 100644 --- a/src/resources/extensions/sf/commands-todo.js +++ b/src/resources/extensions/sf/commands-todo.js @@ -7,6 +7,8 @@ * * Consumer: `/sf todo triage` command. */ + +import { createHash } from "node:crypto"; import { existsSync, mkdirSync, @@ -14,11 +16,11 @@ import { rmSync, writeFileSync, } from "node:fs"; -import { createHash } from "node:crypto"; import { dirname, join } from "node:path"; import { projectRoot } from "./commands/context.js"; import { sfRoot } from "./paths.js"; -const EMPTY_TODO = "# TODO\n\nDump anything here.\n"; + +const _EMPTY_TODO = "# TODO\n\nDump anything here.\n"; const MAX_DUMP_CHARS = 48_000; const PREFERRED_TRIAGE_MODEL_PATTERNS = [ /minimax.*m2\.7.*highspeed/i, diff --git a/src/resources/extensions/sf/commands-workflow-templates.js b/src/resources/extensions/sf/commands-workflow-templates.js index 059b09019..9ec88b778 100644 --- a/src/resources/extensions/sf/commands-workflow-templates.js +++ b/src/resources/extensions/sf/commands-workflow-templates.js @@ -35,6 +35,7 @@ import { loadWorkflowTemplate, resolveByName, } from "./workflow-templates.js"; + // ─── Helpers ───────────────────────────────────────────────────────────────── /** * Generate a URL-friendly slug from text. diff --git a/src/resources/extensions/sf/commands-worktree.js b/src/resources/extensions/sf/commands-worktree.js index 4be017674..447c1014c 100644 --- a/src/resources/extensions/sf/commands-worktree.js +++ b/src/resources/extensions/sf/commands-worktree.js @@ -6,22 +6,23 @@ // to the CLI surface. import { existsSync } from "node:fs"; import { projectRoot } from "./commands/context.js"; +import { SF_GIT_ERROR, SFError } from "./errors.js"; +import { inferCommitType } from "./git-service.js"; +import { + nativeCommitCountBetween, + nativeDetectMainBranch, + nativeHasChanges, +} from "./native-git-bridge.js"; +import { autoCommitCurrentBranch } from "./worktree.js"; import { - listWorktrees, - removeWorktree, - mergeWorktreeToMain, diffWorktreeAll, diffWorktreeNumstat, + listWorktrees, + mergeWorktreeToMain, + removeWorktree, worktreeBranchName, } from "./worktree-manager.js"; -import { - nativeHasChanges, - nativeDetectMainBranch, - nativeCommitCountBetween, -} from "./native-git-bridge.js"; -import { inferCommitType } from "./git-service.js"; -import { autoCommitCurrentBranch } from "./worktree.js"; -import { SFError, SF_GIT_ERROR } from "./errors.js"; + // ─── Status helper ───────────────────────────────────────────────────────── function getStatus(basePath, name, wtPath) { const diff = diffWorktreeAll(basePath, name); diff --git a/src/resources/extensions/sf/commands.js b/src/resources/extensions/sf/commands.js index 3c5dd01a1..d08cdb5da 100644 --- a/src/resources/extensions/sf/commands.js +++ b/src/resources/extensions/sf/commands.js @@ -1,4 +1,5 @@ import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; + export { registerSFCommand } from "./commands/index.js"; export async function handleSFCommand(...args) { const { handleSFCommand: dispatch } = await importExtensionModule( diff --git a/src/resources/extensions/sf/commands/handlers/auto.js b/src/resources/extensions/sf/commands/handlers/auto.js index c2de8ec0b..9ce8e72ba 100644 --- a/src/resources/extensions/sf/commands/handlers/auto.js +++ b/src/resources/extensions/sf/commands/handlers/auto.js @@ -13,6 +13,7 @@ import { handleRate } from "../../commands-rate.js"; import { enableDebug } from "../../debug-logger.js"; import { findMilestoneIds } from "../../milestone-id-utils.js"; import { guardRemoteSession, projectRoot } from "../context.js"; + /** * Parse --yolo flag and optional file path from the autonomous command string. * Supports: `/sf autonomous --yolo path/to/file.md`, `/sf auto --yolo path/to/file.md`, diff --git a/src/resources/extensions/sf/commands/handlers/notifications-handler.js b/src/resources/extensions/sf/commands/handlers/notifications-handler.js index e9ad851ef..4fd1d0e35 100644 --- a/src/resources/extensions/sf/commands/handlers/notifications-handler.js +++ b/src/resources/extensions/sf/commands/handlers/notifications-handler.js @@ -8,6 +8,7 @@ import { suppressPersistence, unsuppressPersistence, } from "../../notification-store.js"; + const MAX_INLINE_ENTRIES = 40; function severityIcon(severity) { switch (severity) { diff --git a/src/resources/extensions/sf/commands/handlers/ops.js b/src/resources/extensions/sf/commands/handlers/ops.js index 1632cd0ff..ee6d9619a 100644 --- a/src/resources/extensions/sf/commands/handlers/ops.js +++ b/src/resources/extensions/sf/commands/handlers/ops.js @@ -1,6 +1,8 @@ import { handleRemote } from "../../../remote-questions/mod.js"; import { dispatchDirectPhase } from "../../auto-direct-dispatch.js"; import { handleConfig } from "../../commands-config.js"; +import { handleDebug } from "../../commands-debug.js"; +import { handleEscalate } from "../../commands-escalate.js"; import { handleCapture, handleDoctor, @@ -11,8 +13,6 @@ import { handleTriage, handleUpdate, } from "../../commands-handlers.js"; -import { handleDebug } from "../../commands-debug.js"; -import { handleEscalate } from "../../commands-escalate.js"; import { handleInspect } from "../../commands-inspect.js"; import { handleLogs } from "../../commands-logs.js"; import { diff --git a/src/resources/extensions/sf/commands/handlers/parallel.js b/src/resources/extensions/sf/commands/handlers/parallel.js index e625fe43b..1d1ff6953 100644 --- a/src/resources/extensions/sf/commands/handlers/parallel.js +++ b/src/resources/extensions/sf/commands/handlers/parallel.js @@ -20,6 +20,7 @@ import { resolveParallelConfig, } from "../../preferences.js"; import { projectRoot } from "../context.js"; + function emitParallelMessage(pi, content) { pi.sendMessage({ customType: "sf-parallel", content, display: true }); } diff --git a/src/resources/extensions/sf/commands/handlers/workflow.js b/src/resources/extensions/sf/commands/handlers/workflow.js index 0e3a572d8..07eac8c6d 100644 --- a/src/resources/extensions/sf/commands/handlers/workflow.js +++ b/src/resources/extensions/sf/commands/handlers/workflow.js @@ -32,6 +32,7 @@ import { handleQuick } from "../../quick.js"; import { createRun, listRuns } from "../../run-manager.js"; import { deriveState } from "../../state.js"; import { projectRoot } from "../context.js"; + // ─── Custom Workflow Subcommands ───────────────────────────────────────── const WORKFLOW_USAGE = [ "Usage: /sf workflow ", diff --git a/src/resources/extensions/sf/complexity-classifier.js b/src/resources/extensions/sf/complexity-classifier.js index 3208636df..0869cfba1 100644 --- a/src/resources/extensions/sf/complexity-classifier.js +++ b/src/resources/extensions/sf/complexity-classifier.js @@ -6,6 +6,7 @@ import { join } from "node:path"; import { sfRoot } from "./paths.js"; import { getAdaptiveTierAdjustment } from "./routing-history.js"; import { parseUnitId } from "./unit-id.js"; + // ─── Unit Type → Default Tier Mapping ──────────────────────────────────────── const UNIT_TYPE_TIERS = { // Tier 1 — Light: mechanical hooks and tiny maintenance only. diff --git a/src/resources/extensions/sf/component-loader.js b/src/resources/extensions/sf/component-loader.js index 14b2f228d..401e3e6bf 100644 --- a/src/resources/extensions/sf/component-loader.js +++ b/src/resources/extensions/sf/component-loader.js @@ -11,13 +11,14 @@ */ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { basename, dirname, join } from "node:path"; -import { parse as parseYaml } from "yaml"; import { parseFrontmatter } from "@singularity-forge/pi-coding-agent"; +import { parse as parseYaml } from "yaml"; import { - validateComponentName, - validateComponentDescription, computeComponentId, + validateComponentDescription, + validateComponentName, } from "./component-types.js"; + const SUPPORTED_COMPONENT_KINDS = ["skill", "agent"]; const SUPPORTED_API_VERSIONS = ["sf/v1"]; // ============================================================================ diff --git a/src/resources/extensions/sf/config-overlay.js b/src/resources/extensions/sf/config-overlay.js index 719b48b6a..b2973f037 100644 --- a/src/resources/extensions/sf/config-overlay.js +++ b/src/resources/extensions/sf/config-overlay.js @@ -18,6 +18,7 @@ import { resolveEffectiveProfile, resolveModelWithFallbacksForUnit, } from "./preferences.js"; + function collectConfigSections() { const sections = []; const globalPrefs = loadGlobalSFPreferences(); diff --git a/src/resources/extensions/sf/context-budget.js b/src/resources/extensions/sf/context-budget.js index 16e6dda21..c98c6eda5 100644 --- a/src/resources/extensions/sf/context-budget.js +++ b/src/resources/extensions/sf/context-budget.js @@ -8,6 +8,7 @@ * @see D001 (module location), D002 (200K fallback), D003 (section-boundary truncation) */ import { getCharsPerToken } from "./token-counter.js"; + // ─── Budget ratio constants ────────────────────────────────────────────────── // Percentages of total context window allocated to each budget category. // These are applied after tokens→chars conversion. diff --git a/src/resources/extensions/sf/context-injector.js b/src/resources/extensions/sf/context-injector.js index d09606111..21cf6ff1c 100644 --- a/src/resources/extensions/sf/context-injector.js +++ b/src/resources/extensions/sf/context-injector.js @@ -15,6 +15,7 @@ import { existsSync, readFileSync } from "node:fs"; import { resolve, sep } from "node:path"; import { readFrozenDefinition } from "./definition-io.js"; + /** Maximum characters per artifact to prevent context window blowout. */ const MAX_CONTEXT_CHARS = 10_000; /** diff --git a/src/resources/extensions/sf/crash-recovery.js b/src/resources/extensions/sf/crash-recovery.js index b81be8b29..d98b4b0b8 100644 --- a/src/resources/extensions/sf/crash-recovery.js +++ b/src/resources/extensions/sf/crash-recovery.js @@ -15,6 +15,7 @@ import { atomicWriteSync } from "./atomic-write.js"; import { emitJournalEvent, queryJournal } from "./journal.js"; import { sfRoot } from "./paths.js"; import { effectiveLockFile } from "./session-lock.js"; + function lockPath(basePath) { return join(sfRoot(basePath), effectiveLockFile()); } diff --git a/src/resources/extensions/sf/custom-verification.js b/src/resources/extensions/sf/custom-verification.js index 27cc7a39b..9b9f8ea4e 100644 --- a/src/resources/extensions/sf/custom-verification.js +++ b/src/resources/extensions/sf/custom-verification.js @@ -133,7 +133,7 @@ function handleShellCommand(runDir, verify) { // Covers: command substitution $(…), backticks `…`, chained dangerous commands, // logical operators (&& ||), pipe (|), and background operator (&). const dangerousPatterns = - /\$\(|`|;\s*(rm|curl|wget|nc|bash|sh|eval)\b|&&|\|\||(? 5_000) { diff --git a/src/resources/extensions/sf/dashboard-overlay.js b/src/resources/extensions/sf/dashboard-overlay.js index b419cd2bd..59642c9ea 100644 --- a/src/resources/extensions/sf/dashboard-overlay.js +++ b/src/resources/extensions/sf/dashboard-overlay.js @@ -47,6 +47,7 @@ import { getMilestoneSlices, getSliceTasks, isDbAvailable } from "./sf-db.js"; import { formattedShortcutPair } from "./shortcut-defs.js"; import { deriveState } from "./state.js"; import { getActiveWorktreeName } from "./worktree-command.js"; + function unitLabel(type) { switch (type) { case "discuss-milestone": diff --git a/src/resources/extensions/sf/debug-logger.js b/src/resources/extensions/sf/debug-logger.js index 73bd67496..297c8b8b9 100644 --- a/src/resources/extensions/sf/debug-logger.js +++ b/src/resources/extensions/sf/debug-logger.js @@ -4,6 +4,7 @@ import { appendFileSync, mkdirSync, readdirSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { sfRoot } from "./paths.js"; + // ─── State ──────────────────────────────────────────────────────────────────── let _enabled = false; let _logPath = null; diff --git a/src/resources/extensions/sf/debug-session-store.js b/src/resources/extensions/sf/debug-session-store.js index ab06574e9..c95a99061 100644 --- a/src/resources/extensions/sf/debug-session-store.js +++ b/src/resources/extensions/sf/debug-session-store.js @@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { atomicWriteSync } from "./atomic-write.js"; import { sfRoot } from "./paths.js"; + const DEFAULT_PHASE = "queued"; const DEFAULT_STATUS = "active"; const SESSION_FILE_SUFFIX = ".json"; diff --git a/src/resources/extensions/sf/deep-project-setup-policy.js b/src/resources/extensions/sf/deep-project-setup-policy.js index be1cc4add..97b7cf0e0 100644 --- a/src/resources/extensions/sf/deep-project-setup-policy.js +++ b/src/resources/extensions/sf/deep-project-setup-policy.js @@ -1,9 +1,10 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { clearParseCache } from "./files.js"; -import { sfRoot, clearPathCache } from "./paths.js"; +import { clearPathCache, sfRoot } from "./paths.js"; import { getProjectResearchStatus } from "./project-research-policy.js"; import { validateArtifact } from "./schemas/validate.js"; + const EXPLICIT_RESEARCH_SOURCES = new Set(["research-decision", "user"]); function clearCaches() { clearPathCache(); diff --git a/src/resources/extensions/sf/definition-loader.js b/src/resources/extensions/sf/definition-loader.js index 5fa06c362..e9d9724f5 100644 --- a/src/resources/extensions/sf/definition-loader.js +++ b/src/resources/extensions/sf/definition-loader.js @@ -381,7 +381,6 @@ export function substituteParams(definition, overrides) { for (const step of substitutedSteps) { let m; const re = new RegExp(PARAM_PATTERN.source, "g"); - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((m = re.exec(step.prompt)) !== null) { unresolved.add(m[1]); } diff --git a/src/resources/extensions/sf/detection.js b/src/resources/extensions/sf/detection.js index 35ca03661..4ae64e598 100644 --- a/src/resources/extensions/sf/detection.js +++ b/src/resources/extensions/sf/detection.js @@ -17,6 +17,7 @@ import { import { homedir } from "node:os"; import { join } from "node:path"; import { sfRoot } from "./paths.js"; + const sfHome = process.env.SF_HOME || join(homedir(), ".sf"); // ─── Project File Markers ─────────────────────────────────────────────────────── export const PROJECT_FILES = [ @@ -547,7 +548,6 @@ function detectXcodePlatforms(basePath) { const sdkRe = /SDKROOT\s*=\s*"?([a-z]+)"?\s*;/gi; let m; let foundExplicit = false; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((m = sdkRe.exec(content)) !== null) { const val = m[1].toLowerCase(); if (val === "auto") continue; // handled below via SUPPORTED_PLATFORMS @@ -560,7 +560,6 @@ function detectXcodePlatforms(basePath) { // Xcode 15+ defaults SDKROOT to "auto"; fall back to SUPPORTED_PLATFORMS if (!foundExplicit) { let sp; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((sp = SUPPORTED_PLATFORMS_RE.exec(content)) !== null) { for (const tok of sp[1].split(/\s+/)) { const canonical = SDKROOT_MAP[tok.toLowerCase()]; @@ -1011,7 +1010,6 @@ function containsSpringBootMarker( `alias\\(\\s*${accessor}\\.plugins\\.([a-z0-9_.-]+)\\s*\\)`, "gi", ); - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = aliasRe.exec(normalized)) !== null) { usedPluginAliases.add(normalizePluginAlias(match[1])); } @@ -1019,7 +1017,6 @@ function containsSpringBootMarker( `\\b${accessor}\\.((?!plugins\\b)[a-z0-9_.-]+)`, "gi", ); - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = libraryAliasRe.exec(normalized)) !== null) { usedLibraryAliases.add(normalizePluginAlias(match[1])); } @@ -1044,18 +1041,15 @@ function containsSpringBootMarker( const aliasRe = /^\s*([A-Za-z0-9_.-]+)\s*=\s*\{[^\n}]*\bid\s*=\s*["']org\.springframework\.boot["'][^\n}]*\}/gm; let match; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = aliasRe.exec(content)) !== null) { springBootAliases.add(normalizePluginAlias(match[1])); } const libraryRe = /^\s*([A-Za-z0-9_.-]+)\s*=\s*\{[^\n}]*\b(module\s*=\s*["']org\.springframework\.boot:[^"']+["']|group\s*=\s*["']org\.springframework\.boot["'][^\n}]*\bname\s*=\s*["']spring-boot[^"']*["'])[^\n}]*\}/gm; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = libraryRe.exec(content)) !== null) { springBootLibraries.add(normalizePluginAlias(match[1])); } const bundleRe = /^\s*([A-Za-z0-9_.-]+)\s*=\s*\[([\s\S]*?)\]/gm; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = bundleRe.exec(content)) !== null) { pendingSpringBootBundles.push({ bundleAlias: normalizePluginAlias(`bundles.${match[1]}`), @@ -1145,7 +1139,6 @@ function containsFastapiInPyproject(content) { } const quotedSpecRe = /["']([^"']+)["']/g; let match; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = quotedSpecRe.exec(line)) !== null) { if (extractRequirementName(match[1]) === "fastapi") { return true; @@ -1257,7 +1250,6 @@ function resolveVersionCatalogAccessors( const createRe = /create\(\s*["']([A-Za-z0-9_]+)["']\s*\)\s*\{[\s\S]*?([A-Za-z0-9_.-]+\.versions\.toml)["']?\s*\)\s*\)/g; let match; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = createRe.exec(content)) !== null) { const accessor = match[1].toLowerCase(); const catalogBasename = match[2].replaceAll("\\", "/").split("/").pop(); diff --git a/src/resources/extensions/sf/diff-context.js b/src/resources/extensions/sf/diff-context.js index b890e33a6..ad6357404 100644 --- a/src/resources/extensions/sf/diff-context.js +++ b/src/resources/extensions/sf/diff-context.js @@ -8,6 +8,7 @@ import { execFile, execFileSync } from "node:child_process"; import { resolve } from "node:path"; import { SF_PARSE_ERROR, SFError } from "./errors.js"; + // ─── Helpers ──────────────────────────────────────────────────────────────── const EXEC_OPTS = { encoding: "utf-8", diff --git a/src/resources/extensions/sf/dispatch-guard.js b/src/resources/extensions/sf/dispatch-guard.js index a992bc9b9..7cdeaf239 100644 --- a/src/resources/extensions/sf/dispatch-guard.js +++ b/src/resources/extensions/sf/dispatch-guard.js @@ -6,6 +6,7 @@ import { resolveMilestoneFile } from "./paths.js"; import { getMilestoneSlices, isDbAvailable } from "./sf-db.js"; import { isClosedStatus } from "./status-guards.js"; import { parseUnitId } from "./unit-id.js"; + const SLICE_DISPATCH_TYPES = new Set([ "research-slice", "plan-slice", diff --git a/src/resources/extensions/sf/doc-checker.js b/src/resources/extensions/sf/doc-checker.js index 01b08f518..edba82108 100644 --- a/src/resources/extensions/sf/doc-checker.js +++ b/src/resources/extensions/sf/doc-checker.js @@ -10,6 +10,7 @@ */ import { existsSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; + /** Files created by ensureAgenticDocsScaffold that should contain real content. */ const SCAFFOLD_FILES = [ // Root routing diff --git a/src/resources/extensions/sf/doctor-providers.js b/src/resources/extensions/sf/doctor-providers.js index a8b271799..12a2a1490 100644 --- a/src/resources/extensions/sf/doctor-providers.js +++ b/src/resources/extensions/sf/doctor-providers.js @@ -15,6 +15,7 @@ import { getEnvApiKey } from "@singularity-forge/pi-ai"; import { AuthStorage } from "@singularity-forge/pi-coding-agent"; import { getAuthPath, PROVIDER_REGISTRY } from "./key-manager.js"; import { loadEffectiveSFPreferences } from "./preferences.js"; + // ── Model → Provider ID mapping ─────────────────────────────────────────────── /** * Infer the auth provider ID from a model string. @@ -341,7 +342,6 @@ export function formatProviderReport(results) { const lines = []; const groups = {}; for (const r of results) { - // biome-ignore lint/suspicious/noAssignInExpressions: intentional group-by idiom (groups[r.category] ??= []).push(r); } const categoryLabels = { diff --git a/src/resources/extensions/sf/doctor-runtime-checks.js b/src/resources/extensions/sf/doctor-runtime-checks.js index 391c2abfb..e7acd370f 100644 --- a/src/resources/extensions/sf/doctor-runtime-checks.js +++ b/src/resources/extensions/sf/doctor-runtime-checks.js @@ -13,12 +13,11 @@ import { isLockProcessAlive, readCrashLock, } from "./crash-recovery.js"; -import { getAuditEmitFailureCount } from "./workflow-logger.js"; import { saveFile } from "./files.js"; import { - SF_RUNTIME_PATTERNS, ensureGitignore, isSfGitignored, + SF_RUNTIME_PATTERNS, } from "./gitignore.js"; import { recoverFailedMigration } from "./migrate-external.js"; import { @@ -35,6 +34,7 @@ import { removeSessionStatus, } from "./session-status-io.js"; import { deriveState } from "./state.js"; +import { getAuditEmitFailureCount } from "./workflow-logger.js"; export async function checkRuntimeHealth( basePath, issues, diff --git a/src/resources/extensions/sf/ecosystem/loader.js b/src/resources/extensions/sf/ecosystem/loader.js index d023ade86..2e891af1c 100644 --- a/src/resources/extensions/sf/ecosystem/loader.js +++ b/src/resources/extensions/sf/ecosystem/loader.js @@ -8,6 +8,7 @@ import { pathToFileURL } from "node:url"; import { getAgentDir } from "@singularity-forge/pi-coding-agent"; import { logWarning } from "../workflow-logger.js"; import { createSFExtensionAPI } from "./sf-extension-api.js"; + // ─── Trust check (inlined; pi does not export isProjectTrusted from its // package root, and constraint forbids modifying packages/pi-coding-agent/) ─ const TRUSTED_PROJECTS_FILE = "trusted-projects.json"; diff --git a/src/resources/extensions/sf/ecosystem/sf-extension-api.js b/src/resources/extensions/sf/ecosystem/sf-extension-api.js index 08eb3ce49..c4176a181 100644 --- a/src/resources/extensions/sf/ecosystem/sf-extension-api.js +++ b/src/resources/extensions/sf/ecosystem/sf-extension-api.js @@ -10,6 +10,7 @@ // `.pi/extensions/` behavior. Only re-launching the CLI rebinds the snapshot. import { getCurrentPhase, isSFActive } from "../../shared/sf-phase-state.js"; import { logWarning } from "../workflow-logger.js"; + // ─── Auto-loop phase mapping ──────────────────────────────────────────── const AUTO_LOOP_PHASE_MAP = { "plan-milestone": "planning", diff --git a/src/resources/extensions/sf/exec-history.js b/src/resources/extensions/sf/exec-history.js index 383076bf4..8caf6db2d 100644 --- a/src/resources/extensions/sf/exec-history.js +++ b/src/resources/extensions/sf/exec-history.js @@ -12,6 +12,7 @@ import { statSync, } from "node:fs"; import { join, resolve } from "node:path"; + function listMetaFiles(baseDir) { const dir = resolve(baseDir, ".sf", "exec"); try { diff --git a/src/resources/extensions/sf/exec-sandbox.js b/src/resources/extensions/sf/exec-sandbox.js index 7816d46d9..4c0df868c 100644 --- a/src/resources/extensions/sf/exec-sandbox.js +++ b/src/resources/extensions/sf/exec-sandbox.js @@ -8,6 +8,7 @@ import { spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; + const ALWAYS_FORWARD_ENV = ["PATH", "HOME"]; export const EXEC_DEFAULTS = { clampTimeoutMs: 600_000, diff --git a/src/resources/extensions/sf/execution-instruction-guard.js b/src/resources/extensions/sf/execution-instruction-guard.js index 8322b089e..229dff034 100644 --- a/src/resources/extensions/sf/execution-instruction-guard.js +++ b/src/resources/extensions/sf/execution-instruction-guard.js @@ -7,6 +7,7 @@ import { appendEvent } from "./workflow-events.js"; import { logWarning } from "./workflow-logger.js"; import { writeManifest } from "./workflow-manifest.js"; import { renderAllProjections } from "./workflow-projections.js"; + const REPO_INSTRUCTION_FILES = [ "AGENTS.md", "CLAUDE.md", diff --git a/src/resources/extensions/sf/file-lock.js b/src/resources/extensions/sf/file-lock.js index 970c4a6ef..fed902726 100644 --- a/src/resources/extensions/sf/file-lock.js +++ b/src/resources/extensions/sf/file-lock.js @@ -1,6 +1,7 @@ import { existsSync } from "node:fs"; import { createRequire } from "node:module"; import { join } from "node:path"; + // The file-lock module is loaded in both CJS builds and ESM sources. Under ESM // the bare `require` identifier is not defined, so we always go through // createRequire. We try the current module's resolution context first and fall diff --git a/src/resources/extensions/sf/files.js b/src/resources/extensions/sf/files.js index 0ffa2a197..3030aa6fe 100644 --- a/src/resources/extensions/sf/files.js +++ b/src/resources/extensions/sf/files.js @@ -21,8 +21,10 @@ import { resolveMilestoneFile, resolveSfRootFile, } from "./paths.js"; + // Re-export for downstream consumers export { parseFrontmatterMap, splitFrontmatter }; + // ─── Parse Cache ────────────────────────────────────────────────────────── /** Fast composite key: length + first/mid/last 100 chars. The middle sample * prevents collisions when only a few characters change in the interior of @@ -612,7 +614,6 @@ export function countMustHavesMentionedInSummary(mustHaves, summaryContent) { const codeTokens = []; const codeRegex = /`([^`]+)`/g; let match; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = codeRegex.exec(mh.text)) !== null) { codeTokens.push(match[1]); } @@ -656,7 +657,6 @@ export function parseTaskPlanIO(content) { if (!trimmed || trimmed.startsWith("#")) continue; let match; backtickPathRegex.lastIndex = 0; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = backtickPathRegex.exec(trimmed)) !== null) { const candidate = normalizePlannedFileReference(match[1]); // Filter out things that look like code tokens rather than file paths @@ -818,7 +818,6 @@ export async function appendKnowledge(basePath, type, entry, scope) { const idPattern = new RegExp(`^\\| ${prefix}(\\d+)`, "gm"); let maxId = 0; let match; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = idPattern.exec(existing)) !== null) { const num = parseInt(match[1], 10); if (num > maxId) maxId = num; diff --git a/src/resources/extensions/sf/forensics.js b/src/resources/extensions/sf/forensics.js index 87d5f33e8..803307721 100644 --- a/src/resources/extensions/sf/forensics.js +++ b/src/resources/extensions/sf/forensics.js @@ -62,6 +62,7 @@ import { percentile, summarizeWorktreeTelemetry, } from "./worktree-telemetry.js"; + // ─── Duplicate Detection ────────────────────────────────────────────────────── const DEDUP_PROMPT_SECTION = ` ## Pre-Investigation: Duplicate Check (REQUIRED) diff --git a/src/resources/extensions/sf/gap-audit.js b/src/resources/extensions/sf/gap-audit.js index e191a33a0..ab2c2efd8 100644 --- a/src/resources/extensions/sf/gap-audit.js +++ b/src/resources/extensions/sf/gap-audit.js @@ -17,6 +17,7 @@ import { } from "node:fs"; import { join, relative } from "node:path"; import { recordSelfFeedback } from "./self-feedback.js"; + const EXTENSION_SRC = import.meta.dirname; const PROMPTS_DIR = join(EXTENSION_SRC, "prompts"); const COMMANDS_DIR = join(EXTENSION_SRC, "commands"); @@ -185,7 +186,7 @@ function findOrphanCommands() { } // Detect grouped/aliased match: includes("cmd") in command arrays or switch cases // Look for the command in switch/case patterns: case "cmd": or case 'cmd': - if (new RegExp(`case\s+["']${cmd}["']`).test(content)) { + if (new RegExp(`cases+["']${cmd}["']`).test(content)) { dispatched = true; break; } diff --git a/src/resources/extensions/sf/git-self-heal.js b/src/resources/extensions/sf/git-self-heal.js index 4531592c9..5182767a0 100644 --- a/src/resources/extensions/sf/git-self-heal.js +++ b/src/resources/extensions/sf/git-self-heal.js @@ -17,6 +17,7 @@ import { nativeRebaseAbort, nativeResetHard, } from "./native-git-bridge.js"; + // Re-export for consumers export { MergeConflictError }; /** diff --git a/src/resources/extensions/sf/gitignore.js b/src/resources/extensions/sf/gitignore.js index da15c86b7..82887b6ad 100644 --- a/src/resources/extensions/sf/gitignore.js +++ b/src/resources/extensions/sf/gitignore.js @@ -15,7 +15,9 @@ import { SF_RUNTIME_PATTERNS } from "./git-runtime-patterns.js"; import { nativeLsFiles, nativeRmCached } from "./native-git-bridge.js"; import { sfRoot } from "./paths.js"; import { bodyHash as preferencesBodyHash } from "./scaffold-versioning.js"; + export { SF_RUNTIME_PATTERNS } from "./git-runtime-patterns.js"; + /** * SF runtime exclusion patterns for repos where .sf/ is a LOCAL DIRECTORY. * Granular so that durable planning artifacts (.sf/milestones/, .sf/PROJECT.md, diff --git a/src/resources/extensions/sf/graph-context.js b/src/resources/extensions/sf/graph-context.js index 21a9b98da..c73189a95 100644 --- a/src/resources/extensions/sf/graph-context.js +++ b/src/resources/extensions/sf/graph-context.js @@ -8,6 +8,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { logWarning } from "./workflow-logger.js"; + let cachedGraphApi = null; let resolvedGraphApi = false; function readGraphFile(projectDir) { diff --git a/src/resources/extensions/sf/graph.js b/src/resources/extensions/sf/graph.js index 57a212282..b20cc1c5a 100644 --- a/src/resources/extensions/sf/graph.js +++ b/src/resources/extensions/sf/graph.js @@ -23,6 +23,7 @@ import { } from "node:fs"; import { join } from "node:path"; import { parse, stringify } from "yaml"; + // ─── YAML schema mapping ───────────────────────────────────────────────── const GRAPH_FILENAME = "GRAPH.yaml"; // ─── Functions ─────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/guided-flow.js b/src/resources/extensions/sf/guided-flow.js index 7491a89c9..66960e632 100644 --- a/src/resources/extensions/sf/guided-flow.js +++ b/src/resources/extensions/sf/guided-flow.js @@ -85,6 +85,7 @@ import { getWorkflowTransportSupportError, supportsStructuredQuestions, } from "./workflow-mcp.js"; + export { buildExistingMilestonesContext, handleQueueReorder, @@ -106,7 +107,9 @@ export { parseMilestoneId, reserveMilestoneId, } from "./milestone-ids.js"; + import { logWarning } from "./workflow-logger.js"; + // ─── Todo/Spec File Detection ──────────────────────────────────────────────── const TODO_FILE_NAMES = ["todo.md", "TODO.md", "SPEC.md", "spec.md"]; /** diff --git a/src/resources/extensions/sf/health-widget.js b/src/resources/extensions/sf/health-widget.js index 40b2d508c..9be7a494d 100644 --- a/src/resources/extensions/sf/health-widget.js +++ b/src/resources/extensions/sf/health-widget.js @@ -26,6 +26,7 @@ import { nativeLastCommitEpoch, } from "./native-git-bridge.js"; import { loadEffectiveSFPreferences } from "./preferences.js"; + // ── Data loader ──────────────────────────────────────────────────────────────── function loadHealthWidgetData(basePath) { let budgetCeiling; diff --git a/src/resources/extensions/sf/hook-emitter.js b/src/resources/extensions/sf/hook-emitter.js index 1e73827d7..24d57423c 100644 --- a/src/resources/extensions/sf/hook-emitter.js +++ b/src/resources/extensions/sf/hook-emitter.js @@ -8,6 +8,7 @@ // missing `pi` (e.g. in standalone unit tests) logs a warning so callers know // hooks won't fire, but never throws. import { logWarning } from "./workflow-logger.js"; + let _pi; let _missingPiWarningLogged = false; export function setHookEmitter(pi) { diff --git a/src/resources/extensions/sf/init-wizard.js b/src/resources/extensions/sf/init-wizard.js index 7a2f7d872..60fc71938 100644 --- a/src/resources/extensions/sf/init-wizard.js +++ b/src/resources/extensions/sf/init-wizard.js @@ -9,13 +9,14 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { showNextAction } from "../shared/tui.js"; import { ensureAgenticDocsScaffold } from "./agentic-docs-scaffold.js"; -import { generateCodebaseMap, writeCodebaseMap } from "./codebase-generator.js"; import { ensureSiftIndexWarmup } from "./code-intelligence.js"; +import { generateCodebaseMap, writeCodebaseMap } from "./codebase-generator.js"; import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js"; import { nativeInit } from "./native-git-bridge.js"; import { sfRoot } from "./paths.js"; import { runSkillInstallStep } from "./skill-catalog.js"; import { assertSafeDirectory } from "./validate-directory.js"; + // ─── Defaults ─────────────────────────────────────────────────────────────────── const DEFAULT_PREFS = { mode: "solo", diff --git a/src/resources/extensions/sf/journal.js b/src/resources/extensions/sf/journal.js index c8986bc72..e8805d771 100644 --- a/src/resources/extensions/sf/journal.js +++ b/src/resources/extensions/sf/journal.js @@ -26,6 +26,7 @@ import { withFileLockSync } from "./file-lock.js"; import { sfRuntimeRoot } from "./paths.js"; import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js"; import { isAuditEnvelopeEnabled } from "./uok/audit-toggle.js"; + // Per-session dedup for journal write failures to prevent log flooding. let _journalWriteFailureNotified = false; // ─── Emit ───────────────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/json-persistence.js b/src/resources/extensions/sf/json-persistence.js index bcb566943..c2c8f7964 100644 --- a/src/resources/extensions/sf/json-persistence.js +++ b/src/resources/extensions/sf/json-persistence.js @@ -5,13 +5,14 @@ import { fsyncSync, mkdirSync, openSync, - readFileSync, readdirSync, + readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs"; import { basename, dirname } from "node:path"; + /** * Clean up orphan .tmp.* files for the given target path. * Scans the directory for stale temporary files matching the basename pattern. diff --git a/src/resources/extensions/sf/learning/runtime.js b/src/resources/extensions/sf/learning/runtime.js index 71b6729f2..b5115c266 100644 --- a/src/resources/extensions/sf/learning/runtime.js +++ b/src/resources/extensions/sf/learning/runtime.js @@ -3,6 +3,7 @@ import { logWarning } from "../workflow-logger.js"; import { createBeforeModelSelectHandler } from "./hook-handler.mjs"; import { loadCapabilityOverrides } from "./loadCapabilityOverrides.mjs"; import { validateOutcome } from "./outcome-recorder.mjs"; + const DEFAULT_N_PRIOR = 10; const DEFAULT_ROLLING_DAYS = 30; const DEFAULT_UCB_C = 1.4; diff --git a/src/resources/extensions/sf/markdown-renderer.js b/src/resources/extensions/sf/markdown-renderer.js index d6481efbb..1579dffc2 100644 --- a/src/resources/extensions/sf/markdown-renderer.js +++ b/src/resources/extensions/sf/markdown-renderer.js @@ -10,6 +10,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import { join, relative } from "node:path"; import { clearParseCache, saveFile } from "./files.js"; +import { parsePlan, parseRoadmap } from "./parsers.js"; import { buildSliceFileName, buildTaskFileName, @@ -34,7 +35,7 @@ import { import { invalidateStateCache } from "./state.js"; import { isClosedStatus } from "./status-guards.js"; import { logWarning } from "./workflow-logger.js"; -import { parseRoadmap, parsePlan } from "./parsers.js"; + const parsers = { parseRoadmap, parsePlan }; // ─── Helpers ────────────────────────────────────────────────────────────── /** diff --git a/src/resources/extensions/sf/marketplace-discovery.js b/src/resources/extensions/sf/marketplace-discovery.js index 54d78ddea..97d91d204 100644 --- a/src/resources/extensions/sf/marketplace-discovery.js +++ b/src/resources/extensions/sf/marketplace-discovery.js @@ -16,6 +16,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { getErrorMessage } from "./error-utils.js"; + // ============================================================================ // Helper Functions // ============================================================================ diff --git a/src/resources/extensions/sf/md-importer.js b/src/resources/extensions/sf/md-importer.js index 7a13dbd79..6b1a3a740 100644 --- a/src/resources/extensions/sf/md-importer.js +++ b/src/resources/extensions/sf/md-importer.js @@ -30,6 +30,7 @@ import { upsertRequirement, } from "./sf-db.js"; import { logWarning } from "./workflow-logger.js"; + // ─── DECISIONS.md Parser ─────────────────────────────────────────────────── const VALID_MADE_BY = new Set(["human", "agent", "collaborative"]); /** diff --git a/src/resources/extensions/sf/memory-backfill.js b/src/resources/extensions/sf/memory-backfill.js index 7f2ac41b9..fd0d5e71f 100644 --- a/src/resources/extensions/sf/memory-backfill.js +++ b/src/resources/extensions/sf/memory-backfill.js @@ -10,8 +10,9 @@ // only ever fires once per project. Costs O(N) inserts on first run where // N is the active-decisions count; subsequent runs are an O(N) lookup that // finds existing markers and exits. -import { isDbAvailable, _getAdapter } from "./sf-db.js"; + import { createMemory } from "./memory-store.js"; +import { _getAdapter, isDbAvailable } from "./sf-db.js"; import { logWarning } from "./workflow-logger.js"; /** * Backfill active decisions rows into the memories table. diff --git a/src/resources/extensions/sf/memory-embeddings-llm-gateway.js b/src/resources/extensions/sf/memory-embeddings-llm-gateway.js index a1036597f..f0412e038 100644 --- a/src/resources/extensions/sf/memory-embeddings-llm-gateway.js +++ b/src/resources/extensions/sf/memory-embeddings-llm-gateway.js @@ -11,6 +11,7 @@ // embed-fn discovery surface stays clean and the gateway can be swapped or // disabled without touching the consumer. import { logWarning } from "./workflow-logger.js"; + const DEFAULT_TIMEOUT_MS = 30_000; // Throttle the "rerank worker offline" warning so per-query log spam doesn't // drown out other diagnostics when SF_LLM_GATEWAY_RERANK_MODEL is set but no diff --git a/src/resources/extensions/sf/memory-embeddings.js b/src/resources/extensions/sf/memory-embeddings.js index 7ce6f6ab6..fc583524d 100644 --- a/src/resources/extensions/sf/memory-embeddings.js +++ b/src/resources/extensions/sf/memory-embeddings.js @@ -17,11 +17,12 @@ // to static (confidence × hit_count) ranking. import { _getAdapter, + deleteMemoryEmbedding, isDbAvailable, upsertMemoryEmbedding, - deleteMemoryEmbedding, } from "./sf-db.js"; import { logWarning } from "./workflow-logger.js"; + // ─── Model selection ──────────────────────────────────────────────────────── const EMBEDDING_ID_HINTS = [ "embed", diff --git a/src/resources/extensions/sf/memory-extractor.js b/src/resources/extensions/sf/memory-extractor.js index d7c447d71..8b08bfb33 100644 --- a/src/resources/extensions/sf/memory-extractor.js +++ b/src/resources/extensions/sf/memory-extractor.js @@ -11,6 +11,7 @@ import { isUnitProcessed, markUnitProcessed, } from "./memory-store.js"; + // ─── Concurrency Guard ────────────────────────────────────────────────────── let _extracting = false; let _lastExtractionTime = 0; diff --git a/src/resources/extensions/sf/memory-ingest.js b/src/resources/extensions/sf/memory-ingest.js index e15edd3ab..87fe5bf15 100644 --- a/src/resources/extensions/sf/memory-ingest.js +++ b/src/resources/extensions/sf/memory-ingest.js @@ -11,11 +11,12 @@ // `/sf memory rebuild` can re-extract from persisted sources. import { existsSync, readFileSync, statSync } from "node:fs"; import { basename, isAbsolute, resolve } from "node:path"; -import { createMemorySource } from "./memory-source-store.js"; import { buildMemoryLLMCall, parseMemoryResponse } from "./memory-extractor.js"; +import { createMemorySource } from "./memory-source-store.js"; import { applyMemoryActions, getActiveMemories } from "./memory-store.js"; import { resolveMilestoneFile } from "./paths.js"; import { logWarning } from "./workflow-logger.js"; + const DEFAULT_MAX_BYTES = 256 * 1024; const INGEST_EXTRACTION_SYSTEM = `You are a memory extraction agent for a software project. Analyze the provided content and extract durable knowledge worth remembering. diff --git a/src/resources/extensions/sf/memory-source-store.js b/src/resources/extensions/sf/memory-source-store.js index ffc0f104f..1c5be15db 100644 --- a/src/resources/extensions/sf/memory-source-store.js +++ b/src/resources/extensions/sf/memory-source-store.js @@ -7,10 +7,11 @@ import { createHash, randomUUID } from "node:crypto"; import { _getAdapter, - isDbAvailable, - insertMemorySourceRow, deleteMemorySourceRow, + insertMemorySourceRow, + isDbAvailable, } from "./sf-db.js"; + function rowToSource(row) { const tagsRaw = typeof row["tags"] === "string" ? row["tags"] : "[]"; let tags = []; diff --git a/src/resources/extensions/sf/memory-store.js b/src/resources/extensions/sf/memory-store.js index be78577a4..f4299152b 100644 --- a/src/resources/extensions/sf/memory-store.js +++ b/src/resources/extensions/sf/memory-store.js @@ -17,6 +17,7 @@ import { transaction, updateMemoryContentRow, } from "./sf-db.js"; + // ─── Category Display Order ───────────────────────────────────────────────── const CATEGORY_PRIORITY = { gotcha: 0, diff --git a/src/resources/extensions/sf/metrics.js b/src/resources/extensions/sf/metrics.js index b4d5aeae1..addbb9f81 100644 --- a/src/resources/extensions/sf/metrics.js +++ b/src/resources/extensions/sf/metrics.js @@ -18,17 +18,19 @@ import { loadJsonFileOrNull, saveJsonFile, } from "./json-persistence.js"; +import { formatModelIdentity } from "./model-identity.js"; import { sfRuntimeRoot } from "./paths.js"; import { getDatabase } from "./sf-db.js"; import { getAndClearSkills } from "./skill-telemetry.js"; -import { formatModelIdentity } from "./model-identity.js"; import { parseUnitId } from "./unit-id.js"; import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js"; import { isAuditEnvelopeEnabled } from "./uok/audit-toggle.js"; + // Re-export from shared — import directly from format-utils to avoid pulling // in the full barrel (mod.js → ui.js → @singularity-forge/pi-tui) which breaks when loaded // outside jiti's alias resolution (e.g. dynamic import in auto-loop reports). export { formatTokenCount } from "../shared/format-utils.js"; + // ─── Learning Integration ───────────────────────────────────────────────────── function formatAggregateModelIdentity(modelId) { const slashIdx = modelId.indexOf("/"); diff --git a/src/resources/extensions/sf/migrate/command.js b/src/resources/extensions/sf/migrate/command.js index a322b8d6b..c4b7eda3d 100644 --- a/src/resources/extensions/sf/migrate/command.js +++ b/src/resources/extensions/sf/migrate/command.js @@ -19,6 +19,7 @@ import { validatePlanningDirectory, writeSFDirectory, } from "./index.js"; + /** Format preview stats for embedding in the review prompt. */ function formatPreviewStats(preview) { const lines = [ diff --git a/src/resources/extensions/sf/migrate/parser.js b/src/resources/extensions/sf/migrate/parser.js index 189a36314..747c1b41e 100644 --- a/src/resources/extensions/sf/migrate/parser.js +++ b/src/resources/extensions/sf/migrate/parser.js @@ -14,6 +14,7 @@ import { parseOldSummary, } from "./parsers.js"; import { validatePlanningDirectory } from "./validator.js"; + // ─── Helpers ─────────────────────────────────────────────────────────────── /** Read a file, returning null if it doesn't exist. */ function readOptional(path) { diff --git a/src/resources/extensions/sf/migrate/parsers.js b/src/resources/extensions/sf/migrate/parsers.js index c6ee92151..c672a075e 100644 --- a/src/resources/extensions/sf/migrate/parsers.js +++ b/src/resources/extensions/sf/migrate/parsers.js @@ -7,6 +7,7 @@ import { parseFrontmatterMap, splitFrontmatter, } from "../files.js"; + // Re-export PlanningProjectMeta — not in types.ts yet, use string for project field // Actually PlanningProjectMeta isn't in types.ts — project is stored as string | null. // We'll keep parseOldProject returning a simple shape. @@ -29,7 +30,6 @@ function extractTasks(content) { const tasks = []; const regex = /([\s\S]*?)<\/task>/gi; let match; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = regex.exec(tasksBlock)) !== null) { const trimmed = match[1].trim(); if (trimmed) tasks.push(trimmed); diff --git a/src/resources/extensions/sf/migrate/validator.js b/src/resources/extensions/sf/migrate/validator.js index 157bf0247..01e4fc910 100644 --- a/src/resources/extensions/sf/migrate/validator.js +++ b/src/resources/extensions/sf/migrate/validator.js @@ -3,6 +3,7 @@ // Pure functions, zero Pi dependencies — uses only Node built-ins + exported helpers. import { existsSync, statSync } from "node:fs"; import { join } from "node:path"; + function issue(file, severity, message) { return { file, severity, message }; } diff --git a/src/resources/extensions/sf/migrate/writer.js b/src/resources/extensions/sf/migrate/writer.js index ba6d51435..98ffc49c5 100644 --- a/src/resources/extensions/sf/migrate/writer.js +++ b/src/resources/extensions/sf/migrate/writer.js @@ -5,6 +5,7 @@ import { join } from "node:path"; import { saveFile } from "../files.js"; import { sfRoot } from "../paths.js"; + // ─── Local Helpers ───────────────────────────────────────────────────────── /** * Serialize a flat key-value map into YAML frontmatter block. diff --git a/src/resources/extensions/sf/milestone-id-reservation.js b/src/resources/extensions/sf/milestone-id-reservation.js index 59a296804..68c60eaf1 100644 --- a/src/resources/extensions/sf/milestone-id-reservation.js +++ b/src/resources/extensions/sf/milestone-id-reservation.js @@ -1,13 +1,14 @@ import { existsSync } from "node:fs"; import { join } from "node:path"; -import { isDbAvailable, getAllMilestones, getMilestone } from "./sf-db.js"; import { getReservedMilestoneIds, milestoneIdSort, nextMilestoneId, reserveMilestoneId, } from "./milestone-ids.js"; -import { sfRoot, resolveMilestoneFile } from "./paths.js"; +import { resolveMilestoneFile, sfRoot } from "./paths.js"; +import { getAllMilestones, getMilestone, isDbAvailable } from "./sf-db.js"; + /** * A milestone is "reusable ghost" if it has no DB row, no worktree, and no * content files. This is a stricter definition than `isGhostMilestone`: diff --git a/src/resources/extensions/sf/native-git-bridge.js b/src/resources/extensions/sf/native-git-bridge.js index 0e619832a..2a269b3c4 100644 --- a/src/resources/extensions/sf/native-git-bridge.js +++ b/src/resources/extensions/sf/native-git-bridge.js @@ -10,6 +10,7 @@ import { isAbsolute, join } from "node:path"; import { getErrorMessage } from "./error-utils.js"; import { SF_GIT_ERROR, SFError } from "./errors.js"; import { GIT_NO_PROMPT_ENV } from "./git-constants.js"; + /** * Return true when any directory component of `relPath` (relative to `basePath`) * is itself a symlink. Git refuses to add paths that traverse a symlink, so diff --git a/src/resources/extensions/sf/native-parser-bridge.js b/src/resources/extensions/sf/native-parser-bridge.js index 33d36a592..e30a3d671 100644 --- a/src/resources/extensions/sf/native-parser-bridge.js +++ b/src/resources/extensions/sf/native-parser-bridge.js @@ -4,6 +4,7 @@ // // Functions fall back to JS implementations if the native module is unavailable. import { createRequire } from "node:module"; + // Prefer the Rust parser for SF markdown. The bridge still falls back to the // JS implementation when the native addon is unavailable, and this env var // keeps a runtime escape hatch for platform-specific native issues. diff --git a/src/resources/extensions/sf/notification-overlay.js b/src/resources/extensions/sf/notification-overlay.js index 05539b273..492c4b32f 100644 --- a/src/resources/extensions/sf/notification-overlay.js +++ b/src/resources/extensions/sf/notification-overlay.js @@ -15,6 +15,7 @@ import { readNotifications, } from "./notification-store.js"; import { formattedShortcutPair } from "./shortcut-defs.js"; + /** * Cycle of filter modes used when cycling through filter states with 'f' key. */ diff --git a/src/resources/extensions/sf/notification-store.js b/src/resources/extensions/sf/notification-store.js index 7f4c4be0d..5d9bee308 100644 --- a/src/resources/extensions/sf/notification-store.js +++ b/src/resources/extensions/sf/notification-store.js @@ -16,6 +16,7 @@ import { } from "node:fs"; import { join } from "node:path"; import { sfRuntimeRoot } from "./paths.js"; + // ─── Constants ────────────────────────────────────────────────────────── const MAX_ENTRIES = 500; const FILENAME = "notifications.jsonl"; diff --git a/src/resources/extensions/sf/observability-validator.js b/src/resources/extensions/sf/observability-validator.js index 3b00a14df..c40a9ae8e 100644 --- a/src/resources/extensions/sf/observability-validator.js +++ b/src/resources/extensions/sf/observability-validator.js @@ -5,6 +5,7 @@ import { resolveTaskFiles, resolveTasksDir, } from "./paths.js"; + function getSection(content, heading, level = 2) { const prefix = "#".repeat(level) + " "; const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); diff --git a/src/resources/extensions/sf/orphan-worktree-sweep.js b/src/resources/extensions/sf/orphan-worktree-sweep.js index 626ed904b..db9ae3904 100644 --- a/src/resources/extensions/sf/orphan-worktree-sweep.js +++ b/src/resources/extensions/sf/orphan-worktree-sweep.js @@ -18,9 +18,10 @@ import { join } from "node:path"; import { emitJournalEvent } from "./journal.js"; import { removeWorktree, - worktreesDir, worktreePath, + worktreesDir, } from "./worktree-manager.js"; + // ─── Internal Helpers ───────────────────────────────────────────────────────── /** * Read the auto.lock file for a worktree and return the PID, or null if absent. diff --git a/src/resources/extensions/sf/parallel-eligibility.js b/src/resources/extensions/sf/parallel-eligibility.js index 2396fec65..60abe2e06 100644 --- a/src/resources/extensions/sf/parallel-eligibility.js +++ b/src/resources/extensions/sf/parallel-eligibility.js @@ -8,6 +8,7 @@ import { findMilestoneIds } from "./guided-flow.js"; import { getWorkerStatuses } from "./parallel-orchestrator.js"; import { getMilestoneSlices, getSliceTasks, isDbAvailable } from "./sf-db.js"; import { deriveState } from "./state.js"; + // ─── File Collection ───────────────────────────────────────────────────────── /** * Collect all `filesLikelyTouched` across every slice plan in a milestone. diff --git a/src/resources/extensions/sf/parallel-monitor-overlay.js b/src/resources/extensions/sf/parallel-monitor-overlay.js index fc0f6e1c0..a8179e736 100644 --- a/src/resources/extensions/sf/parallel-monitor-overlay.js +++ b/src/resources/extensions/sf/parallel-monitor-overlay.js @@ -21,6 +21,7 @@ import { join } from "node:path"; import { Key, matchesKey } from "@singularity-forge/pi-tui"; import { formatDuration } from "../shared/mod.js"; import { formattedShortcutPair } from "./shortcut-defs.js"; + // ─── Async SQLite Helper ────────────────────────────────────────────────── function runSqliteAsync(dbPath, sql) { return new Promise((resolve) => { diff --git a/src/resources/extensions/sf/parallel-orchestrator.js b/src/resources/extensions/sf/parallel-orchestrator.js index d047d6875..11253f92a 100644 --- a/src/resources/extensions/sf/parallel-orchestrator.js +++ b/src/resources/extensions/sf/parallel-orchestrator.js @@ -24,6 +24,7 @@ import { } from "./auto-worktree.js"; import { getErrorMessage } from "./error-utils.js"; import { readIntegrationBranch } from "./git-service.js"; +import { emitJournalEvent } from "./journal.js"; import { nativeBranchExists } from "./native-git-bridge.js"; import { analyzeParallelEligibility } from "./parallel-eligibility.js"; import { sfRoot } from "./paths.js"; @@ -38,9 +39,9 @@ import { } from "./session-status-io.js"; import { selectConflictFreeBatch } from "./uok/execution-graph.js"; import { resolveUokFlags } from "./uok/flags.js"; -import { emitJournalEvent } from "./journal.js"; import { logWarning } from "./workflow-logger.js"; import { createWorktree, worktreePath } from "./worktree-manager.js"; + // ─── Module State ────────────────────────────────────────────────────────── let state = null; function overlapKey(a, b) { diff --git a/src/resources/extensions/sf/parsers.js b/src/resources/extensions/sf/parsers.js index 8f1a99b35..8b23aa833 100644 --- a/src/resources/extensions/sf/parsers.js +++ b/src/resources/extensions/sf/parsers.js @@ -18,7 +18,9 @@ import { import { nativeParseRoadmap } from "./native-parser-bridge.js"; // Re-export parseRoadmapSlices so callers can import all parsers from one module import { parseRoadmapSlices } from "./roadmap-slices.js"; + export { parseRoadmapSlices }; + // ─── Parse Cache (local to this module) ─────────────────────────────────── /** Fast composite key: length + first/mid/last 100 chars. The middle sample * prevents collisions when only a few characters change in the interior of diff --git a/src/resources/extensions/sf/paths.js b/src/resources/extensions/sf/paths.js index 81c40fca8..7018f3e2e 100644 --- a/src/resources/extensions/sf/paths.js +++ b/src/resources/extensions/sf/paths.js @@ -12,14 +12,15 @@ import { spawnSync } from "node:child_process"; import { Dirent, existsSync, - readFileSync, readdirSync, + readFileSync, realpathSync, } from "node:fs"; import { homedir } from "node:os"; import { dirname, join, normalize } from "node:path"; import { DIR_CACHE_MAX } from "./constants.js"; import { nativeScanSfTree } from "./native-parser-bridge.js"; + // ─── Directory Listing Cache ────────────────────────────────────────────────── const dirEntryCache = new Map(); const dirListCache = new Map(); diff --git a/src/resources/extensions/sf/phase-anchor.js b/src/resources/extensions/sf/phase-anchor.js index b6ffedc81..2e562e98c 100644 --- a/src/resources/extensions/sf/phase-anchor.js +++ b/src/resources/extensions/sf/phase-anchor.js @@ -7,6 +7,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { atomicWriteSync } from "./atomic-write.js"; import { sfRoot } from "./paths.js"; + function anchorsDir(basePath, milestoneId) { return join(sfRoot(basePath), "milestones", milestoneId, "anchors"); } diff --git a/src/resources/extensions/sf/planning-depth.js b/src/resources/extensions/sf/planning-depth.js index 340972f1b..449e0e614 100644 --- a/src/resources/extensions/sf/planning-depth.js +++ b/src/resources/extensions/sf/planning-depth.js @@ -9,6 +9,7 @@ import { dirname, join } from "node:path"; import { parse as parseYaml, stringify as stringifyYaml } from "yaml"; import { sfRoot } from "./paths.js"; import { logWarning } from "./workflow-logger.js"; + const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/; /** * Resolve the path to the project-level .sf/PREFERENCES.md file. diff --git a/src/resources/extensions/sf/post-execution-checks.js b/src/resources/extensions/sf/post-execution-checks.js index 3eb0bd6ba..ee09637f0 100644 --- a/src/resources/extensions/sf/post-execution-checks.js +++ b/src/resources/extensions/sf/post-execution-checks.js @@ -60,7 +60,6 @@ export function extractRelativeImports(source) { let match; // Reset lastIndex for each line importPattern.lastIndex = 0; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = importPattern.exec(line)) !== null) { // Check if this match is after a // comment marker on the same line const beforeMatch = line.substring(0, match.index); @@ -178,7 +177,6 @@ function extractFunctionSignatures(source, fileName) { const line = lines[i]; funcPattern.lastIndex = 0; let match; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = funcPattern.exec(line)) !== null) { const [, name, params, returnType] = match; signatures.push({ @@ -344,7 +342,6 @@ function checkNamingConsistency(source, fileName) { const funcPattern = /(?:function\s+|const\s+|let\s+|var\s+)(\w+)(?:\s*=\s*(?:async\s*)?\(|\s*\()/g; let match; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = funcPattern.exec(source)) !== null) { functionNames.push(match[1]); } diff --git a/src/resources/extensions/sf/post-unit-hooks.js b/src/resources/extensions/sf/post-unit-hooks.js index 22a291b3a..13fe47627 100644 --- a/src/resources/extensions/sf/post-unit-hooks.js +++ b/src/resources/extensions/sf/post-unit-hooks.js @@ -4,6 +4,7 @@ // registry instance; these exported functions delegate through getOrCreateRegistry() // so existing call-sites and tests work without modification. import { getOrCreateRegistry } from "./rule-registry.js"; + // Re-export resolveHookArtifactPath so existing importers still work. export { resolveHookArtifactPath } from "./rule-registry.js"; // ─── Post-Unit Hooks ─────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/pre-execution-checks.js b/src/resources/extensions/sf/pre-execution-checks.js index 344f9bccd..40bac1d21 100644 --- a/src/resources/extensions/sf/pre-execution-checks.js +++ b/src/resources/extensions/sf/pre-execution-checks.js @@ -16,6 +16,7 @@ import { spawn } from "node:child_process"; import { existsSync } from "node:fs"; import { resolve } from "node:path"; + const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm"; // ─── Package Existence Check ───────────────────────────────────────────────── /** @@ -47,7 +48,6 @@ export function extractPackageReferences(description) { const installCmdPattern = /(?:npm\s+(?:install|i|add)|yarn\s+add|pnpm\s+add)\s+/g; let cmdMatch; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((cmdMatch = installCmdPattern.exec(description)) !== null) { // Start after the install command const afterCmd = description.slice(cmdMatch.index + cmdMatch[0].length); @@ -96,7 +96,6 @@ export function extractPackageReferences(description) { const importPattern = /(?:require\s*\(\s*['"]|from\s+['"])([a-zA-Z0-9@/_-]+)['")]/g; let importMatch; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((importMatch = importPattern.exec(description)) !== null) { // Skip relative imports and node builtins const pkg = importMatch[1]; @@ -415,7 +414,6 @@ function extractFunctionSignatures(description, taskId) { const codeBlockPattern = /```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/g; let blockMatch; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((blockMatch = codeBlockPattern.exec(description)) !== null) { const codeBlock = blockMatch[1]; // Match function declarations and exports @@ -428,7 +426,6 @@ function extractFunctionSignatures(description, taskId) { const funcPattern = /(?:export\s+)?(?:async\s+)?(?:function\s+|const\s+)(\w+)(?:\s*=\s*)?\s*\(([^)]*)\)(?:\s*:\s*([^{=>\n]+))?/g; let funcMatch; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((funcMatch = funcPattern.exec(codeBlock)) !== null) { const [raw, name, params, returnType] = funcMatch; signatures.push({ @@ -443,7 +440,6 @@ function extractFunctionSignatures(description, taskId) { // Pattern: methodName(params): ReturnType; const methodPattern = /^\s*(\w+)\s*\(([^)]*)\)\s*:\s*([^;]+);/gm; let methodMatch; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((methodMatch = methodPattern.exec(codeBlock)) !== null) { const [raw, name, params, returnType] = methodMatch; signatures.push({ diff --git a/src/resources/extensions/sf/preferences-models.js b/src/resources/extensions/sf/preferences-models.js index 313beaa04..ad0beb81d 100644 --- a/src/resources/extensions/sf/preferences-models.js +++ b/src/resources/extensions/sf/preferences-models.js @@ -13,12 +13,6 @@ import { getModels, getProviders, } from "@singularity-forge/pi-ai"; -import { selectByBenchmarks } from "./benchmark-selector.js"; -import { defaultRoutingConfig } from "./model-router.js"; -import { - getGlobalSFPreferencesPath, - loadEffectiveSFPreferences, -} from "./preferences.js"; import { DEFAULT_RUNAWAY_CHANGED_FILES_WARNING, DEFAULT_RUNAWAY_DIAGNOSTIC_TURNS, @@ -26,6 +20,13 @@ import { DEFAULT_RUNAWAY_TOKEN_WARNING, DEFAULT_RUNAWAY_TOOL_CALL_WARNING, } from "./auto-runaway-guard.js"; +import { selectByBenchmarks } from "./benchmark-selector.js"; +import { defaultRoutingConfig } from "./model-router.js"; +import { + getGlobalSFPreferencesPath, + loadEffectiveSFPreferences, +} from "./preferences.js"; + const OPENCODE_FREE_MODEL_IDS = new Set([ "big-pickle", "gpt-5-nano", diff --git a/src/resources/extensions/sf/preferences.js b/src/resources/extensions/sf/preferences.js index 5302ff7a9..a1e521e48 100644 --- a/src/resources/extensions/sf/preferences.js +++ b/src/resources/extensions/sf/preferences.js @@ -16,14 +16,15 @@ import { parse as parseYaml } from "yaml"; import { normalizeStringArray } from "../shared/format-utils.js"; import { sfRoot } from "./paths.js"; import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js"; +import { upgradePreferencesFileIfDrifted } from "./preferences-template-upgrade.js"; import { formatSkillRef, KNOWN_PREFERENCE_KEYS, MODE_DEFAULTS, } from "./preferences-types.js"; -import { upgradePreferencesFileIfDrifted } from "./preferences-template-upgrade.js"; import { validatePreferences } from "./preferences-validation.js"; import { logWarning } from "./workflow-logger.js"; + // ─── Re-exports: types ────────────────────────────────────────────────────── // Every type/interface that was previously exported from this file is // re-exported so that downstream `import { Foo } from "./preferences.js"` @@ -69,6 +70,7 @@ export { updatePreferencesModels, validateModelId, } from "./preferences-models.js"; + // ─── Path Constants & Getters ─────────────────────────────────────────────── function sfHome() { return process.env.SF_HOME || join(homedir(), ".sf"); diff --git a/src/resources/extensions/sf/preparation.js b/src/resources/extensions/sf/preparation.js index 418e33dff..d183aa5de 100644 --- a/src/resources/extensions/sf/preparation.js +++ b/src/resources/extensions/sf/preparation.js @@ -16,6 +16,7 @@ import { import { join } from "node:path"; import { detectProjectSignals, scanProjectFiles } from "./detection.js"; import { loadFile } from "./files.js"; + // ─── Constants ────────────────────────────────────────────────────────────────── /** Maximum characters for the codebase section. */ const MAX_CODEBASE_BRIEF_CHARS = 3000; diff --git a/src/resources/extensions/sf/progress-score.js b/src/resources/extensions/sf/progress-score.js index ed53cac8f..2b7b96950 100644 --- a/src/resources/extensions/sf/progress-score.js +++ b/src/resources/extensions/sf/progress-score.js @@ -16,6 +16,7 @@ import { getLatestHealthFixes, getLatestHealthIssues, } from "./doctor-proactive.js"; + function escalateLevel(level, next) { const ranks = { green: 0, diff --git a/src/resources/extensions/sf/project-research-policy.js b/src/resources/extensions/sf/project-research-policy.js index 9d0ee0fc0..eb252d0f9 100644 --- a/src/resources/extensions/sf/project-research-policy.js +++ b/src/resources/extensions/sf/project-research-policy.js @@ -1,8 +1,8 @@ import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { classifyMilestoneScope } from "./milestone-scope-classifier.js"; import { clearParseCache } from "./files.js"; -import { sfRoot, clearPathCache } from "./paths.js"; +import { classifyMilestoneScope } from "./milestone-scope-classifier.js"; +import { clearPathCache, sfRoot } from "./paths.js"; import { parseProject, parseRequirements } from "./schemas/parsers.js"; export const PROJECT_RESEARCH_DIMENSIONS = [ "STACK", diff --git a/src/resources/extensions/sf/prompt-loader.js b/src/resources/extensions/sf/prompt-loader.js index 530c220b3..2ac6cfaa1 100644 --- a/src/resources/extensions/sf/prompt-loader.js +++ b/src/resources/extensions/sf/prompt-loader.js @@ -21,6 +21,7 @@ import { homedir } from "node:os"; import { join } from "node:path"; import { SF_PARSE_ERROR, SFError } from "./errors.js"; import { logWarning } from "./workflow-logger.js"; + /** * Resolve the SF extension directory. * diff --git a/src/resources/extensions/sf/python-resolver.js b/src/resources/extensions/sf/python-resolver.js index 8fdbecb46..f9a54db82 100644 --- a/src/resources/extensions/sf/python-resolver.js +++ b/src/resources/extensions/sf/python-resolver.js @@ -13,6 +13,7 @@ * @module python-resolver */ import { spawnSync } from "node:child_process"; + /** Cached result of `detectPythonExecutable`. `undefined` means not yet probed. */ let cached; /** diff --git a/src/resources/extensions/sf/queue-order.js b/src/resources/extensions/sf/queue-order.js index 2ce91cd32..0cacae3fa 100644 --- a/src/resources/extensions/sf/queue-order.js +++ b/src/resources/extensions/sf/queue-order.js @@ -12,6 +12,7 @@ import { join } from "node:path"; import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js"; import { milestoneIdSort } from "./milestone-ids.js"; import { sfRoot } from "./paths.js"; + // ─── Path ──────────────────────────────────────────────────────────────────── function queueOrderPath(basePath) { return join(sfRoot(basePath), "QUEUE-ORDER.json"); diff --git a/src/resources/extensions/sf/quick.js b/src/resources/extensions/sf/quick.js index d519ff7be..34e557888 100644 --- a/src/resources/extensions/sf/quick.js +++ b/src/resources/extensions/sf/quick.js @@ -22,6 +22,7 @@ import { nativeHasStagedChanges } from "./native-git-bridge.js"; import { sfRoot } from "./paths.js"; import { loadEffectiveSFPreferences } from "./preferences.js"; import { loadPrompt } from "./prompt-loader.js"; + let pendingQuickReturn = null; // ─── Quick Task Helpers ─────────────────────────────────────────────────────── /** diff --git a/src/resources/extensions/sf/record-promoter.js b/src/resources/extensions/sf/record-promoter.js index bd750b255..1870ad211 100644 --- a/src/resources/extensions/sf/record-promoter.js +++ b/src/resources/extensions/sf/record-promoter.js @@ -209,7 +209,7 @@ export function promoteActionableRecords(basePath) { try { const milestoneId = nextMilestoneId(milestonesPath); const title = titleFromFilename(filename); - const slugBase = toSlug( + const _slugBase = toSlug( basename(filename, ".md").replace(/^\d{4}-\d{2}-\d{2}-/, ""), ); const milestoneDir = join(milestonesPath, milestoneId); diff --git a/src/resources/extensions/sf/repo-identity.js b/src/resources/extensions/sf/repo-identity.js index bccc208bc..71c250f0c 100644 --- a/src/resources/extensions/sf/repo-identity.js +++ b/src/resources/extensions/sf/repo-identity.js @@ -23,6 +23,7 @@ import { } from "node:fs"; import { homedir } from "node:os"; import { basename, dirname, join, resolve } from "node:path"; + const sfHome = process.env.SF_HOME || join(homedir(), ".sf"); function isRepoMeta(value) { if (!value || typeof value !== "object") return false; diff --git a/src/resources/extensions/sf/repo-profiler.js b/src/resources/extensions/sf/repo-profiler.js index 62efed324..5f77d4e81 100644 --- a/src/resources/extensions/sf/repo-profiler.js +++ b/src/resources/extensions/sf/repo-profiler.js @@ -9,6 +9,7 @@ import { createHash } from "node:crypto"; import { existsSync, readFileSync, realpathSync, statSync } from "node:fs"; import { basename, extname, join, sep } from "node:path"; import { GIT_NO_PROMPT_ENV } from "./git-constants.js"; + const HASH_READ_LIMIT_BYTES = 1024 * 1024; function git(args, cwd, allowFailure = true) { try { diff --git a/src/resources/extensions/sf/repository-vcs-context.js b/src/resources/extensions/sf/repository-vcs-context.js index eea545ae2..95c44f900 100644 --- a/src/resources/extensions/sf/repository-vcs-context.js +++ b/src/resources/extensions/sf/repository-vcs-context.js @@ -1,5 +1,6 @@ import { existsSync } from "node:fs"; import { dirname, join, relative } from "node:path"; + const PUSH_WRAPPER_CANDIDATES = [ join("scripts", "ace_safe_push.sh"), join("scripts", "safe_push.sh"), diff --git a/src/resources/extensions/sf/requirement-promoter.js b/src/resources/extensions/sf/requirement-promoter.js index d32b43707..391896971 100644 --- a/src/resources/extensions/sf/requirement-promoter.js +++ b/src/resources/extensions/sf/requirement-promoter.js @@ -15,6 +15,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { sfRoot } from "./paths.js"; import { markResolved, readAllSelfFeedback } from "./self-feedback.js"; + // ─── Constants ─────────────────────────────────────────────────────────────── const COUNT_THRESHOLD = 5; const MILESTONE_THRESHOLD = 3; diff --git a/src/resources/extensions/sf/roadmap-slices.js b/src/resources/extensions/sf/roadmap-slices.js index 60996854c..af7ead7b9 100644 --- a/src/resources/extensions/sf/roadmap-slices.js +++ b/src/resources/extensions/sf/roadmap-slices.js @@ -244,7 +244,6 @@ function parseProseSliceHeaders(content) { let match; // Check for checkmark before the slice ID (e.g., "## checkmark S01: Title") const prefixCheckPattern = /^\s*#{1,4}\s+\*{0,2}[\u2713\u2705]\s+/; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = headerPattern.exec(content)) !== null) { const id = match[1]; let title = match[2] diff --git a/src/resources/extensions/sf/routing-history.js b/src/resources/extensions/sf/routing-history.js index a7e1cc5ee..850072a98 100644 --- a/src/resources/extensions/sf/routing-history.js +++ b/src/resources/extensions/sf/routing-history.js @@ -4,6 +4,7 @@ import { join } from "node:path"; import { loadJsonFile, saveJsonFile } from "./json-persistence.js"; import { sfRuntimeRoot } from "./paths.js"; + // ─── Constants ─────────────────────────────────────────────────────────────── const HISTORY_FILE = "routing-history.json"; const ROLLING_WINDOW = 50; // only consider last N entries per pattern diff --git a/src/resources/extensions/sf/run-manager.js b/src/resources/extensions/sf/run-manager.js index 513abf492..79c1cbd44 100644 --- a/src/resources/extensions/sf/run-manager.js +++ b/src/resources/extensions/sf/run-manager.js @@ -28,6 +28,7 @@ import { validateDefinition, } from "./definition-loader.js"; import { initializeGraph, readGraph, writeGraph } from "./graph.js"; + // ─── Constants ─────────────────────────────────────────────────────────── const RUNS_DIR = "workflow-runs"; const DEFS_DIR = "workflow-defs"; diff --git a/src/resources/extensions/sf/safety/evidence-collector.js b/src/resources/extensions/sf/safety/evidence-collector.js index 764698c08..3cada98d6 100644 --- a/src/resources/extensions/sf/safety/evidence-collector.js +++ b/src/resources/extensions/sf/safety/evidence-collector.js @@ -11,16 +11,18 @@ * Follows the same module-level Map pattern as auto-tool-tracking.ts. * Copyright (c) 2026 Jeremy McSpadden */ + +import { randomBytes } from "node:crypto"; import { existsSync, mkdirSync, readFileSync, - writeFileSync, renameSync, unlinkSync, + writeFileSync, } from "node:fs"; -import { join, dirname } from "node:path"; -import { randomBytes } from "node:crypto"; +import { dirname, join } from "node:path"; + // ─── Module State ─────────────────────────────────────────────────────────── let unitEvidence = []; // ─── Public API ───────────────────────────────────────────────────────────── @@ -165,7 +167,7 @@ export function recordToolCall(toolCallId, toolName, input) { * at dispatch time) and fills in exit code + output. Prior versions matched * by `kind + empty-string` which corrupted parallel tool calls. */ -export function recordToolResult(toolCallId, toolName, result, isError) { +export function recordToolResult(toolCallId, _toolName, result, isError) { const entry = unitEvidence.find((e) => e.toolCallId === toolCallId); if (!entry) return; if (entry.kind === "bash") { diff --git a/src/resources/extensions/sf/safety/git-checkpoint.js b/src/resources/extensions/sf/safety/git-checkpoint.js index 7f30e2758..5a1faa3b7 100644 --- a/src/resources/extensions/sf/safety/git-checkpoint.js +++ b/src/resources/extensions/sf/safety/git-checkpoint.js @@ -9,6 +9,7 @@ */ import { execFileSync } from "node:child_process"; import { logWarning } from "../workflow-logger.js"; + // ─── Constants ────────────────────────────────────────────────────────────── const CHECKPOINT_PREFIX = "refs/sf/checkpoints/"; /** diff --git a/src/resources/extensions/sf/scaffold-drift.js b/src/resources/extensions/sf/scaffold-drift.js index b3f929cf4..5171096eb 100644 --- a/src/resources/extensions/sf/scaffold-drift.js +++ b/src/resources/extensions/sf/scaffold-drift.js @@ -17,6 +17,7 @@ import { stampScaffoldFile, } from "./scaffold-versioning.js"; import { logWarning } from "./workflow-logger.js"; + /** * Files in `SCAFFOLD_FILES` that intentionally carry no inline marker. * Per ADR-021 §2, the `.siftignore` file uses the manifest as its diff --git a/src/resources/extensions/sf/scaffold-keeper.js b/src/resources/extensions/sf/scaffold-keeper.js index f4ce466c5..fb2080fe0 100644 --- a/src/resources/extensions/sf/scaffold-keeper.js +++ b/src/resources/extensions/sf/scaffold-keeper.js @@ -28,6 +28,7 @@ import { dirname, join } from "node:path"; import { SCAFFOLD_FILES } from "./agentic-docs-scaffold.js"; import { detectScaffoldDrift } from "./scaffold-drift.js"; import { logWarning } from "./workflow-logger.js"; + /** * Build the .proposed body shipped by the Phase-D stub: the current scaffold * template body, prefixed with a structured `` block diff --git a/src/resources/extensions/sf/schedule/schedule-milestone.js b/src/resources/extensions/sf/schedule/schedule-milestone.js index c61d1385e..27e8227cb 100644 --- a/src/resources/extensions/sf/schedule/schedule-milestone.js +++ b/src/resources/extensions/sf/schedule/schedule-milestone.js @@ -8,8 +8,8 @@ * Consumer: plan-milestone.js and complete-milestone.js handlers. */ import { createScheduleStore } from "./schedule-store.js"; -import { generateULID } from "./schedule-ulid.js"; import { isValidKind } from "./schedule-types.js"; +import { generateULID } from "./schedule-ulid.js"; /** * Convert a ScheduleSpec into a ScheduleEntry and append it to the store. diff --git a/src/resources/extensions/sf/schemas/validate.js b/src/resources/extensions/sf/schemas/validate.js index 3abfac02e..00d8be6d8 100644 --- a/src/resources/extensions/sf/schemas/validate.js +++ b/src/resources/extensions/sf/schemas/validate.js @@ -6,6 +6,7 @@ // auto-start to catch malformed artifacts early. import { existsSync, readFileSync } from "node:fs"; import { parseProject, parseRequirements, parseRoadmap } from "./parsers.js"; + const REQUIRED_PROJECT_SECTIONS = [ "What This Is", "Core Value", diff --git a/src/resources/extensions/sf/self-feedback-drain.js b/src/resources/extensions/sf/self-feedback-drain.js index 5032fee10..5a81c77c4 100644 --- a/src/resources/extensions/sf/self-feedback-drain.js +++ b/src/resources/extensions/sf/self-feedback-drain.js @@ -22,6 +22,7 @@ import { readAllSelfFeedback, readUpstreamSelfFeedback, } from "./self-feedback.js"; + const CLAIM_TTL_MS = 30 * 60 * 1000; function claimPath(basePath) { return join( diff --git a/src/resources/extensions/sf/self-feedback.js b/src/resources/extensions/sf/self-feedback.js index 478b839ca..1fab49751 100644 --- a/src/resources/extensions/sf/self-feedback.js +++ b/src/resources/extensions/sf/self-feedback.js @@ -38,6 +38,7 @@ import { import { homedir } from "node:os"; import { dirname, join } from "node:path"; import { resolveMilestoneFile, sfRuntimeRoot } from "./paths.js"; + const SF_HOME = process.env.SF_HOME || join(homedir(), ".sf"); const SELF_FEEDBACK_HEADER = "# SF Self-Feedback\n\n" + diff --git a/src/resources/extensions/sf/service-tier.js b/src/resources/extensions/sf/service-tier.js index 8be9f7aed..8ecbd4852 100644 --- a/src/resources/extensions/sf/service-tier.js +++ b/src/resources/extensions/sf/service-tier.js @@ -18,6 +18,7 @@ import { loadEffectiveSFPreferences, loadGlobalSFPreferences, } from "./preferences.js"; + const SERVICE_TIER_SCOPE_NOTE = "Only affects gpt-5.4 models, regardless of provider."; // ─── Gating ────────────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/session-forensics.js b/src/resources/extensions/sf/session-forensics.js index 1ad79536e..3f16069fd 100644 --- a/src/resources/extensions/sf/session-forensics.js +++ b/src/resources/extensions/sf/session-forensics.js @@ -27,6 +27,7 @@ import { } from "./native-git-bridge.js"; import { nativeParseJsonlTail } from "./native-parser-bridge.js"; import { sfRuntimeRoot } from "./paths.js"; + // ─── JSONL Parsing ──────────────────────────────────────────────────────────── // MAX_JSONL_BYTES and parseJSONL are imported from ./jsonl-utils.js /** diff --git a/src/resources/extensions/sf/session-lock.js b/src/resources/extensions/sf/session-lock.js index f5c3a8cd2..4c728e703 100644 --- a/src/resources/extensions/sf/session-lock.js +++ b/src/resources/extensions/sf/session-lock.js @@ -28,6 +28,7 @@ import { createRequire } from "node:module"; import { dirname, join } from "node:path"; import { atomicWriteSync } from "./atomic-write.js"; import { sfRoot } from "./paths.js"; + const _require = createRequire(import.meta.url); // ─── Module State ─────────────────────────────────────────────────────────── /** Release function from proper-lockfile — calling it releases the OS lock. */ diff --git a/src/resources/extensions/sf/session-status-io.js b/src/resources/extensions/sf/session-status-io.js index fdc6bc6e4..aeaad4f38 100644 --- a/src/resources/extensions/sf/session-status-io.js +++ b/src/resources/extensions/sf/session-status-io.js @@ -13,6 +13,7 @@ import { existsSync, mkdirSync, readdirSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { loadJsonFileOrNull, writeJsonFileAtomic } from "./json-persistence.js"; import { sfRoot } from "./paths.js"; + // ─── Constants ───────────────────────────────────────────────────────────── const PARALLEL_DIR = "parallel"; const STATUS_SUFFIX = ".status.json"; diff --git a/src/resources/extensions/sf/sf-db.js b/src/resources/extensions/sf/sf-db.js index 9da410385..b3bfb5531 100644 --- a/src/resources/extensions/sf/sf-db.js +++ b/src/resources/extensions/sf/sf-db.js @@ -24,6 +24,7 @@ import { DatabaseSync } from "node:sqlite"; import { SF_STALE_STATE, SFError } from "./errors.js"; import { getGateIdsForTurn } from "./gate-registry.js"; import { logError, logWarning } from "./workflow-logger.js"; + let loadAttempted = false; function loadProvider() { if (loadAttempted) return; @@ -2768,7 +2769,6 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) { // Sanitize path: reject any characters that could break ATTACH syntax. // ATTACH DATABASE doesn't support parameterized paths in all providers, // so we use strict allowlist validation instead. - // biome-ignore lint/suspicious/noControlCharactersInRegex: null byte check for SQL safety if (/['";\x00]/.test(worktreeDbPath)) { logError( "db", diff --git a/src/resources/extensions/sf/skill-discovery.js b/src/resources/extensions/sf/skill-discovery.js index df0de659e..33e690bfd 100644 --- a/src/resources/extensions/sf/skill-discovery.js +++ b/src/resources/extensions/sf/skill-discovery.js @@ -10,6 +10,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; + /** Skills directories — skills.sh ecosystem + Claude Code official + legacy Pi */ const SKILLS_DIR = join(homedir(), ".agents", "skills"); const CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills"); diff --git a/src/resources/extensions/sf/skill-health.js b/src/resources/extensions/sf/skill-health.js index 6f29d9df8..658d6a10e 100644 --- a/src/resources/extensions/sf/skill-health.js +++ b/src/resources/extensions/sf/skill-health.js @@ -17,6 +17,7 @@ import { homedir } from "node:os"; import { join } from "node:path"; import { formatCost, formatTokenCount, loadLedgerFromDisk } from "./metrics.js"; import { detectStaleSkills } from "./skill-telemetry.js"; + // ─── Constants ──────────────────────────────────────────────────────────────── /** Default staleness threshold in days */ const DEFAULT_STALE_DAYS = 60; diff --git a/src/resources/extensions/sf/skill-manifest.js b/src/resources/extensions/sf/skill-manifest.js index 3c1f69a39..49eaf1241 100644 --- a/src/resources/extensions/sf/skill-manifest.js +++ b/src/resources/extensions/sf/skill-manifest.js @@ -15,6 +15,7 @@ // Additional unit types can be added incrementally; each addition is a pure // data change with no wiring cost. import { logWarning } from "./workflow-logger.js"; + /** Normalize a skill reference the same way callers do (lowercase, trim). */ function normalize(name) { return name.trim().toLowerCase(); diff --git a/src/resources/extensions/sf/skill-telemetry.js b/src/resources/extensions/sf/skill-telemetry.js index 2a24b7ece..97f445015 100644 --- a/src/resources/extensions/sf/skill-telemetry.js +++ b/src/resources/extensions/sf/skill-telemetry.js @@ -13,6 +13,7 @@ import { existsSync, readdirSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; + // ─── In-memory state ────────────────────────────────────────────────────────── /** Skills available in the system prompt for the current unit */ let availableSkills = []; diff --git a/src/resources/extensions/sf/slice-cadence.js b/src/resources/extensions/sf/slice-cadence.js index 5806d6b8f..f2421170a 100644 --- a/src/resources/extensions/sf/slice-cadence.js +++ b/src/resources/extensions/sf/slice-cadence.js @@ -38,6 +38,7 @@ import { emitMilestoneResquash, emitSliceMerged, } from "./worktree-telemetry.js"; + /** * Auto-worktree milestone branch name. Must match autoWorktreeBranch() in * auto-worktree.ts; duplicated here to avoid a cyclic import. diff --git a/src/resources/extensions/sf/slice-parallel-conflict.js b/src/resources/extensions/sf/slice-parallel-conflict.js index f713f666a..c7e46b679 100644 --- a/src/resources/extensions/sf/slice-parallel-conflict.js +++ b/src/resources/extensions/sf/slice-parallel-conflict.js @@ -9,6 +9,7 @@ */ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; + // ─── File Path Extraction ───────────────────────────────────────────────────── /** * Extract file paths from a PLAN.md content string. diff --git a/src/resources/extensions/sf/slice-parallel-orchestrator.js b/src/resources/extensions/sf/slice-parallel-orchestrator.js index 416bc9612..0e2d9157b 100644 --- a/src/resources/extensions/sf/slice-parallel-orchestrator.js +++ b/src/resources/extensions/sf/slice-parallel-orchestrator.js @@ -27,6 +27,7 @@ import { removeWorktree, worktreePath, } from "./worktree-manager.js"; + // ─── Module State ────────────────────────────────────────────────────────── let sliceState = null; // ─── Public API ──────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/state.js b/src/resources/extensions/sf/state.js index 11d9aa394..2c90b4582 100644 --- a/src/resources/extensions/sf/state.js +++ b/src/resources/extensions/sf/state.js @@ -116,7 +116,7 @@ export function isValidationTerminal(validationContent) { const CACHE_TTL_MS = 5000; let _stateCache = null; // ── Telemetry counters for derive-path observability ──────────────────────── -let _telemetry = { dbDeriveCount: 0, markdownDeriveCount: 0 }; +const _telemetry = { dbDeriveCount: 0, markdownDeriveCount: 0 }; /** * Invalidate the deriveState() cache. Call this whenever planning files on disk * may have changed (unit completion, merges, file writes). diff --git a/src/resources/extensions/sf/sync-lock.js b/src/resources/extensions/sf/sync-lock.js index 7138cb384..42349d7b4 100644 --- a/src/resources/extensions/sf/sync-lock.js +++ b/src/resources/extensions/sf/sync-lock.js @@ -5,6 +5,7 @@ import { existsSync, statSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { atomicWriteSync } from "./atomic-write.js"; + const STALE_THRESHOLD_MS = 60_000; // 60 seconds const DEFAULT_TIMEOUT_MS = 5_000; // 5 seconds const SPIN_INTERVAL_MS = 100; // 100ms polling interval diff --git a/src/resources/extensions/sf/tests/agentic-docs-scaffold.test.mjs b/src/resources/extensions/sf/tests/agentic-docs-scaffold.test.mjs new file mode 100644 index 000000000..c7df6b7f9 --- /dev/null +++ b/src/resources/extensions/sf/tests/agentic-docs-scaffold.test.mjs @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { + existsSync, + mkdirSync, + mkdtempSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { afterEach, test } from "vitest"; +import { ensureAgenticDocsScaffold } from "../agentic-docs-scaffold.js"; +import { stampScaffoldFile } from "../scaffold-versioning.js"; + +const tmpRoots = []; + +afterEach(() => { + for (const dir of tmpRoots.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } +}); + +function makeProject() { + const root = mkdtempSync(join(tmpdir(), "sf-agentic-docs-scaffold-")); + tmpRoots.push(root); + return root; +} + +function writeLegacyPendingScaffold(root, relPath) { + const target = join(root, relPath); + mkdirSync(dirname(target), { recursive: true }); + writeFileSync(target, `# ${relPath}\n`, "utf-8"); + stampScaffoldFile(target, relPath, "2.75.0", "pending"); +} + +test("ensureAgenticDocsScaffold_removes_owned_root_harness_and_writes_sf_harness", () => { + const root = makeProject(); + writeLegacyPendingScaffold(root, "harness/specs/bootstrap.md"); + + ensureAgenticDocsScaffold(root); + + assert.equal(existsSync(join(root, "harness")), false); + assert.equal(existsSync(join(root, ".sf/harness/specs/bootstrap.md")), true); +}); diff --git a/src/resources/extensions/sf/tests/auto-post-unit-staging.test.mjs b/src/resources/extensions/sf/tests/auto-post-unit-staging.test.mjs index 2ee73a2ef..5b6560104 100644 --- a/src/resources/extensions/sf/tests/auto-post-unit-staging.test.mjs +++ b/src/resources/extensions/sf/tests/auto-post-unit-staging.test.mjs @@ -5,10 +5,11 @@ * first segment is `.sf`, preventing those paths from reaching nativeAddPaths. * Consumer: CI gate via `npx vitest run ...`. */ -import { describe, expect, test, vi } from "vitest"; + import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { describe, expect, test, vi } from "vitest"; // ─── Mock nativeAddPaths to capture what reaches it ──────────────────────── diff --git a/src/resources/extensions/sf/tests/bootstrap-workflow-high.test.mjs b/src/resources/extensions/sf/tests/bootstrap-workflow-high.test.mjs index a1a92a778..dac3632ef 100644 --- a/src/resources/extensions/sf/tests/bootstrap-workflow-high.test.mjs +++ b/src/resources/extensions/sf/tests/bootstrap-workflow-high.test.mjs @@ -6,10 +6,11 @@ * * Consumer: CI gate via `npx vitest run bootstrap-workflow-high`. */ -import { describe, expect, test, vi } from "vitest"; -import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from "node:fs"; + +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { describe, expect, test, vi } from "vitest"; // ─── Hoisted mocks ───────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/tests/bootstrap-workflow-medium.test.mjs b/src/resources/extensions/sf/tests/bootstrap-workflow-medium.test.mjs index 5aefde7b5..66bb5edc6 100644 --- a/src/resources/extensions/sf/tests/bootstrap-workflow-medium.test.mjs +++ b/src/resources/extensions/sf/tests/bootstrap-workflow-medium.test.mjs @@ -4,16 +4,17 @@ * Purpose: prevent regression on MEDIUM bugs in the bootstrap+workflow cluster. * Consumer: CI gate via `npx vitest run bootstrap-workflow-medium`. */ -import { describe, expect, test, vi } from "vitest"; + import { + chmodSync, mkdirSync, mkdtempSync, - writeFileSync, rmSync, - chmodSync, + writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { describe, expect, test } from "vitest"; // ─── Helpers ─────────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/tests/code-intelligence-sift.test.mjs b/src/resources/extensions/sf/tests/code-intelligence-sift.test.mjs index 57816908d..3e839df41 100644 --- a/src/resources/extensions/sf/tests/code-intelligence-sift.test.mjs +++ b/src/resources/extensions/sf/tests/code-intelligence-sift.test.mjs @@ -15,9 +15,9 @@ import { buildSiftEnv, detectSift, ensureSiftRuntimeDirs, + resolveEffectiveCodebaseIndexerBackendName, resolveSiftSearchScope, resolveSiftWarmupRuntimeDirs, - resolveEffectiveCodebaseIndexerBackendName, } from "../code-intelligence.js"; const tmpRoots = []; diff --git a/src/resources/extensions/sf/tests/commands-schedule.test.mjs b/src/resources/extensions/sf/tests/commands-schedule.test.mjs index 9772c9808..84875cb86 100644 --- a/src/resources/extensions/sf/tests/commands-schedule.test.mjs +++ b/src/resources/extensions/sf/tests/commands-schedule.test.mjs @@ -11,7 +11,7 @@ import { mkdirSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, it } from "vitest"; -import { parseDuration, handleSchedule } from "../commands-schedule.js"; +import { handleSchedule, parseDuration } from "../commands-schedule.js"; function mockCtx() { const notifications = []; diff --git a/src/resources/extensions/sf/tests/file-change-validator-sf.test.mjs b/src/resources/extensions/sf/tests/file-change-validator-sf.test.mjs index ba44faa5b..0e597921d 100644 --- a/src/resources/extensions/sf/tests/file-change-validator-sf.test.mjs +++ b/src/resources/extensions/sf/tests/file-change-validator-sf.test.mjs @@ -12,7 +12,9 @@ import { validateStagedFileChanges } from "../safety/file-change-validator.js"; vi.mock("../workflow-logger.js", () => ({ logWarning: vi.fn(), })); + import { logWarning } from "../workflow-logger.js"; + const mockLogWarning = vi.mocked(logWarning); // ─── Tests ───────────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/tests/memory-state-cache.test.mjs b/src/resources/extensions/sf/tests/memory-state-cache.test.mjs index 46cf93410..56a2f9f1b 100644 --- a/src/resources/extensions/sf/tests/memory-state-cache.test.mjs +++ b/src/resources/extensions/sf/tests/memory-state-cache.test.mjs @@ -4,17 +4,18 @@ * Purpose: prevent regression on the memory+state+cache cluster fixes. * Consumer: CI gate via `npm run test:unit -- 'memory-state-cache'`. */ -import { describe, expect, test, vi } from "vitest"; + import { + existsSync, mkdirSync, mkdtempSync, - writeFileSync, - rmSync, readFileSync, - existsSync, + rmSync, + writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; -import { join, dirname } from "node:path"; +import { dirname, join } from "node:path"; +import { describe, expect, test, vi } from "vitest"; // ─── Helpers ─────────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/tests/native-git-bridge-add-skip.test.mjs b/src/resources/extensions/sf/tests/native-git-bridge-add-skip.test.mjs index 1970e15f3..c350e9ee3 100644 --- a/src/resources/extensions/sf/tests/native-git-bridge-add-skip.test.mjs +++ b/src/resources/extensions/sf/tests/native-git-bridge-add-skip.test.mjs @@ -5,16 +5,17 @@ * `.sf`, regardless of whether `.sf` is a real directory or a symlink. * Consumer: CI gate via `npx vitest run ...`. */ -import { describe, expect, test, vi } from "vitest"; + import { mkdirSync, mkdtempSync, - symlinkSync, rmSync, + symlinkSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { describe, expect, test, vi } from "vitest"; // ─── Hoisted mock for gitFileExec so we can capture calls ───────────────── diff --git a/src/resources/extensions/sf/tests/notification-detection-headless-high.test.mjs b/src/resources/extensions/sf/tests/notification-detection-headless-high.test.mjs index 5b407a985..1c9d9d05b 100644 --- a/src/resources/extensions/sf/tests/notification-detection-headless-high.test.mjs +++ b/src/resources/extensions/sf/tests/notification-detection-headless-high.test.mjs @@ -1,21 +1,21 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { - mapStatusToExitCode, - EXIT_SUCCESS, - EXIT_ERROR, EXIT_BLOCKED, EXIT_CANCELLED, + EXIT_ERROR, EXIT_RELOAD, + EXIT_SUCCESS, + mapStatusToExitCode, } from "../../../../../dist/headless-events.js"; +import { detectProjectSignals } from "../detection.js"; import { - appendNotification, _resetNotificationStore, + appendNotification, initNotificationStore, } from "../notification-store.js"; -import { detectProjectSignals } from "../detection.js"; -import { mkdirSync, writeFileSync, rmSync, readFileSync } from "node:fs"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; describe("S08 HIGH: notification + detection + headless", () => { describe("EXIT_RELOAD exit code mapping", () => { diff --git a/src/resources/extensions/sf/tests/notification-detection-headless-medium-low.test.mjs b/src/resources/extensions/sf/tests/notification-detection-headless-medium-low.test.mjs index 43ab8b8b6..cb4539d0c 100644 --- a/src/resources/extensions/sf/tests/notification-detection-headless-medium-low.test.mjs +++ b/src/resources/extensions/sf/tests/notification-detection-headless-medium-low.test.mjs @@ -1,13 +1,13 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { isMilestoneReadyText } from "../../../../../dist/headless-events.js"; import { - appendNotification, _resetNotificationStore, + appendNotification, initNotificationStore, } from "../notification-store.js"; -import { mkdirSync, writeFileSync, rmSync, readFileSync } from "node:fs"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; describe("S08 MEDIUM: notification + detection + headless", () => { describe("stale lock NaN detection", () => { diff --git a/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs b/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs index 216c3f95b..9581af34e 100644 --- a/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs +++ b/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs @@ -11,12 +11,12 @@ import { mkdirSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, it } from "vitest"; +import { createScheduleStore } from "../schedule/schedule-store.js"; +import { generateULID } from "../schedule/schedule-ulid.js"; import { printScheduleBanner, showScheduleBanner, } from "../schedule-launch-banner.js"; -import { createScheduleStore } from "../schedule/schedule-store.js"; -import { generateULID } from "../schedule/schedule-ulid.js"; function captureStderr(fn) { const chunks = []; diff --git a/src/resources/extensions/sf/tests/worktree-fixes.test.mjs b/src/resources/extensions/sf/tests/worktree-fixes.test.mjs index 4e473b986..fb49aeff0 100644 --- a/src/resources/extensions/sf/tests/worktree-fixes.test.mjs +++ b/src/resources/extensions/sf/tests/worktree-fixes.test.mjs @@ -4,18 +4,19 @@ * Purpose: prevent regression on the worktree+git cluster fixes. * Consumer: CI gate via `npm run test:unit -- 'worktree-fixes'`. */ -import { describe, expect, test, vi } from "vitest"; + import { mkdirSync, mkdtempSync, - writeFileSync, - symlinkSync, - rmSync, readFileSync, + rmSync, + symlinkSync, + writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; -import { join, dirname } from "node:path"; +import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; +import { describe, expect, test, vi } from "vitest"; // ─── Top-level mock state for native-git-bridge.js ───────────────────────── diff --git a/src/resources/extensions/sf/tools/complete-milestone.js b/src/resources/extensions/sf/tools/complete-milestone.js index 704da7a84..5bf44e8a0 100644 --- a/src/resources/extensions/sf/tools/complete-milestone.js +++ b/src/resources/extensions/sf/tools/complete-milestone.js @@ -9,6 +9,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { clearParseCache, saveFile } from "../files.js"; import { clearPathCache, resolveMilestonePath } from "../paths.js"; +import { checkSafeIds } from "../safety/safe-id.js"; import { appendScheduleSpecs } from "../schedule/schedule-milestone.js"; import { getMilestone, @@ -17,7 +18,6 @@ import { transaction, updateMilestoneStatus, } from "../sf-db.js"; -import { checkSafeIds } from "../safety/safe-id.js"; import { invalidateStateCache } from "../state.js"; import { isClosedStatus } from "../status-guards.js"; import { extractVerdict } from "../verdict-parser.js"; @@ -28,6 +28,7 @@ import { renderAllProjections, stripIdPrefix, } from "../workflow-projections.js"; + function renderMilestoneSummaryMarkdown(params) { const now = new Date().toISOString(); const displayTitle = stripIdPrefix(params.title, params.milestoneId); diff --git a/src/resources/extensions/sf/tools/complete-slice.js b/src/resources/extensions/sf/tools/complete-slice.js index 4a3e2d156..8a3243c30 100644 --- a/src/resources/extensions/sf/tools/complete-slice.js +++ b/src/resources/extensions/sf/tools/complete-slice.js @@ -37,6 +37,7 @@ import { appendEvent } from "../workflow-events.js"; import { logError, logWarning } from "../workflow-logger.js"; import { writeManifest } from "../workflow-manifest.js"; import { renderAllProjections } from "../workflow-projections.js"; + async function ensureWritableParent(filePath) { const parentDir = dirname(filePath); await fs.mkdir(parentDir, { recursive: true }); diff --git a/src/resources/extensions/sf/tools/complete-task.js b/src/resources/extensions/sf/tools/complete-task.js index 5ee4fa03d..852203fef 100644 --- a/src/resources/extensions/sf/tools/complete-task.js +++ b/src/resources/extensions/sf/tools/complete-task.js @@ -41,6 +41,7 @@ import { renderAllProjections, renderSummaryContent, } from "../workflow-projections.js"; + /** * Map an execute-task-owned gate id to the CompleteTaskParams field whose * presence drives `pass` vs. `omitted`. Keep in lockstep with the gates @@ -62,7 +63,7 @@ function taskGateFieldForId(id, params) { * Normalize a list parameter that may arrive as a string (newline-delimited * bullet list from the LLM) into a string array (#3361). */ -function normalizeListParam(value, field = "list") { +function normalizeListParam(value, _field = "list") { if (Array.isArray(value)) return value.map(String); if (typeof value === "string" && value.trim()) { return value diff --git a/src/resources/extensions/sf/tools/memory-tools.js b/src/resources/extensions/sf/tools/memory-tools.js index ed468f2fc..df2439def 100644 --- a/src/resources/extensions/sf/tools/memory-tools.js +++ b/src/resources/extensions/sf/tools/memory-tools.js @@ -10,6 +10,7 @@ import { reinforceMemory, } from "../memory-store.js"; import { isDbAvailable } from "../sf-db.js"; + function dbUnavailable(operation) { return { content: [ diff --git a/src/resources/extensions/sf/tools/plan-milestone.js b/src/resources/extensions/sf/tools/plan-milestone.js index 4c384576d..4f8cffa78 100644 --- a/src/resources/extensions/sf/tools/plan-milestone.js +++ b/src/resources/extensions/sf/tools/plan-milestone.js @@ -24,6 +24,7 @@ import { logWarning } from "../workflow-logger.js"; import { writeManifest } from "../workflow-manifest.js"; import { renderAllProjections } from "../workflow-projections.js"; import { scaffoldMilestoneSlices } from "../workflow-templates.js"; + function validateRiskEntries(value) { if (!Array.isArray(value)) { throw new Error("keyRisks must be an array"); diff --git a/src/resources/extensions/sf/tools/plan-slice.js b/src/resources/extensions/sf/tools/plan-slice.js index cb53c2086..9074a1eca 100644 --- a/src/resources/extensions/sf/tools/plan-slice.js +++ b/src/resources/extensions/sf/tools/plan-slice.js @@ -21,6 +21,7 @@ import { appendEvent } from "../workflow-events.js"; import { logWarning } from "../workflow-logger.js"; import { writeManifest } from "../workflow-manifest.js"; import { renderAllProjections } from "../workflow-projections.js"; + const PLANNING_MEETING_REQUIRED_MESSAGE = "planningMeeting must be a populated object — write at least 2-3 perspectives. Skipping is not allowed."; function validateTasks(value) { diff --git a/src/resources/extensions/sf/tools/plan-task.js b/src/resources/extensions/sf/tools/plan-task.js index 11458a108..a51be83e6 100644 --- a/src/resources/extensions/sf/tools/plan-task.js +++ b/src/resources/extensions/sf/tools/plan-task.js @@ -18,6 +18,7 @@ import { appendEvent } from "../workflow-events.js"; import { logWarning } from "../workflow-logger.js"; import { writeManifest } from "../workflow-manifest.js"; import { renderAllProjections } from "../workflow-projections.js"; + function validateParams(params) { if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required"); diff --git a/src/resources/extensions/sf/tools/reassess-roadmap.js b/src/resources/extensions/sf/tools/reassess-roadmap.js index f2c468951..086374022 100644 --- a/src/resources/extensions/sf/tools/reassess-roadmap.js +++ b/src/resources/extensions/sf/tools/reassess-roadmap.js @@ -24,6 +24,7 @@ import { appendEvent } from "../workflow-events.js"; import { logWarning } from "../workflow-logger.js"; import { writeManifest } from "../workflow-manifest.js"; import { renderAllProjections } from "../workflow-projections.js"; + function validateParams(params) { if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required"); diff --git a/src/resources/extensions/sf/tools/replan-slice.js b/src/resources/extensions/sf/tools/replan-slice.js index b00d17c81..a696fafd2 100644 --- a/src/resources/extensions/sf/tools/replan-slice.js +++ b/src/resources/extensions/sf/tools/replan-slice.js @@ -26,6 +26,7 @@ import { appendEvent } from "../workflow-events.js"; import { logWarning } from "../workflow-logger.js"; import { writeManifest } from "../workflow-manifest.js"; import { renderAllProjections } from "../workflow-projections.js"; + function validateParams(params) { if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required"); diff --git a/src/resources/extensions/sf/tools/sift-search-tool.js b/src/resources/extensions/sf/tools/sift-search-tool.js index e934cb004..9d0082757 100644 --- a/src/resources/extensions/sf/tools/sift-search-tool.js +++ b/src/resources/extensions/sf/tools/sift-search-tool.js @@ -18,7 +18,7 @@ import { resolveSiftSearchScope, } from "../code-intelligence.js"; -const KNOWN_STRATEGIES = [ +const _KNOWN_STRATEGIES = [ "hybrid", "page-index-hybrid", "path-hybrid", @@ -113,7 +113,7 @@ function parseSiftOutput(rawStdout, rawStderr) { function runSift(binaryPath, args, timeoutMs, projectRoot) { return new Promise((resolve, reject) => { ensureSiftRuntimeDirs(projectRoot); - const child = execFile( + const _child = execFile( binaryPath, args, { diff --git a/src/resources/extensions/sf/tools/validate-milestone.js b/src/resources/extensions/sf/tools/validate-milestone.js index 1ed1e29ab..19d5c4f9f 100644 --- a/src/resources/extensions/sf/tools/validate-milestone.js +++ b/src/resources/extensions/sf/tools/validate-milestone.js @@ -29,6 +29,7 @@ import { } from "../verdict-parser.js"; import { logWarning } from "../workflow-logger.js"; import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js"; + function renderValidationMarkdown(params) { let md = `--- verdict: ${params.verdict} diff --git a/src/resources/extensions/sf/triage-self-feedback.js b/src/resources/extensions/sf/triage-self-feedback.js index 5f1bca7c8..bf7bcc81b 100644 --- a/src/resources/extensions/sf/triage-self-feedback.js +++ b/src/resources/extensions/sf/triage-self-feedback.js @@ -14,6 +14,7 @@ import { readAllSelfFeedback, readUpstreamSelfFeedback, } from "./self-feedback.js"; + /** * Read all open (unresolved) feedback entries from the feedback channel. */ diff --git a/src/resources/extensions/sf/triage-ui.js b/src/resources/extensions/sf/triage-ui.js index 64ff56033..cfe3b6554 100644 --- a/src/resources/extensions/sf/triage-ui.js +++ b/src/resources/extensions/sf/triage-ui.js @@ -11,6 +11,7 @@ import { showNextAction } from "../shared/tui.js"; import { markCaptureResolved } from "./captures.js"; import { ensureDeferMilestoneDir } from "./triage-resolution.js"; + // ─── Classification Labels ──────────────────────────────────────────────────── const CLASSIFICATION_LABELS = { "quick-task": { diff --git a/src/resources/extensions/sf/unit-ownership.js b/src/resources/extensions/sf/unit-ownership.js index 6f33b58a2..5dbb3c785 100644 --- a/src/resources/extensions/sf/unit-ownership.js +++ b/src/resources/extensions/sf/unit-ownership.js @@ -14,6 +14,7 @@ import { mkdirSync } from "node:fs"; import { join } from "node:path"; import { DatabaseSync } from "node:sqlite"; + let loadAttempted = false; function loadProvider() { if (loadAttempted) return; diff --git a/src/resources/extensions/sf/unit-runtime.js b/src/resources/extensions/sf/unit-runtime.js index 7ccc75a59..0598b3245 100644 --- a/src/resources/extensions/sf/unit-runtime.js +++ b/src/resources/extensions/sf/unit-runtime.js @@ -538,7 +538,7 @@ export function reconcileStaleCompleteSliceRecords(basePath) { _runtimeCache.delete(join(dir, file)); cleared++; details.push(`${record.unitId} (was ${state.status})`); - } catch (err) { + } catch (_err) { // Non-fatal — record stays, but at least we tried } } diff --git a/src/resources/extensions/sf/uok/audit.js b/src/resources/extensions/sf/uok/audit.js index 441482b82..36cb56d8d 100644 --- a/src/resources/extensions/sf/uok/audit.js +++ b/src/resources/extensions/sf/uok/audit.js @@ -11,6 +11,7 @@ import { isStaleWrite } from "../auto/turn-epoch.js"; import { withFileLockSync } from "../file-lock.js"; import { sfRuntimeRoot } from "../paths.js"; import { insertAuditEvent, isDbAvailable } from "../sf-db.js"; + function auditLogPath(basePath) { return join(sfRuntimeRoot(basePath), "audit", "events.jsonl"); } diff --git a/src/resources/extensions/sf/uok/flags.js b/src/resources/extensions/sf/uok/flags.js index 24f9b9bfa..257835aab 100644 --- a/src/resources/extensions/sf/uok/flags.js +++ b/src/resources/extensions/sf/uok/flags.js @@ -1,4 +1,5 @@ import { loadEffectiveSFPreferences } from "../preferences.js"; + function envForcesLegacyFallback() { const raw = process.env.SF_UOK_FORCE_LEGACY ?? process.env.SF_UOK_LEGACY_FALLBACK; diff --git a/src/resources/extensions/sf/uok/gate-runner.js b/src/resources/extensions/sf/uok/gate-runner.js index 83359cb01..92aabbc99 100644 --- a/src/resources/extensions/sf/uok/gate-runner.js +++ b/src/resources/extensions/sf/uok/gate-runner.js @@ -1,5 +1,6 @@ import { insertGateRun } from "../sf-db.js"; import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js"; + const RETRY_MATRIX = { none: 0, policy: 0, diff --git a/src/resources/extensions/sf/uok/kernel.js b/src/resources/extensions/sf/uok/kernel.js index 2d7080b04..1cac2c72a 100644 --- a/src/resources/extensions/sf/uok/kernel.js +++ b/src/resources/extensions/sf/uok/kernel.js @@ -9,6 +9,7 @@ import { signalKernelEnter, } from "./parity-diff-capture.js"; import { writeParityHeartbeat, writeParityReport } from "./parity-report.js"; + function refreshParityReport(basePath) { try { return writeParityReport(basePath); diff --git a/src/resources/extensions/sf/uok/parity-diff-capture.js b/src/resources/extensions/sf/uok/parity-diff-capture.js index be73e16d7..0f5a4f0b6 100644 --- a/src/resources/extensions/sf/uok/parity-diff-capture.js +++ b/src/resources/extensions/sf/uok/parity-diff-capture.js @@ -15,6 +15,7 @@ */ import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js"; import { captureParityDiff } from "./parity-report.js"; + // ── Session-scoped commit-block flag ──────────────────────────────────────── // Set to true when a critical plane divergence is detected. Persists for the // lifetime of the session; the next session_start resets it. Stored in module diff --git a/src/resources/extensions/sf/uok/parity-report.js b/src/resources/extensions/sf/uok/parity-report.js index 7b7b9db7e..7ab171319 100644 --- a/src/resources/extensions/sf/uok/parity-report.js +++ b/src/resources/extensions/sf/uok/parity-report.js @@ -8,6 +8,7 @@ import { } from "node:fs"; import { join } from "node:path"; import { sfRoot } from "../paths.js"; + function parityLogPath(basePath) { return join(sfRoot(basePath), "runtime", "uok-parity.jsonl"); } diff --git a/src/resources/extensions/sf/uok/plan-v2.js b/src/resources/extensions/sf/uok/plan-v2.js index ff21a9f88..ef2d1c373 100644 --- a/src/resources/extensions/sf/uok/plan-v2.js +++ b/src/resources/extensions/sf/uok/plan-v2.js @@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { resolveMilestoneFile, resolveSliceFile, sfRoot } from "../paths.js"; import { getMilestoneSlices, getSliceTasks, isDbAvailable } from "../sf-db.js"; + const PLAN_V2_CLARIFY_ROUND_LIMIT = 3; export const EXECUTION_ENTRY_PHASES = new Set([ "executing", diff --git a/src/resources/extensions/sf/uok/writer.js b/src/resources/extensions/sf/uok/writer.js index fec6a932c..a7a670c09 100644 --- a/src/resources/extensions/sf/uok/writer.js +++ b/src/resources/extensions/sf/uok/writer.js @@ -1,8 +1,9 @@ +import { randomUUID } from "node:crypto"; import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; -import { randomUUID } from "node:crypto"; import { atomicWriteSync } from "../atomic-write.js"; import { sfRoot } from "../paths.js"; + const activeTokens = new Map(); function tokenKey(basePath, turnId) { return `${basePath}:${turnId}`; diff --git a/src/resources/extensions/sf/upstream-bridge.js b/src/resources/extensions/sf/upstream-bridge.js index 0b136a09a..39063a4ce 100644 --- a/src/resources/extensions/sf/upstream-bridge.js +++ b/src/resources/extensions/sf/upstream-bridge.js @@ -12,6 +12,7 @@ import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; import { readAllSelfFeedback, recordSelfFeedback } from "./self-feedback.js"; + // ─── Constants ──────────────────────────────────────────────────────────────── const SEVERITY_ORDER = ["low", "medium", "high", "critical"]; const ROLLUP_CAP = "medium"; diff --git a/src/resources/extensions/sf/validate-directory.js b/src/resources/extensions/sf/validate-directory.js index a470a03ee..32d5cd712 100644 --- a/src/resources/extensions/sf/validate-directory.js +++ b/src/resources/extensions/sf/validate-directory.js @@ -7,6 +7,7 @@ import { readdirSync, realpathSync } from "node:fs"; import { homedir, platform, tmpdir } from "node:os"; import { resolve } from "node:path"; + // ─── Blocked Paths ────────────────────────────────────────────────────────────── /** Paths where SF must never create .sf/ — no override possible. */ const UNIX_BLOCKED_PATHS = new Set([ diff --git a/src/resources/extensions/sf/verification-gate.js b/src/resources/extensions/sf/verification-gate.js index 3c2c46cee..900bd51cf 100644 --- a/src/resources/extensions/sf/verification-gate.js +++ b/src/resources/extensions/sf/verification-gate.js @@ -7,6 +7,7 @@ import { existsSync, readFileSync } from "node:fs"; import { basename, join } from "node:path"; import { rewriteCommandWithRtk } from "../shared/rtk.js"; import { DEFAULT_COMMAND_TIMEOUT_MS } from "./constants.js"; + /** Maximum bytes of stdout/stderr to retain per command (10 KB). */ const MAX_OUTPUT_BYTES = 10 * 1024; /** Truncate a string to maxBytes, appending a marker if truncated. */ diff --git a/src/resources/extensions/sf/visualizer-overlay.js b/src/resources/extensions/sf/visualizer-overlay.js index b8e1ab200..abd0d9f1f 100644 --- a/src/resources/extensions/sf/visualizer-overlay.js +++ b/src/resources/extensions/sf/visualizer-overlay.js @@ -22,6 +22,7 @@ import { renderProgressView, renderTimelineView, } from "./visualizer-views.js"; + const TAB_COUNT = 10; const TAB_LABELS = [ "1 Progress", @@ -111,7 +112,6 @@ export class SFVisualizerOverlay { }, 30_000); } parseSGRMouse(data) { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI SGR mouse sequence const match = data.match(/^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/); if (!match) return null; return { diff --git a/src/resources/extensions/sf/visualizer-views.js b/src/resources/extensions/sf/visualizer-views.js index 62bcb30af..05507c3d1 100644 --- a/src/resources/extensions/sf/visualizer-views.js +++ b/src/resources/extensions/sf/visualizer-views.js @@ -9,6 +9,7 @@ import { sparkline, } from "../shared/mod.js"; import { classifyUnitPhase, formatCost, formatTokenCount } from "./metrics.js"; + function formatCompletionDate(input) { if (!input) return "unknown"; const parsed = new Date(input); diff --git a/src/resources/extensions/sf/watch/header-renderer.js b/src/resources/extensions/sf/watch/header-renderer.js index 8baa10d5b..365b6079e 100644 --- a/src/resources/extensions/sf/watch/header-renderer.js +++ b/src/resources/extensions/sf/watch/header-renderer.js @@ -6,6 +6,7 @@ import { homedir } from "node:os"; import { join } from "node:path"; import { truncateToWidth, visibleWidth } from "@singularity-forge/pi-tui"; import { loadEffectiveSFPreferences } from "../preferences.js"; + // ─── Constants ──────────────────────────────────────────────────────────────── /** * SF ASCII logo — inlined here because the canonical src/logo.ts is outside diff --git a/src/resources/extensions/sf/workflow-dispatch.js b/src/resources/extensions/sf/workflow-dispatch.js index d3a2f867c..28c8e23cd 100644 --- a/src/resources/extensions/sf/workflow-dispatch.js +++ b/src/resources/extensions/sf/workflow-dispatch.js @@ -7,6 +7,7 @@ */ import { readFileSync } from "node:fs"; import { loadPrompt } from "./prompt-loader.js"; + // ─── Oneshot dispatch ──────────────────────────────────────────────────── /** * Strip the `` block from markdown content so it's not diff --git a/src/resources/extensions/sf/workflow-events.js b/src/resources/extensions/sf/workflow-events.js index b890efb32..67d604824 100644 --- a/src/resources/extensions/sf/workflow-events.js +++ b/src/resources/extensions/sf/workflow-events.js @@ -2,9 +2,10 @@ import { createHash, randomUUID } from "node:crypto"; import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { atomicWriteSync } from "./atomic-write.js"; -import { sfRuntimeRoot } from "./paths.js"; import { withFileLockSync } from "./file-lock.js"; +import { sfRuntimeRoot } from "./paths.js"; import { logWarning } from "./workflow-logger.js"; + // ─── Session ID ─────────────────────────────────────────────────────────── /** * Engine-generated session ID — stable for the lifetime of this process. @@ -57,7 +58,7 @@ export function readEvents(logPath) { const line = lines[i]; try { events.push(JSON.parse(line)); - } catch (err) { + } catch (_err) { corruptCount++; const snippet = line.slice(0, 80); logWarning( diff --git a/src/resources/extensions/sf/workflow-install.js b/src/resources/extensions/sf/workflow-install.js index c58ffb981..a12fe8573 100644 --- a/src/resources/extensions/sf/workflow-install.js +++ b/src/resources/extensions/sf/workflow-install.js @@ -13,6 +13,8 @@ * records source URL, timestamp, and sha256 so `/sf workflow uninstall` can * clean up and future `/sf workflow update` can refresh. */ + +import { createHash } from "node:crypto"; import { existsSync, mkdirSync, @@ -21,11 +23,11 @@ import { unlinkSync, writeFileSync, } from "node:fs"; -import { extname, join, resolve, sep as pathSep } from "node:path"; import { homedir } from "node:os"; -import { createHash } from "node:crypto"; +import { extname, join, sep as pathSep, resolve } from "node:path"; import { parse as parseYaml } from "yaml"; import { validateDefinition } from "./definition-loader.js"; + // ─── Constants ─────────────────────────────────────────────────────────── const MAX_RESPONSE_BYTES = 256 * 1024; const FETCH_TIMEOUT_MS = 15_000; @@ -163,7 +165,7 @@ export async function fetchWorkflowSource(url) { pathname = new URL(url).pathname; } let filebasename = pathname.slice(pathname.lastIndexOf("/") + 1); - let rawExt = extname(filebasename).toLowerCase(); + const rawExt = extname(filebasename).toLowerCase(); let ext; if (rawExt === ".yaml" || rawExt === ".yml" || rawExt === ".md") { ext = rawExt; diff --git a/src/resources/extensions/sf/workflow-logger.js b/src/resources/extensions/sf/workflow-logger.js index 8cdaa8aca..227bec6b5 100644 --- a/src/resources/extensions/sf/workflow-logger.js +++ b/src/resources/extensions/sf/workflow-logger.js @@ -28,6 +28,7 @@ import { withFileLockSync } from "./file-lock.js"; import { appendNotification } from "./notification-store.js"; import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js"; import { isAuditEnvelopeEnabled } from "./uok/audit-toggle.js"; + // ─── Buffer & Persistent Audit ────────────────────────────────────────── const MAX_BUFFER = 100; let _buffer = []; diff --git a/src/resources/extensions/sf/workflow-manifest.js b/src/resources/extensions/sf/workflow-manifest.js index b6d21bfd3..9e129a36e 100644 --- a/src/resources/extensions/sf/workflow-manifest.js +++ b/src/resources/extensions/sf/workflow-manifest.js @@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { atomicWriteSync } from "./atomic-write.js"; import { _getAdapter, readTransaction, restoreManifest } from "./sf-db.js"; + // ─── helpers ───────────────────────────────────────────────────────────── /** * Get the database adapter or throw if no database is open. diff --git a/src/resources/extensions/sf/workflow-mcp-auto-prep.js b/src/resources/extensions/sf/workflow-mcp-auto-prep.js index d6c9b73fe..dd148b2a8 100644 --- a/src/resources/extensions/sf/workflow-mcp-auto-prep.js +++ b/src/resources/extensions/sf/workflow-mcp-auto-prep.js @@ -1,5 +1,6 @@ import { ensureProjectWorkflowMcpConfig } from "./mcp-project-config.js"; import { usesWorkflowMcpTransport } from "./workflow-mcp.js"; + function getAuthModeSafe(ctx, provider) { if (!provider) return undefined; const getAuthMode = ctx.modelRegistry?.getProviderAuthMode; diff --git a/src/resources/extensions/sf/workflow-mcp.js b/src/resources/extensions/sf/workflow-mcp.js index dff039726..334765587 100644 --- a/src/resources/extensions/sf/workflow-mcp.js +++ b/src/resources/extensions/sf/workflow-mcp.js @@ -2,6 +2,7 @@ import { execSync } from "node:child_process"; import { existsSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; + const MCP_WORKFLOW_TOOL_SURFACE = new Set([ "ask_user_questions", "sf_decision_save", diff --git a/src/resources/extensions/sf/workflow-plugins.js b/src/resources/extensions/sf/workflow-plugins.js index 96cf092dc..b68eaa196 100644 --- a/src/resources/extensions/sf/workflow-plugins.js +++ b/src/resources/extensions/sf/workflow-plugins.js @@ -12,11 +12,12 @@ * * Precedence: project > global > bundled. Same-named file wins. */ -import { readFileSync, readdirSync, existsSync, statSync } from "node:fs"; -import { join, extname, basename } from "node:path"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { homedir } from "node:os"; +import { basename, extname, join } from "node:path"; import { parse as parseYaml } from "yaml"; import { loadRegistry } from "./workflow-templates.js"; + // ─── Path resolution ───────────────────────────────────────────────────── const sfHome = process.env.SF_HOME || join(homedir(), ".sf"); function resolveBundledDir() { diff --git a/src/resources/extensions/sf/workflow-templates.js b/src/resources/extensions/sf/workflow-templates.js index a9a44758c..5ff47f325 100644 --- a/src/resources/extensions/sf/workflow-templates.js +++ b/src/resources/extensions/sf/workflow-templates.js @@ -7,6 +7,7 @@ import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; + const __extensionDir = resolveSfExtensionDir(); const registryPath = join( __extensionDir, diff --git a/src/resources/extensions/sf/workspace-index.js b/src/resources/extensions/sf/workspace-index.js index 49c4f86c7..ed87af90e 100644 --- a/src/resources/extensions/sf/workspace-index.js +++ b/src/resources/extensions/sf/workspace-index.js @@ -11,6 +11,7 @@ import { getMilestoneSlices, getSliceTasks, isDbAvailable } from "./sf-db.js"; import { deriveState } from "./state.js"; import { extractVerdict } from "./verdict-parser.js"; import { detectWorktreeName, getSliceBranchName } from "./worktree.js"; + // Extract milestone title from roadmap header without using parsers. // Falls back to the milestone ID if no title line found. function titleFromRoadmapHeader(content, fallbackId) { diff --git a/src/resources/extensions/sf/worktree-command-bootstrap.js b/src/resources/extensions/sf/worktree-command-bootstrap.js index 96e2d12f7..f23e4153f 100644 --- a/src/resources/extensions/sf/worktree-command-bootstrap.js +++ b/src/resources/extensions/sf/worktree-command-bootstrap.js @@ -1,4 +1,5 @@ import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; + const WORKTREE_SUBCOMMANDS = [ { cmd: "list", desc: "List existing worktrees" }, { cmd: "merge", desc: "Merge a worktree into a target branch" }, diff --git a/src/resources/extensions/sf/worktree-command.js b/src/resources/extensions/sf/worktree-command.js index 2af84523f..c8a69b4e3 100644 --- a/src/resources/extensions/sf/worktree-command.js +++ b/src/resources/extensions/sf/worktree-command.js @@ -41,6 +41,7 @@ import { worktreeBranchName, worktreePath, } from "./worktree-manager.js"; + /** * Tracks the original project root so we can switch back. * Set when we first chdir into a worktree, cleared on return. diff --git a/src/resources/extensions/sf/worktree-health.js b/src/resources/extensions/sf/worktree-health.js index facf9f009..10cb8fa2a 100644 --- a/src/resources/extensions/sf/worktree-health.js +++ b/src/resources/extensions/sf/worktree-health.js @@ -16,6 +16,7 @@ import { nativeWorkingTreeStatus, } from "./native-git-bridge.js"; import { listWorktrees } from "./worktree-manager.js"; + // ─── Configuration ───────────────────────────────────────────────────────── /** Default number of days without commits before a worktree is considered stale. */ const DEFAULT_STALE_DAYS = 14; diff --git a/src/resources/extensions/sf/worktree-root.js b/src/resources/extensions/sf/worktree-root.js index d1de1cdd6..ff4628347 100644 --- a/src/resources/extensions/sf/worktree-root.js +++ b/src/resources/extensions/sf/worktree-root.js @@ -1,6 +1,7 @@ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; + function sfHome() { return process.env.SF_HOME || join(homedir(), ".sf"); } diff --git a/src/resources/extensions/sf/worktree-telemetry.js b/src/resources/extensions/sf/worktree-telemetry.js index bd04ad739..d80d1ec1d 100644 --- a/src/resources/extensions/sf/worktree-telemetry.js +++ b/src/resources/extensions/sf/worktree-telemetry.js @@ -20,6 +20,7 @@ */ import { randomUUID } from "node:crypto"; import { emitJournalEvent, queryJournal } from "./journal.js"; + function now() { return new Date().toISOString(); } diff --git a/src/resources/extensions/sf/worktree.js b/src/resources/extensions/sf/worktree.js index f4221e838..55cd869a0 100644 --- a/src/resources/extensions/sf/worktree.js +++ b/src/resources/extensions/sf/worktree.js @@ -16,7 +16,9 @@ import { homedir } from "node:os"; import { join, resolve } from "node:path"; import { GitServiceImpl, writeIntegrationBranch } from "./git-service.js"; import { loadEffectiveSFPreferences } from "./preferences.js"; + export { MergeConflictError } from "./git-service.js"; + // ─── Lazy GitServiceImpl Cache ───────────────────────────────────────────── let cachedService = null; let cachedBasePath = null; @@ -232,6 +234,7 @@ export function getSliceBranchName(milestoneId, sliceId, worktreeName) { } /** Re-export for backward compatibility — canonical definition in branch-patterns.ts */ export { SLICE_BRANCH_RE } from "./branch-patterns.js"; + import { SLICE_BRANCH_RE } from "./branch-patterns.js"; /** * Parse a slice branch name into its components. diff --git a/src/resources/extensions/sf/write-intercept.js b/src/resources/extensions/sf/write-intercept.js index 1ef00912d..e193494f4 100644 --- a/src/resources/extensions/sf/write-intercept.js +++ b/src/resources/extensions/sf/write-intercept.js @@ -3,6 +3,7 @@ // an error directing the agent to use the engine tool API instead. import { realpathSync } from "node:fs"; import { resolve } from "node:path"; + /** * Patterns matching authoritative .sf/ state files that agents must NOT write directly. * diff --git a/src/resources/extensions/shared/format-utils.js b/src/resources/extensions/shared/format-utils.js index e3cb974b0..8e2309304 100644 --- a/src/resources/extensions/shared/format-utils.js +++ b/src/resources/extensions/shared/format-utils.js @@ -75,7 +75,6 @@ export function fileLink(filePath, displayText) { // ─── ANSI Stripping ─────────────────────────────────────────────────────────── /** Strip ANSI escape sequences from a string. */ export function stripAnsi(s) { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequence return s.replace(/\x1b\[[0-9;]*m/g, ""); } // ─── String Array Normalization ───────────────────────────────────────────── diff --git a/src/resources/extensions/shared/interview-ui.js b/src/resources/extensions/shared/interview-ui.js index 0498fed65..329cac0cd 100644 --- a/src/resources/extensions/shared/interview-ui.js +++ b/src/resources/extensions/shared/interview-ui.js @@ -31,6 +31,7 @@ import { truncateToWidth, } from "@singularity-forge/pi-tui"; import { INDENT, makeUI } from "./ui.js"; + // ─── Constants ──────────────────────────────────────────────────────────────── const OTHER_OPTION_LABEL = "None of the above"; const OTHER_OPTION_DESCRIPTION = "Select to type your own answer."; diff --git a/src/resources/extensions/shared/notify.js b/src/resources/extensions/shared/notify.js index 950b4f306..cc54ab9d9 100644 --- a/src/resources/extensions/shared/notify.js +++ b/src/resources/extensions/shared/notify.js @@ -9,6 +9,7 @@ import * as fsPromises from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { promisify } from "node:util"; + const execAsync = promisify(child_process.exec); export const BEEP_SOUNDS = [ "Tink", diff --git a/src/resources/extensions/shared/rtk-session-stats.js b/src/resources/extensions/shared/rtk-session-stats.js index c5115da03..1b169a189 100644 --- a/src/resources/extensions/shared/rtk-session-stats.js +++ b/src/resources/extensions/shared/rtk-session-stats.js @@ -4,6 +4,7 @@ import { join } from "node:path"; import { sfRoot } from "../sf/paths.js"; import { formatTokenCount } from "./format-utils.js"; import { buildRtkEnv, isRtkEnabled, resolveRtkBinaryPath } from "./rtk.js"; + const SESSION_BASELINES_FILE = "rtk-session-baselines.json"; const CURRENT_SUMMARY_TTL_MS = 15_000; const CURRENT_SUMMARY_TIMEOUT_MS = 5_000; diff --git a/src/resources/extensions/shared/rtk.js b/src/resources/extensions/shared/rtk.js index 2a68437ac..873e75e0c 100644 --- a/src/resources/extensions/shared/rtk.js +++ b/src/resources/extensions/shared/rtk.js @@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process"; import { existsSync } from "node:fs"; import { homedir } from "node:os"; import { delimiter, join } from "node:path"; + const SF_RTK_PATH_ENV = "SF_RTK_PATH"; const SF_RTK_DISABLED_ENV = "SF_RTK_DISABLED"; const SF_RTK_REWRITE_TIMEOUT_MS_ENV = "SF_RTK_REWRITE_TIMEOUT_MS"; diff --git a/src/resources/extensions/shared/sanitize.js b/src/resources/extensions/shared/sanitize.js index d5087f127..dd936fe64 100644 --- a/src/resources/extensions/shared/sanitize.js +++ b/src/resources/extensions/shared/sanitize.js @@ -3,6 +3,7 @@ * Also provides maskEditorLine for masking sensitive TUI editor input. */ import { CURSOR_MARKER } from "@singularity-forge/pi-tui"; + const TOKEN_PATTERNS = [ /xoxb-[A-Za-z0-9-]+/g, // Slack bot tokens /xoxp-[A-Za-z0-9-]+/g, // Slack user tokens @@ -33,7 +34,6 @@ export function maskEditorLine(line) { i += CURSOR_MARKER.length; continue; } - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequence const ansiMatch = /^\x1b\[[0-9;]*m/.exec(line.slice(i)); if (ansiMatch) { output += ansiMatch[0]; diff --git a/src/resources/extensions/subagent/agents.js b/src/resources/extensions/subagent/agents.js index 59f97609b..9e40ea53a 100644 --- a/src/resources/extensions/subagent/agents.js +++ b/src/resources/extensions/subagent/agents.js @@ -7,6 +7,7 @@ import { getAgentDir, parseFrontmatter, } from "@singularity-forge/pi-coding-agent"; + const PROJECT_AGENT_DIR_CANDIDATES = [".sf", ".pi"]; export function parseConflictsWith(value) { if (typeof value !== "string") return undefined; diff --git a/src/resources/extensions/subagent/index.js b/src/resources/extensions/subagent/index.js index aa4b2ef59..06caba7f6 100644 --- a/src/resources/extensions/subagent/index.js +++ b/src/resources/extensions/subagent/index.js @@ -39,6 +39,7 @@ import { readIsolationMode, } from "./isolation.js"; import { registerWorker, updateWorker } from "./worker-registry.js"; + const MAX_PARALLEL_TASKS = 8; const MAX_CONCURRENCY = 4; const COLLAPSED_ITEM_COUNT = 10; diff --git a/src/resources/extensions/subagent/isolation.js b/src/resources/extensions/subagent/isolation.js index 5e259bb8a..4c532b7ec 100644 --- a/src/resources/extensions/subagent/isolation.js +++ b/src/resources/extensions/subagent/isolation.js @@ -10,6 +10,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { promisify } from "node:util"; + const execFile = promisify(execFileCb); // ============================================================================ // Directory helpers diff --git a/src/resources/extensions/ttsr/index.js b/src/resources/extensions/ttsr/index.js index a80807d18..fe639fad5 100644 --- a/src/resources/extensions/ttsr/index.js +++ b/src/resources/extensions/ttsr/index.js @@ -16,6 +16,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { loadRules } from "./rule-loader.js"; import { TtsrManager } from "./ttsr-manager.js"; + const __dirname = import.meta.dirname; function buildInterruptContent(rule) { const template = readFileSync(join(__dirname, "ttsr-interrupt.md"), "utf-8"); @@ -69,6 +70,7 @@ function extractDeltaContext(event) { } return null; } + export { loadRules } from "./rule-loader.js"; // Re-exports for external consumers export { TtsrManager } from "./ttsr-manager.js"; diff --git a/src/resources/extensions/ttsr/rule-loader.js b/src/resources/extensions/ttsr/rule-loader.js index 14de42bae..36e9de643 100644 --- a/src/resources/extensions/ttsr/rule-loader.js +++ b/src/resources/extensions/ttsr/rule-loader.js @@ -8,11 +8,14 @@ import { existsSync, readdirSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { basename, join } from "node:path"; + const sfHome = process.env.SF_HOME || join(homedir(), ".sf"); + import { parseFrontmatterMap, splitFrontmatter, } from "../shared/frontmatter.js"; + function parseRuleFile(filePath) { let content; try { diff --git a/src/resources/extensions/ttsr/ttsr-manager.js b/src/resources/extensions/ttsr/ttsr-manager.js index 8cfa1cd91..2a98f110a 100644 --- a/src/resources/extensions/ttsr/ttsr-manager.js +++ b/src/resources/extensions/ttsr/ttsr-manager.js @@ -11,6 +11,7 @@ */ import { createRequire } from "node:module"; import { debugCount, debugPeak, debugTime } from "../sf/debug-logger.js"; + const _require = createRequire(import.meta.url); const picomatch = _require("picomatch"); // ── Native TTSR engine (optional) ───────────────────────────────────── diff --git a/src/resources/extensions/universal-config/discovery.js b/src/resources/extensions/universal-config/discovery.js index e666838c9..1ee8e24ef 100644 --- a/src/resources/extensions/universal-config/discovery.js +++ b/src/resources/extensions/universal-config/discovery.js @@ -7,6 +7,7 @@ import { homedir } from "node:os"; import { SCANNERS } from "./scanners.js"; import { TOOLS } from "./tools.js"; + /** Inline concurrency limiter to cap parallel async operations. */ function pLimit(concurrency) { const queue = []; diff --git a/src/resources/extensions/universal-config/format.js b/src/resources/extensions/universal-config/format.js index 15e57ae18..027763924 100644 --- a/src/resources/extensions/universal-config/format.js +++ b/src/resources/extensions/universal-config/format.js @@ -174,7 +174,6 @@ export function formatDiscoveryForCommand(result) { function groupByType(items) { const groups = {}; for (const item of items) { - // biome-ignore lint/suspicious/noAssignInExpressions: intentional group-by idiom (groups[item.type] ??= []).push(item); } return groups; diff --git a/src/resources/extensions/universal-config/index.js b/src/resources/extensions/universal-config/index.js index ed2b77550..0b03b0ae1 100644 --- a/src/resources/extensions/universal-config/index.js +++ b/src/resources/extensions/universal-config/index.js @@ -16,6 +16,7 @@ import { Type } from "@sinclair/typebox"; import { discoverAllConfigs } from "./discovery.js"; import { formatDiscoveryForCommand, formatDiscoveryForTool } from "./format.js"; + // Cache discovery result within a session to avoid re-scanning let cachedResult = null; export default function universalConfig(pi) { diff --git a/src/resources/extensions/universal-config/scanners.js b/src/resources/extensions/universal-config/scanners.js index ebecbcc90..5c5a624c2 100644 --- a/src/resources/extensions/universal-config/scanners.js +++ b/src/resources/extensions/universal-config/scanners.js @@ -9,6 +9,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs"; import { readdir, readFile, stat } from "node:fs/promises"; import { basename, join } from "node:path"; + // ── Helpers ─────────────────────────────────────────────────────────────────── function source(tool, path, level) { return { tool: tool.id, toolName: tool.name, path, level }; diff --git a/src/resources/extensions/vectordrive/manager.js b/src/resources/extensions/vectordrive/manager.js index 235b8a618..82104a7a5 100644 --- a/src/resources/extensions/vectordrive/manager.js +++ b/src/resources/extensions/vectordrive/manager.js @@ -7,6 +7,7 @@ */ import { mkdirSync } from "node:fs"; import { dirname } from "node:path"; + const DB_DIR = ".sf/vectordrive"; const DB_PATH = `${DB_DIR}/forge.vectors`; const DIMENSIONS = 384; @@ -39,7 +40,6 @@ export class VectordriveManager { db = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any vd = null; - constructor() {} static getInstance() { if (!VectordriveManager.instance) { VectordriveManager.instance = new VectordriveManager(); diff --git a/src/resources/extensions/vectordrive/tool-search.js b/src/resources/extensions/vectordrive/tool-search.js index 8741e38e4..f45342ac3 100644 --- a/src/resources/extensions/vectordrive/tool-search.js +++ b/src/resources/extensions/vectordrive/tool-search.js @@ -6,7 +6,7 @@ * when vectordrive is offline. */ import { Type } from "@sinclair/typebox"; -import { VectordriveManager, textToVector } from "./manager.js"; +import { textToVector, VectordriveManager } from "./manager.js"; export function registerVectordriveSearchTool(pi) { pi.registerTool({ name: "vectordrive_search", diff --git a/src/resources/extensions/vectordrive/tool-store.js b/src/resources/extensions/vectordrive/tool-store.js index edd5fb80d..5ae2b3d8f 100644 --- a/src/resources/extensions/vectordrive/tool-store.js +++ b/src/resources/extensions/vectordrive/tool-store.js @@ -4,7 +4,7 @@ * Store a vector with metadata in the native VectorDb. */ import { Type } from "@sinclair/typebox"; -import { VectordriveManager, textToVector } from "./manager.js"; +import { textToVector, VectordriveManager } from "./manager.js"; export function registerVectordriveStoreTool(pi) { pi.registerTool({ name: "vectordrive_store", diff --git a/src/resources/extensions/voice/index.js b/src/resources/extensions/voice/index.js index 0f28f75a6..8c9281c37 100644 --- a/src/resources/extensions/voice/index.js +++ b/src/resources/extensions/voice/index.js @@ -15,6 +15,7 @@ import { ensureVoiceVenv, linuxPython, } from "./linux-ready.js"; + const __extensionDir = import.meta.dirname; const SWIFT_SRC = path.join(__extensionDir, "speech-recognizer.swift"); const RECOGNIZER_BIN = path.join(__extensionDir, "speech-recognizer"); diff --git a/src/startup-model-validation.ts b/src/startup-model-validation.ts index 591079585..813a6b13a 100644 --- a/src/startup-model-validation.ts +++ b/src/startup-model-validation.ts @@ -9,8 +9,8 @@ * user's valid choice to be silently overwritten with a built-in fallback. */ -import { getPiDefaultModelAndProvider } from "./pi-migration.js"; import { error, formatStructuredError } from "./errors.js"; +import { getPiDefaultModelAndProvider } from "./pi-migration.js"; interface MinimalModel { provider: string; @@ -82,7 +82,7 @@ export function validateConfiguredModel( : undefined) || availableModels[0]; if (preferred) { - const reason = !configuredModel + const _reason = !configuredModel ? "no model configured" : `"${configuredProvider}/${configuredModel}" is no longer available`; process.stderr.write( diff --git a/src/tests/app-smoke.test.ts b/src/tests/app-smoke.test.ts index 262eb01f8..c272b4dc6 100644 --- a/src/tests/app-smoke.test.ts +++ b/src/tests/app-smoke.test.ts @@ -22,8 +22,8 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; import { fileURLToPath } from "node:url"; +import { afterEach, test } from "vitest"; const projectRoot = join(fileURLToPath(import.meta.url), "..", "..", ".."); @@ -72,7 +72,7 @@ test("app-paths resolve to ~/.sf/", async () => { // 2. loader env vars // ═══════════════════════════════════════════════════════════════════════════ -test("loader sets all 4 SF_ env vars and PI_PACKAGE_DIR", async (t) => { +test("loader sets all 4 SF_ env vars and PI_PACKAGE_DIR", async (_t) => { // Run loader in a subprocess that prints env vars and exits before TUI starts const script = ` import { fileURLToPath } from 'url'; @@ -345,7 +345,7 @@ test("initResources syncs extensions, agents, and skills to target dir", async ( assertExtensionIndexExists(fakeAgentDir, "sf"); }); -test("initResources skips copy when managed version matches current version", async (t) => { +test("initResources skips copy when managed version matches current version", async (_t) => { const { initResources, readManagedResourceVersion } = await import( "../resource-loader.ts" ); @@ -396,7 +396,7 @@ test("initResources skips copy when managed version matches current version", as // 4. wizard loadStoredEnvKeys hydration // ═══════════════════════════════════════════════════════════════════════════ -test("loadStoredEnvKeys hydrates process.env from auth.json", async (t) => { +test("loadStoredEnvKeys hydrates process.env from auth.json", async (_t) => { const { loadStoredEnvKeys } = await import("../wizard.ts"); const { AuthStorage } = await import("@singularity-forge/pi-coding-agent"); @@ -481,7 +481,7 @@ test("loadStoredEnvKeys hydrates process.env from auth.json", async (t) => { // 5. loadStoredEnvKeys does NOT overwrite existing env vars // ═══════════════════════════════════════════════════════════════════════════ -test("loadStoredEnvKeys does not overwrite existing env vars", async (t) => { +test("loadStoredEnvKeys does not overwrite existing env vars", async (_t) => { const { loadStoredEnvKeys } = await import("../wizard.ts"); const { AuthStorage } = await import("@singularity-forge/pi-coding-agent"); @@ -516,7 +516,7 @@ test("loadStoredEnvKeys does not overwrite existing env vars", async (t) => { // 6. State derivation — Gap 2 // ═══════════════════════════════════════════════════════════════════════════ -test("deriveState returns pre-planning phase for empty .sf/ directory", async (t) => { +test("deriveState returns pre-planning phase for empty .sf/ directory", async (_t) => { const { deriveState } = await import("../resources/extensions/sf/state.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-state-smoke-")); @@ -541,7 +541,7 @@ test("deriveState returns pre-planning phase for empty .sf/ directory", async (t assert.ok(state.nextAction.length > 0, "nextAction is non-empty"); }); -test("deriveState returns pre-planning phase when no .sf/ directory exists", async (t) => { +test("deriveState returns pre-planning phase when no .sf/ directory exists", async (_t) => { const { deriveState } = await import("../resources/extensions/sf/state.ts"); // Use a temp dir with no .sf/ subdirectory at all const tmp = mkdtempSync(join(tmpdir(), "sf-state-nosf-")); @@ -558,7 +558,7 @@ test("deriveState returns pre-planning phase when no .sf/ directory exists", asy assert.equal(state.activeMilestone, null, "no active milestone"); }); -test("deriveState shape is structurally complete", async (t) => { +test("deriveState shape is structurally complete", async (_t) => { const { deriveState } = await import("../resources/extensions/sf/state.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-state-shape-")); mkdirSync(join(tmp, ".sf"), { recursive: true }); @@ -605,7 +605,7 @@ test("deriveState shape is structurally complete", async (t) => { // 7. Doctor health checks — Gap 3 // ═══════════════════════════════════════════════════════════════════════════ -test("runSFDoctor completes without throwing on empty .sf/ directory", async (t) => { +test("runSFDoctor completes without throwing on empty .sf/ directory", async (_t) => { const { runSFDoctor } = await import("../resources/extensions/sf/doctor.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-doctor-smoke-")); mkdirSync(join(tmp, ".sf"), { recursive: true }); @@ -632,7 +632,7 @@ test("runSFDoctor completes without throwing on empty .sf/ directory", async (t) assert.equal(report.fixesApplied.length, 0, "no fixes applied in audit mode"); }); -test("runSFDoctor issue objects have required fields", async (t) => { +test("runSFDoctor issue objects have required fields", async (_t) => { const { runSFDoctor } = await import("../resources/extensions/sf/doctor.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-doctor-fields-")); mkdirSync(join(tmp, ".sf"), { recursive: true }); @@ -665,7 +665,7 @@ test("runSFDoctor issue objects have required fields", async (t) => { } }); -test("runSFDoctor with fix:false never modifies the filesystem", async (t) => { +test("runSFDoctor with fix:false never modifies the filesystem", async (_t) => { const { runSFDoctor } = await import("../resources/extensions/sf/doctor.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-doctor-readonly-")); const sfDir = join(tmp, ".sf"); diff --git a/src/tests/artifact-manager.test.ts b/src/tests/artifact-manager.test.ts index 9712e32b5..9ba8bde60 100644 --- a/src/tests/artifact-manager.test.ts +++ b/src/tests/artifact-manager.test.ts @@ -7,7 +7,7 @@ import assert from "node:assert/strict"; import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { ArtifactManager } from "../../packages/pi-coding-agent/src/core/artifact-manager.ts"; @@ -26,7 +26,7 @@ function makeTmpSession(): { sessionFile: string; cleanup: () => void } { // save / getPath // ═══════════════════════════════════════════════════════════════════════════ -test("save creates artifact file with sequential ID", (t) => { +test("save creates artifact file with sequential ID", (_t) => { const { sessionFile, cleanup } = makeTmpSession(); afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); @@ -45,7 +45,7 @@ test("save creates artifact file with sequential ID", (t) => { assert.equal(readFileSync(path1, "utf-8"), "output 1"); }); -test("artifact directory is named after session file without .jsonl", (t) => { +test("artifact directory is named after session file without .jsonl", (_t) => { const { sessionFile, cleanup } = makeTmpSession(); afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); @@ -53,7 +53,7 @@ test("artifact directory is named after session file without .jsonl", (t) => { assert.equal(mgr.dir, expectedDir); }); -test("artifact directory is created lazily on first write", (t) => { +test("artifact directory is created lazily on first write", (_t) => { const { sessionFile, cleanup } = makeTmpSession(); afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); @@ -68,7 +68,7 @@ test("artifact directory is created lazily on first write", (t) => { // exists // ═══════════════════════════════════════════════════════════════════════════ -test("exists returns true for saved artifact", (t) => { +test("exists returns true for saved artifact", (_t) => { const { sessionFile, cleanup } = makeTmpSession(); afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); @@ -76,7 +76,7 @@ test("exists returns true for saved artifact", (t) => { assert.ok(mgr.exists(id)); }); -test("exists returns false for missing artifact", (t) => { +test("exists returns false for missing artifact", (_t) => { const { sessionFile, cleanup } = makeTmpSession(); afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); @@ -87,7 +87,7 @@ test("exists returns false for missing artifact", (t) => { // allocatePath // ═══════════════════════════════════════════════════════════════════════════ -test("allocatePath returns path without writing", (t) => { +test("allocatePath returns path without writing", (_t) => { const { sessionFile, cleanup } = makeTmpSession(); afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); @@ -103,7 +103,7 @@ test("allocatePath returns path without writing", (t) => { // Session resume — ID continuity // ═══════════════════════════════════════════════════════════════════════════ -test("new manager picks up where previous left off", (t) => { +test("new manager picks up where previous left off", (_t) => { const { sessionFile, cleanup } = makeTmpSession(); afterEach(cleanup); const mgr1 = new ArtifactManager(sessionFile); @@ -121,7 +121,7 @@ test("new manager picks up where previous left off", (t) => { // listFiles // ═══════════════════════════════════════════════════════════════════════════ -test("listFiles returns all artifact filenames", (t) => { +test("listFiles returns all artifact filenames", (_t) => { const { sessionFile, cleanup } = makeTmpSession(); afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); @@ -134,7 +134,7 @@ test("listFiles returns all artifact filenames", (t) => { assert.ok(files.some((f) => f === "1.fetch.log")); }); -test("listFiles returns empty for nonexistent dir", (t) => { +test("listFiles returns empty for nonexistent dir", (_t) => { const { sessionFile, cleanup } = makeTmpSession(); afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); diff --git a/src/tests/auto-mode-piped.test.ts b/src/tests/auto-mode-piped.test.ts index 6a413a40c..f35838561 100644 --- a/src/tests/auto-mode-piped.test.ts +++ b/src/tests/auto-mode-piped.test.ts @@ -9,8 +9,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const projectRoot = join(fileURLToPath(import.meta.url), "..", "..", ".."); diff --git a/src/tests/auto-resume-resource-loader.test.ts b/src/tests/auto-resume-resource-loader.test.ts index 6eba8a744..0a0f7ad92 100644 --- a/src/tests/auto-resume-resource-loader.test.ts +++ b/src/tests/auto-resume-resource-loader.test.ts @@ -4,8 +4,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); const autoTsPath = join( diff --git a/src/tests/bg-shell-session-cleanup.test.ts b/src/tests/bg-shell-session-cleanup.test.ts index 2735e0c26..f6a3c1227 100644 --- a/src/tests/bg-shell-session-cleanup.test.ts +++ b/src/tests/bg-shell-session-cleanup.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { cleanupAll, @@ -22,7 +22,7 @@ function isPidAlive(pid: number | undefined): boolean { // without relying on platform-specific quoting for `node -e "..."` const sleeperCommand = "sleep 30"; -test("cleanupSessionProcesses reaps only session-scoped processes from the previous session", async (t) => { +test("cleanupSessionProcesses reaps only session-scoped processes from the previous session", async (_t) => { afterEach(cleanupAll); const owned = startProcess({ command: sleeperCommand, diff --git a/src/tests/blob-store.test.ts b/src/tests/blob-store.test.ts index 9d01b9a96..3dca27c15 100644 --- a/src/tests/blob-store.test.ts +++ b/src/tests/blob-store.test.ts @@ -8,7 +8,7 @@ import { createHash } from "node:crypto"; import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { BlobStore, @@ -33,7 +33,7 @@ function sha256(data: Buffer): string { // BlobStore.put / get / has // ═══════════════════════════════════════════════════════════════════════════ -test("put stores data and returns correct hash", (t) => { +test("put stores data and returns correct hash", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -45,7 +45,7 @@ test("put stores data and returns correct hash", (t) => { assert.deepEqual(readFileSync(result.path), data); }); -test("put is idempotent — same data returns same hash, no duplicate write", (t) => { +test("put is idempotent — same data returns same hash, no duplicate write", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -57,7 +57,7 @@ test("put is idempotent — same data returns same hash, no duplicate write", (t assert.equal(r1.path, r2.path); }); -test("get retrieves stored data", (t) => { +test("get retrieves stored data", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -68,7 +68,7 @@ test("get retrieves stored data", (t) => { assert.deepEqual(retrieved, data); }); -test("get returns null for nonexistent hash", (t) => { +test("get returns null for nonexistent hash", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -76,7 +76,7 @@ test("get returns null for nonexistent hash", (t) => { assert.equal(store.get(fakeHash), null); }); -test("has returns true for stored blob", (t) => { +test("has returns true for stored blob", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -84,14 +84,14 @@ test("has returns true for stored blob", (t) => { assert.ok(store.has(hash)); }); -test("has returns false for missing blob", (t) => { +test("has returns false for missing blob", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); assert.equal(store.has("b".repeat(64)), false); }); -test("ref property returns correct blob: URI", (t) => { +test("ref property returns correct blob: URI", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -104,7 +104,7 @@ test("ref property returns correct blob: URI", (t) => { // Path traversal protection // ═══════════════════════════════════════════════════════════════════════════ -test("get rejects non-hex hash (path traversal attempt)", (t) => { +test("get rejects non-hex hash (path traversal attempt)", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -113,7 +113,7 @@ test("get rejects non-hex hash (path traversal attempt)", (t) => { assert.equal(store.get("not-a-valid-hash"), null); }); -test("has rejects non-hex hash", (t) => { +test("has rejects non-hex hash", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -122,7 +122,7 @@ test("has rejects non-hex hash", (t) => { assert.equal(store.has("Z".repeat(64)), false); // uppercase not valid }); -test("get rejects hash with wrong length", (t) => { +test("get rejects hash with wrong length", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -160,7 +160,7 @@ test("parseBlobRef rejects invalid hash format", () => { // externalizeImageData / resolveImageData // ═══════════════════════════════════════════════════════════════════════════ -test("externalizeImageData stores base64 and returns blob ref", (t) => { +test("externalizeImageData stores base64 and returns blob ref", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -171,7 +171,7 @@ test("externalizeImageData stores base64 and returns blob ref", (t) => { assert.ok(store.has(parseBlobRef(ref)!)); }); -test("externalizeImageData passes through existing blob refs", (t) => { +test("externalizeImageData passes through existing blob refs", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -179,7 +179,7 @@ test("externalizeImageData passes through existing blob refs", (t) => { assert.equal(externalizeImageData(store, existingRef), existingRef); }); -test("resolveImageData round-trips with externalizeImageData", (t) => { +test("resolveImageData round-trips with externalizeImageData", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); @@ -190,14 +190,14 @@ test("resolveImageData round-trips with externalizeImageData", (t) => { assert.equal(resolved, base64); }); -test("resolveImageData returns non-ref strings unchanged", (t) => { +test("resolveImageData returns non-ref strings unchanged", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); assert.equal(resolveImageData(store, "plain text"), "plain text"); }); -test("resolveImageData returns ref unchanged when blob is missing", (t) => { +test("resolveImageData returns ref unchanged when blob is missing", (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); diff --git a/src/tests/create-sf-extension-paths.test.ts b/src/tests/create-sf-extension-paths.test.ts index f3f1e3756..d5d0c13b9 100644 --- a/src/tests/create-sf-extension-paths.test.ts +++ b/src/tests/create-sf-extension-paths.test.ts @@ -12,8 +12,8 @@ import assert from "node:assert/strict"; import { existsSync, readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { test, describe } from "vitest"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); const skillDir = join( diff --git a/src/tests/extension-discovery.test.ts b/src/tests/extension-discovery.test.ts index cb6cd9287..7e04bcb51 100644 --- a/src/tests/extension-discovery.test.ts +++ b/src/tests/extension-discovery.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, describe, afterEach } from "vitest"; +import { afterEach, describe, test } from "vitest"; import { discoverExtensionEntryPaths, resolveExtensionEntries, @@ -18,7 +18,7 @@ function makeTempDir(): string { } describe("resolveExtensionEntries", () => { - test("returns index.ts when no package.json exists", (t) => { + test("returns index.ts when no package.json exists", (_t) => { const dir = makeTempDir(); afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync(join(dir, "index.ts"), "export default function() {}"); @@ -27,7 +27,7 @@ describe("resolveExtensionEntries", () => { assert.ok(entries[0].endsWith("index.ts")); }); - test("returns index.js when no package.json and no index.ts", (t) => { + test("returns index.js when no package.json and no index.ts", (_t) => { const dir = makeTempDir(); afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync(join(dir, "index.js"), "module.exports = function() {}"); @@ -36,7 +36,7 @@ describe("resolveExtensionEntries", () => { assert.ok(entries[0].endsWith("index.js")); }); - test("returns declared extensions from pi.extensions array", (t) => { + test("returns declared extensions from pi.extensions array", (_t) => { const dir = makeTempDir(); afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync( @@ -52,7 +52,7 @@ describe("resolveExtensionEntries", () => { assert.ok(entries[0].endsWith("main.js")); }); - test("returns empty array when pi manifest has no extensions (library opt-out)", (t) => { + test("returns empty array when pi manifest has no extensions (library opt-out)", (_t) => { const dir = makeTempDir(); afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync( @@ -71,7 +71,7 @@ describe("resolveExtensionEntries", () => { ); }); - test("returns empty array when pi.extensions is an empty array", (t) => { + test("returns empty array when pi.extensions is an empty array", (_t) => { const dir = makeTempDir(); afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync( @@ -85,7 +85,7 @@ describe("resolveExtensionEntries", () => { assert.equal(entries.length, 0); }); - test("falls back to index.ts when package.json has no pi field", (t) => { + test("falls back to index.ts when package.json has no pi field", (_t) => { const dir = makeTempDir(); afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync( @@ -100,7 +100,7 @@ describe("resolveExtensionEntries", () => { }); describe("discoverExtensionEntryPaths", () => { - test("ignores TypeScript declaration files in top-level extensions directory", (t) => { + test("ignores TypeScript declaration files in top-level extensions directory", (_t) => { const root = makeTempDir(); afterEach(() => rmSync(root, { recursive: true, force: true })); writeFileSync(join(root, "ask-user-questions.d.ts"), "export {}"); @@ -116,7 +116,7 @@ describe("discoverExtensionEntryPaths", () => { ]); }); - test("skips library directories with pi: {} opt-out", (t) => { + test("skips library directories with pi: {} opt-out", (_t) => { const root = makeTempDir(); afterEach(() => rmSync(root, { recursive: true, force: true })); // Real extension diff --git a/src/tests/extension-smoke.test.ts b/src/tests/extension-smoke.test.ts index 1d91f9ca1..5f630cfca 100644 --- a/src/tests/extension-smoke.test.ts +++ b/src/tests/extension-smoke.test.ts @@ -13,8 +13,8 @@ import assert from "node:assert/strict"; import { join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath, pathToFileURL } from "node:url"; +import { test } from "vitest"; const projectRoot = join(fileURLToPath(import.meta.url), "..", "..", ".."); const extensionsDir = join(projectRoot, "src", "resources", "extensions"); diff --git a/src/tests/google-search-auth.repro.test.ts b/src/tests/google-search-auth.repro.test.ts index 8da1cfda6..501284ac3 100644 --- a/src/tests/google-search-auth.repro.test.ts +++ b/src/tests/google-search-auth.repro.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import googleSearchExtension from "../resources/extensions/google-search/index.js"; function createMockPI() { @@ -43,7 +43,7 @@ function restoreEnv(name: string, value: string | undefined) { else process.env[name] = value; } -test("fix: google-search uses OAuth if GEMINI_API_KEY is missing", async (t) => { +test("fix: google-search uses OAuth if GEMINI_API_KEY is missing", async (_t) => { const originalKey = process.env.GEMINI_API_KEY; const originalAlias = process.env.GOOGLE_GENERATIVE_AI_API_KEY; delete process.env.GEMINI_API_KEY; @@ -109,7 +109,7 @@ test("fix: google-search uses OAuth if GEMINI_API_KEY is missing", async (t) => assert.ok(result.content[0].text.includes("Mocked AI Answer")); }); -test("google-search warns if NO authentication is present", async (t) => { +test("google-search warns if NO authentication is present", async (_t) => { const originalKey = process.env.GEMINI_API_KEY; const originalAlias = process.env.GOOGLE_GENERATIVE_AI_API_KEY; delete process.env.GEMINI_API_KEY; @@ -148,7 +148,7 @@ test("google-search warns if NO authentication is present", async (t) => { assert.ok(result.content[0].text.includes("No authentication found")); }); -test("google-search uses GEMINI_API_KEY if present (precedence)", async (t) => { +test("google-search uses GEMINI_API_KEY if present (precedence)", async (_t) => { const originalKey = process.env.GEMINI_API_KEY; const originalAlias = process.env.GOOGLE_GENERATIVE_AI_API_KEY; process.env.GEMINI_API_KEY = "mock-api-key"; @@ -184,7 +184,7 @@ test("google-search uses GEMINI_API_KEY if present (precedence)", async (t) => { ); }); -test("google-search accepts GOOGLE_GENERATIVE_AI_API_KEY", async (t) => { +test("google-search accepts GOOGLE_GENERATIVE_AI_API_KEY", async (_t) => { const originalKey = process.env.GEMINI_API_KEY; const originalAlias = process.env.GOOGLE_GENERATIVE_AI_API_KEY; delete process.env.GEMINI_API_KEY; diff --git a/src/tests/google-search-oauth-shape.test.ts b/src/tests/google-search-oauth-shape.test.ts index 827846fc7..8f6ffc2f7 100644 --- a/src/tests/google-search-oauth-shape.test.ts +++ b/src/tests/google-search-oauth-shape.test.ts @@ -11,7 +11,7 @@ */ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import googleSearchExtension from "../resources/extensions/google-search/index.js"; // ── Helpers ───────────────────────────────────────────────────────────────── @@ -80,7 +80,7 @@ function makeOkSSEBody() { // ── Tests ──────────────────────────────────────────────────────────────────── -test("#2963: OAuth fallback URL must include ?alt=sse query parameter", async (t) => { +test("#2963: OAuth fallback URL must include ?alt=sse query parameter", async (_t) => { const originalKey = process.env.GEMINI_API_KEY; delete process.env.GEMINI_API_KEY; const originalFetch = global.fetch; @@ -122,7 +122,7 @@ test("#2963: OAuth fallback URL must include ?alt=sse query parameter", async (t ); }); -test("#2963: OAuth fallback body must include userAgent field", async (t) => { +test("#2963: OAuth fallback body must include userAgent field", async (_t) => { const originalKey = process.env.GEMINI_API_KEY; delete process.env.GEMINI_API_KEY; const originalFetch = global.fetch; @@ -166,7 +166,7 @@ test("#2963: OAuth fallback body must include userAgent field", async (t) => { ); }); -test("#2963: OAuth fallback body must contain google_search tool in correct format", async (t) => { +test("#2963: OAuth fallback body must contain google_search tool in correct format", async (_t) => { const originalKey = process.env.GEMINI_API_KEY; delete process.env.GEMINI_API_KEY; const originalFetch = global.fetch; @@ -211,7 +211,7 @@ test("#2963: OAuth fallback body must contain google_search tool in correct form ); }); -test("#2963: OAuth fallback body has correct top-level structure", async (t) => { +test("#2963: OAuth fallback body has correct top-level structure", async (_t) => { const originalKey = process.env.GEMINI_API_KEY; delete process.env.GEMINI_API_KEY; const originalFetch = global.fetch; diff --git a/src/tests/headless-events.test.ts b/src/tests/headless-events.test.ts index 77c5cc3f7..da9bace4a 100644 --- a/src/tests/headless-events.test.ts +++ b/src/tests/headless-events.test.ts @@ -197,8 +197,8 @@ import { isAutoResumeScheduledNotification, isBlockedNotification, isInteractiveHeadlessTool, - isPauseNotification, isMilestoneReadyText, + isPauseNotification, isTerminalNotification, mapStatusToExitCode, shouldArmHeadlessIdleTimeout, diff --git a/src/tests/headless-multi-turn.test.ts b/src/tests/headless-multi-turn.test.ts index b757b9f8b..ab01ab9f1 100644 --- a/src/tests/headless-multi-turn.test.ts +++ b/src/tests/headless-multi-turn.test.ts @@ -6,8 +6,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); diff --git a/src/tests/headless-query-db-open.test.ts b/src/tests/headless-query-db-open.test.ts index d6c51ee32..17208a9a5 100644 --- a/src/tests/headless-query-db-open.test.ts +++ b/src/tests/headless-query-db-open.test.ts @@ -6,8 +6,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); const src = readFileSync(join(__dirname, "..", "headless-query.ts"), "utf-8"); diff --git a/src/tests/headless-query-extension-path.test.ts b/src/tests/headless-query-extension-path.test.ts index b1c40a208..54f16ac6e 100644 --- a/src/tests/headless-query-extension-path.test.ts +++ b/src/tests/headless-query-extension-path.test.ts @@ -6,8 +6,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); diff --git a/src/tests/initial-sf-header-filter.test.ts b/src/tests/initial-sf-header-filter.test.ts index e227ce465..6375b46bb 100644 --- a/src/tests/initial-sf-header-filter.test.ts +++ b/src/tests/initial-sf-header-filter.test.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { test } from "vitest"; -const { filterInitialSfHeader: filterInitialSfHeader } = await import( +const { filterInitialSfHeader } = await import( "../../web/lib/initial-sf-header-filter.ts" ); diff --git a/src/tests/integration/e2e-headless.test.ts b/src/tests/integration/e2e-headless.test.ts index 412151406..b0d9f3ddd 100644 --- a/src/tests/integration/e2e-headless.test.ts +++ b/src/tests/integration/e2e-headless.test.ts @@ -21,7 +21,7 @@ import { spawn } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const projectRoot = process.cwd(); const loaderPath = join(projectRoot, "dist", "loader.js"); @@ -128,7 +128,6 @@ function spawnSf( /** Strip ANSI escape codes from a string. */ function stripAnsi(s: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequence return s.replace(/\x1b\[[0-9;]*[A-Za-z]/g, ""); } @@ -165,7 +164,7 @@ function assertNoCrashMarkers(output: string): void { // 1. JSON batch mode suppresses streaming — stdout is a single JSON result // =========================================================================== -test("headless --output-format json emits a single HeadlessJsonResult on stdout", async (t) => { +test("headless --output-format json emits a single HeadlessJsonResult on stdout", async (_t) => { const tmpDir = createTempWithSf("sf-e2e-json-batch-"); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); @@ -260,7 +259,7 @@ test("headless --output-format json emits a single HeadlessJsonResult on stdout" // 2. SIGINT produces exit code 11 (EXIT_CANCELLED) // =========================================================================== -test("headless exits with code 11 after SIGINT", async (t) => { +test("headless exits with code 11 after SIGINT", async (_t) => { const tmpDir = createTempWithSf("sf-e2e-sigint-"); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); @@ -337,7 +336,7 @@ test("headless exits with code 11 after SIGINT", async (t) => { // 3. stream-json emits NDJSON on stdout (each line is valid JSON) // =========================================================================== -test("headless --output-format stream-json emits NDJSON on stdout", async (t) => { +test("headless --output-format stream-json emits NDJSON on stdout", async (_t) => { const tmpDir = createTempWithSf("sf-e2e-stream-json-"); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); @@ -399,7 +398,7 @@ test("headless --output-format stream-json emits NDJSON on stdout", async (t) => // 4. --resume with nonexistent ID exits 1 with clean error // =========================================================================== -test("headless --resume with nonexistent ID exits 1 with descriptive error", async (t) => { +test("headless --resume with nonexistent ID exits 1 with descriptive error", async (_t) => { const tmpDir = createTempWithSf("sf-e2e-resume-bad-"); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); @@ -439,7 +438,7 @@ test("headless --resume with nonexistent ID exits 1 with descriptive error", asy // 5. --output-format with invalid value exits 1 with helpful message // =========================================================================== -test("headless --output-format with invalid value exits 1", async (t) => { +test("headless --output-format with invalid value exits 1", async (_t) => { const tmpDir = createTempWithSf("sf-e2e-bad-format-"); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); diff --git a/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts b/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts index a4f4afc3a..e2b235a7a 100644 --- a/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts +++ b/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts @@ -15,7 +15,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { collectAuthoritativeAutoDashboardData } from "../../web/auto-dashboard-service.ts"; @@ -84,7 +84,7 @@ const INACTIVE_PAYLOAD = { // ─── Tests ────────────────────────────────────────────────────────── -test("#2705 regression: subprocess reports active=false but session lock exists with live PID → reconcile to active=true", async (t) => { +test("#2705 regression: subprocess reports active=false but session lock exists with live PID → reconcile to active=true", async (_t) => { const fixture = makeTempFixture(); afterEach(() => fixture.cleanup()); @@ -120,7 +120,7 @@ test("#2705 regression: subprocess reports active=false but session lock exists ); }); -test("#2705: subprocess reports active=false and no session lock → remains inactive", async (t) => { +test("#2705: subprocess reports active=false and no session lock → remains inactive", async (_t) => { const fixture = makeTempFixture(); afterEach(() => fixture.cleanup()); @@ -142,7 +142,7 @@ test("#2705: subprocess reports active=false and no session lock → remains ina assert.equal(result.paused, false); }); -test("#2705: subprocess reports active=false but paused-session.json exists → reconcile to paused=true", async (t) => { +test("#2705: subprocess reports active=false but paused-session.json exists → reconcile to paused=true", async (_t) => { const fixture = makeTempFixture(); afterEach(() => fixture.cleanup()); @@ -174,7 +174,7 @@ test("#2705: subprocess reports active=false but paused-session.json exists → ); }); -test("#2705: subprocess reports active=true → no reconciliation needed", async (t) => { +test("#2705: subprocess reports active=true → no reconciliation needed", async (_t) => { const fixture = makeTempFixture(); afterEach(() => fixture.cleanup()); @@ -207,7 +207,7 @@ test("#2705: subprocess reports active=true → no reconciliation needed", async ); }); -test("#2705: session lock exists but PID is dead → remains inactive (stale lock)", async (t) => { +test("#2705: session lock exists but PID is dead → remains inactive (stale lock)", async (_t) => { const fixture = makeTempFixture(); afterEach(() => fixture.cleanup()); diff --git a/src/tests/integration/web-bridge-contract.test.ts b/src/tests/integration/web-bridge-contract.test.ts index 8ba7158a2..48c8f8f39 100644 --- a/src/tests/integration/web-bridge-contract.test.ts +++ b/src/tests/integration/web-bridge-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -297,7 +297,7 @@ async function readSseEvents( return events; } -test("/api/boot returns current-project workspace data, resumable sessions, onboarding seam, and bridge snapshot", async (t) => { +test("/api/boot returns current-project workspace data, resumable sessions, onboarding seam, and bridge snapshot", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -379,7 +379,7 @@ test("/api/boot returns current-project workspace data, resumable sessions, onbo assert.equal(harness.spawnCalls, 1); }); -test("/api/boot uses the authoritative auto helper by default and stays snapshot-shaped", async (t) => { +test("/api/boot uses the authoritative auto helper by default and stays snapshot-shaped", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -494,7 +494,7 @@ test("/api/boot uses the authoritative auto helper by default and stays snapshot ); }); -test("bridge service is a singleton for the project runtime and /api/session/command forwards real RPC responses", async (t) => { +test("bridge service is a singleton for the project runtime and /api/session/command forwards real RPC responses", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -577,7 +577,7 @@ test("bridge service is a singleton for the project runtime and /api/session/com assert.equal(harness.spawnCalls, 1); }); -test("/api/session/events streams bridge status, agent events, and extension_ui_request payloads over SSE", async (t) => { +test("/api/session/events streams bridge status, agent events, and extension_ui_request payloads over SSE", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -666,7 +666,7 @@ test("/api/session/events streams bridge status, agent events, and extension_ui_ ); }); -test("bridge command/runtime failures are inspectable and redact secret material", async (t) => { +test("bridge command/runtime failures are inspectable and redact secret material", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -771,7 +771,7 @@ test("bridge command/runtime failures are inspectable and redact secret material // (Fixes #1936: /api/boot returns 500 when readdirSync is missing) // --------------------------------------------------------------------------- -test("/api/boot lists sessions from the real filesystem via readdirSync (#1936)", async (t) => { +test("/api/boot lists sessions from the real filesystem via readdirSync (#1936)", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, diff --git a/src/tests/integration/web-bridge-terminal-contract.test.ts b/src/tests/integration/web-bridge-terminal-contract.test.ts index 9745d3610..8f2354037 100644 --- a/src/tests/integration/web-bridge-terminal-contract.test.ts +++ b/src/tests/integration/web-bridge-terminal-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -175,7 +175,7 @@ function createHarness( return harness; } -test("/api/bridge-terminal/stream attaches to the main bridge runtime and forwards native terminal output", async (t) => { +test("/api/bridge-terminal/stream attaches to the main bridge runtime and forwards native terminal output", async (_t) => { const fixture = makeWorkspaceFixture(); const harness = createHarness((command, current) => { if (command.type === "get_state") { @@ -269,7 +269,7 @@ test("/api/bridge-terminal/stream attaches to the main bridge runtime and forwar ); }); -test("bridge-terminal input and resize routes forward browser terminal traffic onto the authoritative bridge session", async (t) => { +test("bridge-terminal input and resize routes forward browser terminal traffic onto the authoritative bridge session", async (_t) => { const fixture = makeWorkspaceFixture(); const harness = createHarness((command, current) => { if (command.type === "get_state") { @@ -368,7 +368,7 @@ test("bridge-terminal input and resize routes forward browser terminal traffic o ); }); -test("session_state_changed from the native main-session TUI refreshes bridge state and emits matching live invalidations", async (t) => { +test("session_state_changed from the native main-session TUI refreshes bridge state and emits matching live invalidations", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionAPath = join(fixture.sessionsDir, "sess-a.jsonl"); const sessionBPath = join(fixture.sessionsDir, "sess-b.jsonl"); diff --git a/src/tests/integration/web-cli-entry.test.ts b/src/tests/integration/web-cli-entry.test.ts index 28c293f76..60ab1e424 100644 --- a/src/tests/integration/web-cli-entry.test.ts +++ b/src/tests/integration/web-cli-entry.test.ts @@ -2,8 +2,8 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; import { pathToFileURL } from "node:url"; +import { afterEach, test } from "vitest"; const { resolveSfCliEntry } = await import("../../web/cli-entry.ts"); @@ -17,7 +17,7 @@ function makeFixture(paths: string[]): string { return root; } -test("resolveSfCliEntry prefers the built loader for packaged standalone interactive sessions", (t) => { +test("resolveSfCliEntry prefers the built loader for packaged standalone interactive sessions", (_t) => { const packageRoot = makeFixture([ "dist/loader.js", "src/loader.ts", @@ -43,7 +43,7 @@ test("resolveSfCliEntry prefers the built loader for packaged standalone interac }); }); -test("resolveSfCliEntry prefers the source loader for source-dev interactive sessions", (t) => { +test("resolveSfCliEntry prefers the source loader for source-dev interactive sessions", (_t) => { const packageRoot = makeFixture([ "dist/loader.js", "src/loader.ts", @@ -84,7 +84,7 @@ test("resolveSfCliEntry prefers the source loader for source-dev interactive ses }); }); -test("resolveSfCliEntry appends rpc arguments for bridge sessions", (t) => { +test("resolveSfCliEntry appends rpc arguments for bridge sessions", (_t) => { const packageRoot = makeFixture(["dist/loader.js"]); afterEach(() => { diff --git a/src/tests/integration/web-live-interaction-contract.test.ts b/src/tests/integration/web-live-interaction-contract.test.ts index f6b924a18..9df19ab5d 100644 --- a/src/tests/integration/web-live-interaction-contract.test.ts +++ b/src/tests/integration/web-live-interaction-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -440,7 +440,7 @@ function routeEvent(state: MinimalLiveState, event: any): MinimalLiveState { // Tests // --------------------------------------------------------------------------- -test("(a) SSE emits extension_ui_request with method 'select' → typed payload with options and allowMultiple", async (t) => { +test("(a) SSE emits extension_ui_request with method 'select' → typed payload with options and allowMultiple", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -558,7 +558,7 @@ test("(b) Multiple concurrent UI requests queue correctly keyed by id", async () assert.equal(state.pendingUiRequests[3].prefill, "initial text"); }); -test("(c) Responding to a UI request posts extension_ui_response with correct id and value to the bridge", async (t) => { +test("(c) Responding to a UI request posts extension_ui_response with correct id and value to the bridge", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -614,7 +614,7 @@ test("(c) Responding to a UI request posts extension_ui_response with correct id assert.equal(uiResponseCmd.value, "option-b"); }); -test("(d) Dismissing a UI request posts cancelled: true and removes from pending", async (t) => { +test("(d) Dismissing a UI request posts cancelled: true and removes from pending", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -689,7 +689,7 @@ test("(d) Dismissing a UI request posts cancelled: true and removes from pending assert.equal(state.pendingUiRequests.length, 0); }); -test("(e) SSE emits message_update with text delta → streamingAssistantText accumulates", async (t) => { +test("(e) SSE emits message_update with text delta → streamingAssistantText accumulates", async (_t) => { let state = createMinimalLiveState(); state = routeEvent(state, { @@ -991,7 +991,7 @@ test("(g-3) tool_execution_start clears provisional streaming text so only post- ]); }); -test("(h) steer and abort commands post the correct RPC command type", async (t) => { +test("(h) steer and abort commands post the correct RPC command type", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -1116,7 +1116,7 @@ test("(failure-path) UI response errors are visible as lastClientError and pendi ); }); -test("(session-controls) browser session RPCs round-trip through /api/session/command", async (t) => { +test("(session-controls) browser session RPCs round-trip through /api/session/command", async (_t) => { const fixture = makeWorkspaceFixture(); const activeSessionPath = createSessionFile( fixture.projectCwd, diff --git a/src/tests/integration/web-live-state-contract.test.ts b/src/tests/integration/web-live-state-contract.test.ts index 47e9c4053..b9417d47d 100644 --- a/src/tests/integration/web-live-state-contract.test.ts +++ b/src/tests/integration/web-live-state-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -383,7 +383,7 @@ async function readSseEventsUntil( throw new Error("Timed out waiting for the expected SSE contract events"); } -test("/api/session/events exposes explicit live_state_invalidation events for agent and auto recovery boundaries", async (t) => { +test("/api/session/events exposes explicit live_state_invalidation events for agent and auto recovery boundaries", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -506,7 +506,7 @@ test("/api/session/events exposes explicit live_state_invalidation events for ag await waitForMicrotasks(); }); -test("workspace cache only busts on real boundaries and session mutations emit targeted invalidations", async (t) => { +test("workspace cache only busts on real boundaries and session mutations emit targeted invalidations", async (_t) => { const fixture = makeWorkspaceFixture(); const activeSessionPath = createSessionFile( fixture.projectCwd, @@ -743,7 +743,7 @@ test("workspace cache only busts on real boundaries and session mutations emit t unsubscribe(); }); -test("turn_end events invalidate workspace so milestones list reflects current state (issue #2706)", async (t) => { +test("turn_end events invalidate workspace so milestones list reflects current state (issue #2706)", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, diff --git a/src/tests/integration/web-mode-assembled.test.ts b/src/tests/integration/web-mode-assembled.test.ts index 8f826b71a..f81f20903 100644 --- a/src/tests/integration/web-mode-assembled.test.ts +++ b/src/tests/integration/web-mode-assembled.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const repoRoot = process.cwd(); @@ -263,7 +263,7 @@ async function readSseEvents( // Assembled lifecycle test // --------------------------------------------------------------------------- -test("assembled lifecycle: boot → onboard → prompt → streaming text → tool execution → blocking UI request → UI response → turn boundary", async (t) => { +test("assembled lifecycle: boot → onboard → prompt → streaming text → tool execution → blocking UI request → UI response → turn boundary", async (_t) => { const fixture = makeWorkspaceFixture(); const authStorage = AuthStorage.inMemory({}); const sessionPath = createSessionFile( @@ -746,7 +746,7 @@ test("assembled lifecycle: boot → onboard → prompt → streaming text → to } }); -test("assembled settings controls keep retry visibility and daily-use mutations authoritative", async (t) => { +test("assembled settings controls keep retry visibility and daily-use mutations authoritative", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -985,7 +985,7 @@ test("assembled settings controls keep retry visibility and daily-use mutations ); }); -test("assembled recovery route exposes actionable browser diagnostics without raw transcript leakage", async (t) => { +test("assembled recovery route exposes actionable browser diagnostics without raw transcript leakage", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, @@ -1143,7 +1143,7 @@ test("assembled recovery route exposes actionable browser diagnostics without ra ); }); -test("assembled slash-command behavior keeps built-ins safe while preserving SF prompt commands", async (t) => { +test("assembled slash-command behavior keeps built-ins safe while preserving SF prompt commands", async (_t) => { const fixture = makeWorkspaceFixture(); const sessionPath = createSessionFile( fixture.projectCwd, diff --git a/src/tests/integration/web-mode-cli.test.ts b/src/tests/integration/web-mode-cli.test.ts index bd4cddd3f..931f631a0 100644 --- a/src/tests/integration/web-mode-cli.test.ts +++ b/src/tests/integration/web-mode-cli.test.ts @@ -60,7 +60,7 @@ test("web mode launcher defines or imports a browser opener", () => { assert.match(source, /openBrowser/); }); -test("cli.ts branches to web mode before interactive startup and preserves cwd-scoped launch inputs", async (t) => { +test("cli.ts branches to web mode before interactive startup and preserves cwd-scoped launch inputs", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-cli-")); const cwd = join(tmp, "project space"); mkdirSync(cwd, { recursive: true }); @@ -126,7 +126,7 @@ test("cli.ts branches to web mode before interactive startup and preserves cwd-s }); }); -test("launchWebMode prefers the packaged standalone host and opens the resolved URL", async (t) => { +test("launchWebMode prefers the packaged standalone host and opens the resolved URL", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-host-")); const standaloneRoot = join(tmp, "dist", "web", "standalone"); const serverPath = join(standaloneRoot, "server.js"); @@ -237,7 +237,7 @@ test("launchWebMode prefers the packaged standalone host and opens the resolved assert.equal(webMode.readPidFile(pidFilePath), 99999); }); -test("stopWebMode kills process by PID and removes PID file", (t) => { +test("stopWebMode kills process by PID and removes PID file", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-stop-")); const pidFilePath = join(tmp, "web-server.pid"); let stderrOutput = ""; @@ -267,7 +267,7 @@ test("stopWebMode kills process by PID and removes PID file", (t) => { assert.match(stderrOutput, /pid=12345/); }); -test("stopWebMode reports error when no PID file exists", (t) => { +test("stopWebMode reports error when no PID file exists", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-stop-nopid-")); const pidFilePath = join(tmp, "web-server.pid"); let stderrOutput = ""; @@ -293,7 +293,7 @@ test("stopWebMode reports error when no PID file exists", (t) => { assert.match(stderrOutput, /not running/); }); -test('runWebCliBranch handles "web stop" subcommand without --web flag', async (t) => { +test('runWebCliBranch handles "web stop" subcommand without --web flag', async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-branch-stop-")); const pidFilePath = join(tmp, "web-server.pid"); let _stderrOutput = ""; @@ -363,7 +363,7 @@ test("parseCliArgs does not capture --web followed by a flag as path", () => { assert.equal(flags.model, "test"); }); -test("sf web is handled as web start with path", async (t) => { +test("sf web is handled as web start with path", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-path-")); const projectDir = join(tmp, "my-project"); mkdirSync(projectDir, { recursive: true }); @@ -405,7 +405,7 @@ test("sf web is handled as web start with path", async (t) => { assert.equal(launchedCwd, projectDir); }); -test("sf web start resolves path and launches", async (t) => { +test("sf web start resolves path and launches", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-start-path-")); const projectDir = join(tmp, "another-project"); mkdirSync(projectDir, { recursive: true }); @@ -448,7 +448,7 @@ test("sf web start resolves path and launches", async (t) => { assert.equal(launchedCwd, projectDir); }); -test("sf --web resolves path and launches", async (t) => { +test("sf --web resolves path and launches", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-flag-path-")); const projectDir = join(tmp, "flagged-project"); mkdirSync(projectDir, { recursive: true }); @@ -519,7 +519,7 @@ test("sf --web fails with clear error", async () => { assert.match(stderrOutput, /does not exist/); }); -test("launch failure surfaces status and reason before browser open", async (t) => { +test("launch failure surfaces status and reason before browser open", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-missing-host-")); let openedUrl = ""; let stderrOutput = ""; @@ -560,7 +560,7 @@ test("launch failure surfaces status and reason before browser open", async (t) // ─── Instance registry tests ───────────────────────────────────────── -test("registerInstance and readInstanceRegistry round-trip", (t) => { +test("registerInstance and readInstanceRegistry round-trip", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-registry-")); const registryPath = join(tmp, "web-instances.json"); @@ -586,7 +586,7 @@ test("registerInstance and readInstanceRegistry round-trip", (t) => { assert.ok(registry[resolve("/tmp/project-a")]?.startedAt); }); -test("unregisterInstance removes a single entry", (t) => { +test("unregisterInstance removes a single entry", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-unreg-")); const registryPath = join(tmp, "web-instances.json"); @@ -659,7 +659,7 @@ test("sf web stop all is parsed and dispatched", async () => { assert.equal(stopOptions?.projectCwd, undefined); }); -test("sf web stop is parsed and dispatched with resolved path", async (t) => { +test("sf web stop is parsed and dispatched with resolved path", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-stop-path-")); let stopOptions: { projectCwd?: string; all?: boolean } | undefined; @@ -692,7 +692,7 @@ test("sf web stop is parsed and dispatched with resolved path", async (t) // ─── Context-aware launch detection tests ────────────────────────────── -test("resolveContextAwareCwd returns project cwd when inside a project under dev root", (t) => { +test("resolveContextAwareCwd returns project cwd when inside a project under dev root", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-ctx-aware-")); const devRoot = join(tmp, "devroot"); const projectA = join(devRoot, "projectA"); @@ -709,7 +709,7 @@ test("resolveContextAwareCwd returns project cwd when inside a project under dev assert.equal(result, projectA); }); -test("resolveContextAwareCwd returns cwd unchanged when AT dev root", (t) => { +test("resolveContextAwareCwd returns cwd unchanged when AT dev root", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-ctx-aware-")); const devRoot = join(tmp, "devroot"); const prefsPath = join(tmp, "web-preferences.json"); @@ -725,7 +725,7 @@ test("resolveContextAwareCwd returns cwd unchanged when AT dev root", (t) => { assert.equal(result, devRoot); }); -test("resolveContextAwareCwd returns cwd unchanged when no dev root configured", (t) => { +test("resolveContextAwareCwd returns cwd unchanged when no dev root configured", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-ctx-aware-")); const prefsPath = join(tmp, "web-preferences.json"); const cwd = join(tmp, "somedir"); @@ -741,7 +741,7 @@ test("resolveContextAwareCwd returns cwd unchanged when no dev root configured", assert.equal(result, cwd); }); -test("resolveContextAwareCwd returns cwd unchanged when prefs file missing", (t) => { +test("resolveContextAwareCwd returns cwd unchanged when prefs file missing", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-ctx-aware-")); const prefsPath = join(tmp, "nonexistent-prefs.json"); const cwd = join(tmp, "somedir"); @@ -756,7 +756,7 @@ test("resolveContextAwareCwd returns cwd unchanged when prefs file missing", (t) assert.equal(result, cwd); }); -test("resolveContextAwareCwd returns cwd unchanged when dev root path is stale", (t) => { +test("resolveContextAwareCwd returns cwd unchanged when dev root path is stale", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-ctx-aware-")); const prefsPath = join(tmp, "web-preferences.json"); const cwd = join(tmp, "somedir"); @@ -773,7 +773,7 @@ test("resolveContextAwareCwd returns cwd unchanged when dev root path is stale", assert.equal(result, cwd); }); -test("resolveContextAwareCwd resolves nested cwd to one-level-deep project", (t) => { +test("resolveContextAwareCwd resolves nested cwd to one-level-deep project", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-ctx-aware-")); const devRoot = join(tmp, "devroot"); const projectA = join(devRoot, "projectA"); @@ -791,7 +791,7 @@ test("resolveContextAwareCwd resolves nested cwd to one-level-deep project", (t) assert.equal(result, projectA); }); -test("resolveContextAwareCwd returns cwd unchanged when outside dev root", (t) => { +test("resolveContextAwareCwd returns cwd unchanged when outside dev root", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-ctx-aware-")); const devRoot = join(tmp, "devroot"); const outsideDir = join(tmp, "elsewhere"); @@ -811,7 +811,7 @@ test("resolveContextAwareCwd returns cwd unchanged when outside dev root", (t) = // ─── Stale instance cleanup tests ───────────────────────────────────── -test("launchWebMode kills stale instance for same cwd before spawning", async (t) => { +test("launchWebMode kills stale instance for same cwd before spawning", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-stale-")); const standaloneRoot = join(tmp, "dist", "web", "standalone"); const serverPath = join(standaloneRoot, "server.js"); @@ -879,7 +879,7 @@ test("launchWebMode kills stale instance for same cwd before spawning", async (t assert.equal(registry[resolve(cwd)]?.pid, 88888); }); -test("launchWebMode does not log cleanup when no stale instance exists", async (t) => { +test("launchWebMode does not log cleanup when no stale instance exists", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-no-stale-")); const standaloneRoot = join(tmp, "dist", "web", "standalone"); const serverPath = join(standaloneRoot, "server.js"); diff --git a/src/tests/integration/web-mode-network-flags.test.ts b/src/tests/integration/web-mode-network-flags.test.ts index 7d0687726..871619982 100644 --- a/src/tests/integration/web-mode-network-flags.test.ts +++ b/src/tests/integration/web-mode-network-flags.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const cliWeb = await import("../../cli-web-branch.ts"); const webMode = await import("../../web-mode.ts"); @@ -106,7 +106,7 @@ test("parseCliArgs does not set network flags when not provided", () => { // ─── launchWebMode env forwarding ──────────────────────────────────── -test("launchWebMode forwards custom host, port, and allowed origins to subprocess env", async (t) => { +test("launchWebMode forwards custom host, port, and allowed origins to subprocess env", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-net-")); const standaloneRoot = join(tmp, "dist", "web", "standalone"); const serverPath = join(standaloneRoot, "server.js"); @@ -161,7 +161,7 @@ test("launchWebMode forwards custom host, port, and allowed origins to subproces ); }); -test("launchWebMode omits SF_WEB_ALLOWED_ORIGINS when none provided", async (t) => { +test("launchWebMode omits SF_WEB_ALLOWED_ORIGINS when none provided", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-no-origins-")); const standaloneRoot = join(tmp, "dist", "web", "standalone"); const serverPath = join(standaloneRoot, "server.js"); @@ -201,7 +201,7 @@ test("launchWebMode omits SF_WEB_ALLOWED_ORIGINS when none provided", async (t) // ─── runWebCliBranch end-to-end forwarding ─────────────────────────── -test("runWebCliBranch forwards --host, --port, --allowed-origins to launchWebMode", async (t) => { +test("runWebCliBranch forwards --host, --port, --allowed-origins to launchWebMode", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-branch-flags-")); const projectDir = join(tmp, "project"); mkdirSync(projectDir, { recursive: true }); diff --git a/src/tests/integration/web-mode-onboarding.test.ts b/src/tests/integration/web-mode-onboarding.test.ts index 2879b44f4..77ca386bb 100644 --- a/src/tests/integration/web-mode-onboarding.test.ts +++ b/src/tests/integration/web-mode-onboarding.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { killProcessOnPort, @@ -333,7 +333,7 @@ function configureBridgeRuntime( }; } -test("successful browser onboarding restarts the stale bridge child and unlocks the first prompt", async (t) => { +test("successful browser onboarding restarts the stale bridge child and unlocks the first prompt", async (_t) => { const fixture = makeWorkspaceFixture(); const authStorage = AuthStorage.inMemory({}); const harness = configureBridgeRuntime(fixture, authStorage); @@ -412,7 +412,7 @@ test("successful browser onboarding restarts the stale bridge child and unlocks ]); }); -test("refresh failures keep the workspace locked and expose the failed bridge-refresh reason", async (t) => { +test("refresh failures keep the workspace locked and expose the failed bridge-refresh reason", async (_t) => { const fixture = makeWorkspaceFixture(); const authStorage = AuthStorage.inMemory({}); const harness = configureBridgeRuntime(fixture, authStorage, { @@ -494,7 +494,7 @@ test("refresh failures keep the workspace locked and expose the failed bridge-re ); }, 120_000); -test("fresh sf --web browser onboarding stays locked on failed validation and unlocks after a successful retry", async (t) => { +test("fresh sf --web browser onboarding stays locked on failed validation and unlocks after a successful retry", async (_t) => { if (process.platform === "win32") { return; // skip: "runtime launch test uses POSIX browser-open stubs"; return; diff --git a/src/tests/integration/web-mode-windows-hide.test.ts b/src/tests/integration/web-mode-windows-hide.test.ts index ca12a9836..94f78b1b7 100644 --- a/src/tests/integration/web-mode-windows-hide.test.ts +++ b/src/tests/integration/web-mode-windows-hide.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const webMode = await import("../../web-mode.ts"); @@ -11,7 +11,7 @@ const webMode = await import("../../web-mode.ts"); // `windowsHide: true` to prevent console windows from flashing on screen. // --------------------------------------------------------------------------- -test("launchWebMode passes windowsHide: true in spawn options", async (t) => { +test("launchWebMode passes windowsHide: true in spawn options", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-winhide-")); const standaloneRoot = join(tmp, "dist", "web", "standalone"); const serverPath = join(standaloneRoot, "server.js"); @@ -65,7 +65,7 @@ test("launchWebMode passes windowsHide: true in spawn options", async (t) => { ); }); -test("launchWebMode source-dev host also passes windowsHide: true", async (t) => { +test("launchWebMode source-dev host also passes windowsHide: true", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-winhide-src-")); const webRoot = join(tmp, "web"); mkdirSync(webRoot, { recursive: true }); diff --git a/src/tests/integration/web-multi-project-contract.test.ts b/src/tests/integration/web-multi-project-contract.test.ts index fdb5c6d3e..0b5f78779 100644 --- a/src/tests/integration/web-multi-project-contract.test.ts +++ b/src/tests/integration/web-multi-project-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -249,7 +249,7 @@ function createHarness(sessionId: string) { // Tests — multi-project bridge coexistence // --------------------------------------------------------------------------- -test("multi-project: getProjectBridgeServiceForCwd returns distinct instances for different project paths", async (t) => { +test("multi-project: getProjectBridgeServiceForCwd returns distinct instances for different project paths", async (_t) => { const fixtureA = makeWorkspaceFixture("A"); const fixtureB = makeWorkspaceFixture("B"); @@ -286,7 +286,7 @@ test("multi-project: getProjectBridgeServiceForCwd returns distinct instances fo assert.equal(snapB.projectCwd, fixtureB.projectCwd); }); -test("multi-project: getProjectBridgeServiceForCwd returns same instance for same path", async (t) => { +test("multi-project: getProjectBridgeServiceForCwd returns same instance for same path", async (_t) => { const fixtureA = makeWorkspaceFixture("idempotent"); bridge.configureBridgeServiceForTests({ @@ -312,7 +312,7 @@ test("multi-project: getProjectBridgeServiceForCwd returns same instance for sam assert.strictEqual(first, second, "same path must return the same instance"); }); -test("multi-project: each bridge receives commands independently", async (t) => { +test("multi-project: each bridge receives commands independently", async (_t) => { const fixtureA = makeWorkspaceFixture("cmd-A"); const fixtureB = makeWorkspaceFixture("cmd-B"); const _sessionPathA = createSessionFile( @@ -395,7 +395,7 @@ test("multi-project: each bridge receives commands independently", async (t) => ); }); -test("multi-project: SSE subscribers are isolated per bridge", async (t) => { +test("multi-project: SSE subscribers are isolated per bridge", async (_t) => { const fixtureA = makeWorkspaceFixture("sse-A"); const fixtureB = makeWorkspaceFixture("sse-B"); @@ -476,7 +476,7 @@ test("multi-project: resolveProjectCwd reads ?project= from request URL", () => assert.equal(result, "/tmp/my-project"); }); -test("multi-project: resolveProjectCwd falls back to SF_WEB_PROJECT_CWD when no ?project= present", (t) => { +test("multi-project: resolveProjectCwd falls back to SF_WEB_PROJECT_CWD when no ?project= present", (_t) => { bridge.configureBridgeServiceForTests({ env: { ...process.env, @@ -499,7 +499,7 @@ test("multi-project: resolveProjectCwd falls back to SF_WEB_PROJECT_CWD when no assert.equal(result, "/fallback/path"); }); -test("multi-project: getProjectBridgeService backward compat shim works", async (t) => { +test("multi-project: getProjectBridgeService backward compat shim works", async (_t) => { const fixture = makeWorkspaceFixture("compat"); const harness = createHarness("sess-compat"); @@ -542,7 +542,7 @@ test("multi-project: getProjectBridgeService backward compat shim works", async ); }); -test("multi-project: resetBridgeServiceForTests clears all registry entries", async (t) => { +test("multi-project: resetBridgeServiceForTests clears all registry entries", async (_t) => { const fixtureA = makeWorkspaceFixture("reset-A"); const fixtureB = makeWorkspaceFixture("reset-B"); diff --git a/src/tests/integration/web-onboarding-contract.test.ts b/src/tests/integration/web-onboarding-contract.test.ts index ef736b80c..eee120849 100644 --- a/src/tests/integration/web-onboarding-contract.test.ts +++ b/src/tests/integration/web-onboarding-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -349,7 +349,7 @@ function configureBridgeFixture( return harness; } -test("boot and onboarding routes expose locked required state plus explicitly skippable optional setup when auth is missing", async (t) => { +test("boot and onboarding routes expose locked required state plus explicitly skippable optional setup when auth is missing", async (_t) => { const fixture = makeWorkspaceFixture(); clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); @@ -415,7 +415,7 @@ test("boot and onboarding routes expose locked required state plus explicitly sk assert.equal(onboardingPayload.onboarding.optional.skippable, true); }); -test("runtime env-backed auth unlocks boot onboarding state and reports the environment source", async (t) => { +test("runtime env-backed auth unlocks boot onboarding state and reports the environment source", async (_t) => { const fixture = makeWorkspaceFixture(); clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); @@ -461,7 +461,7 @@ test("runtime env-backed auth unlocks boot onboarding state and reports the envi assert.equal(copilotProvider.configuredVia, "environment"); }); -test("failed API-key validation stays locked, redacts the error, and is reflected in boot state without persisting auth", async (t) => { +test("failed API-key validation stays locked, redacts the error, and is reflected in boot state without persisting auth", async (_t) => { const fixture = makeWorkspaceFixture(); clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); @@ -529,7 +529,7 @@ test("failed API-key validation stays locked, redacts the error, and is reflecte ); }); -test("direct prompt commands cannot bypass onboarding while required setup is still locked", async (t) => { +test("direct prompt commands cannot bypass onboarding while required setup is still locked", async (_t) => { const fixture = makeWorkspaceFixture(); clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); @@ -578,7 +578,7 @@ test("direct prompt commands cannot bypass onboarding while required setup is st assert.equal(harness.spawnCalls, 1); }); -test("bridge auth refresh failures remain inspectable and keep the workspace locked after credentials validate", async (t) => { +test("bridge auth refresh failures remain inspectable and keep the workspace locked after credentials validate", async (_t) => { const fixture = makeWorkspaceFixture(); clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); @@ -642,7 +642,7 @@ test("bridge auth refresh failures remain inspectable and keep the workspace loc assert.equal(bootPayload.onboarding.bridgeAuthRefresh.phase, "failed"); }); -test("successful API-key validation persists the credential and unlocks onboarding", async (t) => { +test("successful API-key validation persists the credential and unlocks onboarding", async (_t) => { const fixture = makeWorkspaceFixture(); clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); @@ -701,7 +701,7 @@ test("successful API-key validation persists the credential and unlocks onboardi assert.equal(bootPayload.onboardingNeeded, false); }); -test("logout_provider removes saved auth, refreshes the bridge, and relocks onboarding when it was the only provider", async (t) => { +test("logout_provider removes saved auth, refreshes the bridge, and relocks onboarding when it was the only provider", async (_t) => { const fixture = makeWorkspaceFixture(); clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({ @@ -763,7 +763,7 @@ test("logout_provider removes saved auth, refreshes the bridge, and relocks onbo assert.equal(bootAfterPayload.onboarding.required.satisfied, false); }); -test("logout_provider fails clearly for environment-backed auth that the browser cannot remove", async (t) => { +test("logout_provider fails clearly for environment-backed auth that the browser cannot remove", async (_t) => { const fixture = makeWorkspaceFixture(); clearOnboardingEnv(); const authStorage = AuthStorage.inMemory({}); diff --git a/src/tests/integration/web-project-tab-preservation.test.ts b/src/tests/integration/web-project-tab-preservation.test.ts index 4a8f6638c..4ae40c7a0 100644 --- a/src/tests/integration/web-project-tab-preservation.test.ts +++ b/src/tests/integration/web-project-tab-preservation.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test, describe } from "vitest"; +import { describe, test } from "vitest"; // --------------------------------------------------------------------------- // Test: project switching preserves the active tab (view) instead of diff --git a/src/tests/integration/web-recovery-diagnostics-contract.test.ts b/src/tests/integration/web-recovery-diagnostics-contract.test.ts index 878b7ff24..b2fa2d476 100644 --- a/src/tests/integration/web-recovery-diagnostics-contract.test.ts +++ b/src/tests/integration/web-recovery-diagnostics-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -252,7 +252,7 @@ function fakeSessionState(sessionId: string, sessionPath?: string) { }; } -test("/api/recovery returns structured recovery diagnostics and redacts secrets", async (t) => { +test("/api/recovery returns structured recovery diagnostics and redacts secrets", async (_t) => { const fixture = makeRecoveryFixture(); const sessionPath = createRecoverySessionFile( fixture.projectCwd, @@ -343,7 +343,7 @@ test("/api/recovery returns structured recovery diagnostics and redacts secrets" ); }); -test("/api/recovery prefers the current-project resumable session when the live bridge session is out of scope", async (t) => { +test("/api/recovery prefers the current-project resumable session when the live bridge session is out of scope", async (_t) => { const fixture = makeRecoveryFixture(); const sessionPath = createRecoverySessionFile( fixture.projectCwd, @@ -405,7 +405,7 @@ test("/api/recovery prefers the current-project resumable session when the live ); }); -test("/api/recovery returns a structured empty-project payload without leaking raw diagnostics", async (t) => { +test("/api/recovery returns a structured empty-project payload without leaking raw diagnostics", async (_t) => { const fixture = makeEmptyProjectFixture(); const harness = createHarness((command, current) => { if (command.type === "get_state") { diff --git a/src/tests/integration/web-session-parity-contract.test.ts b/src/tests/integration/web-session-parity-contract.test.ts index c596dbc8e..984705dcc 100644 --- a/src/tests/integration/web-session-parity-contract.test.ts +++ b/src/tests/integration/web-session-parity-contract.test.ts @@ -12,7 +12,7 @@ import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -268,7 +268,7 @@ function configureBridgeFixture( }); } -test("/api/session/browser stays current-project scoped and carries threaded/search metadata outside /api/boot", async (t) => { +test("/api/session/browser stays current-project scoped and carries threaded/search metadata outside /api/boot", async (_t) => { const fixture = makeWorkspaceFixture(); const rootPath = createSessionFile({ projectCwd: fixture.projectCwd, @@ -399,7 +399,7 @@ test("/api/session/browser stays current-project scoped and carries threaded/sea assert.equal(searchPayload.sessions[0].name, "Release Notes"); }); -test("/api/session/manage renames the active session through bridge-aware RPC instead of mutating the file directly", async (t) => { +test("/api/session/manage renames the active session through bridge-aware RPC instead of mutating the file directly", async (_t) => { const fixture = makeWorkspaceFixture(); const activePath = createSessionFile({ projectCwd: fixture.projectCwd, @@ -494,7 +494,7 @@ test("/api/session/manage renames the active session through bridge-aware RPC in assert.equal(getLatestSessionName(activePath), "Before Active Rename"); }); -test("/api/session/manage renames inactive sessions via authoritative session-file mutation and rejects out-of-scope paths", async (t) => { +test("/api/session/manage renames inactive sessions via authoritative session-file mutation and rejects out-of-scope paths", async (_t) => { const fixture = makeWorkspaceFixture(); const activePath = createSessionFile({ projectCwd: fixture.projectCwd, @@ -618,7 +618,7 @@ test("/api/session/manage renames inactive sessions via authoritative session-fi assert.equal(getLatestSessionName(outsidePath), "Outside Session"); }); -test("/api/git returns a current-project-scoped repo summary and ignores changes outside the current project subtree", async (t) => { +test("/api/git returns a current-project-scoped repo summary and ignores changes outside the current project subtree", async (_t) => { const root = mkdtempSync(join(tmpdir(), "sf-web-git-summary-")); const repoRoot = join(root, "repo"); const projectCwd = join(repoRoot, "apps", "current-project"); @@ -689,7 +689,7 @@ test("/api/git returns a current-project-scoped repo summary and ignores changes }); }); -test("/api/git exposes an explicit not-a-repo state instead of failing silently", async (t) => { +test("/api/git exposes an explicit not-a-repo state instead of failing silently", async (_t) => { const projectCwd = mkdtempSync(join(tmpdir(), "sf-web-not-repo-")); afterEach(() => { diff --git a/src/tests/integration/web-state-surfaces-contract.test.ts b/src/tests/integration/web-state-surfaces-contract.test.ts index 396e13184..f52d56118 100644 --- a/src/tests/integration/web-state-surfaces-contract.test.ts +++ b/src/tests/integration/web-state-surfaces-contract.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; // ─── Imports ────────────────────────────────────────────────────────── const workspaceIndex = await import( @@ -32,7 +32,7 @@ function makeSfFixture(): { root: string; sfDir: string; cleanup: () => void } { } // ─── Group 1: Workspace index — risk/depends/demo fields ───────────── -test("indexWorkspace extracts risk, depends, and demo from roadmap", async (t) => { +test("indexWorkspace extracts risk, depends, and demo from roadmap", async (_t) => { const { root, sfDir, cleanup } = makeSfFixture(); afterEach(() => { @@ -90,7 +90,7 @@ test("indexWorkspace extracts risk, depends, and demo from roadmap", async (t) = assert.equal(slice.tasks[0].done, false); }); -test("indexWorkspace handles slices without risk/depends/demo", async (t) => { +test("indexWorkspace handles slices without risk/depends/demo", async (_t) => { const { root, sfDir, cleanup } = makeSfFixture(); afterEach(() => { @@ -235,7 +235,7 @@ test("getTaskStatus returns correct statuses", () => { }); // ─── Group 3: Files API — tree listing ─────────────────────────────── -test("files API returns tree listing of .sf/ directory", async (t) => { +test("files API returns tree listing of .sf/ directory", async (_t) => { const { root, sfDir, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; @@ -286,7 +286,7 @@ test("files API returns tree listing of .sf/ directory", async (t) => { }); // ─── Group 4: Files API — file content ─────────────────────────────── -test("files API returns file content for valid path", async (t) => { +test("files API returns file content for valid path", async (_t) => { const { root, sfDir, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; @@ -308,7 +308,7 @@ test("files API returns file content for valid path", async (t) => { assert.equal(data.content, fileContent); }); -test("files API returns content for nested files", async (t) => { +test("files API returns content for nested files", async (_t) => { const { root, sfDir, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; @@ -334,7 +334,7 @@ test("files API returns content for nested files", async (t) => { }); // ─── Group 5: Files API — security: path traversal rejection ───────── -test("files API rejects path traversal with ../", async (t) => { +test("files API rejects path traversal with ../", async (_t) => { const { root, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; @@ -355,7 +355,7 @@ test("files API rejects path traversal with ../", async (t) => { assert.ok(data.error, "Expected error message in response"); }); -test("files API rejects absolute paths", async (t) => { +test("files API rejects absolute paths", async (_t) => { const { root, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; @@ -376,7 +376,7 @@ test("files API rejects absolute paths", async (t) => { assert.ok(data.error); }); -test("files API returns 404 for missing files", async (t) => { +test("files API returns 404 for missing files", async (_t) => { const { root, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; @@ -397,7 +397,7 @@ test("files API returns 404 for missing files", async (t) => { assert.ok(data.error); }); -test("files API returns empty tree when .sf/ does not exist", async (t) => { +test("files API returns empty tree when .sf/ does not exist", async (_t) => { const root = mkdtempSync(join(tmpdir(), "sf-state-surfaces-empty-")); const origEnv = process.env.SF_WEB_PROJECT_CWD; diff --git a/src/tests/integration/web-switch-project.test.ts b/src/tests/integration/web-switch-project.test.ts index d9f5ef030..d53db5691 100644 --- a/src/tests/integration/web-switch-project.test.ts +++ b/src/tests/integration/web-switch-project.test.ts @@ -10,7 +10,7 @@ import { } from "node:fs"; import { homedir, tmpdir } from "node:os"; import { isAbsolute, join, resolve } from "node:path"; -import { test, after, afterAll, describe } from "vitest"; +import { afterAll, describe, test } from "vitest"; // --------------------------------------------------------------------------- // Test the core validation + persistence logic used by /api/switch-root diff --git a/src/tests/integration/web-workflow-action-execution.test.ts b/src/tests/integration/web-workflow-action-execution.test.ts index 1bc887725..2b95e38e4 100644 --- a/src/tests/integration/web-workflow-action-execution.test.ts +++ b/src/tests/integration/web-workflow-action-execution.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const { derivePendingWorkflowCommandLabel, @@ -29,7 +29,7 @@ test("derivePendingWorkflowCommandLabel falls back to the command type when no i assert.equal(label, "/abort"); }); -test("navigateToSFView dispatches the shared browser navigation event", (t) => { +test("navigateToSFView dispatches the shared browser navigation event", (_t) => { const originalWindow = (globalThis as { window?: EventTarget }).window; const fakeWindow = new EventTarget(); const seen: string[] = []; @@ -49,7 +49,7 @@ test("navigateToSFView dispatches the shared browser navigation event", (t) => { assert.deepEqual(seen, ["power"]); }); -test("executeWorkflowActionInPowerMode calls dispatch and navigates to the appropriate view", async (t) => { +test("executeWorkflowActionInPowerMode calls dispatch and navigates to the appropriate view", async (_t) => { const originalWindow = (globalThis as { window?: EventTarget }).window; const originalLocalStorage = (globalThis as any).localStorage; const fakeWindow = new EventTarget(); diff --git a/src/tests/llm-context-tavily.test.ts b/src/tests/llm-context-tavily.test.ts index ba5905928..2dba328d0 100644 --- a/src/tests/llm-context-tavily.test.ts +++ b/src/tests/llm-context-tavily.test.ts @@ -11,7 +11,7 @@ */ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import type { TavilyResult } from "../resources/extensions/search-the-web/tavily.ts"; import { publishedDateToAge } from "../resources/extensions/search-the-web/tavily.ts"; import { budgetContent } from "../resources/extensions/search-the-web/tool-llm-context.ts"; @@ -405,7 +405,7 @@ test("no-key error message mentions both TAVILY_API_KEY and BRAVE_API_KEY", () = ); }); -test("Tavily LLM context request uses POST with Bearer auth and advanced search depth", async (t) => { +test("Tavily LLM context request uses POST with Bearer auth and advanced search depth", async (_t) => { const apiKey = "tvly-test-key-abc123"; const query = "typescript handbook"; diff --git a/src/tests/marketplace-discovery.test.ts b/src/tests/marketplace-discovery.test.ts index a0a026aa7..1ddb4a19c 100644 --- a/src/tests/marketplace-discovery.test.ts +++ b/src/tests/marketplace-discovery.test.ts @@ -13,7 +13,7 @@ import assert from "node:assert"; import * as fs from "node:fs"; import * as path from "node:path"; -import { describe, it, afterEach } from "vitest"; +import { afterEach, describe, it } from "vitest"; import { discoverMarketplace, inspectPlugin, @@ -347,7 +347,7 @@ describe("Marketplace Discovery Contract Tests", { skip: skipReason }, () => { assert.strictEqual(result.summary.error, 0); }); - it("should return error for directory without marketplace.json", (t) => { + it("should return error for directory without marketplace.json", (_t) => { // Create a temp directory without marketplace.json const tmpDir = "/tmp/test-no-marketplace-" + Date.now(); fs.mkdirSync(tmpDir, { recursive: true }); @@ -363,7 +363,7 @@ describe("Marketplace Discovery Contract Tests", { skip: skipReason }, () => { ); }); - it("should return error for malformed marketplace.json", (t) => { + it("should return error for malformed marketplace.json", (_t) => { const tmpDir = "/tmp/test-malformed-marketplace-" + Date.now(); fs.mkdirSync(tmpDir + "/.claude-plugin", { recursive: true }); fs.writeFileSync( @@ -382,7 +382,7 @@ describe("Marketplace Discovery Contract Tests", { skip: skipReason }, () => { ); }); - it("should return error for marketplace.json missing required fields", (t) => { + it("should return error for marketplace.json missing required fields", (_t) => { const tmpDir = "/tmp/test-invalid-marketplace-" + Date.now(); fs.mkdirSync(tmpDir + "/.claude-plugin", { recursive: true }); // Valid JSON but missing required 'name' and 'plugins' fields @@ -403,7 +403,7 @@ describe("Marketplace Discovery Contract Tests", { skip: skipReason }, () => { } }); - it("should handle missing plugin directory gracefully", (t) => { + it("should handle missing plugin directory gracefully", (_t) => { const tmpDir = "/tmp/test-missing-plugin-" + Date.now(); fs.mkdirSync(tmpDir + "/.claude-plugin", { recursive: true }); fs.writeFileSync( diff --git a/src/tests/mcp-client-schema.test.ts b/src/tests/mcp-client-schema.test.ts index b3342e6a6..eec7c3234 100644 --- a/src/tests/mcp-client-schema.test.ts +++ b/src/tests/mcp-client-schema.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; -import { test } from "vitest"; import { Type } from "@sinclair/typebox"; +import { test } from "vitest"; test("mcp_call args schema uses additionalProperties instead of patternProperties", () => { const schema = Type.Object({ diff --git a/src/tests/mcp-createRequire.test.ts b/src/tests/mcp-createRequire.test.ts index 6261b8edf..3a50a99af 100644 --- a/src/tests/mcp-createRequire.test.ts +++ b/src/tests/mcp-createRequire.test.ts @@ -17,8 +17,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { describe, test } from "vitest"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/src/tests/model-registry-custom-provider.test.ts b/src/tests/model-registry-custom-provider.test.ts index 64f35a57b..30d281e18 100644 --- a/src/tests/model-registry-custom-provider.test.ts +++ b/src/tests/model-registry-custom-provider.test.ts @@ -6,8 +6,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); diff --git a/src/tests/native-search.test.ts b/src/tests/native-search.test.ts index 4647b74b3..2f76e97c3 100644 --- a/src/tests/native-search.test.ts +++ b/src/tests/native-search.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { BRAVE_TOOL_NAMES, CUSTOM_SEARCH_TOOL_NAMES, @@ -337,7 +337,7 @@ test("before_provider_request skips when payload is falsy", async () => { assert.equal(result, undefined, "Should return undefined for null payload"); }); -test("model_select disables Brave tools when Anthropic + no BRAVE_API_KEY", async (t) => { +test("model_select disables Brave tools when Anthropic + no BRAVE_API_KEY", async (_t) => { const originalKey = process.env.BRAVE_API_KEY; delete process.env.BRAVE_API_KEY; @@ -372,7 +372,7 @@ test("model_select disables Brave tools when Anthropic + no BRAVE_API_KEY", asyn assert.ok(active.includes("bash"), "Other tools should remain active"); }); -test("model_select disables all custom search tools when Anthropic even with BRAVE_API_KEY", async (t) => { +test("model_select disables all custom search tools when Anthropic even with BRAVE_API_KEY", async (_t) => { const originalKey = process.env.BRAVE_API_KEY; process.env.BRAVE_API_KEY = "test-key"; @@ -406,7 +406,7 @@ test("model_select disables all custom search tools when Anthropic even with BRA assert.ok(active.includes("fetch_page"), "fetch_page should remain active"); }); -test("model_select re-enables Brave tools when switching away from Anthropic", async (t) => { +test("model_select re-enables Brave tools when switching away from Anthropic", async (_t) => { const originalKey = process.env.BRAVE_API_KEY; delete process.env.BRAVE_API_KEY; @@ -478,7 +478,7 @@ test("model_select shows 'Native Anthropic web search active' for Anthropic prov ); }); -test("model_select shows warning for non-Anthropic without Brave key", async (t) => { +test("model_select shows warning for non-Anthropic without Brave key", async (_t) => { const keys = [ "BRAVE_API_KEY", "TAVILY_API_KEY", @@ -548,7 +548,7 @@ test("CUSTOM_SEARCH_TOOL_NAMES contains all custom search tools", () => { ]); }); -test("before_provider_request removes Brave tools from payload when no BRAVE_API_KEY", async (t) => { +test("before_provider_request removes Brave tools from payload when no BRAVE_API_KEY", async (_t) => { const originalKey = process.env.BRAVE_API_KEY; delete process.env.BRAVE_API_KEY; @@ -605,7 +605,7 @@ test("before_provider_request removes Brave tools from payload when no BRAVE_API ); }); -test("before_provider_request removes all custom search tools from payload even with BRAVE_API_KEY", async (t) => { +test("before_provider_request removes all custom search tools from payload even with BRAVE_API_KEY", async (_t) => { const originalKey = process.env.BRAVE_API_KEY; process.env.BRAVE_API_KEY = "test-key"; @@ -662,7 +662,7 @@ test("before_provider_request removes all custom search tools from payload even // ─── BUG-1 regression: duplicate Brave tools on repeated provider toggle ──── -test("model_select re-enable does not duplicate Brave tools across toggle cycles", async (t) => { +test("model_select re-enable does not duplicate Brave tools across toggle cycles", async (_t) => { const originalKey = process.env.BRAVE_API_KEY; delete process.env.BRAVE_API_KEY; @@ -1210,7 +1210,7 @@ test("stripThinkingFromHistory handles string content (no array)", () => { // ─── Minimax search tests (R115) ──────────────────────────────────────────── -test("getMiniMaxSearchApiKey returns MINIMAX_CODE_PLAN_KEY when set", async (t) => { +test("getMiniMaxSearchApiKey returns MINIMAX_CODE_PLAN_KEY when set", async (_t) => { const original = process.env.MINIMAX_CODE_PLAN_KEY; const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; @@ -1232,7 +1232,7 @@ test("getMiniMaxSearchApiKey returns MINIMAX_CODE_PLAN_KEY when set", async (t) ); }); -test("getMiniMaxSearchApiKey falls back to MINIMAX_CODING_API_KEY", async (t) => { +test("getMiniMaxSearchApiKey falls back to MINIMAX_CODING_API_KEY", async (_t) => { const original = process.env.MINIMAX_CODE_PLAN_KEY; const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; @@ -1253,7 +1253,7 @@ test("getMiniMaxSearchApiKey falls back to MINIMAX_CODING_API_KEY", async (t) => ); }); -test("getMiniMaxSearchApiKey falls back to MINIMAX_API_KEY", async (t) => { +test("getMiniMaxSearchApiKey falls back to MINIMAX_API_KEY", async (_t) => { const original = process.env.MINIMAX_CODE_PLAN_KEY; const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; @@ -1274,7 +1274,7 @@ test("getMiniMaxSearchApiKey falls back to MINIMAX_API_KEY", async (t) => { ); }); -test("getMiniMaxSearchApiKey returns empty string when no keys set", async (t) => { +test("getMiniMaxSearchApiKey returns empty string when no keys set", async (_t) => { const original = process.env.MINIMAX_CODE_PLAN_KEY; const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; @@ -1295,7 +1295,7 @@ test("getMiniMaxSearchApiKey returns empty string when no keys set", async (t) = ); }); -test("resolveSearchProvider returns minimax when MINIMAX_API_KEY is set and preference is auto", async (t) => { +test("resolveSearchProvider returns minimax when MINIMAX_API_KEY is set and preference is auto", async (_t) => { const original = process.env.MINIMAX_CODE_PLAN_KEY; const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; @@ -1321,7 +1321,7 @@ test("resolveSearchProvider returns minimax when MINIMAX_API_KEY is set and pref ); }); -test("resolveSearchProvider prefers tavily over minimax in auto mode", async (t) => { +test("resolveSearchProvider prefers tavily over minimax in auto mode", async (_t) => { const original = process.env.TAVILY_API_KEY; const original2 = process.env.MINIMAX_API_KEY; afterEach(() => { @@ -1341,7 +1341,7 @@ test("resolveSearchProvider prefers tavily over minimax in auto mode", async (t) ); }); -test("resolveSearchProvider with explicit minimax preference returns minimax when key exists", async (t) => { +test("resolveSearchProvider with explicit minimax preference returns minimax when key exists", async (_t) => { const original = process.env.MINIMAX_API_KEY; const originalTavily = process.env.TAVILY_API_KEY; afterEach(() => { @@ -1360,7 +1360,7 @@ test("resolveSearchProvider with explicit minimax preference returns minimax whe ); }); -test("resolveSearchProvider minimax preference falls back when key missing", async (t) => { +test("resolveSearchProvider minimax preference falls back when key missing", async (_t) => { const original = process.env.MINIMAX_API_KEY; const originalCP = process.env.MINIMAX_CODE_PLAN_KEY; const originalCA = process.env.MINIMAX_CODING_API_KEY; @@ -1389,7 +1389,7 @@ test("resolveSearchProvider minimax preference falls back when key missing", asy ); }); -test("resolveSearchProvider returns null when no keys set", async (t) => { +test("resolveSearchProvider returns null when no keys set", async (_t) => { const keys = [ "TAVILY_API_KEY", "MINIMAX_API_KEY", diff --git a/src/tests/node-modules-symlink.test.ts b/src/tests/node-modules-symlink.test.ts index 285cbbd00..4ba214f22 100644 --- a/src/tests/node-modules-symlink.test.ts +++ b/src/tests/node-modules-symlink.test.ts @@ -19,12 +19,12 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; -import { test, afterEach } from "vitest"; import { fileURLToPath } from "node:url"; +import { afterEach, test } from "vitest"; // --- Integration tests via initResources (source/monorepo path) --- -test("initResources creates node_modules symlink in agent dir", async (t) => { +test("initResources creates node_modules symlink in agent dir", async (_t) => { const { initResources } = await import("../resource-loader.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-symlink-")); const fakeAgentDir = join(tmp, "agent"); @@ -48,7 +48,7 @@ test("initResources creates node_modules symlink in agent dir", async (t) => { ); }); -test("initResources replaces a real directory blocking node_modules with a symlink", async (t) => { +test("initResources replaces a real directory blocking node_modules with a symlink", async (_t) => { const { initResources } = await import("../resource-loader.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-symlink-realdir-")); const fakeAgentDir = join(tmp, "agent"); @@ -85,7 +85,7 @@ test("initResources replaces a real directory blocking node_modules with a symli ); }); -test("initResources replaces a stale symlink with a correct one", async (t) => { +test("initResources replaces a stale symlink with a correct one", async (_t) => { const { initResources } = await import("../resource-loader.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-symlink-stale-")); const fakeAgentDir = join(tmp, "agent"); @@ -121,7 +121,7 @@ test("initResources replaces a stale symlink with a correct one", async (t) => { ); }); -test("initResources replaces symlink whose target was deleted", async (t) => { +test("initResources replaces symlink whose target was deleted", async (_t) => { const { initResources } = await import("../resource-loader.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-symlink-missing-")); const fakeAgentDir = join(tmp, "agent"); @@ -155,7 +155,7 @@ test("initResources replaces symlink whose target was deleted", async (t) => { // These simulate the filesystem layout without going through initResources, // since packageRoot is fixed at module load time. -test("pnpm layout: merged node_modules contains entries from both hoisted and internal", (t) => { +test("pnpm layout: merged node_modules contains entries from both hoisted and internal", (_t) => { // Simulate pnpm global layout: // hoisted/node_modules/ // yaml/ ← external dep @@ -242,7 +242,7 @@ test("pnpm layout: merged node_modules contains entries from both hoisted and in ); }); -test("pnpm layout: non-@sf internal deps (e.g. @anthropic-ai) are included in merged dir", (t) => { +test("pnpm layout: non-@sf internal deps (e.g. @anthropic-ai) are included in merged dir", (_t) => { // Regression: PR #3564 narrowed the internal overlay to @sf* only, // dropping optionalDependencies like @anthropic-ai/claude-agent-sdk // that npm installs internally rather than hoisting. @@ -302,7 +302,7 @@ test("pnpm layout: non-@sf internal deps (e.g. @anthropic-ai) are included in me assert.ok(existsSync(join(agentNodeModules, "yaml")), "yaml should resolve"); }); -test("hasMissingWorkspaceScopes detects pnpm layout", (t) => { +test("hasMissingWorkspaceScopes detects pnpm layout", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-pnpm-detect-")); afterEach(() => rmSync(tmp, { recursive: true, force: true })); @@ -343,7 +343,7 @@ test("hasMissingWorkspaceScopes detects pnpm layout", (t) => { ); }); -test("merged node_modules marker uses fingerprint including directory entries", (t) => { +test("merged node_modules marker uses fingerprint including directory entries", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-pnpm-marker-")); afterEach(() => rmSync(tmp, { recursive: true, force: true })); diff --git a/src/tests/non-extension-library.test.ts b/src/tests/non-extension-library.test.ts index 3584f1c41..c1211e0ec 100644 --- a/src/tests/non-extension-library.test.ts +++ b/src/tests/non-extension-library.test.ts @@ -20,7 +20,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join, parse } from "node:path"; -import { test, describe, afterEach } from "vitest"; +import { afterEach, describe, test } from "vitest"; function makeTempDir(): string { const dir = join( @@ -61,7 +61,7 @@ function isNonExtensionLibrary(resolvedPath: string): boolean { } describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { - test("returns true for a file inside a directory with pi: {} (cmux pattern)", (t) => { + test("returns true for a file inside a directory with pi: {} (cmux pattern)", (_t) => { const root = makeTempDir(); afterEach(() => rmSync(root, { recursive: true, force: true })); const libDir = join(root, "cmux"); @@ -87,7 +87,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { ); }); - test("returns true for pi.extensions as empty array", (t) => { + test("returns true for pi.extensions as empty array", (_t) => { const root = makeTempDir(); afterEach(() => rmSync(root, { recursive: true, force: true })); const libDir = join(root, "lib-empty"); @@ -111,7 +111,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { ); }); - test("returns false for a directory without pi manifest (broken extension)", (t) => { + test("returns false for a directory without pi manifest (broken extension)", (_t) => { const root = makeTempDir(); afterEach(() => rmSync(root, { recursive: true, force: true })); const extDir = join(root, "broken-ext"); @@ -134,7 +134,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { ); }); - test("returns false when pi.extensions declares actual entries", (t) => { + test("returns false when pi.extensions declares actual entries", (_t) => { const root = makeTempDir(); afterEach(() => rmSync(root, { recursive: true, force: true })); const extDir = join(root, "declared-ext"); @@ -158,7 +158,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { ); }); - test("returns false when no package.json exists at all", (t) => { + test("returns false when no package.json exists at all", (_t) => { const root = makeTempDir(); afterEach(() => rmSync(root, { recursive: true, force: true })); const noManifest = join(root, "no-manifest"); @@ -174,7 +174,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { ); }); - test("handles malformed package.json gracefully", (t) => { + test("handles malformed package.json gracefully", (_t) => { const root = makeTempDir(); afterEach(() => rmSync(root, { recursive: true, force: true })); const badDir = join(root, "bad-json"); @@ -189,7 +189,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { ); }); - test("pi manifest with other fields but no extensions still opts out", (t) => { + test("pi manifest with other fields but no extensions still opts out", (_t) => { const root = makeTempDir(); afterEach(() => rmSync(root, { recursive: true, force: true })); const libDir = join(root, "lib-with-skills"); diff --git a/src/tests/parse-cli-args.test.ts b/src/tests/parse-cli-args.test.ts index 6cebdbacb..411fae24f 100644 --- a/src/tests/parse-cli-args.test.ts +++ b/src/tests/parse-cli-args.test.ts @@ -2,7 +2,7 @@ // Copyright (c) 2026 Jeremy McSpadden import assert from "node:assert/strict"; -import { test, describe } from "vitest"; +import { describe, test } from "vitest"; import { parseCliArgs } from "../cli-web-branch.ts"; function parse(...args: string[]) { diff --git a/src/tests/pi-ai-event-stream-factory.test.ts b/src/tests/pi-ai-event-stream-factory.test.ts index dcec994c1..ec8608100 100644 --- a/src/tests/pi-ai-event-stream-factory.test.ts +++ b/src/tests/pi-ai-event-stream-factory.test.ts @@ -1,9 +1,9 @@ import assert from "node:assert/strict"; -import { describe, it } from "vitest"; import { AssistantMessageEventStream, createAssistantMessageEventStream, } from "@singularity-forge/pi-ai"; +import { describe, it } from "vitest"; describe("@singularity-forge/pi-ai event stream exports", () => { it("exports createAssistantMessageEventStream for package consumers", () => { diff --git a/src/tests/postinstall.test.ts b/src/tests/postinstall.test.ts index 2d5038809..caa050c58 100644 --- a/src/tests/postinstall.test.ts +++ b/src/tests/postinstall.test.ts @@ -1,8 +1,8 @@ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { dirname, join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const projectRoot = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); diff --git a/src/tests/provider-manager-enter-key.test.ts b/src/tests/provider-manager-enter-key.test.ts index ce2d8eef9..75347be07 100644 --- a/src/tests/provider-manager-enter-key.test.ts +++ b/src/tests/provider-manager-enter-key.test.ts @@ -12,8 +12,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { describe, test } from "vitest"; import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/src/tests/provider-manager-remove.test.ts b/src/tests/provider-manager-remove.test.ts index 33c724849..851969d9e 100644 --- a/src/tests/provider-manager-remove.test.ts +++ b/src/tests/provider-manager-remove.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; const { ModelsJsonWriter } = await import( "../../packages/pi-coding-agent/src/core/models-json-writer.ts" @@ -97,7 +97,7 @@ function createComponent(options: { }; } -test("provider manager skips remove when provider has no auth", (t) => { +test("provider manager skips remove when provider has no auth", (_t) => { const modelsJsonPath = createTempModelsJsonPath(); const rootDir = join(modelsJsonPath, ".."); afterEach(() => rmSync(rootDir, { recursive: true, force: true })); @@ -117,7 +117,7 @@ test("provider manager skips remove when provider has no auth", (t) => { assert.equal(getRenderCalls(), 0); }); -test("provider manager removes provider models with confirmation when auth is stored", (t) => { +test("provider manager removes provider models with confirmation when auth is stored", (_t) => { const modelsJsonPath = createTempModelsJsonPath(); const rootDir = join(modelsJsonPath, ".."); afterEach(() => rmSync(rootDir, { recursive: true, force: true })); @@ -149,7 +149,7 @@ test("provider manager removes provider models with confirmation when auth is st assert.equal((component as any).selectedIndex, 0); }); -test("provider manager clamps selection after removing the selected provider", (t) => { +test("provider manager clamps selection after removing the selected provider", (_t) => { const modelsJsonPath = createTempModelsJsonPath(); const rootDir = join(modelsJsonPath, ".."); afterEach(() => rmSync(rootDir, { recursive: true, force: true })); diff --git a/src/tests/provider.test.ts b/src/tests/provider.test.ts index 550bffd27..a21c53197 100644 --- a/src/tests/provider.test.ts +++ b/src/tests/provider.test.ts @@ -11,7 +11,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -58,7 +58,7 @@ function makeTmpAuth(data: Record = {}): { // 1. resolveSearchProvider — 8 scenarios // ═══════════════════════════════════════════════════════════════════════════ -test("resolveSearchProvider returns tavily when only TAVILY_API_KEY is set", async (t) => { +test("resolveSearchProvider returns tavily when only TAVILY_API_KEY is set", async (_t) => { const { resolveSearchProvider } = await import( "../resources/extensions/search-the-web/provider.ts" ); @@ -307,7 +307,7 @@ test("resolveSearchProvider falls back to other provider when preferred key miss // 2. Preference get/set round-trip // ═══════════════════════════════════════════════════════════════════════════ -test("getSearchProviderPreference returns auto when no preference stored", async (t) => { +test("getSearchProviderPreference returns auto when no preference stored", async (_t) => { const { getSearchProviderPreference } = await import( "../resources/extensions/search-the-web/provider.ts" ); @@ -320,7 +320,7 @@ test("getSearchProviderPreference returns auto when no preference stored", async assert.equal(pref, "auto"); }); -test("getSearchProviderPreference reads from auth.json via AuthStorage", async (t) => { +test("getSearchProviderPreference reads from auth.json via AuthStorage", async (_t) => { const { getSearchProviderPreference } = await import( "../resources/extensions/search-the-web/provider.ts" ); @@ -335,7 +335,7 @@ test("getSearchProviderPreference reads from auth.json via AuthStorage", async ( assert.equal(pref, "tavily"); }); -test("setSearchProviderPreference writes to auth.json via AuthStorage", async (t) => { +test("setSearchProviderPreference writes to auth.json via AuthStorage", async (_t) => { const { getSearchProviderPreference, setSearchProviderPreference } = await import("../resources/extensions/search-the-web/provider.ts"); const { authPath, cleanup } = makeTmpAuth(); @@ -359,7 +359,7 @@ test("setSearchProviderPreference writes to auth.json via AuthStorage", async (t assert.equal(getSearchProviderPreference(authPath), "auto"); }); -test("getSearchProviderPreference returns auto for invalid stored value", async (t) => { +test("getSearchProviderPreference returns auto for invalid stored value", async (_t) => { const { getSearchProviderPreference } = await import( "../resources/extensions/search-the-web/provider.ts" ); diff --git a/src/tests/read-tool-offset-clamp.test.ts b/src/tests/read-tool-offset-clamp.test.ts index b2f652777..76d3593b1 100644 --- a/src/tests/read-tool-offset-clamp.test.ts +++ b/src/tests/read-tool-offset-clamp.test.ts @@ -10,7 +10,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { createReadTool } from "../../packages/pi-coding-agent/src/core/tools/read.ts"; @@ -35,7 +35,7 @@ function writeLines(dir: string, name: string, lineCount: number): string { // Offset beyond file bounds — should clamp, not throw (#3007) // ═══════════════════════════════════════════════════════════════════════════ -test("read tool: offset exceeding file length should NOT throw (#3007)", async (t) => { +test("read tool: offset exceeding file length should NOT throw (#3007)", async (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); writeLines(dir, "small-artifact.md", 13); @@ -64,7 +64,7 @@ test("read tool: offset exceeding file length should NOT throw (#3007)", async ( ); }); -test("read tool: offset 100 on a 5-line file clamps to last line", async (t) => { +test("read tool: offset 100 on a 5-line file clamps to last line", async (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); writeLines(dir, "tiny-file.txt", 5); @@ -82,7 +82,7 @@ test("read tool: offset 100 on a 5-line file clamps to last line", async (t) => ); }); -test("read tool: offset at exact last line works normally", async (t) => { +test("read tool: offset at exact last line works normally", async (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); writeLines(dir, "exact-offset.txt", 5); @@ -98,7 +98,7 @@ test("read tool: offset at exact last line works normally", async (t) => { assert.ok(text.includes("Line 5"), "should include line 5"); }); -test("read tool: clamped offset includes notice about adjustment", async (t) => { +test("read tool: clamped offset includes notice about adjustment", async (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); writeLines(dir, "notice-test.md", 10); @@ -119,7 +119,7 @@ test("read tool: clamped offset includes notice about adjustment", async (t) => ); }); -test("read tool: streaming with offset and limit on large file reads only requested lines", async (t) => { +test("read tool: streaming with offset and limit on large file reads only requested lines", async (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); @@ -150,7 +150,7 @@ test("read tool: streaming with offset and limit on large file reads only reques ); }); -test("read tool: streaming with limit only reads first N lines", async (t) => { +test("read tool: streaming with limit only reads first N lines", async (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); writeLines(dir, "medium-file.txt", 500); @@ -168,7 +168,7 @@ test("read tool: streaming with limit only reads first N lines", async (t) => { assert.ok(!text.includes("Line 6:"), "should NOT include line 6"); }); -test("read tool: streaming with offset only reads from offset to end", async (t) => { +test("read tool: streaming with offset only reads from offset to end", async (_t) => { const { dir, cleanup } = makeTmpDir(); afterEach(cleanup); writeLines(dir, "offset-only.txt", 100); diff --git a/src/tests/resource-loader-content-hash.test.ts b/src/tests/resource-loader-content-hash.test.ts index a2804875b..0ab3a4251 100644 --- a/src/tests/resource-loader-content-hash.test.ts +++ b/src/tests/resource-loader-content-hash.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; /** * Regression test for SF build #4787. @@ -25,7 +25,7 @@ import { test, afterEach } from "vitest"; * resync on next launch. */ -test("computeResourceFingerprint detects same-size content edits (#4787)", async (t) => { +test("computeResourceFingerprint detects same-size content edits (#4787)", async (_t) => { const { computeResourceFingerprint } = await import("../resource-loader.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-fingerprint-content-")); @@ -58,7 +58,7 @@ test("computeResourceFingerprint detects same-size content edits (#4787)", async ); }); -test("syncResourceDir overwrites same-size stale content on refresh (#4787)", async (t) => { +test("syncResourceDir overwrites same-size stale content on refresh (#4787)", async (_t) => { const { syncResourceDir } = await import("../resource-loader.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-sync-samesize-")); diff --git a/src/tests/resource-loader.test.ts b/src/tests/resource-loader.test.ts index 17a5cc80e..c46a9edb7 100644 --- a/src/tests/resource-loader.test.ts +++ b/src/tests/resource-loader.test.ts @@ -9,7 +9,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join, parse } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; function overrideHomeEnv(homeDir: string): () => void { const original = { @@ -62,7 +62,7 @@ test("getExtensionKey normalizes top-level .ts and .js entry names to the same k ); }); -test("hasStaleCompiledExtensionSiblings only flags top-level .ts/.js sibling pairs", async (t) => { +test("hasStaleCompiledExtensionSiblings only flags top-level .ts/.js sibling pairs", async (_t) => { const { hasStaleCompiledExtensionSiblings } = await import( "../resource-loader.ts" ); @@ -102,7 +102,7 @@ test("hasStaleCompiledExtensionSiblings only flags top-level .ts/.js sibling pai ); }); -test("buildResourceLoader excludes duplicate top-level pi extensions when bundled resources use .js", async (t) => { +test("buildResourceLoader excludes duplicate top-level pi extensions when bundled resources use .js", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-resource-loader-home-")); const piExtensionsDir = join(tmp, ".pi", "agent", "extensions"); const fakeAgentDir = join(tmp, ".sf", "agent"); @@ -176,7 +176,7 @@ test("initResources manifest tracks all bundled extension subdirectories includi } }); -test("initResources prunes stale top-level extension siblings next to bundled compiled extensions", async (t) => { +test("initResources prunes stale top-level extension siblings next to bundled compiled extensions", async (_t) => { const { initResources } = await import("../resource-loader.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-resource-loader-sync-")); const fakeAgentDir = join(tmp, "agent"); diff --git a/src/tests/resource-sync-staleness.test.ts b/src/tests/resource-sync-staleness.test.ts index 8f733eb9b..965bfb0e4 100644 --- a/src/tests/resource-sync-staleness.test.ts +++ b/src/tests/resource-sync-staleness.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; /** * Integration test for resource sync staleness detection. @@ -12,7 +12,7 @@ import { test, afterEach } from "vitest"; * with a broken import to persist at ~/.sf/agent/extensions/). */ -test("resource manifest includes contentHash", async (t) => { +test("resource manifest includes contentHash", async (_t) => { // We can't easily call initResources directly because it depends on // module-level resolved paths. Instead, verify the manifest schema // by simulating what writeManagedResourceManifest produces. diff --git a/src/tests/rtk-execution-seams.test.ts b/src/tests/rtk-execution-seams.test.ts index a4b1c0be0..3c0eeab60 100644 --- a/src/tests/rtk-execution-seams.test.ts +++ b/src/tests/rtk-execution-seams.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { createAsyncBashTool } from "../resources/extensions/async-jobs/async-bash-tool.ts"; import { AsyncJobManager } from "../resources/extensions/async-jobs/job-manager.ts"; import { runOnSession } from "../resources/extensions/bg-shell/interaction.ts"; @@ -210,7 +210,7 @@ test("async_bash executes the RTK-rewritten command", async () => { }); }); -test("bg_shell start and runOnSession both execute RTK-rewritten commands", async (t) => { +test("bg_shell start and runOnSession both execute RTK-rewritten commands", async (_t) => { if (process.platform === "win32") { return; // skip: "bg_shell requires bash; Windows CI runners don't have Git Bash"; return; diff --git a/src/tests/rtk-session-stats.test.ts b/src/tests/rtk-session-stats.test.ts index 38ec72299..2ccc1d891 100644 --- a/src/tests/rtk-session-stats.test.ts +++ b/src/tests/rtk-session-stats.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach, beforeEach } from "vitest"; +import { afterEach, beforeEach, test } from "vitest"; import { clearRtkSessionBaseline, diff --git a/src/tests/rtk.test.ts b/src/tests/rtk.test.ts index ce656e67d..18af13393 100644 --- a/src/tests/rtk.test.ts +++ b/src/tests/rtk.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { delimiter, join } from "node:path"; -import { test, afterEach, beforeEach } from "vitest"; +import { afterEach, beforeEach, test } from "vitest"; import { buildRtkEnv, diff --git a/src/tests/schedule-headless-query.test.ts b/src/tests/schedule-headless-query.test.ts index e06c86bee..10641f690 100644 --- a/src/tests/schedule-headless-query.test.ts +++ b/src/tests/schedule-headless-query.test.ts @@ -9,8 +9,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); const src = readFileSync(join(__dirname, "..", "headless-query.ts"), "utf-8"); diff --git a/src/tests/search-loop-guard.test.ts b/src/tests/search-loop-guard.test.ts index 3d0093751..afa1e820d 100644 --- a/src/tests/search-loop-guard.test.ts +++ b/src/tests/search-loop-guard.test.ts @@ -10,7 +10,7 @@ */ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import searchExtension from "../resources/extensions/search-the-web/index.ts"; import { registerSearchTool, @@ -140,7 +140,7 @@ async function callSearch( * state (lastSearchKey, consecutiveDupeCount) starts fresh here. */ -test("search loop guard fires after MAX_CONSECUTIVE_DUPES duplicates", async (t) => { +test("search loop guard fires after MAX_CONSECUTIVE_DUPES duplicates", async (_t) => { process.env.BRAVE_API_KEY = "test-key-loop-guard"; delete process.env.TAVILY_API_KEY; delete process.env.OLLAMA_API_KEY; @@ -176,7 +176,7 @@ test("search loop guard fires after MAX_CONSECUTIVE_DUPES duplicates", async (t) ); }); -test("search loop guard resets at session_start boundary", async (t) => { +test("search loop guard resets at session_start boundary", async (_t) => { process.env.BRAVE_API_KEY = "test-key-loop-guard-session"; delete process.env.TAVILY_API_KEY; delete process.env.OLLAMA_API_KEY; @@ -216,7 +216,7 @@ test("search loop guard resets at session_start boundary", async (t) => { ); }); -test("search loop guard stays armed after firing — subsequent duplicates immediately re-trigger (#1671)", async (t) => { +test("search loop guard stays armed after firing — subsequent duplicates immediately re-trigger (#1671)", async (_t) => { process.env.BRAVE_API_KEY = "test-key-loop-guard-2"; delete process.env.TAVILY_API_KEY; delete process.env.OLLAMA_API_KEY; @@ -264,7 +264,7 @@ test("search loop guard stays armed after firing — subsequent duplicates immed ); }); -test("search loop guard resets cleanly when a different query is issued", async (t) => { +test("search loop guard resets cleanly when a different query is issued", async (_t) => { process.env.BRAVE_API_KEY = "test-key-loop-guard-3"; delete process.env.TAVILY_API_KEY; delete process.env.OLLAMA_API_KEY; @@ -296,7 +296,7 @@ test("search loop guard resets cleanly when a different query is issued", async ); }); -test("session search budget blocks after MAX_SEARCHES_PER_SESSION varied queries", async (t) => { +test("session search budget blocks after MAX_SEARCHES_PER_SESSION varied queries", async (_t) => { process.env.BRAVE_API_KEY = "test-key-budget"; delete process.env.TAVILY_API_KEY; delete process.env.OLLAMA_API_KEY; @@ -340,7 +340,7 @@ test("session search budget blocks after MAX_SEARCHES_PER_SESSION varied queries ); }); -test("session search budget resets via resetSearchLoopGuardState", async (t) => { +test("session search budget resets via resetSearchLoopGuardState", async (_t) => { process.env.BRAVE_API_KEY = "test-key-budget-reset"; delete process.env.TAVILY_API_KEY; delete process.env.OLLAMA_API_KEY; diff --git a/src/tests/search-provider-command.test.ts b/src/tests/search-provider-command.test.ts index 420dc90a2..20d2219d9 100644 --- a/src/tests/search-provider-command.test.ts +++ b/src/tests/search-provider-command.test.ts @@ -14,7 +14,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; // ─── Helpers (reused from provider.test.ts pattern) ──────────────────────── @@ -140,7 +140,7 @@ async function loadCommand(): Promise { // 1. Direct arg — tavily // ═══════════════════════════════════════════════════════════════════════════ -test('direct arg "tavily" sets preference and notifies', async (t) => { +test('direct arg "tavily" sets preference and notifies', async (_t) => { const { setSearchProviderPreference } = await import( "../resources/extensions/search-the-web/provider.ts" ); @@ -187,7 +187,7 @@ test('direct arg "tavily" sets preference and notifies', async (t) => { // 2. Direct arg — brave // ═══════════════════════════════════════════════════════════════════════════ -test('direct arg "brave" sets preference and notifies', async (t) => { +test('direct arg "brave" sets preference and notifies', async (_t) => { const cmd = await loadCommand(); const { cleanup } = makeTmpAuth(); @@ -216,7 +216,7 @@ test('direct arg "brave" sets preference and notifies', async (t) => { // 3. Direct arg — auto // ═══════════════════════════════════════════════════════════════════════════ -test('direct arg "auto" sets preference and notifies', async (t) => { +test('direct arg "auto" sets preference and notifies', async (_t) => { const cmd = await loadCommand(); const { cleanup } = makeTmpAuth(); @@ -292,7 +292,7 @@ test("no arg shows select UI with 8 options, user picks brave", async () => { // 5. Cancel (select returns undefined) — no side effects // ═══════════════════════════════════════════════════════════════════════════ -test("cancel (select returns undefined) produces no side effects", async (t) => { +test("cancel (select returns undefined) produces no side effects", async (_t) => { const { setSearchProviderPreference } = await import( "../resources/extensions/search-the-web/provider.ts" ); diff --git a/src/tests/search-tavily.test.ts b/src/tests/search-tavily.test.ts index d18d4b697..5d8c9cfa5 100644 --- a/src/tests/search-tavily.test.ts +++ b/src/tests/search-tavily.test.ts @@ -12,7 +12,7 @@ */ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { resolveSearchProvider } from "../resources/extensions/search-the-web/provider.ts"; import { mapFreshnessToTavily } from "../resources/extensions/search-the-web/tavily.ts"; @@ -93,7 +93,7 @@ function mockFetch(responseBody: unknown, status = 200) { // Test: executeTavilySearch produces correct CachedSearchResult shape // ============================================================================= -test("executeTavilySearch sends POST to Tavily API and produces CachedSearchResult", async (t) => { +test("executeTavilySearch sends POST to Tavily API and produces CachedSearchResult", async (_t) => { // Set TAVILY_API_KEY for this test const origKey = process.env.TAVILY_API_KEY; process.env.TAVILY_API_KEY = "tvly-test-key-12345"; @@ -173,7 +173,7 @@ test("executeTavilySearch sends POST to Tavily API and produces CachedSearchResu // Test: Provider branching — resolveSearchProvider returns correct provider // ============================================================================= -test("resolveSearchProvider returns 'tavily' when TAVILY_API_KEY is set and BRAVE_API_KEY is not", (t) => { +test("resolveSearchProvider returns 'tavily' when TAVILY_API_KEY is set and BRAVE_API_KEY is not", (_t) => { const origTavily = process.env.TAVILY_API_KEY; const origBrave = process.env.BRAVE_API_KEY; @@ -191,7 +191,7 @@ test("resolveSearchProvider returns 'tavily' when TAVILY_API_KEY is set and BRAV assert.equal(provider, "tavily"); }); -test("resolveSearchProvider returns 'brave' when only BRAVE_API_KEY is set", (t) => { +test("resolveSearchProvider returns 'brave' when only BRAVE_API_KEY is set", (_t) => { const origTavily = process.env.TAVILY_API_KEY; const origBrave = process.env.BRAVE_API_KEY; @@ -312,7 +312,7 @@ test("no-key error message contains both TAVILY_API_KEY and BRAVE_API_KEY", () = // Test: Tavily answer mapping — answer field flows through as summary text // ============================================================================= -test("Tavily answer field maps to summaryText in CachedSearchResult", async (t) => { +test("Tavily answer field maps to summaryText in CachedSearchResult", async (_t) => { const origKey = process.env.TAVILY_API_KEY; process.env.TAVILY_API_KEY = "tvly-test-key"; @@ -391,7 +391,7 @@ test("freshness='week' maps to time_range='week' in Tavily request body", () => // Test: Domain mapping — include_domains, not site: prefix // ============================================================================= -test("Tavily domain filter uses include_domains, not site: prefix in query", async (t) => { +test("Tavily domain filter uses include_domains, not site: prefix in query", async (_t) => { const origKey = process.env.TAVILY_API_KEY; process.env.TAVILY_API_KEY = "tvly-test-key"; diff --git a/src/tests/secret-scan.test.ts b/src/tests/secret-scan.test.ts index a841d82f8..c47bff422 100644 --- a/src/tests/secret-scan.test.ts +++ b/src/tests/secret-scan.test.ts @@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { platform, tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; // Secret scanner requires bash + POSIX grep — skip on Windows const isWindows = platform() === "win32"; @@ -158,7 +158,7 @@ test("skips package-lock.json", { skip: isWindows }, () => { ); }); -test("reports no files cleanly", { skip: isWindows }, (t) => { +test("reports no files cleanly", { skip: isWindows }, (_t) => { const dir = mkdtempSync(join(tmpdir(), "secret-scan-empty-")); afterEach(() => { rmSync(dir, { recursive: true, force: true }); @@ -191,7 +191,7 @@ test("reports multiple secrets in one file", { skip: isWindows }, () => { // ── CI mode (--diff) ───────────────────────────────────────────────── -test("CI mode scans diff against ref", { skip: isWindows }, (t) => { +test("CI mode scans diff against ref", { skip: isWindows }, (_t) => { const dir = mkdtempSync(join(tmpdir(), "secret-scan-ci-")); afterEach(() => { rmSync(dir, { recursive: true, force: true }); diff --git a/src/tests/security-overrides.test.ts b/src/tests/security-overrides.test.ts index 0fd9900a8..fe28e1768 100644 --- a/src/tests/security-overrides.test.ts +++ b/src/tests/security-overrides.test.ts @@ -1,11 +1,11 @@ import assert from "node:assert/strict"; -import { afterEach, beforeEach, describe, it } from "vitest"; import { getAllowedCommandPrefixes, SAFE_COMMAND_PREFIXES, SettingsManager, setAllowedCommandPrefixes, } from "@singularity-forge/pi-coding-agent"; +import { afterEach, beforeEach, describe, it } from "vitest"; import { getFetchAllowedUrls, setFetchAllowedUrls, diff --git a/src/tests/subagent-debate-mode.test.ts b/src/tests/subagent-debate-mode.test.ts index 547f9b6c2..baa893c67 100644 --- a/src/tests/subagent-debate-mode.test.ts +++ b/src/tests/subagent-debate-mode.test.ts @@ -1,8 +1,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); const subagentSrc = readFileSync( diff --git a/src/tests/terminal-cmux.test.ts b/src/tests/terminal-cmux.test.ts index 093c2ce99..c2cbe685d 100644 --- a/src/tests/terminal-cmux.test.ts +++ b/src/tests/terminal-cmux.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { detectCapabilities, resetCapabilitiesCache, @@ -20,7 +20,7 @@ test("isCmuxTerminal detects cmux env vars", () => { ); }); -test("detectCapabilities treats cmux as kitty-capable", (t) => { +test("detectCapabilities treats cmux as kitty-capable", (_t) => { const originalEnv = process.env; process.env = { ...originalEnv, diff --git a/src/tests/tool-bootstrap.test.ts b/src/tests/tool-bootstrap.test.ts index f7022a312..c34e50735 100644 --- a/src/tests/tool-bootstrap.test.ts +++ b/src/tests/tool-bootstrap.test.ts @@ -12,7 +12,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { ensureManagedTools, resolveToolFromPath } from "../tool-bootstrap.js"; @@ -30,7 +30,7 @@ function makeExecutable( return file; } -test("resolveToolFromPath finds fd via fdfind fallback", (t) => { +test("resolveToolFromPath finds fd via fdfind fallback", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-tool-bootstrap-resolve-")); afterEach(() => { rmSync(tmp, { recursive: true, force: true }); @@ -41,7 +41,7 @@ test("resolveToolFromPath finds fd via fdfind fallback", (t) => { assert.equal(resolved, join(tmp, "fdfind")); }); -test("ensureManagedTools provisions fd and rg into managed bin dir", (t) => { +test("ensureManagedTools provisions fd and rg into managed bin dir", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-tool-bootstrap-provision-")); const sourceBin = join(tmp, "source-bin"); const targetBin = join(tmp, "target-bin"); @@ -71,7 +71,7 @@ test("ensureManagedTools provisions fd and rg into managed bin dir", (t) => { ); }); -test("ensureManagedTools copies executable when symlink target already exists as a broken link", (t) => { +test("ensureManagedTools copies executable when symlink target already exists as a broken link", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-tool-bootstrap-copy-")); const sourceBin = join(tmp, "source-bin"); const targetBin = join(tmp, "target-bin"); diff --git a/src/tests/ttsr-rule-loader.test.ts b/src/tests/ttsr-rule-loader.test.ts index 9e348d142..57b2548fc 100644 --- a/src/tests/ttsr-rule-loader.test.ts +++ b/src/tests/ttsr-rule-loader.test.ts @@ -7,7 +7,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { loadRules } from "../../src/resources/extensions/ttsr/index.js"; @@ -48,7 +48,7 @@ function writeRule( // Project-local rule loading // ═══════════════════════════════════════════════════════════════════════════ -test("loads rule from project .sf/rules/", (t) => { +test("loads rule from project .sf/rules/", (_t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); afterEach(() => { cleanup(); @@ -67,7 +67,7 @@ test("loads rule from project .sf/rules/", (t) => { assert.equal(projectRule.content, "Do not use console.log."); }); -test("parses scope and globs from frontmatter", (t) => { +test("parses scope and globs from frontmatter", (_t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); afterEach(() => { cleanup(); @@ -86,7 +86,7 @@ test("parses scope and globs from frontmatter", (t) => { assert.deepEqual(rule.globs, ["*.ts"]); }); -test("skips files without valid frontmatter", (t) => { +test("skips files without valid frontmatter", (_t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); afterEach(() => { cleanup(); @@ -98,7 +98,7 @@ test("skips files without valid frontmatter", (t) => { assert.equal(rules.filter((r) => r.name === "broken").length, 0); }); -test("skips rules with no condition", (t) => { +test("skips rules with no condition", (_t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); afterEach(() => { cleanup(); @@ -114,7 +114,7 @@ test("skips rules with no condition", (t) => { assert.equal(rules.filter((r) => r.name === "no-condition").length, 0); }); -test("returns empty array when .sf/rules/ does not exist", (t) => { +test("returns empty array when .sf/rules/ does not exist", (_t) => { const { cwd, cleanup } = makeTmpProject(); afterEach(() => { cleanup(); @@ -126,7 +126,7 @@ test("returns empty array when .sf/rules/ does not exist", (t) => { assert.ok(Array.isArray(rules)); }); -test("loads multiple rules from same directory", (t) => { +test("loads multiple rules from same directory", (_t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); afterEach(() => { cleanup(); @@ -140,7 +140,7 @@ test("loads multiple rules from same directory", (t) => { assert.ok(names.includes("rule-b")); }); -test("handles quoted values in frontmatter", (t) => { +test("handles quoted values in frontmatter", (_t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); afterEach(() => { cleanup(); diff --git a/src/tests/tui-autocomplete-ghost-lines.test.ts b/src/tests/tui-autocomplete-ghost-lines.test.ts index b97189867..73af9117e 100644 --- a/src/tests/tui-autocomplete-ghost-lines.test.ts +++ b/src/tests/tui-autocomplete-ghost-lines.test.ts @@ -1,11 +1,11 @@ import assert from "node:assert/strict"; -import { describe, it } from "vitest"; import { type Component, CURSOR_MARKER, type Terminal, TUI, } from "@singularity-forge/pi-tui"; +import { describe, it } from "vitest"; class MockTTYTerminal implements Terminal { public writtenData: string[] = []; diff --git a/src/tests/tui-content-cursor-desync.test.ts b/src/tests/tui-content-cursor-desync.test.ts index 56684157a..d41e996ab 100644 --- a/src/tests/tui-content-cursor-desync.test.ts +++ b/src/tests/tui-content-cursor-desync.test.ts @@ -8,13 +8,13 @@ */ import assert from "node:assert/strict"; -import { describe, it } from "vitest"; import { type Component, CURSOR_MARKER, type Terminal, TUI, } from "@singularity-forge/pi-tui"; +import { describe, it } from "vitest"; class MockTTYTerminal implements Terminal { public writtenData: string[] = []; @@ -101,7 +101,6 @@ describe("TUI cursor tracking regression (#3764)", () => { const buffer = terminal.writtenData[0]; // Should not contain large upward jumps (3+ rows) - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI cursor sequence const largeUpJump = buffer.match(/\x1b\[([3-9]|\d{2,})A/); assert.strictEqual( largeUpJump, @@ -154,7 +153,6 @@ describe("TUI cursor tracking regression (#3764)", () => { const buffer = terminal.writtenData[0]; // Verify no extremely large cursor jumps that would cause visual corruption - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI cursor sequence const hugeJump = buffer.match(/\x1b\[(\d{2,})A/); if (hugeJump) { const jumpSize = parseInt(hugeJump[1], 10); @@ -190,7 +188,6 @@ describe("TUI cursor tracking regression (#3764)", () => { const navBuffer = terminal.writtenData[0]; // The differential render should only update the 2 changed lines (16 and 17) // Verify no large upward jumps from wrong baseline - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI cursor sequence const navJump = navBuffer.match(/\x1b\[(\d{2,})A/); if (navJump) { const jumpSize = parseInt(navJump[1], 10); @@ -274,7 +271,6 @@ describe("TUI cursor tracking regression (#3764)", () => { const buffer = terminal.writtenData[0]; // Should not jump to wrong row — only line 9 changed - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI cursor sequence const upJump = buffer.match(/\x1b\[(\d+)A/); if (upJump) { const jumpSize = parseInt(upJump[1], 10); diff --git a/src/tests/tui-non-tty-render-loop.test.ts b/src/tests/tui-non-tty-render-loop.test.ts index 2450c553e..9d1a03c0d 100644 --- a/src/tests/tui-non-tty-render-loop.test.ts +++ b/src/tests/tui-non-tty-render-loop.test.ts @@ -10,9 +10,9 @@ */ import assert from "node:assert/strict"; -import { describe, it } from "vitest"; import type { Terminal } from "@singularity-forge/pi-tui"; import { ProcessTerminal, TUI } from "@singularity-forge/pi-tui"; +import { describe, it } from "vitest"; /** * A mock terminal that tracks writes and render activity. diff --git a/src/tests/update-check.test.ts b/src/tests/update-check.test.ts index 9eeb9ca03..a60a4624b 100644 --- a/src/tests/update-check.test.ts +++ b/src/tests/update-check.test.ts @@ -3,7 +3,7 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { createServer } from "node:http"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { checkForUpdates, @@ -47,7 +47,7 @@ test("compareSemver handles versions with different segment counts", () => { // readUpdateCache / writeUpdateCache // --------------------------------------------------------------------------- -test("readUpdateCache returns null for nonexistent file", (t) => { +test("readUpdateCache returns null for nonexistent file", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-cache-")); afterEach(() => { rmSync(tmp, { recursive: true, force: true }); @@ -57,7 +57,7 @@ test("readUpdateCache returns null for nonexistent file", (t) => { assert.equal(result, null); }); -test("readUpdateCache returns null for malformed JSON", (t) => { +test("readUpdateCache returns null for malformed JSON", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-cache-")); afterEach(() => { rmSync(tmp, { recursive: true, force: true }); @@ -69,7 +69,7 @@ test("readUpdateCache returns null for malformed JSON", (t) => { assert.equal(result, null); }); -test("writeUpdateCache + readUpdateCache round-trips correctly", (t) => { +test("writeUpdateCache + readUpdateCache round-trips correctly", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-cache-")); afterEach(() => { rmSync(tmp, { recursive: true, force: true }); @@ -82,7 +82,7 @@ test("writeUpdateCache + readUpdateCache round-trips correctly", (t) => { assert.deepEqual(result, cache); }); -test("writeUpdateCache creates parent directories", (t) => { +test("writeUpdateCache creates parent directories", (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-cache-")); afterEach(() => { rmSync(tmp, { recursive: true, force: true }); @@ -120,7 +120,7 @@ function startMockRegistry( }); } -test("checkForUpdates calls onUpdate when newer version is available", async (t) => { +test("checkForUpdates calls onUpdate when newer version is available", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({ version: "99.0.0" }); afterEach(async () => { @@ -150,7 +150,7 @@ test("checkForUpdates calls onUpdate when newer version is available", async (t) assert.equal(reportedLatest, "99.0.0"); }); -test("checkForUpdates does not call onUpdate when already on latest", async (t) => { +test("checkForUpdates does not call onUpdate when already on latest", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({ version: "1.0.0" }); afterEach(async () => { @@ -174,7 +174,7 @@ test("checkForUpdates does not call onUpdate when already on latest", async (t) assert.ok(!called, "onUpdate should not be called when versions match"); }); -test("checkForUpdates does not call onUpdate when current is ahead", async (t) => { +test("checkForUpdates does not call onUpdate when current is ahead", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({ version: "1.0.0" }); afterEach(async () => { @@ -198,7 +198,7 @@ test("checkForUpdates does not call onUpdate when current is ahead", async (t) = assert.ok(!called, "onUpdate should not be called when current is ahead"); }); -test("checkForUpdates writes cache after successful fetch", async (t) => { +test("checkForUpdates writes cache after successful fetch", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const cachePath = join(tmp, ".update-check"); const registry = await startMockRegistry({ version: "5.0.0" }); @@ -222,7 +222,7 @@ test("checkForUpdates writes cache after successful fetch", async (t) => { assert.ok(cache!.lastCheck > 0); }); -test("checkForUpdates uses cache and skips fetch when checked recently", async (t) => { +test("checkForUpdates uses cache and skips fetch when checked recently", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const cachePath = join(tmp, ".update-check"); // Write a fresh cache entry @@ -255,7 +255,7 @@ test("checkForUpdates uses cache and skips fetch when checked recently", async ( assert.equal(reportedLatest, "10.0.0"); }); -test("checkForUpdates skips notification when cache is fresh and versions match", async (t) => { +test("checkForUpdates skips notification when cache is fresh and versions match", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const cachePath = join(tmp, ".update-check"); writeUpdateCache( @@ -285,7 +285,7 @@ test("checkForUpdates skips notification when cache is fresh and versions match" ); }); -test("checkForUpdates handles server error gracefully", async (t) => { +test("checkForUpdates handles server error gracefully", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({}, 500); afterEach(async () => { @@ -309,7 +309,7 @@ test("checkForUpdates handles server error gracefully", async (t) => { assert.ok(!called, "onUpdate should not be called on server error"); }); -test("checkForUpdates handles network timeout gracefully", async (t) => { +test("checkForUpdates handles network timeout gracefully", async (_t) => { // Start a server that never responds const server = createServer(() => { /* intentionally never respond */ @@ -339,7 +339,7 @@ test("checkForUpdates handles network timeout gracefully", async (t) => { assert.ok(!called, "onUpdate should not be called on timeout"); }); -test("checkForUpdates handles missing version field in response", async (t) => { +test("checkForUpdates handles missing version field in response", async (_t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({ name: "sf-run" }); // no version field afterEach(async () => { @@ -366,7 +366,7 @@ test("checkForUpdates handles missing version field in response", async (t) => { ); }); -test("fetchLatestVersionFromRegistry returns the registry version string", async (t) => { +test("fetchLatestVersionFromRegistry returns the registry version string", async (_t) => { const registry = await startMockRegistry({ version: "2.67.0" }); afterEach(async () => { await registry.close(); @@ -376,7 +376,7 @@ test("fetchLatestVersionFromRegistry returns the registry version string", async assert.equal(latest, "2.67.0"); }); -test("fetchLatestVersionFromRegistry returns null for blank version strings", async (t) => { +test("fetchLatestVersionFromRegistry returns null for blank version strings", async (_t) => { const registry = await startMockRegistry({ version: "" }); afterEach(async () => { await registry.close(); diff --git a/src/tests/update-cmd-diagnostics.test.ts b/src/tests/update-cmd-diagnostics.test.ts index 6221b9ae9..68c0957c2 100644 --- a/src/tests/update-cmd-diagnostics.test.ts +++ b/src/tests/update-cmd-diagnostics.test.ts @@ -7,8 +7,8 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { test } from "vitest"; import { fileURLToPath } from "node:url"; +import { test } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); diff --git a/src/tests/welcome-screen.test.ts b/src/tests/welcome-screen.test.ts index 0285c9ac0..922edfb1b 100644 --- a/src/tests/welcome-screen.test.ts +++ b/src/tests/welcome-screen.test.ts @@ -3,7 +3,7 @@ */ import assert from "node:assert/strict"; -import { test, afterEach } from "vitest"; +import { afterEach, test } from "vitest"; import { printWelcomeScreen } from "../welcome-screen.js"; @@ -28,7 +28,6 @@ function capture(opts: Parameters[0]): string { } function strip(s: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequence return s.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -60,7 +59,7 @@ test("renders cwd hint", () => { assert.ok(out.includes("/sf to begin"), "hint line missing"); }); -test("skips when not a TTY", (t) => { +test("skips when not a TTY", (_t) => { const chunks: string[] = []; const original = process.stderr.write.bind(process.stderr); (process.stderr as any).write = (chunk: string) => { @@ -99,7 +98,7 @@ test("omits remote channel when not provided", () => { ); }); -test("separator lines extend to full terminal width on wide terminals", (t) => { +test("separator lines extend to full terminal width on wide terminals", (_t) => { const origColumns = process.stderr.columns; (process.stderr as any).columns = 250; afterEach(() => { diff --git a/src/web/knowledge-service.ts b/src/web/knowledge-service.ts index 9535290c5..3336d4fed 100644 --- a/src/web/knowledge-service.ts +++ b/src/web/knowledge-service.ts @@ -77,7 +77,6 @@ export function parseKnowledgeFile(content: string): KnowledgeEntry[] { const tableMatches: Array<{ id: string; rest: string }> = []; let match: RegExpExecArray | null; - // biome-ignore lint/suspicious/noAssignInExpressions: intentional read loop while ((match = tableRowRegex.exec(body)) !== null) { tableMatches.push({ id: match[1], rest: match[2] }); } diff --git a/studio/electron.vite.config.ts b/studio/electron.vite.config.ts index 3acfbdf5a..2c640af91 100644 --- a/studio/electron.vite.config.ts +++ b/studio/electron.vite.config.ts @@ -1,7 +1,7 @@ import { resolve } from "node:path"; -import { defineConfig } from "electron-vite"; -import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "electron-vite"; export default defineConfig({ main: { diff --git a/studio/src/main/index.ts b/studio/src/main/index.ts index be91165a3..44884c1df 100644 --- a/studio/src/main/index.ts +++ b/studio/src/main/index.ts @@ -1,9 +1,9 @@ -import { app, BrowserWindow } from "electron"; import { join } from "node:path"; +import { app, BrowserWindow } from "electron"; const __dirname = import.meta.dirname; -let mainWindow: BrowserWindow | null = null; +let _mainWindow: BrowserWindow | null = null; function createWindow(): BrowserWindow { const preload = join(__dirname, "../preload/index.mjs"); @@ -39,11 +39,11 @@ function createWindow(): BrowserWindow { } app.whenReady().then(() => { - mainWindow = createWindow(); + _mainWindow = createWindow(); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) { - mainWindow = createWindow(); + _mainWindow = createWindow(); } }); }); diff --git a/studio/src/preload/index.d.ts b/studio/src/preload/index.d.ts index f5ca00e4c..6ac3e718f 100644 --- a/studio/src/preload/index.d.ts +++ b/studio/src/preload/index.d.ts @@ -5,5 +5,3 @@ declare global { studio: StudioBridge; } } - -export {}; diff --git a/studio/src/renderer/src/App.tsx b/studio/src/renderer/src/App.tsx index c174792b9..6acaa1b99 100644 --- a/studio/src/renderer/src/App.tsx +++ b/studio/src/renderer/src/App.tsx @@ -1,5 +1,5 @@ import { BracketsCurly, Lightning, Palette } from "@phosphor-icons/react"; -import { colors, fonts, fontSizes } from "./lib/theme/tokens"; +import { colors, fontSizes, fonts } from "./lib/theme/tokens"; const statusRows = [ { label: "Shell", value: "electron-vite + React 19", icon: Lightning }, diff --git a/studio/test/tokens.test.mjs b/studio/test/tokens.test.mjs index 52ea8fef2..9896b34b2 100644 --- a/studio/test/tokens.test.mjs +++ b/studio/test/tokens.test.mjs @@ -1,6 +1,6 @@ -import { test } from "vitest"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; +import { test } from "vitest"; const cssPath = new URL( "../src/renderer/src/styles/index.css", diff --git a/tests/fixtures/provider.ts b/tests/fixtures/provider.ts index 024d8403b..5cfe99e6f 100644 --- a/tests/fixtures/provider.ts +++ b/tests/fixtures/provider.ts @@ -1,5 +1,5 @@ -import { readFileSync, writeFileSync, mkdirSync } from "fs"; -import { dirname } from "path"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname } from "node:path"; /** * A single tool use within a conversation turn. diff --git a/tests/fixtures/record.ts b/tests/fixtures/record.ts index 2f3ec4db5..b3dde98a5 100644 --- a/tests/fixtures/record.ts +++ b/tests/fixtures/record.ts @@ -29,7 +29,7 @@ * Then run `npm run test:fixtures` to validate the recording. */ -import { getFixtureMode, getFixtureDir } from "./provider.ts"; +import { getFixtureDir, getFixtureMode } from "./provider.ts"; const mode = getFixtureMode(); const dir = getFixtureDir(); diff --git a/tests/fixtures/run.ts b/tests/fixtures/run.ts index 44653ef91..a6d2f19fc 100644 --- a/tests/fixtures/run.ts +++ b/tests/fixtures/run.ts @@ -1,7 +1,7 @@ -import { readdirSync } from "fs"; -import { join } from "path"; -import { loadFixture, FixtureReplayer } from "./provider.ts"; -import type { FixtureTurn, FixtureRecording } from "./provider.ts"; +import { readdirSync } from "node:fs"; +import { join } from "node:path"; +import type { FixtureRecording, FixtureTurn } from "./provider.ts"; +import { FixtureReplayer, loadFixture } from "./provider.ts"; const __dirname = import.meta.dirname; const recordingsDir = join(__dirname, "recordings"); diff --git a/tests/live-regression/run.ts b/tests/live-regression/run.ts index d7d8230ef..48a4958ff 100644 --- a/tests/live-regression/run.ts +++ b/tests/live-regression/run.ts @@ -20,18 +20,10 @@ * SF_SMOKE_BINARY=dist/loader.js node --experimental-strip-types tests/live-regression/run.ts */ -import { execFileSync, execSync } from "child_process"; -import { - mkdtempSync, - mkdirSync, - writeFileSync, - existsSync, - readFileSync, - rmSync, - unlinkSync, -} from "fs"; -import { join, dirname } from "path"; -import { tmpdir } from "os"; +import { execFileSync, execSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; // ─── Config ─────────────────────────────────────────────────────────────── @@ -74,7 +66,6 @@ function sfCli( ...process.env, ...env, SF_NON_INTERACTIVE: "1", - SF_NON_INTERACTIVE: "1", }, }, ); diff --git a/tests/live/run.ts b/tests/live/run.ts index 26e6a797a..f7d4e4aa9 100644 --- a/tests/live/run.ts +++ b/tests/live/run.ts @@ -1,6 +1,6 @@ -import { readdirSync } from "fs"; -import { execFileSync } from "child_process"; -import { join } from "path"; +import { execFileSync } from "node:child_process"; +import { readdirSync } from "node:fs"; +import { join } from "node:path"; const __dirname = import.meta.dirname; diff --git a/tests/repro-worktree-bug/repro.mjs b/tests/repro-worktree-bug/repro.mjs index 1727304d7..4d7f38707 100644 --- a/tests/repro-worktree-bug/repro.mjs +++ b/tests/repro-worktree-bug/repro.mjs @@ -16,16 +16,16 @@ * user-level ~/.sf), causing resolveProjectRoot() to return /root (home dir). */ -import { - mkdirSync, - symlinkSync, - existsSync, - realpathSync, - mkdtempSync, -} from "node:fs"; import { execSync } from "node:child_process"; -import { join } from "node:path"; +import { + existsSync, + mkdirSync, + mkdtempSync, + realpathSync, + symlinkSync, +} from "node:fs"; import { homedir, tmpdir } from "node:os"; +import { join } from "node:path"; // ── Reproduce the exact functions from worktree.ts ────────────────────── diff --git a/tests/repro-worktree-bug/verify-fix.mjs b/tests/repro-worktree-bug/verify-fix.mjs index c4df30bc2..33638e36a 100644 --- a/tests/repro-worktree-bug/verify-fix.mjs +++ b/tests/repro-worktree-bug/verify-fix.mjs @@ -5,18 +5,18 @@ * reproduced the bug. Copies the fixed function logic from worktree.ts. */ +import { execSync } from "node:child_process"; import { - mkdirSync, - symlinkSync, existsSync, + mkdirSync, + mkdtempSync, readFileSync, realpathSync, + symlinkSync, writeFileSync, - mkdtempSync, } from "node:fs"; -import { execSync } from "node:child_process"; -import { join, resolve } from "node:path"; import { homedir, tmpdir } from "node:os"; +import { join, resolve } from "node:path"; // ── Fixed functions (copied from worktree.ts after fix) ───────────────── diff --git a/tests/repro-worktree-bug/verify-integration.mjs b/tests/repro-worktree-bug/verify-integration.mjs index 59ff7f118..77757799a 100644 --- a/tests/repro-worktree-bug/verify-integration.mjs +++ b/tests/repro-worktree-bug/verify-integration.mjs @@ -14,18 +14,18 @@ * 6. assertSafeDirectory blocks ~ as a project root */ +import { execSync } from "node:child_process"; import { - mkdirSync, - symlinkSync, existsSync, + mkdirSync, + mkdtempSync, readFileSync, realpathSync, + symlinkSync, writeFileSync, - mkdtempSync, } from "node:fs"; -import { execSync } from "node:child_process"; -import { join, resolve } from "node:path"; import { homedir, tmpdir } from "node:os"; +import { join, resolve } from "node:path"; // ── Fixed functions (from worktree.ts after fix) ───────────────────────── diff --git a/tests/smoke/run.ts b/tests/smoke/run.ts index b66c9986f..b40f37ddf 100644 --- a/tests/smoke/run.ts +++ b/tests/smoke/run.ts @@ -1,6 +1,6 @@ -import { readdirSync } from "fs"; -import { execFileSync } from "child_process"; -import { join } from "path"; +import { execFileSync } from "node:child_process"; +import { readdirSync } from "node:fs"; +import { join } from "node:path"; const __dirname = import.meta.dirname; diff --git a/tests/smoke/test-help.ts b/tests/smoke/test-help.ts index ae9c9853f..e34290808 100644 --- a/tests/smoke/test-help.ts +++ b/tests/smoke/test-help.ts @@ -1,4 +1,4 @@ -import { execFileSync } from "child_process"; +import { execFileSync } from "node:child_process"; const binary = process.env.SF_SMOKE_BINARY || "npx"; const args = process.env.SF_SMOKE_BINARY ? ["--help"] : ["sf-run", "--help"]; diff --git a/tests/smoke/test-init.ts b/tests/smoke/test-init.ts index 594727b71..63567d8d1 100644 --- a/tests/smoke/test-init.ts +++ b/tests/smoke/test-init.ts @@ -1,7 +1,7 @@ -import { execFileSync } from "child_process"; -import { mkdtempSync, existsSync, rmSync } from "fs"; -import { join } from "path"; -import { tmpdir } from "os"; +import { execFileSync } from "node:child_process"; +import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; // Skip in non-TTY environments (CI containers) — init requires interactive mode if (!process.stdin.isTTY && process.env.CI) { diff --git a/tests/smoke/test-version.ts b/tests/smoke/test-version.ts index b0f266d70..681e57e64 100644 --- a/tests/smoke/test-version.ts +++ b/tests/smoke/test-version.ts @@ -1,4 +1,4 @@ -import { execFileSync } from "child_process"; +import { execFileSync } from "node:child_process"; const binary = process.env.SF_SMOKE_BINARY || "npx"; const args = process.env.SF_SMOKE_BINARY diff --git a/vscode-extension/src/activity-feed.ts b/vscode-extension/src/activity-feed.ts index 500a33dc0..79715a27c 100644 --- a/vscode-extension/src/activity-feed.ts +++ b/vscode-extension/src/activity-feed.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import type { SfClient, AgentEvent } from "./sf-client.js"; +import type { AgentEvent, SfClient } from "./sf-client.js"; interface ActivityItem { id: number; @@ -79,7 +79,7 @@ export class SfActivityFeedProvider private maxItems: number; private disposables: vscode.Disposable[] = []; - constructor(private readonly client: SfClient) { + constructor(readonly client: SfClient) { this.maxItems = vscode.workspace .getConfiguration("sf") .get("activityFeedMaxItems", 100); diff --git a/vscode-extension/src/change-tracker.ts b/vscode-extension/src/change-tracker.ts index 558de5547..96f6f148d 100644 --- a/vscode-extension/src/change-tracker.ts +++ b/vscode-extension/src/change-tracker.ts @@ -1,6 +1,6 @@ -import * as vscode from "vscode"; import * as fs from "node:fs"; -import type { SfClient, AgentEvent } from "./sf-client.js"; +import * as vscode from "vscode"; +import type { AgentEvent, SfClient } from "./sf-client.js"; export interface FileSnapshot { uri: vscode.Uri; @@ -43,7 +43,7 @@ export class SfChangeTracker implements vscode.Disposable { private disposables: vscode.Disposable[] = []; - constructor(private readonly client: SfClient) { + constructor(readonly client: SfClient) { this.disposables.push(this._onDidChange, this._onCheckpointChange); this.disposables.push( diff --git a/vscode-extension/src/chat-participant.ts b/vscode-extension/src/chat-participant.ts index a8b92fbe1..dad84c208 100644 --- a/vscode-extension/src/chat-participant.ts +++ b/vscode-extension/src/chat-participant.ts @@ -6,7 +6,7 @@ import type { AgentEvent, SfClient } from "./sf-client.js"; * SF RPC client and streams tool execution events back to the chat. */ export function registerChatParticipant( - context: vscode.ExtensionContext, + _context: vscode.ExtensionContext, client: SfClient, ): vscode.Disposable { const participant = vscode.chat.createChatParticipant( diff --git a/vscode-extension/src/checkpoints.ts b/vscode-extension/src/checkpoints.ts index a78776eb0..b394d3a56 100644 --- a/vscode-extension/src/checkpoints.ts +++ b/vscode-extension/src/checkpoints.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import type { SfChangeTracker, Checkpoint } from "./change-tracker.js"; +import type { Checkpoint, SfChangeTracker } from "./change-tracker.js"; /** * TreeDataProvider that shows agent checkpoints (one per agent turn). diff --git a/vscode-extension/src/code-lens.ts b/vscode-extension/src/code-lens.ts index 793753ac4..4729c3f34 100644 --- a/vscode-extension/src/code-lens.ts +++ b/vscode-extension/src/code-lens.ts @@ -74,7 +74,7 @@ export class SfCodeLensProvider private disposables: vscode.Disposable[] = []; - constructor(private readonly client: SfClient) { + constructor(readonly client: SfClient) { this.disposables.push( this._onDidChangeCodeLenses, client.onConnectionChange(() => this._onDidChangeCodeLenses.fire()), diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index dff53c193..6c60f10d1 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -1,20 +1,20 @@ import * as vscode from "vscode"; -import { SfClient, ThinkingLevel } from "./sf-client.js"; -import { registerChatParticipant } from "./chat-participant.js"; -import { SfSidebarProvider } from "./sidebar.js"; -import { SfFileDecorationProvider } from "./file-decorations.js"; -import { SfBashTerminal } from "./bash-terminal.js"; -import { SfSessionTreeProvider } from "./session-tree.js"; -import { SfConversationHistoryPanel } from "./conversation-history.js"; -import { SfSlashCompletionProvider } from "./slash-completion.js"; -import { SfCodeLensProvider } from "./code-lens.js"; import { SfActivityFeedProvider } from "./activity-feed.js"; +import { SfBashTerminal } from "./bash-terminal.js"; import { SfChangeTracker } from "./change-tracker.js"; -import { SfScmProvider } from "./scm-provider.js"; +import { registerChatParticipant } from "./chat-participant.js"; +import { SfCodeLensProvider } from "./code-lens.js"; +import { SfConversationHistoryPanel } from "./conversation-history.js"; import { SfDiagnosticBridge } from "./diagnostics.js"; -import { SfLineDecorationManager } from "./line-decorations.js"; +import { SfFileDecorationProvider } from "./file-decorations.js"; import { SfGitIntegration } from "./git-integration.js"; +import { SfLineDecorationManager } from "./line-decorations.js"; import { SfPermissionManager } from "./permissions.js"; +import { SfScmProvider } from "./scm-provider.js"; +import { SfSessionTreeProvider } from "./session-tree.js"; +import { SfClient, type ThinkingLevel } from "./sf-client.js"; +import { SfSidebarProvider } from "./sidebar.js"; +import { SfSlashCompletionProvider } from "./slash-completion.js"; let client: SfClient | undefined; let sidebarProvider: SfSidebarProvider | undefined; @@ -63,7 +63,7 @@ function handleError(err: unknown, context: string): void { export function activate(context: vscode.ExtensionContext): void { const startupConfig = resolveTrustedSfStartupConfig(); - const config = vscode.workspace.getConfiguration("sf"); + const _config = vscode.workspace.getConfiguration("sf"); const binaryPath = startupConfig.binaryPath; const cwd = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? process.cwd(); diff --git a/vscode-extension/src/file-decorations.ts b/vscode-extension/src/file-decorations.ts index 240ece282..63232e06e 100644 --- a/vscode-extension/src/file-decorations.ts +++ b/vscode-extension/src/file-decorations.ts @@ -16,7 +16,7 @@ export class SfFileDecorationProvider private modifiedUris = new Set(); private disposables: vscode.Disposable[] = []; - constructor(private readonly client: SfClient) { + constructor(readonly client: SfClient) { this.disposables.push( this._onDidChangeFileDecorations, client.onEvent((evt: AgentEvent) => this.handleEvent(evt)), diff --git a/vscode-extension/src/git-integration.ts b/vscode-extension/src/git-integration.ts index 3972d7f4d..f1f7ca1cb 100644 --- a/vscode-extension/src/git-integration.ts +++ b/vscode-extension/src/git-integration.ts @@ -1,5 +1,5 @@ -import * as vscode from "vscode"; import { execFile } from "node:child_process"; +import * as vscode from "vscode"; import type { SfChangeTracker } from "./change-tracker.js"; /** diff --git a/vscode-extension/src/permissions.ts b/vscode-extension/src/permissions.ts index dcc641eec..141289d12 100644 --- a/vscode-extension/src/permissions.ts +++ b/vscode-extension/src/permissions.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import type { SfClient, AgentEvent } from "./sf-client.js"; +import type { AgentEvent, SfClient } from "./sf-client.js"; type ApprovalMode = "ask" | "auto-approve" | "plan-only"; diff --git a/vscode-extension/src/plan-viewer.ts b/vscode-extension/src/plan-viewer.ts index 137db80ca..17c04ee73 100644 --- a/vscode-extension/src/plan-viewer.ts +++ b/vscode-extension/src/plan-viewer.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import type { SfClient, AgentEvent } from "./sf-client.js"; +import type { AgentEvent, SfClient } from "./sf-client.js"; interface PlanStep { id: number; @@ -28,7 +28,7 @@ export class SfPlanViewerProvider private runningTools = new Map(); // toolUseId -> step id private disposables: vscode.Disposable[] = []; - constructor(private readonly client: SfClient) { + constructor(readonly client: SfClient) { this.disposables.push( this._onDidChangeTreeData, client.onEvent((evt) => this.handleEvent(evt)), diff --git a/vscode-extension/src/scm-provider.ts b/vscode-extension/src/scm-provider.ts index 422c1b124..96aebf64c 100644 --- a/vscode-extension/src/scm-provider.ts +++ b/vscode-extension/src/scm-provider.ts @@ -1,5 +1,5 @@ -import * as vscode from "vscode"; import * as path from "node:path"; +import * as vscode from "vscode"; import type { SfChangeTracker } from "./change-tracker.js"; const SF_ORIGINAL_SCHEME = "sf-original"; @@ -70,7 +70,7 @@ export class SfScmProvider implements vscode.Disposable { this.changesGroup.resourceStates = files.map((filePath) => { const uri = vscode.Uri.file(filePath); const fileName = path.basename(filePath); - const relativePath = path.relative(this.workspaceRoot, filePath); + const _relativePath = path.relative(this.workspaceRoot, filePath); const state: vscode.SourceControlResourceState = { resourceUri: uri, diff --git a/vscode-extension/src/session-tree.ts b/vscode-extension/src/session-tree.ts index 3ac1f679c..b2e117bbe 100644 --- a/vscode-extension/src/session-tree.ts +++ b/vscode-extension/src/session-tree.ts @@ -1,6 +1,6 @@ -import * as vscode from "vscode"; import * as fs from "node:fs"; import * as path from "node:path"; +import * as vscode from "vscode"; import type { SfClient } from "./sf-client.js"; export interface SessionItem { @@ -24,7 +24,6 @@ export class SfSessionTreeProvider readonly onDidChangeTreeData = this._onDidChangeTreeData.event; private sessions: SessionItem[] = []; - private currentSessionFile: string | undefined; private disposables: vscode.Disposable[] = []; constructor(private readonly client: SfClient) { @@ -87,7 +86,7 @@ export class SfSessionTreeProvider continue; } - if (isNaN(timestamp.getTime())) continue; + if (Number.isNaN(timestamp.getTime())) continue; items.push({ label: formatDate(timestamp), diff --git a/vscode-extension/src/sf-client.ts b/vscode-extension/src/sf-client.ts index 80de06ec4..b1568e7ff 100644 --- a/vscode-extension/src/sf-client.ts +++ b/vscode-extension/src/sf-client.ts @@ -1,4 +1,4 @@ -import { ChildProcess, spawn } from "node:child_process"; +import { type ChildProcess, spawn } from "node:child_process"; import * as vscode from "vscode"; /** diff --git a/vscode-extension/src/sidebar.ts b/vscode-extension/src/sidebar.ts index 4ede65e29..5915421e3 100644 --- a/vscode-extension/src/sidebar.ts +++ b/vscode-extension/src/sidebar.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import type { SfClient, SessionStats, ThinkingLevel } from "./sf-client.js"; +import type { SessionStats, SfClient, ThinkingLevel } from "./sf-client.js"; /** * Send a message through VS Code's Chat panel so the user sees the response. @@ -24,7 +24,7 @@ export class SfSidebarProvider implements vscode.WebviewViewProvider { private refreshTimer: ReturnType | undefined; constructor( - private readonly extensionUri: vscode.Uri, + readonly _extensionUri: vscode.Uri, private readonly client: SfClient, ) { this.disposables.push( diff --git a/web/app/api/browse-directories/route.ts b/web/app/api/browse-directories/route.ts index 657881727..3abe7ff63 100644 --- a/web/app/api/browse-directories/route.ts +++ b/web/app/api/browse-directories/route.ts @@ -1,6 +1,6 @@ -import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; -import { resolve, dirname, join } from "node:path"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { homedir, platform } from "node:os"; +import { dirname, join, resolve } from "node:path"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/captures/route.ts b/web/app/api/captures/route.ts index 4b066fd54..998753d05 100644 --- a/web/app/api/captures/route.ts +++ b/web/app/api/captures/route.ts @@ -1,8 +1,8 @@ +import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; import { collectCapturesData, resolveCaptureAction, } from "../../../../src/web/captures-service.ts"; -import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; import type { CaptureResolveRequest } from "../../../lib/knowledge-captures-types.ts"; export const runtime = "nodejs"; diff --git a/web/app/api/cleanup/route.ts b/web/app/api/cleanup/route.ts index 436e17b60..0150ddd97 100644 --- a/web/app/api/cleanup/route.ts +++ b/web/app/api/cleanup/route.ts @@ -1,8 +1,8 @@ +import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; import { collectCleanupData, executeCleanup, } from "../../../../src/web/cleanup-service.ts"; -import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/doctor/route.ts b/web/app/api/doctor/route.ts index bef7d4028..9d58c556f 100644 --- a/web/app/api/doctor/route.ts +++ b/web/app/api/doctor/route.ts @@ -1,8 +1,8 @@ -import { - collectDoctorData, - applyDoctorFixes, -} from "../../../../src/web/doctor-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { + applyDoctorFixes, + collectDoctorData, +} from "../../../../src/web/doctor-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/experimental/route.ts b/web/app/api/experimental/route.ts index eb8e171c4..6191f3174 100644 --- a/web/app/api/experimental/route.ts +++ b/web/app/api/experimental/route.ts @@ -1,6 +1,6 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; -import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs"; -import { join, dirname } from "node:path"; +import { dirname, join } from "node:path"; import { parse as parseYaml, stringify as stringifyYaml } from "yaml"; export const runtime = "nodejs"; diff --git a/web/app/api/export-data/route.ts b/web/app/api/export-data/route.ts index c2588b4e6..7f5238524 100644 --- a/web/app/api/export-data/route.ts +++ b/web/app/api/export-data/route.ts @@ -1,5 +1,5 @@ -import { collectExportData } from "../../../../src/web/export-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectExportData } from "../../../../src/web/export-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/files/route.ts b/web/app/api/files/route.ts index 5468cf62d..d13acd363 100644 --- a/web/app/api/files/route.ts +++ b/web/app/api/files/route.ts @@ -8,7 +8,7 @@ import { statSync, writeFileSync, } from "node:fs"; -import { join, dirname } from "node:path"; +import { dirname, join } from "node:path"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; import { resolveSecurePath } from "../../../lib/secure-path.ts"; diff --git a/web/app/api/forensics/route.ts b/web/app/api/forensics/route.ts index 428522d8e..2c9a63a2b 100644 --- a/web/app/api/forensics/route.ts +++ b/web/app/api/forensics/route.ts @@ -1,5 +1,5 @@ -import { collectForensicsData } from "../../../../src/web/forensics-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectForensicsData } from "../../../../src/web/forensics-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/git/route.ts b/web/app/api/git/route.ts index c4d7ff524..0b974e03d 100644 --- a/web/app/api/git/route.ts +++ b/web/app/api/git/route.ts @@ -1,5 +1,5 @@ -import { collectCurrentProjectGitSummary } from "../../../../src/web/git-summary-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectCurrentProjectGitSummary } from "../../../../src/web/git-summary-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/history/route.ts b/web/app/api/history/route.ts index 36980e307..cb5791a07 100644 --- a/web/app/api/history/route.ts +++ b/web/app/api/history/route.ts @@ -1,5 +1,5 @@ -import { collectHistoryData } from "../../../../src/web/history-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectHistoryData } from "../../../../src/web/history-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/hooks/route.ts b/web/app/api/hooks/route.ts index 944cdf541..1f08a5af8 100644 --- a/web/app/api/hooks/route.ts +++ b/web/app/api/hooks/route.ts @@ -1,5 +1,5 @@ -import { collectHooksData } from "../../../../src/web/hooks-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectHooksData } from "../../../../src/web/hooks-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/inspect/route.ts b/web/app/api/inspect/route.ts index 05ab331ff..2856dbe10 100644 --- a/web/app/api/inspect/route.ts +++ b/web/app/api/inspect/route.ts @@ -1,5 +1,5 @@ -import { collectInspectData } from "../../../../src/web/inspect-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectInspectData } from "../../../../src/web/inspect-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/knowledge/route.ts b/web/app/api/knowledge/route.ts index b20f8cf98..730048e74 100644 --- a/web/app/api/knowledge/route.ts +++ b/web/app/api/knowledge/route.ts @@ -1,5 +1,5 @@ -import { collectKnowledgeData } from "../../../../src/web/knowledge-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectKnowledgeData } from "../../../../src/web/knowledge-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/live-state/route.ts b/web/app/api/live-state/route.ts index 0ecd83f06..c4084cc73 100644 --- a/web/app/api/live-state/route.ts +++ b/web/app/api/live-state/route.ts @@ -1,7 +1,7 @@ import { + type BridgeSelectiveLiveStateDomain, collectSelectiveLiveStatePayload, requireProjectCwd, - type BridgeSelectiveLiveStateDomain, } from "../../../../src/web/bridge-service.ts"; export const runtime = "nodejs"; diff --git a/web/app/api/notifications/route.ts b/web/app/api/notifications/route.ts index 1a8742493..2f7786254 100644 --- a/web/app/api/notifications/route.ts +++ b/web/app/api/notifications/route.ts @@ -1,8 +1,8 @@ -import { - collectNotificationsData, - clearNotificationsData, -} from "../../../../src/web/notifications-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { + clearNotificationsData, + collectNotificationsData, +} from "../../../../src/web/notifications-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/onboarding/route.ts b/web/app/api/onboarding/route.ts index d4025cc54..54c9ff3c9 100644 --- a/web/app/api/onboarding/route.ts +++ b/web/app/api/onboarding/route.ts @@ -1,8 +1,8 @@ +import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; import { getOnboardingService, type OnboardingState, } from "../../../../src/web/onboarding-service.ts"; -import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/preferences/route.ts b/web/app/api/preferences/route.ts index 554892b3b..e7d626d65 100644 --- a/web/app/api/preferences/route.ts +++ b/web/app/api/preferences/route.ts @@ -1,4 +1,4 @@ -import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname } from "node:path"; import { webPreferencesPath } from "../../../../src/app-paths.ts"; diff --git a/web/app/api/projects/route.ts b/web/app/api/projects/route.ts index 3c88b242a..95a19a68c 100644 --- a/web/app/api/projects/route.ts +++ b/web/app/api/projects/route.ts @@ -1,9 +1,9 @@ +import { execSync } from "node:child_process"; import { existsSync, mkdirSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; -import { execSync } from "node:child_process"; -import { discoverProjects } from "../../../../src/web/project-discovery-service.ts"; import { detectProjectKind } from "../../../../src/web/bridge-service.ts"; +import { discoverProjects } from "../../../../src/web/project-discovery-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/recovery/route.ts b/web/app/api/recovery/route.ts index ac6e4a16b..19c1d86b1 100644 --- a/web/app/api/recovery/route.ts +++ b/web/app/api/recovery/route.ts @@ -1,5 +1,5 @@ -import { collectCurrentProjectRecoveryDiagnostics } from "../../../../src/web/recovery-diagnostics-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectCurrentProjectRecoveryDiagnostics } from "../../../../src/web/recovery-diagnostics-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/remote-questions/route.ts b/web/app/api/remote-questions/route.ts index f9990a3f0..6db6e442c 100644 --- a/web/app/api/remote-questions/route.ts +++ b/web/app/api/remote-questions/route.ts @@ -1,12 +1,12 @@ -import { homedir } from "node:os"; import { - readFileSync, - writeFileSync, + chmodSync, existsSync, mkdirSync, - chmodSync, + readFileSync, + writeFileSync, } from "node:fs"; -import { join, dirname } from "node:path"; +import { homedir } from "node:os"; +import { dirname, join } from "node:path"; import { parse as parseYaml, stringify as stringifyYaml } from "yaml"; export const runtime = "nodejs"; diff --git a/web/app/api/session/manage/route.ts b/web/app/api/session/manage/route.ts index a178ea636..a842543b6 100644 --- a/web/app/api/session/manage/route.ts +++ b/web/app/api/session/manage/route.ts @@ -3,9 +3,9 @@ import { requireProjectCwd, } from "../../../../../src/web/bridge-service.ts"; import { - SESSION_BROWSER_SCOPE, isSessionManageAction, type RenameSessionRequest, + SESSION_BROWSER_SCOPE, type SessionManageResponse, } from "../../../../lib/session-browser-contract.ts"; diff --git a/web/app/api/settings-data/route.ts b/web/app/api/settings-data/route.ts index 2e8ad8152..49acf2ce6 100644 --- a/web/app/api/settings-data/route.ts +++ b/web/app/api/settings-data/route.ts @@ -1,5 +1,5 @@ -import { collectSettingsData } from "../../../../src/web/settings-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectSettingsData } from "../../../../src/web/settings-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/shutdown/route.ts b/web/app/api/shutdown/route.ts index 5beea6189..c896a8ae8 100644 --- a/web/app/api/shutdown/route.ts +++ b/web/app/api/shutdown/route.ts @@ -1,5 +1,5 @@ -import { scheduleShutdown } from "../../../lib/shutdown-gate"; import { verifyAuthToken } from "../../../lib/auth-guard"; +import { scheduleShutdown } from "../../../lib/shutdown-gate"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/skill-health/route.ts b/web/app/api/skill-health/route.ts index ed3e31018..3a1693f60 100644 --- a/web/app/api/skill-health/route.ts +++ b/web/app/api/skill-health/route.ts @@ -1,5 +1,5 @@ -import { collectSkillHealthData } from "../../../../src/web/skill-health-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectSkillHealthData } from "../../../../src/web/skill-health-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/steer/route.ts b/web/app/api/steer/route.ts index 6ac41b513..9bd5345b9 100644 --- a/web/app/api/steer/route.ts +++ b/web/app/api/steer/route.ts @@ -2,8 +2,8 @@ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { - resolveBridgeRuntimeConfig, requireProjectCwd, + resolveBridgeRuntimeConfig, } from "../../../../src/web/bridge-service.ts"; import type { SteerData } from "../../../lib/remaining-command-types.ts"; diff --git a/web/app/api/switch-root/route.ts b/web/app/api/switch-root/route.ts index a53b3eb9e..e0dca4f32 100644 --- a/web/app/api/switch-root/route.ts +++ b/web/app/api/switch-root/route.ts @@ -1,12 +1,12 @@ import { existsSync, + mkdirSync, readFileSync, statSync, writeFileSync, - mkdirSync, } from "node:fs"; -import { dirname, resolve } from "node:path"; import { homedir } from "node:os"; +import { dirname, resolve } from "node:path"; import { webPreferencesPath } from "../../../../src/app-paths.ts"; import { discoverProjects } from "../../../../src/web/project-discovery-service.ts"; diff --git a/web/app/api/terminal/sessions/route.ts b/web/app/api/terminal/sessions/route.ts index c0e703966..02601550f 100644 --- a/web/app/api/terminal/sessions/route.ts +++ b/web/app/api/terminal/sessions/route.ts @@ -6,13 +6,13 @@ * DELETE /api/terminal/sessions?id=x — destroy a session */ -import { - listSessions, - getOrCreateSession, - destroySession, - isAllowedTerminalCommand, -} from "../../../../lib/pty-manager"; import { requireProjectCwd } from "../../../../../src/web/bridge-service.ts"; +import { + destroySession, + getOrCreateSession, + isAllowedTerminalCommand, + listSessions, +} from "../../../../lib/pty-manager"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/terminal/stream/route.ts b/web/app/api/terminal/stream/route.ts index 1f04566f0..192625665 100644 --- a/web/app/api/terminal/stream/route.ts +++ b/web/app/api/terminal/stream/route.ts @@ -6,12 +6,12 @@ * Creates the PTY session on first connection if it doesn't exist. */ +import { requireProjectCwd } from "../../../../../src/web/bridge-service.ts"; import { - getOrCreateSession, addListener, + getOrCreateSession, isAllowedTerminalCommand, } from "../../../../lib/pty-manager"; -import { requireProjectCwd } from "../../../../../src/web/bridge-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/terminal/upload/route.ts b/web/app/api/terminal/upload/route.ts index 334c1d6d3..fcd19a094 100644 --- a/web/app/api/terminal/upload/route.ts +++ b/web/app/api/terminal/upload/route.ts @@ -16,10 +16,10 @@ * - No custom cleanup — OS handles temp dir cleanup on reboot */ +import { randomBytes } from "node:crypto"; import { writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { randomBytes } from "node:crypto"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/undo/route.ts b/web/app/api/undo/route.ts index 04553f6d6..9525e1687 100644 --- a/web/app/api/undo/route.ts +++ b/web/app/api/undo/route.ts @@ -1,8 +1,8 @@ +import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; import { collectUndoInfo, executeUndo, } from "../../../../src/web/undo-service.ts"; -import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/api/visualizer/route.ts b/web/app/api/visualizer/route.ts index f210ba782..fe5dda2b9 100644 --- a/web/app/api/visualizer/route.ts +++ b/web/app/api/visualizer/route.ts @@ -1,5 +1,5 @@ -import { collectVisualizerData } from "../../../../src/web/visualizer-service.ts"; import { requireProjectCwd } from "../../../../src/web/bridge-service.ts"; +import { collectVisualizerData } from "../../../../src/web/visualizer-service.ts"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/web/app/globals.css b/web/app/globals.css index 08687837e..0b14e2038 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -183,7 +183,7 @@ .file-viewer-code pre { margin: 0; padding: 1rem; - background: transparent !important; + background: transparent; overflow-x: auto; font-family: var(--font-mono); } @@ -334,7 +334,7 @@ .chat-code-block pre { margin: 0; padding: 1rem; - background: transparent !important; + background: transparent; overflow-x: auto; font-family: var(--font-mono); font-size: 0.8rem; @@ -352,9 +352,9 @@ } .chat-markdown > *:first-child { - margin-top: 0 !important; + margin-top: 0; } .chat-markdown > *:last-child { - margin-bottom: 0 !important; + margin-bottom: 0; } diff --git a/web/app/layout.tsx b/web/app/layout.tsx index fde51d6e1..d3167e18a 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata, Viewport } from "next"; import { Geist, Geist_Mono } from "next/font/google"; -import { Toaster } from "@/components/ui/sonner"; import { ThemeProvider } from "@/components/theme-provider"; +import { Toaster } from "@/components/ui/sonner"; import "./globals.css"; const geistSans = Geist({ diff --git a/web/components/sf/activity-view.tsx b/web/components/sf/activity-view.tsx index 2bb1fd70e..1f474858b 100644 --- a/web/components/sf/activity-view.tsx +++ b/web/components/sf/activity-view.tsx @@ -1,11 +1,11 @@ "use client"; -import { CheckCircle2, Play, Clock, Terminal, AlertCircle } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { AlertCircle, CheckCircle2, Clock, Play, Terminal } from "lucide-react"; import { - useSFWorkspaceState, type TerminalLineType, + useSFWorkspaceState, } from "@/lib/sf-workspace-store"; +import { cn } from "@/lib/utils"; function EventIcon({ type }: { type: TerminalLineType }) { const baseClass = "h-4 w-4"; diff --git a/web/components/sf/app-shell.tsx b/web/components/sf/app-shell.tsx index c0b968eb0..d08dac0b0 100644 --- a/web/components/sf/app-shell.tsx +++ b/web/components/sf/app-shell.tsx @@ -1,56 +1,56 @@ "use client"; +import { Menu, X } from "lucide-react"; import Image from "next/image"; import { - useState, - useEffect, useCallback, + useEffect, useRef, + useState, useSyncExternalStore, } from "react"; -import { Menu, X } from "lucide-react"; -import { - Sidebar, - MilestoneExplorer, - CollapsedMilestoneSidebar, -} from "@/components/sf/sidebar"; -import { ShellTerminal } from "@/components/sf/shell-terminal"; -import { Dashboard } from "@/components/sf/dashboard"; -import { Roadmap } from "@/components/sf/roadmap"; -import { FilesView } from "@/components/sf/files-view"; +import { toast } from "sonner"; import { ActivityView } from "@/components/sf/activity-view"; -import { VisualizerView } from "@/components/sf/visualizer-view"; -import { StatusBar } from "@/components/sf/status-bar"; +import { ChatMode } from "@/components/sf/chat-mode"; +import { CommandSurface } from "@/components/sf/command-surface"; +import { Dashboard } from "@/components/sf/dashboard"; import { DualTerminal } from "@/components/sf/dual-terminal"; +import { FilesView } from "@/components/sf/files-view"; import { FocusedPanel } from "@/components/sf/focused-panel"; import { OnboardingGate } from "@/components/sf/onboarding-gate"; -import { CommandSurface } from "@/components/sf/command-surface"; +import { + ProjectSelectionGate, + ProjectsPanel, +} from "@/components/sf/projects-view"; +import { Roadmap } from "@/components/sf/roadmap"; +import { ScopeBadge } from "@/components/sf/scope-badge"; +import { ShellTerminal } from "@/components/sf/shell-terminal"; +import { + CollapsedMilestoneSidebar, + MilestoneExplorer, + Sidebar, +} from "@/components/sf/sidebar"; +import { StatusBar } from "@/components/sf/status-bar"; +import { UpdateBanner } from "@/components/sf/update-banner"; +import { VisualizerView } from "@/components/sf/visualizer-view"; +import { Badge } from "@/components/ui/badge"; +import { Skeleton } from "@/components/ui/skeleton"; +import { getAuthToken } from "@/lib/auth"; import { DevOverridesProvider } from "@/lib/dev-overrides"; import { ProjectStoreManagerProvider, useProjectStoreManager, } from "@/lib/project-store-manager"; -import { Skeleton } from "@/components/ui/skeleton"; -import { cn } from "@/lib/utils"; -import { toast } from "sonner"; import { - SFWorkspaceProvider, getCurrentScopeLabel, getProjectDisplayName, getStatusPresentation, getVisibleWorkspaceError, - useSFWorkspaceState, + SFWorkspaceProvider, useSFWorkspaceActions, + useSFWorkspaceState, } from "@/lib/sf-workspace-store"; -import { ChatMode } from "@/components/sf/chat-mode"; -import { ScopeBadge } from "@/components/sf/scope-badge"; -import { Badge } from "@/components/ui/badge"; -import { - ProjectsPanel, - ProjectSelectionGate, -} from "@/components/sf/projects-view"; -import { UpdateBanner } from "@/components/sf/update-banner"; -import { getAuthToken } from "@/lib/auth"; +import { cn } from "@/lib/utils"; const KNOWN_VIEWS = new Set([ "dashboard", @@ -326,6 +326,7 @@ function WorkspaceChrome() {
{/* Mobile hamburger menu */} {/* Terminal content */}
{/* Wide invisible grab area overlapping the border */}
@@ -632,6 +644,7 @@ function WorkspaceChrome() { {scopeLabel}
))} + ) : ( + +
+ New project +
+
{ + e.preventDefault(); + void handleCreate(); + }} + className="space-y-2" > -
- -
-
- - Create new project - -

- Initialize a new directory with Git -

-
- - ) : ( - -
- New project -
- { - e.preventDefault(); - void handleCreate(); + { + setNewName(e.target.value); + setCreateError(null); }} - className="space-y-2" - > - { - setNewName(e.target.value); + placeholder="my-project" + autoComplete="off" + className="text-sm" + disabled={creating} + /> + {newName && !nameValid && ( +

+ Letters, numbers, hyphens, underscores, dots. Must start + with a letter or number. +

+ )} + {nameConflict && ( +

+ A project with this name already exists +

+ )} + {createError && ( +

{createError}

+ )} + {newName && nameValid && !nameConflict && ( +

+ {devRoot}/{newName} +

+ )} +
+ + - -
- -
- )} - - )} + className="text-muted-foreground" + > + Cancel + + + +
+ ))} {/* Navigation */} diff --git a/web/components/sf/onboarding/step-provider.tsx b/web/components/sf/onboarding/step-provider.tsx index a6e9bb6a7..935c6139b 100644 --- a/web/components/sf/onboarding/step-provider.tsx +++ b/web/components/sf/onboarding/step-provider.tsx @@ -1,8 +1,8 @@ "use client"; -import { useMemo } from "react"; -import { motion } from "motion/react"; import { ArrowRight, Check, ShieldCheck } from "lucide-react"; +import { motion } from "motion/react"; +import { useMemo } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; diff --git a/web/components/sf/onboarding/step-ready.tsx b/web/components/sf/onboarding/step-ready.tsx index 6e6ae23b1..fd0396a11 100644 --- a/web/components/sf/onboarding/step-ready.tsx +++ b/web/components/sf/onboarding/step-ready.tsx @@ -1,8 +1,8 @@ "use client"; -import Image from "next/image"; -import { motion } from "motion/react"; import { CheckCircle2, Zap } from "lucide-react"; +import { motion } from "motion/react"; +import Image from "next/image"; import { Button } from "@/components/ui/button"; diff --git a/web/components/sf/onboarding/step-remote.tsx b/web/components/sf/onboarding/step-remote.tsx index 682763cb6..8ff52a93f 100644 --- a/web/components/sf/onboarding/step-remote.tsx +++ b/web/components/sf/onboarding/step-remote.tsx @@ -1,7 +1,5 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; -import { motion } from "motion/react"; import { ArrowRight, CheckCircle2, @@ -12,11 +10,13 @@ import { MessageSquare, SkipForward, } from "lucide-react"; +import { motion } from "motion/react"; +import { useCallback, useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { cn } from "@/lib/utils"; import { authFetch } from "@/lib/auth"; +import { cn } from "@/lib/utils"; // ─── Types ────────────────────────────────────────────────────────── diff --git a/web/components/sf/onboarding/step-welcome.tsx b/web/components/sf/onboarding/step-welcome.tsx index 2b445953c..289964dd5 100644 --- a/web/components/sf/onboarding/step-welcome.tsx +++ b/web/components/sf/onboarding/step-welcome.tsx @@ -1,8 +1,8 @@ "use client"; -import Image from "next/image"; -import { motion } from "motion/react"; import { ArrowRight } from "lucide-react"; +import { motion } from "motion/react"; +import Image from "next/image"; import { Button } from "@/components/ui/button"; diff --git a/web/components/sf/onboarding/wizard-stepper.tsx b/web/components/sf/onboarding/wizard-stepper.tsx index e88dcb357..df7cb79dc 100644 --- a/web/components/sf/onboarding/wizard-stepper.tsx +++ b/web/components/sf/onboarding/wizard-stepper.tsx @@ -1,7 +1,7 @@ "use client"; -import { cn } from "@/lib/utils"; import { Check } from "lucide-react"; +import { cn } from "@/lib/utils"; export interface WizardStep { id: string; diff --git a/web/components/sf/project-welcome.tsx b/web/components/sf/project-welcome.tsx index 03b298eb4..e023bc66d 100644 --- a/web/components/sf/project-welcome.tsx +++ b/web/components/sf/project-welcome.tsx @@ -2,16 +2,16 @@ import { ArrowRight, + ArrowUpCircle, + FileCode, + Folder, FolderOpen, GitBranch, Package, - FileCode, Sparkles, - ArrowUpCircle, - Folder, } from "lucide-react"; -import { cn } from "@/lib/utils"; import type { ProjectDetection } from "@/lib/sf-workspace-store"; +import { cn } from "@/lib/utils"; // ─── Variant Config ───────────────────────────────────────────────────────── @@ -188,6 +188,7 @@ export function ProjectWelcome({ {/* Actions */}
+ ) : ( + + ))} {activeTab === "phase" && data.byPhase.length === 0 && ( @@ -557,118 +554,115 @@ export function UndoPanel() { )} - {data && ( - <> - {data.lastUnitType ? ( - <> - {/* Last unit info */} -
-

- Last Completed Unit -

-
- Type - - {data.lastUnitType} - - ID - - {data.lastUnitId ?? "—"} - - Key - - {data.lastUnitKey ?? "—"} - -
+ {data && + (data.lastUnitType ? ( + <> + {/* Last unit info */} +
+

+ Last Completed Unit +

+
+ Type + + {data.lastUnitType} + + ID + + {data.lastUnitId ?? "—"} + + Key + + {data.lastUnitKey ?? "—"} +
+
-
- - {data.commits.length > 0 && ( - - )} -
- - {/* Commit SHAs */} +
+ {data.commits.length > 0 && ( -
-

- Associated Commits -

-
- {data.commits.map((sha) => ( - - {sha.slice(0, 8)} - - ))} -
-
+ )} +
- {/* Confirmation */} - {!confirming ? ( - - ) : ( -
-
- - - This will revert the last unit and its git commits. - -
-
- - -
+ {sha.slice(0, 8)} + + ))}
- )} - - ) : ( - - )} - - )} +
+ )} + + {/* Confirmation */} + {!confirming ? ( + + ) : ( +
+
+ + + This will revert the last unit and its git commits. + +
+
+ + +
+
+ )} + + ) : ( + + ))} ); } diff --git a/web/components/sf/roadmap.tsx b/web/components/sf/roadmap.tsx index f94d9daa9..3aa047e14 100644 --- a/web/components/sf/roadmap.tsx +++ b/web/components/sf/roadmap.tsx @@ -1,18 +1,18 @@ "use client"; import { + AlertTriangle, CheckCircle2, + ChevronRight, Circle, Play, - AlertTriangle, - ChevronRight, } from "lucide-react"; -import { cn } from "@/lib/utils"; import { getLiveWorkspaceIndex, - useSFWorkspaceState, type RiskLevel, + useSFWorkspaceState, } from "@/lib/sf-workspace-store"; +import { cn } from "@/lib/utils"; import { getMilestoneStatus, getSliceStatus, diff --git a/web/components/sf/settings-panels.tsx b/web/components/sf/settings-panels.tsx index 67b886130..2a0be3d99 100644 --- a/web/components/sf/settings-panels.tsx +++ b/web/components/sf/settings-panels.tsx @@ -1,7 +1,5 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; - import { AlertTriangle, CheckCircle2, @@ -16,26 +14,26 @@ import { RefreshCw, Settings, SlidersHorizontal, - Type, } from "lucide-react"; +import { useCallback, useEffect, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { authFetch } from "@/lib/auth"; import type { SettingsData, SettingsPatternHistory, SettingsRoutingHistory, } from "@/lib/settings-types"; -import { cn } from "@/lib/utils"; import { formatCost, formatTokens, useSFWorkspaceActions, useSFWorkspaceState, } from "@/lib/sf-workspace-store"; -import { useTerminalFontSize } from "@/lib/use-terminal-font-size"; import { useEditorFontSize } from "@/lib/use-editor-font-size"; -import { authFetch } from "@/lib/auth"; +import { useTerminalFontSize } from "@/lib/use-terminal-font-size"; +import { cn } from "@/lib/utils"; // ═══════════════════════════════════════════════════════════════════════ // SHARED INFRASTRUCTURE @@ -777,7 +775,7 @@ export function RemoteQuestionsPanel() { const { data, busy, refresh } = useSettingsData(); const existingConfig = data?.preferences?.remoteQuestions ?? null; - const [envVarSet, setEnvVarSet] = useState(false); + const [_envVarSet, setEnvVarSet] = useState(false); const [envVarName, setEnvVarName] = useState(null); const [apiLoading, setApiLoading] = useState(true); const [tokenSet, setTokenSet] = useState(false); @@ -1118,6 +1116,7 @@ export function RemoteQuestionsPanel() { className="flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-muted-foreground transition-colors" >
{/* New terminal button */}