chore: commit current workspace state

This commit is contained in:
Mikael Hugo 2026-05-05 14:46:18 +02:00
parent f11c877224
commit 00a118ea71
822 changed files with 3525 additions and 4215 deletions

View file

@ -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",

View file

@ -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.) |

View file

@ -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.

View file

@ -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}"

View file

@ -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",

View file

@ -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";

View file

@ -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";

View file

@ -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

View file

@ -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<string, unknown>;
// --- discord ---
let discord: DaemonConfig["discord"] = undefined;
let discord: DaemonConfig["discord"];
if (obj["discord"] != null && typeof obj["discord"] === "object") {
const d = obj["discord"] as Record<string, unknown>;
discord = {

View file

@ -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";

View file

@ -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.

View file

@ -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<unknown>;
}
>();
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

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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<ButtonBuilder>();
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);

View file

@ -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";

View file

@ -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
});

View file

@ -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 ---------------

View file

@ -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<LogLevel, number> = {
debug: 0,

View file

@ -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();

View file

@ -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

View file

@ -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

View file

@ -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 ----------

View file

@ -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";
// ---------------------------------------------------------------------------

View file

@ -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",
});

View file

@ -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)

View file

@ -1,7 +1,7 @@
import type {
RpcClient,
SdkAgentEvent,
RpcExtensionUIRequest,
SdkAgentEvent,
} from "@singularity-forge/rpc-client";
/**

View file

@ -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

View file

@ -34,7 +34,7 @@ const DEFAULT_SHOWN: ReadonlySet<string> = new Set([
]);
/** Event types shown only at verbose level. */
const VERBOSE_ONLY: ReadonlySet<string> = new Set([
const _VERBOSE_ONLY: ReadonlySet<string> = new Set([
"cost_update",
"state_update",
"status",

View file

@ -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";

View file

@ -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";

View file

@ -1,29 +1,29 @@
// @singularity-forge/mcp-server — Tests for env-writer utilities
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
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 {

View file

@ -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,

View file

@ -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";

View file

@ -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)

View file

@ -1,8 +1,8 @@
// SF MCP Server — captures reader
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
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

View file

@ -1,16 +1,16 @@
// SF MCP Server — lightweight structural health checks
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
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";
// ---------------------------------------------------------------------------

View file

@ -1,8 +1,8 @@
// SF MCP Server — knowledge base reader
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
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

View file

@ -1,8 +1,8 @@
// SF MCP Server — metrics/history reader
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
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

View file

@ -1,9 +1,9 @@
// SF MCP Server — .sf/ directory resolution
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
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.

View file

@ -1,19 +1,18 @@
// SF MCP Server — reader tests
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
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

View file

@ -1,14 +1,14 @@
// SF MCP Server — roadmap structure reader
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
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";
// ---------------------------------------------------------------------------

View file

@ -1,15 +1,13 @@
// SF MCP Server — project state reader
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
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";
// ---------------------------------------------------------------------------

View file

@ -2,91 +2,18 @@
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
//
// 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<string, unknown>;
handler: (args: Record<string, unknown>) => Promise<unknown>;
}
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<string, unknown> } = {
action: "accept",
content: {},
};
server = {
elicitInput: async (_params: unknown) => {
return this.elicitResponse;
},
};
tool(
name: string,
description: string,
params: Record<string, unknown>,
handler: (args: Record<string, unknown>) => Promise<unknown>,
) {
this.registeredTools.push({ name, description, params, handler });
}
async connect(_transport: unknown) {
/* no-op */
}
async close() {
/* no-op */
}
getToolHandler(
name: string,
): ((args: Record<string, unknown>) => Promise<unknown>) | undefined {
return this.registeredTools.find((t) => t.name === name)?.handler;
}
}
// ---------------------------------------------------------------------------
// Helper to create a mock MCP server with secure_env_collect registered
// Test helpers
// ---------------------------------------------------------------------------
/**

View file

@ -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)

View file

@ -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,

View file

@ -4,9 +4,8 @@
import type {
RpcClient,
SdkAgentEvent,
RpcCostUpdateEvent,
RpcExtensionUIRequest,
SdkAgentEvent,
} from "@singularity-forge/rpc-client";
// ---------------------------------------------------------------------------

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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");

View file

@ -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);

View file

@ -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", () => {

View file

@ -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 },
);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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(

View file

@ -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

View file

@ -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<string, Function>).astGrep(options) as AstFindResult;
return (native as Record<string, (...args: unknown[]) => unknown>).astGrep(
options,
) as AstFindResult;
}
export function astEdit(options: AstReplaceOptions): AstReplaceResult {
return (native as Record<string, Function>).astEdit(
return (native as Record<string, (...args: unknown[]) => unknown>).astEdit(
options,
) as AstReplaceResult;
}

View file

@ -18,9 +18,9 @@ export type { DiffResult, FuzzyMatchResult };
* - Special Unicode spaces to regular space
*/
export function normalizeForFuzzyMatch(text: string): string {
return (native as Record<string, Function>).normalizeForFuzzyMatch(
text,
) as string;
return (
native as Record<string, (...args: unknown[]) => unknown>
).normalizeForFuzzyMatch(text) as string;
}
/**
@ -33,10 +33,9 @@ export function fuzzyFindText(
content: string,
oldText: string,
): FuzzyMatchResult {
return (native as Record<string, Function>).fuzzyFindText(
content,
oldText,
) as FuzzyMatchResult;
return (
native as Record<string, (...args: unknown[]) => unknown>
).fuzzyFindText(content, oldText) as FuzzyMatchResult;
}
/**
@ -53,9 +52,7 @@ export function generateDiff(
newContent: string,
contextLines?: number,
): DiffResult {
return (native as Record<string, Function>).generateDiff(
oldContent,
newContent,
contextLines,
) as DiffResult;
return (
native as Record<string, (...args: unknown[]) => unknown>
).generateDiff(oldContent, newContent, contextLines) as DiffResult;
}

View file

@ -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,

View file

@ -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<string, Function>).parseFrontmatter(
content,
) as FrontmatterResult;
return (
native as Record<string, (...args: unknown[]) => unknown>
).parseFrontmatter(content) as FrontmatterResult;
}
/**
@ -60,11 +60,9 @@ export function extractSection(
heading: string,
level?: number,
): SectionResult {
return (native as Record<string, Function>).extractSection(
content,
heading,
level,
) as SectionResult;
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).extractAllSections(
content,
level,
) as string;
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).batchParseSfFiles(
directory,
) as BatchParseResult;
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).parseRoadmapFile(
content,
) as NativeRoadmap;
return (
native as Record<string, (...args: unknown[]) => unknown>
).parseRoadmapFile(content) as NativeRoadmap;
}
/**
* Scan a `.sf/` directory tree.
*/
export function scanSfTree(directory: string): SfTreeEntry[] {
return (native as Record<string, Function>).scanSfTree(
return (native as Record<string, (...args: unknown[]) => unknown>).scanSfTree(
directory,
) as SfTreeEntry[];
}
@ -121,27 +118,25 @@ export function parseJsonlTail(
maxBytes?: number,
maxEntries?: number,
): JsonlParseResult {
return (native as Record<string, Function>).parseJsonlTail(
filePath,
maxBytes,
maxEntries,
) as JsonlParseResult;
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).parsePlanFile(
content,
) as NativePlan;
return (
native as Record<string, (...args: unknown[]) => unknown>
).parsePlanFile(content) as NativePlan;
}
/**
* Parse a summary markdown file into structured data.
*/
export function parseSummaryFile(content: string): NativeSummary {
return (native as Record<string, Function>).parseSummaryFile(
content,
) as NativeSummary;
return (
native as Record<string, (...args: unknown[]) => unknown>
).parseSummaryFile(content) as NativeSummary;
}

View file

@ -1,5 +1,5 @@
/** File type classification for filesystem entries. */
export const enum FileType {
export enum FileType {
/** Regular file. */
File = 1,
/** Directory. */

View file

@ -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<string, unknown>)
.NativeImage as NativeImageConstructor;

View file

@ -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";

View file

@ -75,7 +75,7 @@ function loadNative(): Record<string, unknown> {
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

View file

@ -38,10 +38,9 @@ export function processStreamChunk(
}
: undefined;
const result = (native as Record<string, Function>).processStreamChunk(
chunk,
napiState,
) as {
const result = (
native as Record<string, (...args: unknown[]) => 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<string, Function>).stripAnsiNative(text) as string;
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).sanitizeBinaryOutputNative(
text,
) as string;
return (
native as Record<string, (...args: unknown[]) => unknown>
).sanitizeBinaryOutputNative(text) as string;
}

View file

@ -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<string, Function>).wrapTextWithAnsi(
text,
width,
tabWidth,
) as string[];
return (
native as Record<string, (...args: unknown[]) => unknown>
).wrapTextWithAnsi(text, width, tabWidth) as string[];
}
/**
@ -47,13 +45,9 @@ export function truncateToWidth(
pad: boolean,
tabWidth?: number,
): string {
return (native as Record<string, Function>).truncateToWidth(
text,
maxWidth,
ellipsisKind,
pad,
tabWidth,
) as string;
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).sliceWithWidth(
line,
startCol,
length,
strict,
tabWidth,
) as SliceResult;
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).extractSegments(
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).sanitizeText(text) as string;
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).visibleWidth(
text,
tabWidth,
) as number;
return (
native as Record<string, (...args: unknown[]) => unknown>
).visibleWidth(text, tabWidth) as number;
}

View file

@ -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<string, Function>).truncateTail(
text,
maxBytes,
) as TruncateResult;
return (
native as Record<string, (...args: unknown[]) => 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<string, Function>).truncateHead(
text,
maxBytes,
) as TruncateResult;
return (
native as Record<string, (...args: unknown[]) => unknown>
).truncateHead(text, maxBytes) as TruncateResult;
}
/**
@ -48,9 +46,7 @@ export function truncateOutput(
maxBytes: number,
mode?: string,
): TruncateOutputResult {
return (native as Record<string, Function>).truncateOutput(
text,
maxBytes,
mode,
) as TruncateOutputResult;
return (
native as Record<string, (...args: unknown[]) => unknown>
).truncateOutput(text, maxBytes, mode) as TruncateOutputResult;
}

View file

@ -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));

View file

@ -22,10 +22,6 @@ import type {
AgentMessage,
AgentState,
AgentTool,
BeforeToolCallContext,
BeforeToolCallResult,
AfterToolCallContext,
AfterToolCallResult,
StreamFn,
ThinkingLevel,
} from "./types.js";

View file

@ -91,7 +91,7 @@ export interface ProxyStreamOptions extends SimpleStreamOptions {
* });
* ```
*/
function streamProxy(
function _streamProxy(
model: Model<any>,
context: Context,
options: ProxyStreamOptions,

View file

@ -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.

View file

@ -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<Model<any>[]> {
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<Model<any>[]> {
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<Model<any>[]> {
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

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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(", ")}`,
);
});

View file

@ -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,

View file

@ -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

View file

@ -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));

View file

@ -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) =>

View file

@ -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;

View file

@ -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;

View file

@ -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 };

View file

@ -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";
// ═══════════════════════════════════════════════════════════════════════════

View file

@ -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";

View file

@ -15,6 +15,7 @@ async function getGoogleGenAIClass(): Promise<typeof GoogleGenAI> {
}
return _GoogleGenAIClass;
}
import { getEnvApiKey } from "../env-api-keys.js";
import { calculateCost } from "../models.js";
import type {

View file

@ -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);
}

View file

@ -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";
/**

Some files were not shown because too many files have changed in this diff Show more