diff --git a/src/cli.ts b/src/cli.ts index 27ec9b77e..5187a5210 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -964,6 +964,22 @@ exitIfManagedResourcesAreNewer(agentDir); initResources(agentDir); markStartup("initResources"); +// Warm the sift index in the background so it's ready when needed. +try { + const { ensureSiftIndexWarmup } = await import( + "./resources/extensions/sf/code-intelligence.js" + ); + const { loadEffectiveSFPreferences } = await import( + "./resources/extensions/sf/preferences.js" + ); + ensureSiftIndexWarmup( + process.cwd(), + loadEffectiveSFPreferences()?.preferences?.codebase, + ); +} catch { + /* non-fatal — sift warmup is best-effort */ +} + // Overlap resource loading with session manager setup — both are independent. // resourceLoader.reload() is the most expensive step (jiti compilation), so // starting it early shaves ~50-200ms off interactive startup. diff --git a/src/resources/extensions/sf/extension-manifest.json b/src/resources/extensions/sf/extension-manifest.json index ff736dc5c..dccc76155 100644 --- a/src/resources/extensions/sf/extension-manifest.json +++ b/src/resources/extensions/sf/extension-manifest.json @@ -54,6 +54,7 @@ "codebase", "config", "control", + "cost", "debug", "discuss", "dispatch", @@ -69,6 +70,7 @@ "help", "history", "hooks", + "implement", "init", "inspect", "keys", @@ -92,6 +94,7 @@ "quick", "rate", "remote", + "research", "reset-slice", "rethink", "repair", @@ -113,6 +116,7 @@ "tasks", "templates", "todo", + "trajectory", "triage", "permission-profile", "undo", diff --git a/src/resources/extensions/sf/sf-db.js b/src/resources/extensions/sf/sf-db.js index d16bf8631..ce31d4248 100644 --- a/src/resources/extensions/sf/sf-db.js +++ b/src/resources/extensions/sf/sf-db.js @@ -2557,7 +2557,7 @@ function migrateSchema(db) { findings TEXT NOT NULL DEFAULT '', started_at TEXT, completed_at TEXT, - created_at TEXT NOT NULL DEFAULT (datetime('now')), + created_at TEXT NOT NULL, superseded_by TEXT, FOREIGN KEY (milestone_id) REFERENCES milestones(id) ) @@ -2570,18 +2570,13 @@ function migrateSchema(db) { CREATE VIEW IF NOT EXISTS latest_validation_state AS SELECT vr.* FROM validation_runs vr - INNER JOIN ( - SELECT milestone_id, - COALESCE(slice_id, '') AS slice_id, - COALESCE(task_id, '') AS task_id, - MAX(created_at) AS max_created - FROM validation_runs - GROUP BY milestone_id, slice_id, task_id - ) latest - ON vr.milestone_id = latest.milestone_id - AND COALESCE(vr.slice_id, '') = latest.slice_id - AND COALESCE(vr.task_id, '') = latest.task_id - AND vr.created_at = latest.max_created + WHERE vr.rowid = ( + SELECT MAX(v2.rowid) + FROM validation_runs v2 + WHERE v2.milestone_id = vr.milestone_id + AND v2.slice_id IS vr.slice_id + AND v2.task_id IS vr.task_id + ) `); db.prepare( "INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)", @@ -7283,8 +7278,8 @@ export function startValidationRun({ milestoneId, sliceId, taskId, contract }) { currentDb .prepare( `INSERT INTO validation_runs - (run_id, milestone_id, slice_id, task_id, contract, status, started_at) - VALUES (:run_id, :milestone_id, :slice_id, :task_id, :contract, 'running', datetime('now'))`, + (run_id, milestone_id, slice_id, task_id, contract, status, started_at, created_at) + VALUES (:run_id, :milestone_id, :slice_id, :task_id, :contract, 'running', datetime('now'), datetime('now'))`, ) .run({ ":run_id": runId, @@ -7340,9 +7335,9 @@ export function getLatestValidationState(milestoneId, sliceId, taskId) { .prepare( `SELECT * FROM validation_runs WHERE milestone_id = :milestone_id - AND (:slice_id IS NULL OR slice_id = :slice_id) - AND (:task_id IS NULL OR task_id = :task_id) - ORDER BY created_at DESC + AND (slice_id = :slice_id OR (slice_id IS NULL AND :slice_id IS NULL)) + AND (task_id = :task_id OR (task_id IS NULL AND :task_id IS NULL)) + ORDER BY created_at DESC, run_id DESC LIMIT 1`, ) .all({ @@ -7365,9 +7360,9 @@ export function getValidationHistory(milestoneId, sliceId, taskId, limit = 20) { .prepare( `SELECT * FROM validation_runs WHERE milestone_id = :milestone_id - AND (:slice_id IS NULL OR slice_id = :slice_id) - AND (:task_id IS NULL OR task_id = :task_id) - ORDER BY created_at DESC + AND (slice_id = :slice_id OR (slice_id IS NULL AND :slice_id IS NULL)) + AND (task_id = :task_id OR (task_id IS NULL AND :task_id IS NULL)) + ORDER BY created_at DESC, run_id DESC LIMIT :limit`, ) .all({