diff --git a/src/shared/migration.test.ts b/src/shared/migration.test.ts index 84e563ae4..c2e6d967b 100644 --- a/src/shared/migration.test.ts +++ b/src/shared/migration.test.ts @@ -118,13 +118,14 @@ describe("migrateHookNames", () => { const hooks = ["anthropic-auto-compact", "comment-checker"] // #when: Migrate hook names - const { migrated, changed } = migrateHookNames(hooks) + const { migrated, changed, removed } = migrateHookNames(hooks) // #then: Legacy hook name should be migrated expect(changed).toBe(true) expect(migrated).toContain("anthropic-context-window-limit-recovery") expect(migrated).toContain("comment-checker") expect(migrated).not.toContain("anthropic-auto-compact") + expect(removed).toEqual([]) }) test("preserves current hook names unchanged", () => { @@ -136,11 +137,12 @@ describe("migrateHookNames", () => { ] // #when: Migrate hook names - const { migrated, changed } = migrateHookNames(hooks) + const { migrated, changed, removed } = migrateHookNames(hooks) // #then: Current names should remain unchanged expect(changed).toBe(false) expect(migrated).toEqual(hooks) + expect(removed).toEqual([]) }) test("handles empty hooks array", () => { @@ -148,11 +150,12 @@ describe("migrateHookNames", () => { const hooks: string[] = [] // #when: Migrate hook names - const { migrated, changed } = migrateHookNames(hooks) + const { migrated, changed, removed } = migrateHookNames(hooks) // #then: Should return empty array with no changes expect(changed).toBe(false) expect(migrated).toEqual([]) + expect(removed).toEqual([]) }) test("migrates multiple legacy hook names", () => { @@ -166,6 +169,51 @@ describe("migrateHookNames", () => { expect(changed).toBe(true) expect(migrated).toEqual(["anthropic-context-window-limit-recovery"]) }) + + test("migrates sisyphus-orchestrator to atlas", () => { + // #given: Config with legacy sisyphus-orchestrator hook + const hooks = ["sisyphus-orchestrator", "comment-checker"] + + // #when: Migrate hook names + const { migrated, changed, removed } = migrateHookNames(hooks) + + // #then: sisyphus-orchestrator should be migrated to atlas + expect(changed).toBe(true) + expect(migrated).toContain("atlas") + expect(migrated).toContain("comment-checker") + expect(migrated).not.toContain("sisyphus-orchestrator") + expect(removed).toEqual([]) + }) + + test("removes obsolete hooks and returns them in removed array", () => { + // #given: Config with removed hooks from v3.0.0 + const hooks = ["preemptive-compaction", "empty-message-sanitizer", "comment-checker"] + + // #when: Migrate hook names + const { migrated, changed, removed } = migrateHookNames(hooks) + + // #then: Removed hooks should be filtered out + expect(changed).toBe(true) + expect(migrated).toEqual(["comment-checker"]) + expect(removed).toContain("preemptive-compaction") + expect(removed).toContain("empty-message-sanitizer") + expect(removed).toHaveLength(2) + }) + + test("handles mixed migration and removal", () => { + // #given: Config with both legacy rename and removed hooks + const hooks = ["anthropic-auto-compact", "preemptive-compaction", "sisyphus-orchestrator"] + + // #when: Migrate hook names + const { migrated, changed, removed } = migrateHookNames(hooks) + + // #then: Legacy should be renamed, removed should be filtered + expect(changed).toBe(true) + expect(migrated).toContain("anthropic-context-window-limit-recovery") + expect(migrated).toContain("atlas") + expect(migrated).not.toContain("preemptive-compaction") + expect(removed).toEqual(["preemptive-compaction"]) + }) }) describe("migrateConfigFile", () => { diff --git a/src/shared/migration.ts b/src/shared/migration.ts index 3f9b005c8..62370d475 100644 --- a/src/shared/migration.ts +++ b/src/shared/migration.ts @@ -36,9 +36,15 @@ export const BUILTIN_AGENT_NAMES = new Set([ ]) // Migration map: old hook names → new hook names (for backward compatibility) -export const HOOK_NAME_MAP: Record = { +// null means the hook was removed and should be filtered out from disabled_hooks +export const HOOK_NAME_MAP: Record = { // Legacy names (backward compatibility) "anthropic-auto-compact": "anthropic-context-window-limit-recovery", + "sisyphus-orchestrator": "atlas", + + // Removed hooks (v3.0.0) - will be filtered out and user warned + "preemptive-compaction": null, + "empty-message-sanitizer": null, } /** @@ -77,19 +83,28 @@ export function migrateAgentNames(agents: Record): { migrated: return { migrated, changed } } -export function migrateHookNames(hooks: string[]): { migrated: string[]; changed: boolean } { +export function migrateHookNames(hooks: string[]): { migrated: string[]; changed: boolean; removed: string[] } { const migrated: string[] = [] + const removed: string[] = [] let changed = false for (const hook of hooks) { - const newHook = HOOK_NAME_MAP[hook] ?? hook + const mapping = HOOK_NAME_MAP[hook] + + if (mapping === null) { + removed.push(hook) + changed = true + continue + } + + const newHook = mapping ?? hook if (newHook !== hook) { changed = true } migrated.push(newHook) } - return { migrated, changed } + return { migrated, changed, removed } } export function migrateAgentConfigToCategory(config: Record): { @@ -167,11 +182,14 @@ export function migrateConfigFile(configPath: string, rawConfig: Record 0) { + log(`Removed obsolete hooks from disabled_hooks: ${removed.join(", ")} (these hooks no longer exist in v3.0.0)`) + } } if (needsWrite) {