diff --git a/Sources/CodeIsland/ConfigInstaller.swift b/Sources/CodeIsland/ConfigInstaller.swift index 1470b67e..a1c60e0c 100644 --- a/Sources/CodeIsland/ConfigInstaller.swift +++ b/Sources/CodeIsland/ConfigInstaller.swift @@ -294,12 +294,25 @@ struct ConfigInstaller { format: .traecli, events: defaultEvents(for: .traecli) ), - // Qoder — Claude Code fork + // Qoder — Claude Code fork with its own documented PermissionRequest hook. CLIConfig( name: "Qoder", source: "qoder", configPath: ".qoder/settings.json", configKey: "hooks", format: .claude, - events: defaultEvents(for: .claude) + events: [ + ("UserPromptSubmit", 5, true), + ("PreToolUse", 5, false), + ("PostToolUse", 5, true), + ("PostToolUseFailure", 5, true), + ("PermissionRequest", 86400, false), + ("Stop", 5, true), + ("SubagentStart", 5, true), + ("SubagentStop", 5, true), + ("SessionStart", 5, false), + ("SessionEnd", 5, true), + ("Notification", 86400, false), + ("PreCompact", 5, true), + ] ), // QoderWork — Qoder's standalone desktop assistant app (not the IDE). // Claude-format hooks, but user-level ~/.qoderwork/settings.json ONLY @@ -309,7 +322,20 @@ struct ConfigInstaller { name: "QoderWork", source: "qoderwork", configPath: ".qoderwork/settings.json", configKey: "hooks", format: .claude, - events: defaultEvents(for: .claude) + events: [ + ("UserPromptSubmit", 5, true), + ("PreToolUse", 5, false), + ("PostToolUse", 5, true), + ("PostToolUseFailure", 5, true), + ("PermissionRequest", 86400, false), + ("Stop", 5, true), + ("SubagentStart", 5, true), + ("SubagentStop", 5, true), + ("SessionStart", 5, false), + ("SessionEnd", 5, true), + ("Notification", 86400, false), + ("PreCompact", 5, true), + ] ), // Factory — Claude Code fork (uses "droid" as source identifier) CLIConfig( diff --git a/Tests/CodeIslandTests/ConfigInstallerTests.swift b/Tests/CodeIslandTests/ConfigInstallerTests.swift index a4e26cb7..46b379c7 100644 --- a/Tests/CodeIslandTests/ConfigInstallerTests.swift +++ b/Tests/CodeIslandTests/ConfigInstallerTests.swift @@ -26,6 +26,16 @@ final class ConfigInstallerTests: XCTestCase { let hooks = try XCTUnwrap(hooksAny as? [Any], file: file, line: line) return hooks.compactMap { $0 as? [String: Any] } } + + func testQoderConfigIncludesPermissionRequestHook() throws { + let cli = try XCTUnwrap(ConfigInstaller.allCLIs.first { $0.source == "qoder" }) + XCTAssertEqual(cli.format, .claude) + XCTAssertEqual(cli.configPath, ".qoder/settings.json") + let permission = try XCTUnwrap(cli.events.first { $0.0 == "PermissionRequest" }) + XCTAssertEqual(permission.1, 86400) + XCTAssertFalse(permission.2) + } + func testRemoveManagedHookEntriesAlsoPrunesLegacyVibeIslandHooks() throws { let hooks: [String: Any] = [ "SessionEnd": [ diff --git a/Tests/CodeIslandTests/QoderWorkSupportTests.swift b/Tests/CodeIslandTests/QoderWorkSupportTests.swift index 8fa72674..4f7c56ff 100644 --- a/Tests/CodeIslandTests/QoderWorkSupportTests.swift +++ b/Tests/CodeIslandTests/QoderWorkSupportTests.swift @@ -20,6 +20,9 @@ final class QoderWorkSupportTests: XCTestCase { XCTFail("QoderWork hooks are Claude-format JSON") } XCTAssertFalse(cli.events.isEmpty) + let permission = try XCTUnwrap(cli.events.first { $0.0 == "PermissionRequest" }) + XCTAssertEqual(permission.1, 86400) + XCTAssertFalse(permission.2) } func testQoderWorkSourceNormalization() {