diff --git a/src/resources/extensions/sf/detection.ts b/src/resources/extensions/sf/detection.ts index 5079a58f3..3742544be 100644 --- a/src/resources/extensions/sf/detection.ts +++ b/src/resources/extensions/sf/detection.ts @@ -760,8 +760,30 @@ function detectVerificationCommands( if (detectedFiles.includes("go.mod")) { // Limit parallelism: Go's default is GOMAXPROCS which can be very high. - commands.push("go test -parallel 2 ./..."); - commands.push("go vet ./..."); + const rootHasGoMod = existsSync(join(basePath, "go.mod")); + if (rootHasGoMod) { + commands.push("go test -parallel 2 ./..."); + commands.push("go vet ./..."); + } else { + // Multi-module repo (no root go.mod, only nested ones — common in + // monorepos like dr-repo/{dr-agent,portal,gateway,...}). Find each + // module dir and emit a per-module loop so commands work from the + // repo root regardless of which modules exist. + const scanned = scanProjectFiles(basePath); + const moduleDirs = scanned + .filter((f) => f.endsWith("/go.mod") || f === "go.mod") + .map((f) => (f === "go.mod" ? "." : f.slice(0, -"/go.mod".length))) + .filter((d) => d.length > 0 && !d.includes("..")); + if (moduleDirs.length > 0) { + const dirsArg = moduleDirs.map((d) => `"${d}"`).join(" "); + commands.push( + `bash -c 'set -e; for d in ${dirsArg}; do (cd "$d" && go vet ./...); done'`, + ); + commands.push( + `bash -c 'set -e; for d in ${dirsArg}; do (cd "$d" && go test -parallel 2 ./...); done'`, + ); + } + } } if ( diff --git a/src/resources/extensions/sf/gitignore.ts b/src/resources/extensions/sf/gitignore.ts index c57fdb654..58d831856 100644 --- a/src/resources/extensions/sf/gitignore.ts +++ b/src/resources/extensions/sf/gitignore.ts @@ -9,6 +9,8 @@ import { execFileSync } from "node:child_process"; import { existsSync, lstatSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; +import { yamlSafeString } from "./commands-prefs-wizard.js"; +import { detectProjectSignals } from "./detection.js"; import { GIT_NO_PROMPT_ENV } from "./git-constants.js"; import { nativeLsFiles, nativeRmCached } from "./native-git-bridge.js"; import { sfRoot } from "./paths.js"; @@ -271,9 +273,26 @@ export function ensurePreferences(basePath: string): boolean { return false; } + // Auto-detect project type and seed verification_commands. Without this, + // projects fall back to the user-level defaults — which point at sf's own + // dev scripts (npm run typecheck:extensions, test:sf-light) and produce + // false negatives on every non-Node project. Detection failure is non-fatal. + let verifySection = ""; + try { + const signals = detectProjectSignals(basePath); + if (signals.verificationCommands.length > 0) { + const lines = signals.verificationCommands.map( + (c) => ` - ${yamlSafeString(c)}`, + ); + verifySection = `verification_commands:\n${lines.join("\n")}\n`; + } + } catch { + // fall through to bare template + } + const template = `--- version: 1 -always_use_skills: [] +${verifySection}always_use_skills: [] prefer_skills: [] avoid_skills: [] skill_rules: []