refactor: eliminate ~400 no-explicit-any warnings across the workspace

Parallel subagent pass: four typescript-pro agents on non-overlapping scopes.

Patterns applied:
- catch (err: any) { ... err.message ... } → catch (err) { ... getErrorMessage(err) ... }
  using the new @fusion/core helper. Bare catch {} where the error was unused.
- SQLite row types: defined typed XxxRow interfaces per table and cast
  .all()/.get() results via `as unknown as XxxRow[]` (the double cast is
  required because better-sqlite3 returns Record<string, SQLOutputValue>).
- rowToX(row: any) converters: typed argument with the matching row interface.
- Dynamic settings key writes: (settings as Record<string, unknown>)[key].
- React event handlers and setState callbacks: inferred types or concrete
  React.{Mouse,Change,Form}Event<...> where needed.
- pi-claude-cli: local PiMessage / PiContext duck types to avoid re-typing
  pi-ai concrete shapes; typed Claude stream event message fields.

72 files changed, ~400 anys eliminated. Typecheck passes across the workspace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-23 18:57:31 -07:00
parent d1bd02b2c9
commit 4cabe7f613
72 changed files with 1298 additions and 783 deletions

View File

@@ -1317,8 +1317,8 @@ async function main() {
console.log(HELP);
process.exit(1);
}
} catch (err: any) {
console.error(`Error: ${err.message}`);
} catch (err) {
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
process.exit(1);
}
}

View File

