chore(sf): autonomous docstring sweep — additional SF extension files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-02 01:52:37 +02:00
parent 038938f2ac
commit c6a7c7772d
20 changed files with 124 additions and 9 deletions

View file

@ -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: {

View file

@ -163,7 +163,7 @@ function saveCustomVerifyRetryCounts(s: AutoSession): void {
return;
}
mkdirSync(customVerifyRetryStateDir(s), { recursive: true });
writeFileSync(
atomicWriteSync(
filePath,
JSON.stringify({
counts: Object.fromEntries(retryCounts),

View file

@ -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 {

View file

@ -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<typeof profileRepository>,
): string {

View file

@ -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 [];

View file

@ -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;

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

@ -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,

View file

@ -179,6 +179,7 @@ export const GATE_REGISTRY = {
},
} as const satisfies Record<GateId, GateDefinition>;
/** 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);
}

View file

@ -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";

View file

@ -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;

View file

@ -13,6 +13,10 @@ import { formattedShortcutPair } from "./shortcut-defs.js";
// ─── Pure rendering ──<E29480><E29480><EFBFBD>────────────────────────<E29480><E29480><EFBFBD>─────────────────────────
/**
* 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 [];

View file

@ -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[];

View file

@ -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;

View file

@ -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,

View file

@ -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)";

View file

@ -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,

View file

@ -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"