refactor(sf-ext): replace inline sfHome patterns with canonical sfHome()

Fix bug in auto.js where SF_HOME env var caused double '.sf' path segment.
Convert 11 files from inline homedir()/.sf or SF_HOME constructs to sfHome().

Files updated:
- auto.js: bug fix (join(SF_HOME, '.sf', 'agent') → join(sfHome(), 'agent'))
- key-manager.js: process.env.SF_HOME || join(HOME, '.sf') → sfHome()
- ui/color-band.js: os.homedir()/.sf → sfHome(); remove os import
- ui/prompt-history.js: homedir()/.sf → sfHome(); remove homedir import
- ui/usage-bar.js: homedir()/.sf/agent/auth.json → sfHome()
- ui/marketplace.js: 2 occurrences — extensions dir → sfHome()
- skill-telemetry.js: 2 occurrences — legacy skills dir → sfHome()
- preferences-skills.js: legacy skills dir → sfHome()
- preferences-models.js: models.json path → sfHome()
- memory-embeddings.js: auth.json path → sfHome(); remove homedir import
- commands/handlers/core.js: dynamic import homedir → static sfHome()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Mikael Hugo 2026-05-11 10:45:35 +02:00
parent 0ece0e5413
commit 756355abf1
11 changed files with 24 additions and 19 deletions

View file

