refactor: major codebase cleanup - BDD comments, file splitting, bug fixes (#1350)
* style(tests): normalize BDD comments from '// #given' to '// given'
- Replace 4,668 Python-style BDD comments across 107 test files
- Patterns changed: // #given -> // given, // #when -> // when, // #then -> // then
- Also handles no-space variants: //#given -> // given
* fix(rules-injector): prefer output.metadata.filePath over output.title
- Extract file path resolution to dedicated output-path.ts module
- Prefer metadata.filePath which contains actual file path
- Fall back to output.title only when metadata unavailable
- Fixes issue where rules weren't injected when tool output title was a label
* feat(slashcommand): add optional user_message parameter
- Add user_message optional parameter for command arguments
- Model can now call: command='publish' user_message='patch'
- Improves error messages with clearer format guidance
- Helps LLMs understand correct parameter usage
* feat(hooks): restore compaction-context-injector hook
- Restore hook deleted in cbbc7bd0 for session compaction context
- Injects 7 mandatory sections: User Requests, Final Goal, Work Completed,
Remaining Tasks, Active Working Context, MUST NOT Do, Agent Verification State
- Re-register in hooks/index.ts and main plugin entry
* refactor(background-agent): split manager.ts into focused modules
- Extract constants.ts for TTL values and internal types (52 lines)
- Extract state.ts for TaskStateManager class (204 lines)
- Extract spawner.ts for task creation logic (244 lines)
- Extract result-handler.ts for completion handling (265 lines)
- Reduce manager.ts from 1377 to 755 lines (45% reduction)
- Maintain backward compatible exports
* refactor(agents): split prometheus-prompt.ts into subdirectory
- Move 1196-line prometheus-prompt.ts to prometheus/ subdirectory
- Organize prompt sections into separate files for maintainability
- Update agents/index.ts exports
* refactor(delegate-task): split tools.ts into focused modules
- Extract categories.ts for category definitions and routing
- Extract executor.ts for task execution logic
- Extract helpers.ts for utility functions
- Extract prompt-builder.ts for prompt construction
- Reduce tools.ts complexity with cleaner separation of concerns
* refactor(builtin-skills): split skills.ts into individual skill files
- Move each skill to dedicated file in skills/ subdirectory
- Create barrel export for backward compatibility
- Improve maintainability with focused skill modules
* chore: update import paths and lockfile
- Update prometheus import path after refactor
- Update bun.lock
* fix(tests): complete BDD comment normalization
- Fix remaining #when/#then patterns missed by initial sed
- Affected: state.test.ts, events.test.ts
---------
Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
This commit is contained in:
+130
-130
@@ -13,17 +13,17 @@ import {
|
||||
|
||||
describe("migrateAgentNames", () => {
|
||||
test("migrates legacy OmO names to lowercase", () => {
|
||||
// #given: Config with legacy OmO agent names
|
||||
// given: Config with legacy OmO agent names
|
||||
const agents = {
|
||||
omo: { model: "anthropic/claude-opus-4-5" },
|
||||
OmO: { temperature: 0.5 },
|
||||
"OmO-Plan": { prompt: "custom prompt" },
|
||||
}
|
||||
|
||||
// #when: Migrate agent names
|
||||
// when: Migrate agent names
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
|
||||
// #then: Legacy names should be migrated to lowercase
|
||||
// then: Legacy names should be migrated to lowercase
|
||||
expect(changed).toBe(true)
|
||||
expect(migrated["sisyphus"]).toEqual({ temperature: 0.5 })
|
||||
expect(migrated["prometheus"]).toEqual({ prompt: "custom prompt" })
|
||||
@@ -33,17 +33,17 @@ describe("migrateAgentNames", () => {
|
||||
})
|
||||
|
||||
test("preserves current agent names unchanged", () => {
|
||||
// #given: Config with current agent names
|
||||
// given: Config with current agent names
|
||||
const agents = {
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
librarian: { model: "google/gemini-3-flash" },
|
||||
explore: { model: "opencode/gpt-5-nano" },
|
||||
}
|
||||
|
||||
// #when: Migrate agent names
|
||||
// when: Migrate agent names
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
|
||||
// #then: Current names should remain unchanged
|
||||
// then: Current names should remain unchanged
|
||||
expect(changed).toBe(false)
|
||||
expect(migrated["oracle"]).toEqual({ model: "openai/gpt-5.2" })
|
||||
expect(migrated["librarian"]).toEqual({ model: "google/gemini-3-flash" })
|
||||
@@ -51,69 +51,69 @@ describe("migrateAgentNames", () => {
|
||||
})
|
||||
|
||||
test("handles case-insensitive migration", () => {
|
||||
// #given: Config with mixed case agent names
|
||||
// given: Config with mixed case agent names
|
||||
const agents = {
|
||||
SISYPHUS: { model: "test" },
|
||||
"planner-sisyphus": { prompt: "test" },
|
||||
"Orchestrator-Sisyphus": { model: "openai/gpt-5.2" },
|
||||
}
|
||||
|
||||
// #when: Migrate agent names
|
||||
// when: Migrate agent names
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
|
||||
// #then: Case-insensitive lookup should migrate correctly
|
||||
// then: Case-insensitive lookup should migrate correctly
|
||||
expect(migrated["sisyphus"]).toEqual({ model: "test" })
|
||||
expect(migrated["prometheus"]).toEqual({ prompt: "test" })
|
||||
expect(migrated["atlas"]).toEqual({ model: "openai/gpt-5.2" })
|
||||
})
|
||||
|
||||
test("passes through unknown agent names unchanged", () => {
|
||||
// #given: Config with unknown agent name
|
||||
// given: Config with unknown agent name
|
||||
const agents = {
|
||||
"custom-agent": { model: "custom/model" },
|
||||
}
|
||||
|
||||
// #when: Migrate agent names
|
||||
// when: Migrate agent names
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
|
||||
// #then: Unknown names should pass through
|
||||
// then: Unknown names should pass through
|
||||
expect(changed).toBe(false)
|
||||
expect(migrated["custom-agent"]).toEqual({ model: "custom/model" })
|
||||
})
|
||||
|
||||
test("migrates orchestrator-sisyphus to atlas", () => {
|
||||
// #given: Config with legacy orchestrator-sisyphus agent name
|
||||
// given: Config with legacy orchestrator-sisyphus agent name
|
||||
const agents = {
|
||||
"orchestrator-sisyphus": { model: "anthropic/claude-opus-4-5" },
|
||||
}
|
||||
|
||||
// #when: Migrate agent names
|
||||
// when: Migrate agent names
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
|
||||
// #then: orchestrator-sisyphus should be migrated to atlas
|
||||
// then: orchestrator-sisyphus should be migrated to atlas
|
||||
expect(changed).toBe(true)
|
||||
expect(migrated["atlas"]).toEqual({ model: "anthropic/claude-opus-4-5" })
|
||||
expect(migrated["orchestrator-sisyphus"]).toBeUndefined()
|
||||
})
|
||||
|
||||
test("migrates lowercase atlas to atlas", () => {
|
||||
// #given: Config with lowercase atlas agent name
|
||||
// given: Config with lowercase atlas agent name
|
||||
const agents = {
|
||||
atlas: { model: "anthropic/claude-opus-4-5" },
|
||||
}
|
||||
|
||||
// #when: Migrate agent names
|
||||
// when: Migrate agent names
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
|
||||
// #then: lowercase atlas should remain atlas (no change needed)
|
||||
// then: lowercase atlas should remain atlas (no change needed)
|
||||
expect(changed).toBe(false)
|
||||
expect(migrated["atlas"]).toEqual({ model: "anthropic/claude-opus-4-5" })
|
||||
})
|
||||
|
||||
test("migrates Sisyphus variants to lowercase", () => {
|
||||
// #given agents config with "Sisyphus" key
|
||||
// #when migrateAgentNames called
|
||||
// #then key becomes "sisyphus"
|
||||
// given agents config with "Sisyphus" key
|
||||
// when migrateAgentNames called
|
||||
// then key becomes "sisyphus"
|
||||
const agents = { "Sisyphus": { model: "test" } }
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
expect(changed).toBe(true)
|
||||
@@ -122,9 +122,9 @@ describe("migrateAgentNames", () => {
|
||||
})
|
||||
|
||||
test("migrates omo key to sisyphus", () => {
|
||||
// #given agents config with "omo" key
|
||||
// #when migrateAgentNames called
|
||||
// #then key becomes "sisyphus"
|
||||
// given agents config with "omo" key
|
||||
// when migrateAgentNames called
|
||||
// then key becomes "sisyphus"
|
||||
const agents = { "omo": { model: "test" } }
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
expect(changed).toBe(true)
|
||||
@@ -133,9 +133,9 @@ describe("migrateAgentNames", () => {
|
||||
})
|
||||
|
||||
test("migrates Atlas variants to lowercase", () => {
|
||||
// #given agents config with "Atlas" key
|
||||
// #when migrateAgentNames called
|
||||
// #then key becomes "atlas"
|
||||
// given agents config with "Atlas" key
|
||||
// when migrateAgentNames called
|
||||
// then key becomes "atlas"
|
||||
const agents = { "Atlas": { model: "test" } }
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
expect(changed).toBe(true)
|
||||
@@ -144,9 +144,9 @@ describe("migrateAgentNames", () => {
|
||||
})
|
||||
|
||||
test("migrates Prometheus variants to lowercase", () => {
|
||||
// #given agents config with "Prometheus (Planner)" key
|
||||
// #when migrateAgentNames called
|
||||
// #then key becomes "prometheus"
|
||||
// given agents config with "Prometheus (Planner)" key
|
||||
// when migrateAgentNames called
|
||||
// then key becomes "prometheus"
|
||||
const agents = { "Prometheus (Planner)": { model: "test" } }
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
expect(changed).toBe(true)
|
||||
@@ -155,9 +155,9 @@ describe("migrateAgentNames", () => {
|
||||
})
|
||||
|
||||
test("migrates Metis variants to lowercase", () => {
|
||||
// #given agents config with "Metis (Plan Consultant)" key
|
||||
// #when migrateAgentNames called
|
||||
// #then key becomes "metis"
|
||||
// given agents config with "Metis (Plan Consultant)" key
|
||||
// when migrateAgentNames called
|
||||
// then key becomes "metis"
|
||||
const agents = { "Metis (Plan Consultant)": { model: "test" } }
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
expect(changed).toBe(true)
|
||||
@@ -166,9 +166,9 @@ describe("migrateAgentNames", () => {
|
||||
})
|
||||
|
||||
test("migrates Momus variants to lowercase", () => {
|
||||
// #given agents config with "Momus (Plan Reviewer)" key
|
||||
// #when migrateAgentNames called
|
||||
// #then key becomes "momus"
|
||||
// given agents config with "Momus (Plan Reviewer)" key
|
||||
// when migrateAgentNames called
|
||||
// then key becomes "momus"
|
||||
const agents = { "Momus (Plan Reviewer)": { model: "test" } }
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
expect(changed).toBe(true)
|
||||
@@ -177,9 +177,9 @@ describe("migrateAgentNames", () => {
|
||||
})
|
||||
|
||||
test("migrates Sisyphus-Junior to lowercase", () => {
|
||||
// #given agents config with "Sisyphus-Junior" key
|
||||
// #when migrateAgentNames called
|
||||
// #then key becomes "sisyphus-junior"
|
||||
// given agents config with "Sisyphus-Junior" key
|
||||
// when migrateAgentNames called
|
||||
// then key becomes "sisyphus-junior"
|
||||
const agents = { "Sisyphus-Junior": { model: "test" } }
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
expect(changed).toBe(true)
|
||||
@@ -188,9 +188,9 @@ describe("migrateAgentNames", () => {
|
||||
})
|
||||
|
||||
test("preserves lowercase passthrough", () => {
|
||||
// #given agents config with "oracle" key
|
||||
// #when migrateAgentNames called
|
||||
// #then key remains "oracle" (no change needed)
|
||||
// given agents config with "oracle" key
|
||||
// when migrateAgentNames called
|
||||
// then key remains "oracle" (no change needed)
|
||||
const agents = { "oracle": { model: "test" } }
|
||||
const { migrated, changed } = migrateAgentNames(agents)
|
||||
expect(changed).toBe(false)
|
||||
@@ -200,13 +200,13 @@ describe("migrateAgentNames", () => {
|
||||
|
||||
describe("migrateHookNames", () => {
|
||||
test("migrates anthropic-auto-compact to anthropic-context-window-limit-recovery", () => {
|
||||
// #given: Config with legacy hook name
|
||||
// given: Config with legacy hook name
|
||||
const hooks = ["anthropic-auto-compact", "comment-checker"]
|
||||
|
||||
// #when: Migrate hook names
|
||||
// when: Migrate hook names
|
||||
const { migrated, changed, removed } = migrateHookNames(hooks)
|
||||
|
||||
// #then: Legacy hook name should be migrated
|
||||
// then: Legacy hook name should be migrated
|
||||
expect(changed).toBe(true)
|
||||
expect(migrated).toContain("anthropic-context-window-limit-recovery")
|
||||
expect(migrated).toContain("comment-checker")
|
||||
@@ -215,55 +215,55 @@ describe("migrateHookNames", () => {
|
||||
})
|
||||
|
||||
test("preserves current hook names unchanged", () => {
|
||||
// #given: Config with current hook names
|
||||
// given: Config with current hook names
|
||||
const hooks = [
|
||||
"anthropic-context-window-limit-recovery",
|
||||
"todo-continuation-enforcer",
|
||||
"session-recovery",
|
||||
]
|
||||
|
||||
// #when: Migrate hook names
|
||||
// when: Migrate hook names
|
||||
const { migrated, changed, removed } = migrateHookNames(hooks)
|
||||
|
||||
// #then: Current names should remain unchanged
|
||||
// then: Current names should remain unchanged
|
||||
expect(changed).toBe(false)
|
||||
expect(migrated).toEqual(hooks)
|
||||
expect(removed).toEqual([])
|
||||
})
|
||||
|
||||
test("handles empty hooks array", () => {
|
||||
// #given: Empty hooks array
|
||||
// given: Empty hooks array
|
||||
const hooks: string[] = []
|
||||
|
||||
// #when: Migrate hook names
|
||||
// when: Migrate hook names
|
||||
const { migrated, changed, removed } = migrateHookNames(hooks)
|
||||
|
||||
// #then: Should return empty array with no changes
|
||||
// then: Should return empty array with no changes
|
||||
expect(changed).toBe(false)
|
||||
expect(migrated).toEqual([])
|
||||
expect(removed).toEqual([])
|
||||
})
|
||||
|
||||
test("migrates multiple legacy hook names", () => {
|
||||
// #given: Multiple legacy hook names (if more are added in future)
|
||||
// given: Multiple legacy hook names (if more are added in future)
|
||||
const hooks = ["anthropic-auto-compact"]
|
||||
|
||||
// #when: Migrate hook names
|
||||
// when: Migrate hook names
|
||||
const { migrated, changed } = migrateHookNames(hooks)
|
||||
|
||||
// #then: All legacy names should be migrated
|
||||
// then: All legacy names should be migrated
|
||||
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
|
||||
// given: Config with legacy sisyphus-orchestrator hook
|
||||
const hooks = ["sisyphus-orchestrator", "comment-checker"]
|
||||
|
||||
// #when: Migrate hook names
|
||||
// when: Migrate hook names
|
||||
const { migrated, changed, removed } = migrateHookNames(hooks)
|
||||
|
||||
// #then: sisyphus-orchestrator should be migrated to atlas
|
||||
// then: sisyphus-orchestrator should be migrated to atlas
|
||||
expect(changed).toBe(true)
|
||||
expect(migrated).toContain("atlas")
|
||||
expect(migrated).toContain("comment-checker")
|
||||
@@ -272,13 +272,13 @@ describe("migrateHookNames", () => {
|
||||
})
|
||||
|
||||
test("removes obsolete hooks and returns them in removed array", () => {
|
||||
// #given: Config with removed hooks from v3.0.0
|
||||
// given: Config with removed hooks from v3.0.0
|
||||
const hooks = ["preemptive-compaction", "empty-message-sanitizer", "comment-checker"]
|
||||
|
||||
// #when: Migrate hook names
|
||||
// when: Migrate hook names
|
||||
const { migrated, changed, removed } = migrateHookNames(hooks)
|
||||
|
||||
// #then: Removed hooks should be filtered out
|
||||
// then: Removed hooks should be filtered out
|
||||
expect(changed).toBe(true)
|
||||
expect(migrated).toEqual(["comment-checker"])
|
||||
expect(removed).toContain("preemptive-compaction")
|
||||
@@ -287,13 +287,13 @@ describe("migrateHookNames", () => {
|
||||
})
|
||||
|
||||
test("handles mixed migration and removal", () => {
|
||||
// #given: Config with both legacy rename and removed hooks
|
||||
// given: Config with both legacy rename and removed hooks
|
||||
const hooks = ["anthropic-auto-compact", "preemptive-compaction", "sisyphus-orchestrator"]
|
||||
|
||||
// #when: Migrate hook names
|
||||
// when: Migrate hook names
|
||||
const { migrated, changed, removed } = migrateHookNames(hooks)
|
||||
|
||||
// #then: Legacy should be renamed, removed should be filtered
|
||||
// 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")
|
||||
@@ -306,22 +306,22 @@ describe("migrateConfigFile", () => {
|
||||
const testConfigPath = "/tmp/nonexistent-path-for-test.json"
|
||||
|
||||
test("migrates omo_agent to sisyphus_agent", () => {
|
||||
// #given: Config with legacy omo_agent key
|
||||
// given: Config with legacy omo_agent key
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
omo_agent: { disabled: false },
|
||||
}
|
||||
|
||||
// #when: Migrate config file
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// #then: omo_agent should be migrated to sisyphus_agent
|
||||
// then: omo_agent should be migrated to sisyphus_agent
|
||||
expect(needsWrite).toBe(true)
|
||||
expect(rawConfig.sisyphus_agent).toEqual({ disabled: false })
|
||||
expect(rawConfig.omo_agent).toBeUndefined()
|
||||
})
|
||||
|
||||
test("migrates legacy agent names in agents object", () => {
|
||||
// #given: Config with legacy agent names
|
||||
// given: Config with legacy agent names
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
omo: { model: "test" },
|
||||
@@ -329,32 +329,32 @@ describe("migrateConfigFile", () => {
|
||||
},
|
||||
}
|
||||
|
||||
// #when: Migrate config file
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// #then: Agent names should be migrated
|
||||
// then: Agent names should be migrated
|
||||
expect(needsWrite).toBe(true)
|
||||
const agents = rawConfig.agents as Record<string, unknown>
|
||||
expect(agents["sisyphus"]).toBeDefined()
|
||||
})
|
||||
|
||||
test("migrates legacy hook names in disabled_hooks", () => {
|
||||
// #given: Config with legacy hook names
|
||||
// given: Config with legacy hook names
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
disabled_hooks: ["anthropic-auto-compact", "comment-checker"],
|
||||
}
|
||||
|
||||
// #when: Migrate config file
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// #then: Hook names should be migrated
|
||||
// then: Hook names should be migrated
|
||||
expect(needsWrite).toBe(true)
|
||||
expect(rawConfig.disabled_hooks).toContain("anthropic-context-window-limit-recovery")
|
||||
expect(rawConfig.disabled_hooks).not.toContain("anthropic-auto-compact")
|
||||
})
|
||||
|
||||
test("does not write if no migration needed", () => {
|
||||
// #given: Config with current names
|
||||
// given: Config with current names
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
sisyphus_agent: { disabled: false },
|
||||
agents: {
|
||||
@@ -363,15 +363,15 @@ describe("migrateConfigFile", () => {
|
||||
disabled_hooks: ["anthropic-context-window-limit-recovery"],
|
||||
}
|
||||
|
||||
// #when: Migrate config file
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// #then: No write should be needed
|
||||
// then: No write should be needed
|
||||
expect(needsWrite).toBe(false)
|
||||
})
|
||||
|
||||
test("handles migration of all legacy items together", () => {
|
||||
// #given: Config with all legacy items
|
||||
// given: Config with all legacy items
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
omo_agent: { disabled: false },
|
||||
agents: {
|
||||
@@ -381,10 +381,10 @@ describe("migrateConfigFile", () => {
|
||||
disabled_hooks: ["anthropic-auto-compact"],
|
||||
}
|
||||
|
||||
// #when: Migrate config file
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// #then: All legacy items should be migrated
|
||||
// then: All legacy items should be migrated
|
||||
expect(needsWrite).toBe(true)
|
||||
expect(rawConfig.sisyphus_agent).toEqual({ disabled: false })
|
||||
expect(rawConfig.omo_agent).toBeUndefined()
|
||||
@@ -397,8 +397,8 @@ describe("migrateConfigFile", () => {
|
||||
|
||||
describe("migration maps", () => {
|
||||
test("AGENT_NAME_MAP contains all expected legacy mappings", () => {
|
||||
// #given/#when: Check AGENT_NAME_MAP
|
||||
// #then: Should contain all legacy → lowercase mappings
|
||||
// given/#when: Check AGENT_NAME_MAP
|
||||
// then: Should contain all legacy → lowercase mappings
|
||||
expect(AGENT_NAME_MAP["omo"]).toBe("sisyphus")
|
||||
expect(AGENT_NAME_MAP["OmO"]).toBe("sisyphus")
|
||||
expect(AGENT_NAME_MAP["OmO-Plan"]).toBe("prometheus")
|
||||
@@ -408,25 +408,25 @@ describe("migration maps", () => {
|
||||
})
|
||||
|
||||
test("HOOK_NAME_MAP contains anthropic-auto-compact migration", () => {
|
||||
// #given/#when: Check HOOK_NAME_MAP
|
||||
// #then: Should contain be legacy hook name mapping
|
||||
// given/#when: Check HOOK_NAME_MAP
|
||||
// then: Should contain be legacy hook name mapping
|
||||
expect(HOOK_NAME_MAP["anthropic-auto-compact"]).toBe("anthropic-context-window-limit-recovery")
|
||||
})
|
||||
})
|
||||
|
||||
describe("migrateAgentConfigToCategory", () => {
|
||||
test("migrates model to category when mapping exists", () => {
|
||||
// #given: Config with a model that has a category mapping
|
||||
// given: Config with a model that has a category mapping
|
||||
const config = {
|
||||
model: "google/gemini-3-pro",
|
||||
temperature: 0.5,
|
||||
top_p: 0.9,
|
||||
}
|
||||
|
||||
// #when: Migrate agent config to category
|
||||
// when: Migrate agent config to category
|
||||
const { migrated, changed } = migrateAgentConfigToCategory(config)
|
||||
|
||||
// #then: Model should be replaced with category
|
||||
// then: Model should be replaced with category
|
||||
expect(changed).toBe(true)
|
||||
expect(migrated.category).toBe("visual-engineering")
|
||||
expect(migrated.model).toBeUndefined()
|
||||
@@ -435,37 +435,37 @@ describe("migrateAgentConfigToCategory", () => {
|
||||
})
|
||||
|
||||
test("does not migrate when model is not in map", () => {
|
||||
// #given: Config with a model that has no mapping
|
||||
// given: Config with a model that has no mapping
|
||||
const config = {
|
||||
model: "custom/model",
|
||||
temperature: 0.5,
|
||||
}
|
||||
|
||||
// #when: Migrate agent config to category
|
||||
// when: Migrate agent config to category
|
||||
const { migrated, changed } = migrateAgentConfigToCategory(config)
|
||||
|
||||
// #then: Config should remain unchanged
|
||||
// then: Config should remain unchanged
|
||||
expect(changed).toBe(false)
|
||||
expect(migrated).toEqual(config)
|
||||
})
|
||||
|
||||
test("does not migrate when model is not a string", () => {
|
||||
// #given: Config with non-string model
|
||||
// given: Config with non-string model
|
||||
const config = {
|
||||
model: { name: "test" },
|
||||
temperature: 0.5,
|
||||
}
|
||||
|
||||
// #when: Migrate agent config to category
|
||||
// when: Migrate agent config to category
|
||||
const { migrated, changed } = migrateAgentConfigToCategory(config)
|
||||
|
||||
// #then: Config should remain unchanged
|
||||
// then: Config should remain unchanged
|
||||
expect(changed).toBe(false)
|
||||
expect(migrated).toEqual(config)
|
||||
})
|
||||
|
||||
test("handles all mapped models correctly", () => {
|
||||
// #given: Configs for each mapped model
|
||||
// given: Configs for each mapped model
|
||||
const configs = [
|
||||
{ model: "google/gemini-3-pro" },
|
||||
{ model: "google/gemini-3-flash" },
|
||||
@@ -477,10 +477,10 @@ describe("migrateAgentConfigToCategory", () => {
|
||||
|
||||
const expectedCategories = ["visual-engineering", "writing", "ultrabrain", "quick", "unspecified-high", "unspecified-low"]
|
||||
|
||||
// #when: Migrate each config
|
||||
// when: Migrate each config
|
||||
const results = configs.map(migrateAgentConfigToCategory)
|
||||
|
||||
// #then: Each model should map to correct category
|
||||
// then: Each model should map to correct category
|
||||
results.forEach((result, index) => {
|
||||
expect(result.changed).toBe(true)
|
||||
expect(result.migrated.category).toBe(expectedCategories[index])
|
||||
@@ -489,7 +489,7 @@ describe("migrateAgentConfigToCategory", () => {
|
||||
})
|
||||
|
||||
test("preserves non-model fields during migration", () => {
|
||||
// #given: Config with multiple fields
|
||||
// given: Config with multiple fields
|
||||
const config = {
|
||||
model: "openai/gpt-5.2",
|
||||
temperature: 0.1,
|
||||
@@ -498,10 +498,10 @@ describe("migrateAgentConfigToCategory", () => {
|
||||
prompt_append: "custom instruction",
|
||||
}
|
||||
|
||||
// #when: Migrate agent config to category
|
||||
// when: Migrate agent config to category
|
||||
const { migrated } = migrateAgentConfigToCategory(config)
|
||||
|
||||
// #then: All non-model fields should be preserved
|
||||
// then: All non-model fields should be preserved
|
||||
expect(migrated.category).toBe("ultrabrain")
|
||||
expect(migrated.temperature).toBe(0.1)
|
||||
expect(migrated.top_p).toBe(0.95)
|
||||
@@ -512,57 +512,57 @@ describe("migrateAgentConfigToCategory", () => {
|
||||
|
||||
describe("shouldDeleteAgentConfig", () => {
|
||||
test("returns true when config only has category field", () => {
|
||||
// #given: Config with only category field (no overrides)
|
||||
// given: Config with only category field (no overrides)
|
||||
const config = { category: "visual-engineering" }
|
||||
|
||||
// #when: Check if config should be deleted
|
||||
// when: Check if config should be deleted
|
||||
const shouldDelete = shouldDeleteAgentConfig(config, "visual-engineering")
|
||||
|
||||
// #then: Should return true (matches category defaults)
|
||||
// then: Should return true (matches category defaults)
|
||||
expect(shouldDelete).toBe(true)
|
||||
})
|
||||
|
||||
test("returns false when category does not exist", () => {
|
||||
// #given: Config with unknown category
|
||||
// given: Config with unknown category
|
||||
const config = { category: "unknown" }
|
||||
|
||||
// #when: Check if config should be deleted
|
||||
// when: Check if config should be deleted
|
||||
const shouldDelete = shouldDeleteAgentConfig(config, "unknown")
|
||||
|
||||
// #then: Should return false (category not found)
|
||||
// then: Should return false (category not found)
|
||||
expect(shouldDelete).toBe(false)
|
||||
})
|
||||
|
||||
test("returns true when all fields match category defaults", () => {
|
||||
// #given: Config with fields matching category defaults
|
||||
// given: Config with fields matching category defaults
|
||||
const config = {
|
||||
category: "visual-engineering",
|
||||
model: "google/gemini-3-pro",
|
||||
}
|
||||
|
||||
// #when: Check if config should be deleted
|
||||
// when: Check if config should be deleted
|
||||
const shouldDelete = shouldDeleteAgentConfig(config, "visual-engineering")
|
||||
|
||||
// #then: Should return true (all fields match defaults)
|
||||
// then: Should return true (all fields match defaults)
|
||||
expect(shouldDelete).toBe(true)
|
||||
})
|
||||
|
||||
test("returns false when fields differ from category defaults", () => {
|
||||
// #given: Config with custom model override
|
||||
// given: Config with custom model override
|
||||
const config = {
|
||||
category: "visual-engineering",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
}
|
||||
|
||||
// #when: Check if config should be deleted
|
||||
// when: Check if config should be deleted
|
||||
const shouldDelete = shouldDeleteAgentConfig(config, "visual-engineering")
|
||||
|
||||
// #then: Should return false (has custom override)
|
||||
// then: Should return false (has custom override)
|
||||
expect(shouldDelete).toBe(false)
|
||||
})
|
||||
|
||||
test("handles different categories with their defaults", () => {
|
||||
// #given: Configs for different categories
|
||||
// given: Configs for different categories
|
||||
const configs = [
|
||||
{ category: "ultrabrain" },
|
||||
{ category: "quick" },
|
||||
@@ -570,32 +570,32 @@ describe("shouldDeleteAgentConfig", () => {
|
||||
{ category: "unspecified-low" },
|
||||
]
|
||||
|
||||
// #when: Check each config
|
||||
// when: Check each config
|
||||
const results = configs.map((config) => shouldDeleteAgentConfig(config, config.category as string))
|
||||
|
||||
// #then: All should be true (all match defaults)
|
||||
// then: All should be true (all match defaults)
|
||||
results.forEach((result) => {
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test("returns false when additional fields are present", () => {
|
||||
// #given: Config with extra fields
|
||||
// given: Config with extra fields
|
||||
const config = {
|
||||
category: "visual-engineering",
|
||||
temperature: 0.7,
|
||||
custom_field: "value", // Extra field not in defaults
|
||||
}
|
||||
|
||||
// #when: Check if config should be deleted
|
||||
// when: Check if config should be deleted
|
||||
const shouldDelete = shouldDeleteAgentConfig(config, "visual-engineering")
|
||||
|
||||
// #then: Should return false (has extra field)
|
||||
// then: Should return false (has extra field)
|
||||
expect(shouldDelete).toBe(false)
|
||||
})
|
||||
|
||||
test("handles complex config with multiple overrides", () => {
|
||||
// #given: Config with multiple custom overrides
|
||||
// given: Config with multiple custom overrides
|
||||
const config = {
|
||||
category: "visual-engineering",
|
||||
temperature: 0.5, // Different from default
|
||||
@@ -603,10 +603,10 @@ describe("shouldDeleteAgentConfig", () => {
|
||||
prompt_append: "custom prompt", // Custom field
|
||||
}
|
||||
|
||||
// #when: Check if config should be deleted
|
||||
// when: Check if config should be deleted
|
||||
const shouldDelete = shouldDeleteAgentConfig(config, "visual-engineering")
|
||||
|
||||
// #then: Should return false (has overrides)
|
||||
// then: Should return false (has overrides)
|
||||
expect(shouldDelete).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -624,7 +624,7 @@ describe("migrateConfigFile with backup", () => {
|
||||
})
|
||||
|
||||
test("creates backup file with timestamp when legacy migration needed", () => {
|
||||
// #given: Config file path with legacy agent names needing migration
|
||||
// given: Config file path with legacy agent names needing migration
|
||||
const testConfigPath = "/tmp/test-config-migration.json"
|
||||
const testConfigContent = globalThis.JSON.stringify({ agents: { omo: { model: "test" } } }, null, 2)
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
@@ -636,10 +636,10 @@ describe("migrateConfigFile with backup", () => {
|
||||
fs.writeFileSync(testConfigPath, testConfigContent)
|
||||
cleanupPaths.push(testConfigPath)
|
||||
|
||||
// #when: Migrate config file
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// #then: Backup file should be created with timestamp
|
||||
// then: Backup file should be created with timestamp
|
||||
expect(needsWrite).toBe(true)
|
||||
|
||||
const dir = path.dirname(testConfigPath)
|
||||
@@ -659,7 +659,7 @@ describe("migrateConfigFile with backup", () => {
|
||||
})
|
||||
|
||||
test("preserves model setting without auto-conversion to category", () => {
|
||||
// #given: Config with model setting (should NOT be converted to category)
|
||||
// given: Config with model setting (should NOT be converted to category)
|
||||
const testConfigPath = "/tmp/test-config-preserve-model.json"
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
@@ -672,10 +672,10 @@ describe("migrateConfigFile with backup", () => {
|
||||
fs.writeFileSync(testConfigPath, globalThis.JSON.stringify(rawConfig, null, 2))
|
||||
cleanupPaths.push(testConfigPath)
|
||||
|
||||
// #when: Migrate config file
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// #then: No migration needed - model settings should be preserved as-is
|
||||
// then: No migration needed - model settings should be preserved as-is
|
||||
expect(needsWrite).toBe(false)
|
||||
|
||||
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
|
||||
@@ -685,7 +685,7 @@ describe("migrateConfigFile with backup", () => {
|
||||
})
|
||||
|
||||
test("preserves category setting when explicitly set", () => {
|
||||
// #given: Config with explicit category setting
|
||||
// given: Config with explicit category setting
|
||||
const testConfigPath = "/tmp/test-config-preserve-category.json"
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
@@ -697,10 +697,10 @@ describe("migrateConfigFile with backup", () => {
|
||||
fs.writeFileSync(testConfigPath, globalThis.JSON.stringify(rawConfig, null, 2))
|
||||
cleanupPaths.push(testConfigPath)
|
||||
|
||||
// #when: Migrate config file
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// #then: No migration needed - category settings should be preserved as-is
|
||||
// then: No migration needed - category settings should be preserved as-is
|
||||
expect(needsWrite).toBe(false)
|
||||
|
||||
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
|
||||
@@ -709,7 +709,7 @@ describe("migrateConfigFile with backup", () => {
|
||||
})
|
||||
|
||||
test("does not write when no migration needed", () => {
|
||||
// #given: Config with no migrations needed
|
||||
// given: Config with no migrations needed
|
||||
const testConfigPath = "/tmp/test-config-no-migration.json"
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
@@ -734,10 +734,10 @@ describe("migrateConfigFile with backup", () => {
|
||||
}
|
||||
})
|
||||
|
||||
// #when: Migrate config file
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// #then: Should not write or create backup
|
||||
// then: Should not write or create backup
|
||||
expect(needsWrite).toBe(false)
|
||||
|
||||
const files = fs.readdirSync(dir)
|
||||
|
||||
Reference in New Issue
Block a user