feat(KB-607): add filesystem validation for tasks in scheduler

- Add filesystem-level validation to ensure task directories exist before scheduling
- Implement validateTaskFilesystem() to check PROMPT.md and task.json integrity
- Add comprehensive test coverage with 217 new test lines for validation scenarios
- Create changeset for patch release documenting the fix
- Prevent scheduler errors by validating task filesystem state upfront
This commit is contained in:
gsxdsm
2026-03-31 15:09:28 -07:00
parent 65b4620656
commit b6ceeb8ae3
3 changed files with 268 additions and 1 deletions

View File

@@ -1,4 +1,7 @@
import { resolveDependencyOrder, type TaskStore, type Task } from "@fusion/core";
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import type { AgentSemaphore } from "./concurrency.js";
import { schedulerLog } from "./logger.js";
import type { PrMonitor } from "./pr-monitor.js";
@@ -162,6 +165,39 @@ export class Scheduler {
});
}
/**
* Validate that a task's filesystem state is intact.
* Checks that the task directory exists and PROMPT.md is present and non-empty.
*
* @param id - The task ID to validate
* @returns Object with `valid: true` if checks pass, or `valid: false` with a `reason` string if they fail
*/
private async validateTaskFilesystem(id: string): Promise<{ valid: boolean; reason?: string }> {
const taskDir = join(this.store.getRootDir(), ".kb", "tasks", id);
// Check if task directory exists
if (!existsSync(taskDir)) {
return { valid: false, reason: "missing directory" };
}
// Check if PROMPT.md exists and has non-empty content
const promptPath = join(taskDir, "PROMPT.md");
if (!existsSync(promptPath)) {
return { valid: false, reason: "missing or empty PROMPT.md" };
}
try {
const content = await readFile(promptPath, "utf-8");
if (!content || content.trim().length === 0) {
return { valid: false, reason: "missing or empty PROMPT.md" };
}
} catch {
return { valid: false, reason: "missing or empty PROMPT.md" };
}
return { valid: true };
}
start(): void {
if (this.running) return;
this.running = true;
@@ -389,6 +425,15 @@ export class Scheduler {
continue;
}
// Validate filesystem state before starting (only for tasks with satisfied deps)
const validation = await this.validateTaskFilesystem(task.id);
if (!validation.valid) {
schedulerLog.warn(`Task ${task.id} filesystem validation failed: ${validation.reason}`);
await this.store.moveTask(task.id, "triage");
await this.store.logEntry(task.id, "Task moved to triage — filesystem validation failed", validation.reason);
continue;
}
// Check file scope overlap when enabled
if (settings.groupOverlappingFiles) {
const taskScope = await this.store.parseFileScopeFromPrompt(task.id);