chore(sf): minor self-feedback + workflow-template tweaks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-02 02:21:13 +02:00
parent 51d0a06bbc
commit 9e8361da23
8 changed files with 18 additions and 16 deletions

View file

@ -723,6 +723,9 @@ async function runHeadlessOnce(
let exitCode = 0;
let milestoneReady = false; // tracks "Milestone X ready." for auto-chaining
let timedOut = false; // true only when the overall timeout timer fires
// Rolling buffer for milestone-ready detection across split streaming deltas.
// Capped at 200 chars — long enough to bridge any realistic delta boundary.
let milestoneDetectionBuffer = "";
let providerAutoResumePending = false;
const recentEvents: TrackedEvent[] = [];
const interactiveToolCallIds = new Set<string>();

View file

@ -161,7 +161,7 @@ export function atomicWriteSyncWithOps(
const tmpPath = ops.createTempPath?.(filePath) ?? defaultTempPath(filePath);
ops.writeFile(tmpPath, content, encoding);
let lastError: unknown = null;
const errors: unknown[] = [];
let attempts = 0;
for (attempts = 1; attempts <= MAX_RENAME_ATTEMPTS; attempts++) {
@ -169,7 +169,7 @@ export function atomicWriteSyncWithOps(
ops.rename(tmpPath, filePath);
return;
} catch (error) {
lastError = error;
errors.push(error);
if (!isTransientLockError(error) || attempts === MAX_RENAME_ATTEMPTS) {
break;
}
@ -178,7 +178,7 @@ export function atomicWriteSyncWithOps(
}
cleanupTempFileSync(tmpPath, ops);
throw buildAtomicWriteError(filePath, attempts, lastError);
throw buildAtomicWriteError(filePath, attempts, errors);
}
const DEFAULT_ASYNC_OPS: AtomicWriteAsyncOps = {

View file

@ -345,16 +345,8 @@ export async function checkRuntimeHealth(
);
// Check for critical runtime patterns that must be present.
// NOTE: SF_RUNTIME_PATTERNS in gitignore.ts is the canonical source of truth.
// This is a minimal subset for the doctor check.
const criticalPatterns = [
".sf/activity/",
".sf/runtime/",
".sf/auto.lock",
".sf/sf.db*",
".sf/completed-units*.json",
".sf/event-log.jsonl",
];
// Use the canonical SF_RUNTIME_PATTERNS list for consistency.
const criticalPatterns = Array.from(SF_RUNTIME_PATTERNS);
// If blanket .sf/ or .sf is present, all patterns are covered
const hasBlanketIgnore =

View file

@ -637,6 +637,7 @@ export async function runSFDoctor(
const requirementsContent = await loadFile(requirementsPath);
issues.push(...auditRequirements(requirementsContent));
const t0state = Date.now();
const state = await deriveState(basePath);
// Provider / auth health checks — only relevant when there is active work to dispatch.

View file

@ -11,6 +11,7 @@ export const MAX_JSONL_BYTES = 10 * 1024 * 1024; // 10 MB
/**
* Parse a raw JSONL string into an array of parsed objects.
*
* If the input exceeds MAX_JSONL_BYTES, only the tail is parsed (most recent entries).
*/
export function parseJSONL(raw: string): unknown[] {

View file

@ -236,7 +236,7 @@ function escapeCell(text: string): string {
function readActiveUnit(basePath: string): SelfFeedbackOccurredIn | undefined {
try {
const lockPath = join(basePath, ".sf", "auto.lock");
const lockPath = join(sfRuntimeRoot(basePath), "auto.lock");
if (!existsSync(lockPath)) return undefined;
const lock = JSON.parse(readFileSync(lockPath, "utf-8"));
const id: string | undefined = lock?.unitId;

View file

@ -133,6 +133,9 @@ export function loadRegistry(): TemplateRegistry {
*
* Consumer: `/sf` command completion catalogs and `/sf start` usage rendering.
*/
/**
* Get registry-backed command definitions for /sf start completion and help.
*/
export function workflowTemplateCommandDefinitions(): WorkflowTemplateCommandDefinition[] {
const registry = loadRegistry();
return Object.entries(registry.templates).map(([id, entry]) => ({

View file

@ -620,11 +620,13 @@ export class WorktreeResolver {
);
// Clean up stale merge state left by failed squash-merge (#1389)
// Use resolveGitDir to handle worktrees where .git is a file (gitdir pointer)
// Use resolveGitDir to handle worktrees where .git is a file (gitdir pointer).
// Use resolve() rather than join() to normalise any backslash separators on
// Windows and avoid mixed-separator paths in downstream comparisons.
try {
const gitDir = resolveGitDir(originalBase || this.s.basePath);
for (const f of ["SQUASH_MSG", "MERGE_HEAD", "MERGE_MSG"]) {
const p = join(gitDir, f);
const p = resolve(gitDir, f);
if (existsSync(p)) unlinkSync(p);
}
} catch {