diff --git a/src/resources/extensions/sf/auto-model-selection.ts b/src/resources/extensions/sf/auto-model-selection.ts index 1f331d552..84e9bb228 100644 --- a/src/resources/extensions/sf/auto-model-selection.ts +++ b/src/resources/extensions/sf/auto-model-selection.ts @@ -81,6 +81,10 @@ export class ModelPolicyDispatchBlockedError extends Error { } } +/** + * Result of model selection and application for a unit dispatch. + * Includes routing metadata and the applied model object. + */ export interface ModelSelectionResult { /** Routing metadata for metrics recording */ routing: { tier: string; modelDowngraded: boolean } | null; @@ -241,6 +245,10 @@ function matchesBareModelId(candidateId: string, requestedId: string): boolean { return bareModelIdAliases(requestedId).has(candidateId.toLowerCase()); } +/** + * Resolve preferred model configuration for a unit type from preferences or dynamic routing. + * Returns undefined if no explicit config and auto-mode is disabled or flat-rate provider detected. + */ export function resolvePreferredModelConfig( unitType: string, autoModeStartModel: { diff --git a/src/resources/extensions/sf/auto/loop.ts b/src/resources/extensions/sf/auto/loop.ts index 46f7338b5..b5d545b6c 100644 --- a/src/resources/extensions/sf/auto/loop.ts +++ b/src/resources/extensions/sf/auto/loop.ts @@ -163,7 +163,7 @@ function saveCustomVerifyRetryCounts(s: AutoSession): void { return; } mkdirSync(customVerifyRetryStateDir(s), { recursive: true }); - writeFileSync( + atomicWriteSync( filePath, JSON.stringify({ counts: Object.fromEntries(retryCounts), diff --git a/src/resources/extensions/sf/bootstrap/write-gate.ts b/src/resources/extensions/sf/bootstrap/write-gate.ts index 980d9ec0a..211a9fdd1 100644 --- a/src/resources/extensions/sf/bootstrap/write-gate.ts +++ b/src/resources/extensions/sf/bootstrap/write-gate.ts @@ -135,6 +135,9 @@ function writeGateSnapshotPath(basePath: string = process.cwd()): string { return join(basePath, ".sf", "runtime", "write-gate-state.json"); } +/** + * Get the current in-memory write gate snapshot. + */ function currentWriteGateSnapshot(): WriteGateSnapshot { return { verifiedDepthMilestones: [...verifiedDepthMilestones].sort(), @@ -160,6 +163,9 @@ function persistWriteGateSnapshot(basePath: string = process.cwd()): void { } } +/** + * Delete the persisted write gate snapshot file if it exists. + */ function clearPersistedWriteGateSnapshot( basePath: string = process.cwd(), ): void { diff --git a/src/resources/extensions/sf/commands-harness.ts b/src/resources/extensions/sf/commands-harness.ts index 92f2b6d94..e66867bef 100644 --- a/src/resources/extensions/sf/commands-harness.ts +++ b/src/resources/extensions/sf/commands-harness.ts @@ -12,6 +12,9 @@ import { projectRoot } from "./commands/context.js"; import { profileRepository } from "./repo-profiler.js"; import { recordRepoProfile } from "./sf-db.js"; +/** + * Format a repo profile summary for user notification. + */ function formatProfileSummary( profile: ReturnType, ): string { diff --git a/src/resources/extensions/sf/commands-logs.ts b/src/resources/extensions/sf/commands-logs.ts index 8e2893884..1736ed07f 100644 --- a/src/resources/extensions/sf/commands-logs.ts +++ b/src/resources/extensions/sf/commands-logs.ts @@ -24,6 +24,9 @@ import { sfRoot } from "./paths.js"; // ─── Types ────────────────────────────────────────────────────────────────── +/** + * Activity log entry metadata parsed from filename. + */ interface LogEntry { seq: number; filename: string; @@ -33,6 +36,9 @@ interface LogEntry { mtime: Date; } +/** + * Debug log entry metadata. + */ interface DebugLogEntry { filename: string; size: number; @@ -41,14 +47,23 @@ interface DebugLogEntry { // ─── Helpers ──────────────────────────────────────────────────────────────── +/** + * Get the activity logs directory path. + */ function activityDir(basePath: string): string { return join(sfRoot(basePath), "activity"); } +/** + * Get the debug logs directory path. + */ function debugDir(basePath: string): string { return join(sfRoot(basePath), "debug"); } +/** + * List all activity logs with parsed metadata from filenames. + */ function listActivityLogs(basePath: string): LogEntry[] { const dir = activityDir(basePath); if (!existsSync(dir)) return []; diff --git a/src/resources/extensions/sf/compaction-snapshot.ts b/src/resources/extensions/sf/compaction-snapshot.ts index 0b2173cc7..2fbd005e2 100644 --- a/src/resources/extensions/sf/compaction-snapshot.ts +++ b/src/resources/extensions/sf/compaction-snapshot.ts @@ -11,6 +11,9 @@ import { getActiveMemoriesRanked, type Memory } from "./memory-store.js"; export const DEFAULT_SNAPSHOT_BYTES = 2048; export const SNAPSHOT_FILENAME = "last-snapshot.md"; +/** + * Input sources for building a compaction snapshot: memories, exec history, and active context. + */ export interface SnapshotSources { memories: Memory[]; execHistory: ExecHistoryEntry[]; @@ -19,6 +22,9 @@ export interface SnapshotSources { activeContext?: string | null; } +/** + * Options for building a snapshot (byte/count caps). + */ export interface BuildSnapshotOptions { /** Hard cap in bytes (UTF-8). Default 2048. */ maxBytes?: number; diff --git a/src/resources/extensions/sf/dispatch-guard.ts b/src/resources/extensions/sf/dispatch-guard.ts index 87cd6040a..8c40452d4 100644 --- a/src/resources/extensions/sf/dispatch-guard.ts +++ b/src/resources/extensions/sf/dispatch-guard.ts @@ -16,6 +16,11 @@ const SLICE_DISPATCH_TYPES = new Set([ "complete-slice", ]); +/** + * Check if a slice/task dispatch should be blocked by incomplete prior slices. + * Returns error message if blocked, null if dispatch is safe. + * Respects milestone locking (SF_MILESTONE_LOCK) for parallel worker isolation. + */ export function getPriorSliceCompletionBlocker( base: string, _mainBranch: string, diff --git a/src/resources/extensions/sf/doctor-runtime-checks.ts b/src/resources/extensions/sf/doctor-runtime-checks.ts index cf9844ca9..40ada0ee0 100644 --- a/src/resources/extensions/sf/doctor-runtime-checks.ts +++ b/src/resources/extensions/sf/doctor-runtime-checks.ts @@ -391,7 +391,6 @@ export async function checkRuntimeHealth( // templates SF currently ships. Non-fatal — automatic sync runs in // ensureAgenticDocsScaffold; this check is the user-visible signal. try { - const { detectScaffoldDrift } = await import("./scaffold-drift.js"); const report = detectScaffoldDrift(basePath); const c = report.countsByBucket; // Only emit a finding when something is actionable. `customized` and diff --git a/src/resources/extensions/sf/doctor.ts b/src/resources/extensions/sf/doctor.ts index bac03479a..a92efeba8 100644 --- a/src/resources/extensions/sf/doctor.ts +++ b/src/resources/extensions/sf/doctor.ts @@ -385,6 +385,20 @@ function detectCircularDependencies(slices: RoadmapSliceEntry[]): string[][] { } // ── Helper: doctor run history ────────────────────────────────────────────── +/** + * Single doctor run history entry for tracking past results and fixes. + * + * @property ts — ISO timestamp of the run + * @property ok — true if no errors were found + * @property errors — count of error-severity issues + * @property warnings — count of warning-severity issues + * @property fixes — count of fixes applied + * @property codes — unique issue codes found + * @property issues — detailed issue messages (Phase 2+) + * @property fixDescriptions — descriptions of fixes applied (Phase 2+) + * @property scope — milestone/slice scope the run was scoped to (e.g., "M001/S02") + * @property summary — human-readable one-line summary + */ export interface DoctorHistoryEntry { ts: string; ok: boolean; diff --git a/src/resources/extensions/sf/execution-instruction-guard.ts b/src/resources/extensions/sf/execution-instruction-guard.ts index b52c031c1..e1a60e2d0 100644 --- a/src/resources/extensions/sf/execution-instruction-guard.ts +++ b/src/resources/extensions/sf/execution-instruction-guard.ts @@ -99,6 +99,9 @@ export function getExecuteTaskInstructionConflict( }; } +/** + * Mark a task skipped due to instruction conflict and log the event. + */ export async function skipExecuteTaskForInstructionConflict( basePath: string, mid: string, diff --git a/src/resources/extensions/sf/gate-registry.ts b/src/resources/extensions/sf/gate-registry.ts index 380c7fab8..48e1666ba 100644 --- a/src/resources/extensions/sf/gate-registry.ts +++ b/src/resources/extensions/sf/gate-registry.ts @@ -179,6 +179,7 @@ export const GATE_REGISTRY = { }, } as const satisfies Record; +/** Type of the GATE_REGISTRY constant. */ export type GateRegistry = typeof GATE_REGISTRY; /** Stable ordered lists per owner turn — iteration order matches declaration. */ @@ -186,7 +187,9 @@ const ORDERED_GATES: readonly GateDefinition[] = Object.values( GATE_REGISTRY, ) as readonly GateDefinition[]; -/** Return every gate owned by a turn, in stable declaration order. */ +/** + * Return every gate owned by a turn, in stable declaration order. + */ export function getGatesForTurn(turn: OwnerTurn): GateDefinition[] { return ORDERED_GATES.filter((g) => g.ownerTurn === turn); } diff --git a/src/resources/extensions/sf/git-service.ts b/src/resources/extensions/sf/git-service.ts index b90fd5182..454942529 100644 --- a/src/resources/extensions/sf/git-service.ts +++ b/src/resources/extensions/sf/git-service.ts @@ -109,15 +109,19 @@ export interface GitPreferences { milestone_resquash?: boolean; } +/** Regex for valid git branch names (alphanumeric, hyphens, underscores, slashes). */ export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-/.]+$/; +/** Options for git commit operations. */ export interface CommitOptions { message: string; allowEmpty?: boolean; } +/** Mode for turn-level git action (commit, snapshot, or status-only check). */ export type TurnGitActionMode = "commit" | "snapshot" | "status-only"; +/** Result from a turn-level git action execution. */ export interface TurnGitActionResult { action: TurnGitActionMode; status: "ok" | "failed"; diff --git a/src/resources/extensions/sf/health-widget-core.ts b/src/resources/extensions/sf/health-widget-core.ts index 0bf71c918..f34c11ff1 100644 --- a/src/resources/extensions/sf/health-widget-core.ts +++ b/src/resources/extensions/sf/health-widget-core.ts @@ -41,8 +41,8 @@ function formatCost(n: number): string { } /** - * Format a Unix epoch (seconds) as a human-readable relative time string. - * Returns "just now" for <1m, "Xm ago" for <1h, "Xh ago" for <24h, "Xd ago" otherwise. + * Format Unix epoch (seconds) as human-readable relative time. + * Returns "just now", "Xm ago", "Xh ago", or "Xd ago". */ export function formatRelativeTime(epochSeconds: number): string { const diffSeconds = Math.floor(Date.now() / 1000) - epochSeconds; diff --git a/src/resources/extensions/sf/notification-widget.ts b/src/resources/extensions/sf/notification-widget.ts index 6d6e6165c..07b4d8e7a 100644 --- a/src/resources/extensions/sf/notification-widget.ts +++ b/src/resources/extensions/sf/notification-widget.ts @@ -13,6 +13,10 @@ import { formattedShortcutPair } from "./shortcut-defs.js"; // ─── Pure rendering ──���────────────────────────���───────────────────────── +/** + * Build the notification widget UI lines. Returns empty array if no unread + * notifications; otherwise shows unread count and keyboard shortcut hint. + */ export function buildNotificationWidgetLines(): string[] { const unread = getUnreadCount(); if (unread === 0) return []; diff --git a/src/resources/extensions/sf/notifications.ts b/src/resources/extensions/sf/notifications.ts index 2399a743b..a74b3bb4a 100644 --- a/src/resources/extensions/sf/notifications.ts +++ b/src/resources/extensions/sf/notifications.ts @@ -10,7 +10,15 @@ import { import { loadEffectiveSFPreferences } from "./preferences.js"; import type { NotificationPreferences } from "./types.js"; +/** + * Notification urgency level, controls icon, sound, and visual prominence. + */ export type NotifyLevel = "info" | "success" | "warning" | "error"; + +/** + * Classification of notification event type, used to gate desktop notification + * delivery via user preferences. + */ export type NotificationKind = | "complete" | "error" @@ -18,6 +26,10 @@ export type NotificationKind = | "milestone" | "attention"; +/** + * Platform-specific command for delivering a desktop notification. + * Specifies the executable and its arguments. + */ interface NotificationCommand { file: string; args: string[]; diff --git a/src/resources/extensions/sf/preferences-models.ts b/src/resources/extensions/sf/preferences-models.ts index e6a166201..00d005b1a 100644 --- a/src/resources/extensions/sf/preferences-models.ts +++ b/src/resources/extensions/sf/preferences-models.ts @@ -328,13 +328,30 @@ export function resolveModelWithFallbacksForUnit( case "plan-milestone": case "roadmap-meeting": case "plan-slice": + case "refine-slice": case "replan-slice": phaseConfig = m.planning; break; case "discuss-milestone": case "discuss-slice": + // Deep-mode project-level discussion units route to the same model + // bucket as milestone-level discussion (interactive interview style). + case "discuss-project": + case "discuss-requirements": + // Workflow preferences and research-decision are tiny ask_user_questions + // style units; they share the discuss bucket because they are + // conversational rather than research/execution. Falling back to planning + // when no `discuss` bucket is set keeps parity with the milestone units. + case "workflow-preferences": + case "research-decision": phaseConfig = m.discuss ?? m.planning; break; + // Deep-mode project research orchestrator. Reads PROJECT.md / REQUIREMENTS.md + // and fans out research subagents. Routes to the research bucket so it + // gets the research-tier model when one is configured. + case "research-project": + phaseConfig = m.research; + break; case "execute-task": case "reactive-execute": phaseConfig = m.execution; diff --git a/src/resources/extensions/sf/preferences-skills.ts b/src/resources/extensions/sf/preferences-skills.ts index f0acd4530..b766f3a5c 100644 --- a/src/resources/extensions/sf/preferences-skills.ts +++ b/src/resources/extensions/sf/preferences-skills.ts @@ -129,8 +129,14 @@ export function resolveSkillReference( } /** - * Resolve all skill references in a preferences object. - * Caches resolution per reference string to avoid redundant filesystem scans. + * Resolve all skill references across a preferences object. + * + * Walks all_use_skills, prefer_skills, avoid_skills, and skill rules; + * caches resolution per reference to avoid redundant directory scans. + * + * @param preferences - Preferences object with skill references. + * @param cwd - Current working directory for project-relative search. + * @returns Map of resolutions and list of unresolved references. */ export function resolveAllSkillReferences( preferences: SFPreferences, diff --git a/src/resources/extensions/sf/structured-data-formatter.ts b/src/resources/extensions/sf/structured-data-formatter.ts index 6d5a16c2b..e38d4c1a8 100644 --- a/src/resources/extensions/sf/structured-data-formatter.ts +++ b/src/resources/extensions/sf/structured-data-formatter.ts @@ -52,7 +52,9 @@ interface TaskPlanInput { // Decisions // --------------------------------------------------------------------------- -/** Compact format for a single decision record (pipe-separated, no padding). */ +/** + * Format a single decision as pipe-separated compact notation. + */ export function formatDecisionCompact(decision: DecisionInput): string { return [ decision.id, @@ -66,7 +68,9 @@ export function formatDecisionCompact(decision: DecisionInput): string { ].join(" | "); } -/** Format multiple decisions in compact notation with a Fields header. */ +/** + * Format multiple decisions in compact notation with Fields header. + */ export function formatDecisionsCompact(decisions: DecisionInput[]): string { if (decisions.length === 0) { return "# Decisions (compact)\n(none)"; diff --git a/src/resources/extensions/sf/visualizer-views.ts b/src/resources/extensions/sf/visualizer-views.ts index 4dfbf4bb7..fbd285b50 100644 --- a/src/resources/extensions/sf/visualizer-views.ts +++ b/src/resources/extensions/sf/visualizer-views.ts @@ -1234,6 +1234,9 @@ export function renderCapturesView( // ─── Health View ───────────────────────────────────────────────────────────── +/** + * Render system health: budget, pressure, routing, providers, progress score, doctor history. + */ export function renderHealthView( data: VisualizerData, th: Theme, diff --git a/src/resources/extensions/sf/worktree-telemetry.ts b/src/resources/extensions/sf/worktree-telemetry.ts index f9dd90da7..540fe9c15 100644 --- a/src/resources/extensions/sf/worktree-telemetry.ts +++ b/src/resources/extensions/sf/worktree-telemetry.ts @@ -46,7 +46,10 @@ function baseEntry( // Closed sets so typos at call sites are rejected at compile time and can't // silently fragment the telemetry buckets produced by summarizeWorktreeTelemetry. +/** Reason a worktree was created or entered (create-milestone | enter-milestone). */ export type WorktreeCreatedReason = "create-milestone" | "enter-milestone"; + +/** Reason auto-mode exited (pause, stop, blocked, merge-conflict, merge-failed, etc.). */ export type AutoExitReason = | "pause" | "stop"