feat: add SelfHealingManager for unattended multi-day operation
Adds four self-healing subsystems to enable the engine to recover from common failure modes without human intervention: - Auto-unpause: clears rate-limit-triggered globalPause with escalating backoff (5 min → 60 min cap), resets on sustained recovery - Stuck kill budget: caps task stuck-kill retries (default 3) to prevent infinite stuck→todo→stuck loops - Periodic maintenance (every 15 min): git worktree prune, orphan cleanup, SQLite WAL checkpoint - Worktree cap enforcement: removes oldest idle worktrees when count exceeds 2× maxWorktrees New settings: autoUnpauseEnabled, autoUnpauseBaseDelayMs, autoUnpauseMaxDelayMs, maxStuckKills, maintenanceIntervalMs. New task field: stuckKillCount (schema v8 migration). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -86,7 +86,7 @@ describe("Database", () => {
|
||||
});
|
||||
|
||||
it("seeds schema version", () => {
|
||||
expect(db.getSchemaVersion()).toBe(7);
|
||||
expect(db.getSchemaVersion()).toBe(8);
|
||||
});
|
||||
|
||||
it("seeds lastModified", () => {
|
||||
@@ -109,7 +109,7 @@ describe("Database", () => {
|
||||
|
||||
it("is idempotent - calling init() twice does not fail", () => {
|
||||
expect(() => db.init()).not.toThrow();
|
||||
expect(db.getSchemaVersion()).toBe(7);
|
||||
expect(db.getSchemaVersion()).toBe(8);
|
||||
});
|
||||
|
||||
it("does not overwrite existing config on re-init", () => {
|
||||
@@ -188,6 +188,18 @@ describe("Database", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("walCheckpoint", () => {
|
||||
it("runs WAL checkpoint and returns stats", () => {
|
||||
const result = db.walCheckpoint();
|
||||
expect(result).toHaveProperty("busy");
|
||||
expect(result).toHaveProperty("log");
|
||||
expect(result).toHaveProperty("checkpointed");
|
||||
expect(typeof result.busy).toBe("number");
|
||||
expect(typeof result.log).toBe("number");
|
||||
expect(typeof result.checkpointed).toBe("number");
|
||||
});
|
||||
});
|
||||
|
||||
describe("transactions", () => {
|
||||
it("commits on success", () => {
|
||||
db.transaction(() => {
|
||||
@@ -704,7 +716,7 @@ describe("schema migrations", () => {
|
||||
db.init();
|
||||
|
||||
// Verify version bumped to 5 (includes v1→v2, v2→v3, v3→v4, and v4→v5 migrations)
|
||||
expect(db.getSchemaVersion()).toBe(7);
|
||||
expect(db.getSchemaVersion()).toBe(8);
|
||||
|
||||
// Verify new columns exist and existing data is intact
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
@@ -729,11 +741,11 @@ describe("schema migrations", () => {
|
||||
const db = new Database(kbDir);
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(7);
|
||||
expect(db.getSchemaVersion()).toBe(8);
|
||||
|
||||
// Re-init should not fail
|
||||
db.init();
|
||||
expect(db.getSchemaVersion()).toBe(7);
|
||||
expect(db.getSchemaVersion()).toBe(8);
|
||||
|
||||
db.close();
|
||||
});
|
||||
@@ -828,7 +840,7 @@ describe("schema migrations", () => {
|
||||
db.init();
|
||||
|
||||
// Verify version bumped to 5
|
||||
expect(db.getSchemaVersion()).toBe(7);
|
||||
expect(db.getSchemaVersion()).toBe(8);
|
||||
|
||||
// Verify new columns exist and existing data is intact
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
@@ -1038,7 +1050,7 @@ describe("createDatabase factory", () => {
|
||||
const db = createDatabase(kbDir);
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(7);
|
||||
expect(db.getSchemaVersion()).toBe(8);
|
||||
expect(db.getLastModified()).toBeGreaterThan(0);
|
||||
|
||||
db.close();
|
||||
|
||||
Reference in New Issue
Block a user