@ -26,9 +26,9 @@ import {
unlinkSync,
writeFileSync,
} from "node:fs";
import { homedir } from "node:os";
import { isAbsolute, join } from "node:path";
import { pathToFileURL } from "node:url";
import { sfHome } from "./sf-home.js";
import {
clearCmuxSidebar,
logCmuxEvent,
@ -1782,7 +1782,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
// Using SF_PKG_ROOT constructs a correct absolute path in both contexts (#3949).
const agentDir =
process.env.SF_CODING_AGENT_DIR ||
join(process.env.SF_HOME || homedir(), ".sf", "agent");
join(sfHome(), "agent");
const pkgRoot = process.env.SF_PKG_ROOT;
const resourceLoaderPath = pkgRoot
? pathToFileURL(join(pkgRoot, "dist", "resource-loader.js")).href

View file

@ -29,6 +29,7 @@ import { formattedShortcutPair } from "../../shortcut-defs.js";
import { deriveState } from "../../state.js";
import { writeUokDiagnostics } from "../../uok/diagnostic-synthesis.js";
import { projectRoot } from "../context.js";
import { sfHome } from "../../sf-home.js";
export function showHelp(ctx, args = "") {
const summaryLines = [
"SF — Singularity Forge\n",
@ -1468,8 +1469,7 @@ async function handleResumeCommand(args, ctx) {
// Fallback: show recent SF headless session files
const { readdirSync, existsSync } = await import("node:fs");
const { join: pathJoin } = await import("node:path");
const { homedir } = await import("node:os");
const sfDir = pathJoin(homedir(), ".sf");
const sfDir = sfHome();
const sessDir = pathJoin(sfDir, "sessions");
if (!existsSync(sessDir)) {
ctx.ui.notify(

View file

@ -9,6 +9,7 @@ import { dirname, join } from "node:path";
import { AuthStorage } from "@singularity-forge/coding-agent";
import { getErrorMessage } from "./error-utils.js";
import { isEnvAuthAllowed } from "./provider-env-auth.js";
import { sfHome } from "./sf-home.js";
export const PROVIDER_REGISTRY = [
// LLM Providers
{
@ -250,8 +251,7 @@ export function describeCredential(cred) {
* Get the auth.json path.
*/
export function getAuthPath() {
const sfHome = process.env.SF_HOME || join(process.env.HOME ?? "~", ".sf");
return join(sfHome, "agent", "auth.json");
return join(sfHome(), "agent", "auth.json");
}
/**
* Create an AuthStorage instance for key management.

View file

@ -13,7 +13,6 @@
// pipeline stages soft-degrade and `getRelevantMemoriesRanked` falls back
// to static (confidence × hit_count) ranking.
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import {
_getAdapter,
@ -22,11 +21,12 @@ import {
upsertMemoryEmbedding,
} from "./sf-db.js";
import { logWarning } from "./workflow-logger.js";
import { sfHome } from "./sf-home.js";
/** Read the llm-gateway entry from ~/.sf/agent/auth.json if present. */
function readGatewayFromAuthJson() {
try {
const authPath = join(homedir(), ".sf", "agent", "auth.json");
const authPath = join(sfHome(), "agent", "auth.json");
if (!existsSync(authPath)) return null;
const data = JSON.parse(readFileSync(authPath, "utf8"));
const entry = data["llm-gateway"];

View file

@ -10,6 +10,7 @@ import { homedir } from "node:os";
import { join } from "node:path";
import { getModels, getProviders } from "@singularity-forge/ai";
import { selectByBenchmarks } from "./benchmark-selector.js";
import { sfHome } from "./sf-home.js";
import { defaultRoutingConfig, MODEL_CAPABILITY_TIER } from "./model-router.js";
import {
@ -535,7 +536,7 @@ export function resolveDefaultSessionModel(sessionProvider) {
export function isCustomProvider(provider) {
if (!provider) return false;
const candidates = [
join(homedir(), ".sf", "agent", "models.json"),
join(sfHome(), "agent", "models.json"),
join(homedir(), ".pi", "agent", "models.json"),
];
for (const path of candidates) {

View file

@ -8,6 +8,7 @@ import { existsSync, readdirSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { isAbsolute, join } from "node:path";
import { validatePreferences } from "./preferences-validation.js";
import { sfHome } from "./sf-home.js";
/**
* Get skill search directories in priority order for resolution.
*
@ -25,7 +26,7 @@ export function getSkillSearchDirs(cwd) {
{ dir: join(cwd, ".claude", "skills"), method: "project-skill" },
];
// Legacy fallback — read skills from old SF directory only if migration hasn't completed
const legacyDir = join(homedir(), ".sf", "agent", "skills");
const legacyDir = join(sfHome(), "agent", "skills");
if (
existsSync(legacyDir) &&
!existsSync(join(legacyDir, ".migrated-to-agents"))

View file

@ -13,6 +13,7 @@
import { existsSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { sfHome } from "./sf-home.js";
// ─── In-memory state ──────────────────────────────────────────────────────────
/** Skills available in the system prompt for the current unit */
@ -28,7 +29,7 @@ const activelyLoadedSkills = new Set();
export function captureAvailableSkills() {
const skillsDir = join(homedir(), ".agents", "skills");
const claudeSkillsDir = join(homedir(), ".claude", "skills");
const legacyDir = join(homedir(), ".sf", "agent", "skills");
const legacyDir = join(sfHome(), "agent", "skills");
const names = listSkillNames(skillsDir);
const claudeNames = listSkillNames(claudeSkillsDir);
// Include skills still in the legacy directory only if migration hasn't completed
@ -102,7 +103,7 @@ export function detectStaleSkills(units, thresholdDays) {
// Check all installed skills, not just those with usage data
const skillsDir = join(homedir(), ".agents", "skills");
const claudeSkillsDir = join(homedir(), ".claude", "skills");
const legacyDir = join(homedir(), ".sf", "agent", "skills");
const legacyDir = join(sfHome(), "agent", "skills");
const legacyMigrated = existsSync(join(legacyDir, ".migrated-to-agents"));
const legacyNames = legacyMigrated ? [] : listSkillNames(legacyDir);
const installedSet = new Set([

View file

@ -4,15 +4,15 @@
* Displays a colored band in the footer to visually distinguish sessions.
*/
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { sfHome } from "../sf-home.js";
const DEFAULT_CONFIG = {
enabledByDefault: true,
blockChar: "▁",
blockCount: "full",
};
const STATE_FILE = path.join(os.homedir(), ".sf", "session-color-state.json");
const STATE_FILE = path.join(sfHome(), "session-color-state.json");
const COLOR_PALETTE = [
196, 51, 226, 129, 46, 208, 27, 213, 118, 160, 87, 220, 93, 34, 202, 75, 199,
154, 124, 45, 214, 135, 40, 166, 69, 205, 190, 88, 80, 228, 97, 28, 172, 63,

View file

@ -1,6 +1,7 @@
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { sfHome } from "../sf-home.js";
import {
Key,
matchesKey,
@ -92,7 +93,7 @@ function scanInstalledExtensions(dir, sourceLabel) {
}
function buildCatalog() {
const installed = scanInstalledExtensions(
join(homedir(), ".sf", "agent", "extensions"),
join(sfHome(), "agent", "extensions"),
"installed",
);
const piCompat = scanInstalledExtensions(
@ -317,7 +318,7 @@ export function installExtensionNpm(packageId, displayName) {
return;
}
import("node:child_process").then(({ spawn }) => {
const target = join(homedir(), ".sf", "agent", "extensions");
const target = join(sfHome(), "agent", "extensions");
const proc = spawn("npm", ["install", "--prefix", target, packageId], {
stdio: ["ignore", "pipe", "pipe"],
detached: false,

View file

@ -1,5 +1,4 @@
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import {
Key,
@ -7,11 +6,12 @@ import {
truncateToWidth,
visibleWidth,
} from "@singularity-forge/tui";
import { sfHome } from "../sf-home.js";
const LIMIT = 20;
const SCAN_LINE_LIMIT = 2000;
function promptHistoryPath() {
return join(homedir(), ".sf", "agent", "prompt-history.jsonl");
return join(sfHome(), "agent", "prompt-history.jsonl");
}
function isEnvTruthy(value) {
return ["1", "true", "TRUE", "yes", "YES"].includes(String(value ?? ""));

View file

@ -19,12 +19,13 @@ import {
setupUser,
} from "@google/gemini-cli-core";
import { visibleWidth } from "@singularity-forge/tui";
import { sfHome } from "../sf-home.js";
// ============================================================================
// Auth helper
// ============================================================================
function loadAuthJson() {
const sfAuthPath = path.join(os.homedir(), ".sf", "agent", "auth.json");
const sfAuthPath = path.join(sfHome(), "agent", "auth.json");
try {
if (fs.existsSync(sfAuthPath)) {
return JSON.parse(fs.readFileSync(sfAuthPath, "utf-8"));