@@ -29,6 +29,7 @@ import {
AuthStorage,
DefaultPackageManager,
ModelRegistry,
SettingsManager,
discoverAndLoadExtensions,
createExtensionRuntime,
} from "@mariozechner/pi-coding-agent";
@@ -214,8 +215,8 @@ export async function runDaemon(opts: DaemonOptions = {}) {
if (opts.interactive) {
try {
selectedPort = await promptForPort(selectedPort);
} catch (err: any) {
if (err.message === "Interactive prompt cancelled") {
} catch (err) {
if (err instanceof Error && err.message === "Interactive prompt cancelled") {
console.log("Cancelled — exiting");
process.exit(0);
}
@@ -298,7 +299,7 @@ export async function runDaemon(opts: DaemonOptions = {}) {
processPullRequestMerge: (s, wd, taskId) =>
processPullRequestMergeTask(s, wd, taskId, githubClient, getTaskMergeBlocker),
getTaskMergeBlocker,
onInsightRunProcessed: onMemoryInsightRunProcessed as any,
onInsightRunProcessed: (s: unknown, r: unknown) => onMemoryInsightRunProcessed(s as ScheduledTask, r as AutomationRunResult),
});
await engineManager.startAll();
@@ -385,7 +386,7 @@ export async function runDaemon(opts: DaemonOptions = {}) {
packageManager = new DefaultPackageManager({
cwd,
agentDir,
settingsManager: createReadOnlyProviderSettingsView(cwd, agentDir) as any,
settingsManager: createReadOnlyProviderSettingsView(cwd, agentDir) as unknown as SettingsManager,
});
const resolvedPaths = await packageManager.resolve();
const packageExtensionPaths = resolvedPaths.extensions
@@ -451,7 +452,7 @@ export async function runDaemon(opts: DaemonOptions = {}) {
// ── Skills adapter for skills discovery and execution toggling ─────────────
const skillsAdapter = packageManager
? createSkillsAdapter({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dashboard's resolve() uses a looser onMissing signature than pi's DefaultPackageManager
packageManager: packageManager as any,
getSettingsPath: (rootDir: string) => getProjectSettingsPath(rootDir),
})

View File

@@ -11,7 +11,7 @@ import {
type RuntimeLogger,
} from "@fusion/dashboard";
import { aiMergeTask, MissionAutopilot, MissionExecutionLoop, HeartbeatMonitor, HeartbeatTriggerScheduler, type WakeContext, ProjectEngineManager, PeerExchangeService } from "@fusion/engine";
import { AuthStorage, DefaultPackageManager, ModelRegistry, discoverAndLoadExtensions, createExtensionRuntime } from "@mariozechner/pi-coding-agent";
import { AuthStorage, DefaultPackageManager, ModelRegistry, SettingsManager, discoverAndLoadExtensions, createExtensionRuntime } from "@mariozechner/pi-coding-agent";
import {
getMergeStrategy,
processPullRequestMergeTask,
@@ -342,8 +342,8 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
if (opts.interactive) {
try {
selectedPort = await promptForPort(port);
} catch (err: any) {
if (err.message === "Interactive prompt cancelled") {
} catch (err) {
if (err instanceof Error && err.message === "Interactive prompt cancelled") {
console.log("Cancelled — exiting");
process.exit(0);
}
@@ -761,7 +761,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
packageManager = new DefaultPackageManager({
cwd,
agentDir,
settingsManager: createReadOnlyProviderSettingsView(cwd, agentDir) as any,
settingsManager: createReadOnlyProviderSettingsView(cwd, agentDir) as unknown as SettingsManager,
});
const resolvedPaths = await packageManager.resolve();
const packageExtensionPaths = resolvedPaths.extensions
@@ -827,7 +827,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
const res = await fetch("https://openrouter.ai/api/v1/models", { headers });
if (!res.ok) return;
const json = await res.json() as { data?: Array<{ id: string; name: string; context_length?: number; top_provider?: { max_completion_tokens?: number }; pricing?: Record<string, string>; architecture?: { modality?: string; input_modalities?: string[] } }> };
const orModels = (json.data || []).map((m: any) => {
const orModels = (json.data || []).map((m) => {
const id = (m.id || "").toLowerCase();
const name = (m.name || "").toLowerCase();
const reasoning = id.includes(":thinking") || id.includes("-r1") || id.includes("/r1") || id.includes("o1-") || id.includes("o3-") || id.includes("o4-") || id.includes("reasoner") || name.includes("thinking") || name.includes("reasoner");
@@ -870,7 +870,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const skillsAdapter = packageManager
? createSkillsAdapter({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dashboard's resolve() uses a looser onMissing signature than pi's DefaultPackageManager
packageManager: packageManager as any,
getSettingsPath: (rootDir: string) => getProjectSettingsPath(rootDir),
})

View File

@@ -160,8 +160,8 @@ export async function fetchGitRemote(remote: string = "origin", cwd: string = pr
try {
const { stdout } = await execAsync(`git fetch ${remote}`, { encoding: "utf-8", timeout: 30000, cwd });
return { fetched: true, message: stdout.trim() || "Fetch completed" };
} catch (err: any) {
const message = err.message || String(err);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes("Could not resolve host") || message.includes("Connection refused")) {
throw new Error("Failed to connect to remote");
}
@@ -177,8 +177,8 @@ export async function pullGitBranch(cwd: string = process.cwd()): Promise<GitPul
try {
const { stdout } = await execAsync("git pull", { encoding: "utf-8", timeout: 30000, cwd });
return { success: true, message: stdout.trim() };
} catch (err: any) {
const message = err.message || String(err);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes("CONFLICT") || message.includes("Merge conflict")) {
return { success: false, message: "Merge conflict detected. Resolve manually.", conflict: true };
}
@@ -193,8 +193,8 @@ export async function pushGitBranch(cwd: string = process.cwd()): Promise<GitPus
try {
const { stdout } = await execAsync("git push", { encoding: "utf-8", timeout: 30000, cwd });
return { success: true, message: stdout.trim() || "Push completed" };
} catch (err: any) {
const message = err.message || String(err);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes("rejected") || message.includes("non-fast-forward")) {
throw new Error("Push rejected. Pull latest changes first.");
}
@@ -281,8 +281,8 @@ export async function runGitFetch(remote?: string, projectName?: string): Promis
console.log();
console.log(` ✓ Fetched from ${targetRemote}`);
console.log();
} catch (err: any) {
console.error(`Error: ${err.message}`);
} catch (err) {
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
process.exit(1);
}
}
@@ -333,8 +333,8 @@ export async function runGitPull(options: { skipConfirm?: boolean; projectName?:
console.log(` ${stdout.trim()}`);
}
console.log();
} catch (err: any) {
const message = err.message || String(err);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes("CONFLICT") || message.includes("Merge conflict")) {
console.error(" ✗ Merge conflict detected. Resolve manually.");
process.exit(1);
@@ -401,8 +401,8 @@ export async function runGitPush(options: { skipConfirm?: boolean; projectName?:
console.log(` ${stdout.trim()}`);
}
console.log();
} catch (err: any) {
const message = err.message || String(err);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes("rejected") || message.includes("non-fast-forward")) {
console.error("Error: Push rejected. Pull latest changes first.");
} else if (message.includes("Could not resolve host") || message.includes("Connection refused")) {

View File

@@ -2,8 +2,8 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { join, dirname, basename } from "node:path";
export interface PackageManagerSettingsView {
getGlobalSettings(): Record<string, any>;
getProjectSettings(): Record<string, any>;
getGlobalSettings(): Record<string, unknown>;
getProjectSettings(): Record<string, unknown>;
getNpmCommand(): string[] | undefined;
}
@@ -14,14 +14,14 @@ function siblingAgentDir(agentDir: string, siblingRoot: ".fusion" | ".pi"): stri
return join(dirname(dirname(agentDir)), siblingRoot, "agent");
}
function readJsonObject(path: string): Record<string, any> {
function readJsonObject(path: string): Record<string, unknown> {
if (!existsSync(path)) {
return {};
}
try {
const parsed = JSON.parse(readFileSync(path, "utf-8"));
return parsed && typeof parsed === "object" ? parsed as Record<string, any> : {};
const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
return parsed !== null && typeof parsed === "object" ? parsed as Record<string, unknown> : {};
} catch {
return {};
}
@@ -60,18 +60,21 @@ export function createReadOnlyProviderSettingsView(cwd: string, agentDir: string
*/
export function createProjectSettingsPersistence(projectPath: string): {
/** Read the current project settings */
read(): Record<string, any>;
read(): Record<string, unknown>;
/** Write the project settings (merges with existing values) */
write(settings: Record<string, any>): void;
write(settings: Record<string, unknown>): void;
/** Get the path to the settings file */
getSettingsPath(): string;
} {
const fusionSettingsPath = join(projectPath, ".fusion", "settings.json");
function readSettings(): Record<string, any> {
function readSettings(): Record<string, unknown> {
if (existsSync(fusionSettingsPath)) {
try {
return JSON.parse(readFileSync(fusionSettingsPath, "utf-8")) as Record<string, any>;
const parsed = JSON.parse(readFileSync(fusionSettingsPath, "utf-8")) as unknown;
if (parsed !== null && typeof parsed === "object") {
return parsed as Record<string, unknown>;
}
} catch {
// Return empty on parse error
}
@@ -79,7 +82,7 @@ export function createProjectSettingsPersistence(projectPath: string): {
return {};
}
function writeSettings(settings: Record<string, any>): void {
function writeSettings(settings: Record<string, unknown>): void {
// Ensure .fusion directory exists
const fusionDir = dirname(fusionSettingsPath);
if (!existsSync(fusionDir)) {

View File

@@ -30,6 +30,7 @@ import {
AuthStorage,
DefaultPackageManager,
ModelRegistry,
SettingsManager,
discoverAndLoadExtensions,
createExtensionRuntime,
} from "@mariozechner/pi-coding-agent";
@@ -218,8 +219,8 @@ export async function runServe(
if (opts.interactive) {
try {
selectedPort = await promptForPort(port);
} catch (err: any) {
if (err.message === "Interactive prompt cancelled") {
} catch (err) {
if (err instanceof Error && err.message === "Interactive prompt cancelled") {
console.log("Cancelled — exiting");
process.exit(0);
}
@@ -319,7 +320,7 @@ export async function runServe(
processPullRequestMerge: (s, wd, taskId) =>
processPullRequestMergeTask(s, wd, taskId, githubClient, getTaskMergeBlocker),
getTaskMergeBlocker,
onInsightRunProcessed: onMemoryInsightRunProcessed as any,
onInsightRunProcessed: (s: unknown, r: unknown) => onMemoryInsightRunProcessed(s as ScheduledTask, r as AutomationRunResult),
});
// Start engines for all registered projects eagerly
@@ -439,7 +440,7 @@ export async function runServe(
packageManager = new DefaultPackageManager({
cwd,
agentDir,
settingsManager: createReadOnlyProviderSettingsView(cwd, agentDir) as any,
settingsManager: createReadOnlyProviderSettingsView(cwd, agentDir) as unknown as SettingsManager,
});
const resolvedPaths = await packageManager.resolve();
const packageExtensionPaths = resolvedPaths.extensions
@@ -618,7 +619,7 @@ export async function runServe(
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const skillsAdapter = packageManager
? createSkillsAdapter({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dashboard's resolve() uses a looser onMissing signature than pi's DefaultPackageManager
packageManager: packageManager as any,
getSettingsPath: (rootDir: string) => getProjectSettingsPath(rootDir),
})

View File

@@ -300,8 +300,8 @@ export async function runSettingsSet(key: string, value: string, projectName?: s
console.log();
console.log(` ✓ Updated ${getSettingLabel(key)} to ${formatSettingValue(key as keyof Settings, parsedValue, currentSettings as Settings)}`);
console.log();
} catch (err: any) {
console.error(`Error: ${err.message}`);
} catch (err) {
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
process.exit(1);
return;
}

View File

@@ -472,8 +472,8 @@ export async function runTaskMerge(id: string, projectName?: string) {
}
console.log(` Status: done`);
console.log();
} catch (err: any) {
console.error(`\n ✗ ${err.message}\n`);
} catch (err) {
console.error(`\n ✗ ${err instanceof Error ? err.message : String(err)}\n`);
process.exit(1);
}
}
@@ -692,8 +692,8 @@ export async function runTaskDelete(id: string, force?: boolean, projectName?: s
console.log();
console.log(` ✓ Deleted ${id}`);
console.log();
} catch (err: any) {
console.error(`✗ Failed to delete ${id}: ${err.message}`);
} catch (err) {
console.error(`✗ Failed to delete ${id}: ${err instanceof Error ? err.message : String(err)}`);
process.exit(1);
return;
}
@@ -733,8 +733,8 @@ export async function runTaskImportGitHubInteractive(
let issues: GitHubIssue[];
try {
issues = await fetchGitHubIssues(owner, repo, { limit, labels });
} catch (err: any) {
console.error(`${err.message}\n`);
} catch (err) {
console.error(`${err instanceof Error ? err.message : String(err)}\n`);
process.exit(1);
}
@@ -921,8 +921,8 @@ export async function runTaskImportFromGitHub(
let issues: GitHubIssue[];
try {
issues = await fetchGitHubIssues(owner, repo, { limit, labels });
} catch (err: any) {
console.error(`${err.message}\n`);
} catch (err) {
console.error(`${err instanceof Error ? err.message : String(err)}\n`);
process.exit(1);
}
@@ -1047,8 +1047,8 @@ export async function runTaskSteer(id: string, message?: string, projectName?: s
let task;
try {
task = await store.addSteeringComment(id, trimmed, "user");
} catch (err: any) {
if (err.code === "ENOENT") {
} catch (err) {
if (typeof err === "object" && err !== null && (err as Record<string, unknown>).code === "ENOENT") {
console.error(`Error: Task not found: ${id}`);
process.exit(1);
}
@@ -1078,8 +1078,8 @@ export async function runTaskPrCreate(id: string, options: PrCreateOptions = {},
let task;
try {
task = await store.getTask(id);
} catch (err: any) {
if (err.code === "ENOENT") {
} catch (err) {
if (typeof err === "object" && err !== null && (err as Record<string, unknown>).code === "ENOENT") {
console.error(`Error: Task ${id} not found`);
process.exit(1);
}
@@ -1168,16 +1168,17 @@ export async function runTaskPrCreate(id: string, options: PrCreateOptions = {},
console.log(` PR #${prInfo.number}: ${prInfo.url}`);
console.log(` Branch: ${branchName}${prInfo.baseBranch}`);
console.log();
} catch (err: any) {
} catch (err) {
// Handle specific error cases
if (err.message?.includes("already exists")) {
const msg = err instanceof Error ? err.message : String(err);
if (msg.includes("already exists")) {
console.error(`Error: A pull request already exists for ${owner}/${repo}:${branchName}`);
process.exit(1);
} else if (err.message?.includes("No commits between")) {
} else if (msg.includes("No commits between")) {
console.error(`Error: No commits between ${options.base || "default base"} and ${branchName}. Push changes before creating PR.`);
process.exit(1);
} else {
console.error(`Error: ${err.message || "Failed to create PR"}`);
console.error(`Error: ${msg || "Failed to create PR"}`);
process.exit(1);
}
}
@@ -1472,7 +1473,7 @@ export async function runTaskPlan(initialPlanArg?: string, yesFlag = false, proj
break;
}
default: {
console.error(`\n Unknown question type: ${(currentQuestion as any).type}`);
console.error(`\n Unknown question type: ${String((currentQuestion as unknown as Record<string, unknown>).type)}`);
process.exit(1);
}
}

View File

@@ -1,5 +1,5 @@
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "typebox";
import { Type, type TSchema } from "typebox";
import { StringEnum } from "@mariozechner/pi-ai";
import {
TaskStore,
@@ -298,7 +298,7 @@ export default function kbExtension(pi: ExtensionAPI) {
column: Type.Optional(
StringEnum([...COLUMNS] as unknown as string[], {
description: "Filter to a specific column",
}) as any,
}) as unknown as TSchema,
),
limit: Type.Optional(
Type.Number({
@@ -1039,10 +1039,10 @@ export default function kbExtension(pi: ExtensionAPI) {
let taskId: string | undefined;
try {
taskId = await runTaskPlan(params.description, true); // Use --yes flag for non-interactive
} catch (err: any) {
} catch (err) {
console.error = originalError;
console.log = originalLog;
throw new Error(`Planning mode failed: ${err.message}`);
throw new Error(`Planning mode failed: ${err instanceof Error ? err.message : String(err)}`);
} finally {
console.error = originalError;
console.log = originalLog;

View File

@@ -268,11 +268,12 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
console.log(`\n ✓ Registered project "${newProject.name}"`);
return createResolvedProject(newProject);
} catch (err: any) {
} catch (err) {
const errMsg = err instanceof Error ? err.message : String(err);
throw new ProjectResolutionError(
`Failed to register project: ${err.message}`,
`Failed to register project: ${errMsg}`,
"NOT_REGISTERED",
{ directory: fusionDir, error: err.message }
{ directory: fusionDir, error: errMsg }
);
}
} else {