#!/usr/bin/env node const { spawnSync } = require("node:child_process"); const { existsSync, readdirSync, statSync } = require("node:fs"); const { join, resolve } = require("node:path"); const root = resolve(__dirname, ".."); const srcResources = join(root, "src", "resources"); const distResources = join(root, "dist", "resources"); const stampPath = join(distResources, ".sf-resource-build-stamp"); const copyResourcesScript = join(root, "scripts", "copy-resources.cjs"); function latestMtimeMs(path) { let latest = 0; const stack = [path]; while (stack.length > 0) { const current = stack.pop(); if (!current) continue; let entries; try { entries = readdirSync(current, { withFileTypes: true }); } catch { continue; } for (const entry of entries) { const entryPath = join(current, entry.name); let stat; try { stat = statSync(entryPath); } catch { continue; } latest = Math.max(latest, stat.mtimeMs); if (entry.isDirectory()) { stack.push(entryPath); } } } return latest; } function sourceInputsMtimeMs() { return Math.max( latestMtimeMs(srcResources), existsSync(copyResourcesScript) ? statSync(copyResourcesScript).mtimeMs : 0, existsSync(join(root, "tsconfig.resources.json")) ? statSync(join(root, "tsconfig.resources.json")).mtimeMs : 0, ); } function hasCompleteResourceBuild() { return ( existsSync(stampPath) && existsSync(join(distResources, "SF-WORKFLOW.md")) && existsSync(join(distResources, "agents")) && existsSync(join(distResources, "extensions")) ); } function shouldRebuild() { if (process.env.SF_DEV_CLI_SKIP_RESOURCE_BUILD === "1") return false; if (process.env.SF_SKIP_SOURCE_RESOURCE_BUILD === "1") return false; if (!hasCompleteResourceBuild()) return true; let stampMtime = 0; try { stampMtime = statSync(stampPath).mtimeMs; } catch { return true; } return sourceInputsMtimeMs() > stampMtime; } if (shouldRebuild()) { console.error( "[forge] Source resources changed; rebuilding dist/resources before launch...", ); const result = spawnSync(process.execPath, [copyResourcesScript], { cwd: root, stdio: "inherit", env: process.env, }); if (result.status !== 0) { process.exit(result.status ?? 1); } } /** * Auto-rebuild workspace packages whose src/ is newer than dist/. * * Without this, edits to packages/coding-agent/src/* (or any other workspace * package src) silently land while the dist stays stale — agents continue * loading the old compiled JS and operators see "why didn't my edit take * effect?" symptoms (observed 2026-05-17 with the AST tools wire-in). * * Skipped: * - native: Rust build takes 5–10 min; trigger manually via * `node rust-engine/scripts/build.js --dev` instead. * - any package set in SF_SKIP_WORKSPACE_AUTOBUILD (comma-separated names). * * Override the whole step with SF_SKIP_WORKSPACE_AUTOBUILD=all. */ const WORKSPACE_AUTOBUILD_PACKAGES = [ "agent-core", "ai", "coding-agent", "daemon", "google-gemini-cli-provider", "openai-codex-provider", "rpc-client", "tui", ]; function packageNeedsRebuild(pkgName) { const pkgDir = join(root, "packages", pkgName); const pkgSrc = join(pkgDir, "src"); const pkgDist = join(pkgDir, "dist"); if (!existsSync(pkgSrc) || !existsSync(pkgDist)) return false; const srcMtime = latestMtimeMs(pkgSrc); const distMtime = latestMtimeMs(pkgDist); // Add a small grace window so files written within the same second as a // dist sync don't trigger redundant rebuilds. return srcMtime > distMtime + 100; } function rebuildWorkspacePackagesIfStale() { const skip = (process.env.SF_SKIP_WORKSPACE_AUTOBUILD || "") .split(",") .map((s) => s.trim()) .filter(Boolean); if (skip.includes("all")) return; const stale = WORKSPACE_AUTOBUILD_PACKAGES.filter( (pkg) => !skip.includes(pkg) && packageNeedsRebuild(pkg), ); if (stale.length === 0) return; console.error( `[forge] Workspace src newer than dist for: ${stale.join(", ")} — rebuilding...`, ); for (const pkg of stale) { const result = spawnSync( "npm", ["run", "build", "-w", `@singularity-forge/${pkg}`], { cwd: root, stdio: "inherit", env: process.env }, ); if (result.status !== 0) { console.error(`[forge] FAILED to rebuild packages/${pkg}`); process.exit(result.status ?? 1); } } } rebuildWorkspacePackagesIfStale();