{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAMrE,UAAU,UAAU;IACnB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,8FAA8F;IAC9F,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,sGAAsG;IACtG,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,0FAA0F;IAC1F,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED;;;;;;;;;;;;;;;GAeG;AACH,UAAU,wBAAwB;IACjC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,kFAAkF;AAClF,UAAU,YAAY;IACrB,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,SAAS;IACzB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,8GAA8G;IAC9G,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;CAC/C;AAED,wBAAgB,UAAU,IAAI,SAAS,CAMtC;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAGnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,GAAG,SAAS,CAyC7E;AAcD,wBAAgB,gBAAgB,CAAC,GAAG,OAAO,EAAE,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,CAO/E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAUvD","sourcesContent":["/**\n * hoo-config.json — types, I/O, and merge rules shared by the hoo-core extensions.\n *\n * Config merge order (lowest → highest priority):\n *   1. ~/.hoocode/hoo-config.json    (global defaults)\n *   2. ./.hoocode/hoo-config.json   (project overrides — scalars win; arrays union)\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport { getHooCodeDir } from \"../../config.js\";\n\nconst HOOCODE_DIR = getHooCodeDir();\nconst GLOBAL_CONFIG_PATH = join(HOOCODE_DIR, \"hoo-config.json\");\n\ninterface ModeConfig {\n\t/** Tool names that bypass the permission gate in this mode */\n\tauto_allow?: string[];\n\t/** Tool names available in this mode (if set, only these tools are active) */\n\tenabled_tools?: string[];\n\t/** Tool names explicitly blocked in this mode regardless of enabled_tools */\n\tdenied_tools?: string[];\n\t/** Allowed write paths in this mode (glob patterns, only applies if write/edit is enabled) */\n\tallowed_write_paths?: string[];\n\t/** Regex patterns for allowed bash commands. If set, a command must match at least one to execute. */\n\tallowed_bash_commands?: string[];\n\t/** Regex patterns for denied bash commands. A command matching any pattern is blocked. */\n\tdenied_bash_commands?: string[];\n}\n\n/**\n * Tool-outcome-driven thinking escalation (on by default).\n *\n * The default (fast) path keeps thinking low so mechanical tool turns — reads,\n * greps, successful edits — don't pay the extended-thinking prefill tax. When a\n * tool *fails*, the next turn(s) escalate to a higher thinking level so the model\n * reasons through the failure, then the level is restored. This buys low latency\n * on the happy path while preserving deep reasoning exactly when something breaks.\n *\n * Enabled by default; set `thinking_escalation.enabled` to `false` to turn it off.\n *\n * Note: escalation uses the same setter as manual thinking control, so the\n * escalated level is briefly written to settings and restored when the window\n * ends. If a run is interrupted mid-window, the escalated level may persist until\n * the next change.\n */\ninterface ThinkingEscalationConfig {\n\t/** Master switch. Default: true (set to false to disable). */\n\tenabled?: boolean;\n\t/** Level to escalate to after a tool error. Default: \"high\". */\n\ton_error?: ThinkingLevel;\n\t/** Restrict escalation to errors from these tool names. Default: any tool. */\n\ttools?: string[];\n\t/** Number of subsequent turns to stay escalated after an error. Default: 1. */\n\tcooldown_turns?: number;\n}\n\n/** LLM defaults seeded in hoo-config.json and honoured during model selection. */\ninterface HooLlmConfig {\n\t/** Preferred provider when the hoocode-layer settings.json has no saved default. */\n\tdefault_provider?: string;\n\t/** Preferred model id for `default_provider` (otherwise the provider's built-in default). */\n\tdefault_model?: string;\n\t/** Informational map of provider → env var holding its API key. */\n\tproviders?: Record<string, { api_key_env?: string }>;\n}\n\nexport interface HooConfig {\n\t/** LLM default provider/model preferences. */\n\tllm?: HooLlmConfig;\n\t/** Manually-pinned active mode (overrides default \"build\") */\n\tactive_mode?: string;\n\t/** Per-mode configuration keyed by mode name */\n\tmodes?: Record<string, ModeConfig>;\n\t/** Extra directories to search for `{name}/system.md` mode files (after project + user). */\n\tmode_paths?: string[];\n\t/** Raise thinking after tool failures, restore on success. On by default; set `enabled: false` to disable. */\n\tthinking_escalation?: ThinkingEscalationConfig;\n}\n\nexport function readConfig(): HooConfig {\n\ttry {\n\t\treturn JSON.parse(readFileSync(GLOBAL_CONFIG_PATH, \"utf8\")) as HooConfig;\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nexport function writeConfig(config: HooConfig): void {\n\tif (!existsSync(HOOCODE_DIR)) mkdirSync(HOOCODE_DIR, { recursive: true });\n\twriteFileSync(GLOBAL_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\\n`, \"utf8\");\n}\n\n/**\n * Deep-merges a project-local config on top of the global config.\n *\n * Merge rules:\n * - active_mode: project wins if set\n * - modes[x].auto_allow: union of global + project arrays\n * - modes[x].allowed_write_paths: union of global + project arrays\n * - modes[x].enabled_tools: project wins if set, else falls back to global\n * - mode_paths: project list is prepended so project paths are searched first\n * - thinking_escalation: project wins as a whole if set, else inherit global\n */\nexport function mergeConfigs(global: HooConfig, project: HooConfig): HooConfig {\n\tconst merged: HooConfig = { ...global };\n\n\tif (project.active_mode !== undefined) merged.active_mode = project.active_mode;\n\n\t// thinking_escalation: project wins as a whole if set, else inherit global.\n\tif (project.thinking_escalation !== undefined) merged.thinking_escalation = project.thinking_escalation;\n\n\tif (project.modes) {\n\t\tmerged.modes = { ...(global.modes ?? {}) };\n\t\tfor (const [mode, projectCfg] of Object.entries(project.modes)) {\n\t\t\tconst globalCfg = global.modes?.[mode] ?? {};\n\t\t\tmerged.modes[mode] = {\n\t\t\t\t...globalCfg,\n\t\t\t\t...projectCfg,\n\t\t\t\t// Union both auto_allow lists so project can extend, not just replace\n\t\t\t\tauto_allow: Array.from(new Set([...(globalCfg.auto_allow ?? []), ...(projectCfg.auto_allow ?? [])])),\n\t\t\t\t// Union allowed_write_paths so project can extend\n\t\t\t\tallowed_write_paths: Array.from(\n\t\t\t\t\tnew Set([...(globalCfg.allowed_write_paths ?? []), ...(projectCfg.allowed_write_paths ?? [])]),\n\t\t\t\t),\n\t\t\t\t// enabled_tools: project wins if set, else falls back to global\n\t\t\t\tenabled_tools: projectCfg.enabled_tools ?? globalCfg.enabled_tools,\n\t\t\t\t// denied_tools: union so project can add more denied tools on top of global\n\t\t\t\tdenied_tools: Array.from(new Set([...(globalCfg.denied_tools ?? []), ...(projectCfg.denied_tools ?? [])])),\n\t\t\t\t// allowed_bash_commands: project wins if set, else falls back to global\n\t\t\t\tallowed_bash_commands: projectCfg.allowed_bash_commands ?? globalCfg.allowed_bash_commands,\n\t\t\t\t// denied_bash_commands: union so project can add more denied patterns on top of global\n\t\t\t\tdenied_bash_commands: Array.from(\n\t\t\t\t\tnew Set([...(globalCfg.denied_bash_commands ?? []), ...(projectCfg.denied_bash_commands ?? [])]),\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t}\n\n\tif (project.mode_paths || global.mode_paths) {\n\t\t// Project paths first so they're searched before global paths\n\t\tmerged.mode_paths = dedupePaths([...(project.mode_paths ?? []), ...(global.mode_paths ?? [])]);\n\t}\n\n\treturn merged;\n}\n\nfunction dedupePaths(paths: string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst out: string[] = [];\n\tfor (const p of paths) {\n\t\tif (!seen.has(p)) {\n\t\t\tseen.add(p);\n\t\t\tout.push(p);\n\t\t}\n\t}\n\treturn out;\n}\n\nexport function mergeSearchPaths(...sources: (string[] | undefined)[]): string[] {\n\tconst merged: string[] = [];\n\tfor (const source of sources) {\n\t\tif (!source) continue;\n\t\tmerged.push(...source);\n\t}\n\treturn dedupePaths(merged);\n}\n\n/**\n * Reads the global config and optionally overlays the project-local config at\n * `./.hoocode/hoo-config.json`. Project values win on all scalar fields; arrays are\n * unioned (see mergeConfigs for full rules).\n */\nexport function readMergedConfig(cwd: string): HooConfig {\n\tconst global = readConfig();\n\tconst projectPath = join(cwd, \".hoocode\", \"hoo-config.json\");\n\tif (!existsSync(projectPath)) return global;\n\ttry {\n\t\tconst project = JSON.parse(readFileSync(projectPath, \"utf8\")) as HooConfig;\n\t\treturn mergeConfigs(global, project);\n\t} catch {\n\t\treturn global;\n\t}\n}\n"]}