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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user