@kolisachint/hoocode-agent 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -0
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +1 -0
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/extensions/core/hoo-core.d.ts +23 -1
- package/dist/extensions/core/hoo-core.d.ts.map +1 -1
- package/dist/extensions/core/hoo-core.js +48 -5
- package/dist/extensions/core/hoo-core.js.map +1 -1
- package/dist/utils/plan/parser.d.ts +42 -0
- package/dist/utils/plan/parser.d.ts.map +1 -0
- package/dist/utils/plan/parser.js +184 -0
- package/dist/utils/plan/parser.js.map +1 -0
- package/docs/providers.md +3 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +4 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +4 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +4 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +4 -1
- package/package.json +4 -4
|
@@ -29,5 +29,6 @@ export const BUILT_IN_PROVIDER_DISPLAY_NAMES = {
|
|
|
29
29
|
"xiaomi-token-plan-cn": "Xiaomi MiMo Token Plan (China)",
|
|
30
30
|
"xiaomi-token-plan-ams": "Xiaomi MiMo Token Plan (Amsterdam)",
|
|
31
31
|
"xiaomi-token-plan-sgp": "Xiaomi MiMo Token Plan (Singapore)",
|
|
32
|
+
nvidia: "NVIDIA",
|
|
32
33
|
};
|
|
33
34
|
//# sourceMappingURL=provider-display-names.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-display-names.js","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,+BAA+B,GAA2B;IACtE,SAAS,EAAE,WAAW;IACtB,gBAAgB,EAAE,gBAAgB;IAClC,wBAAwB,EAAE,wBAAwB;IAClD,QAAQ,EAAE,UAAU;IACpB,uBAAuB,EAAE,uBAAuB;IAChD,uBAAuB,EAAE,uBAAuB;IAChD,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,eAAe;IACvB,eAAe,EAAE,kBAAkB;IACnC,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,cAAc;IAC3B,aAAa,EAAE,iBAAiB;IAChC,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,aAAa;IACzB,eAAe,EAAE,qBAAqB;IACtC,QAAQ,EAAE,cAAc;IACxB,aAAa,EAAE,aAAa;IAC5B,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,YAAY;IACxB,QAAQ,EAAE,aAAa;IACvB,mBAAmB,EAAE,mBAAmB;IACxC,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,aAAa;IACrB,sBAAsB,EAAE,gCAAgC;IACxD,uBAAuB,EAAE,oCAAoC;IAC7D,uBAAuB,EAAE,oCAAoC;
|
|
1
|
+
{"version":3,"file":"provider-display-names.js","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,+BAA+B,GAA2B;IACtE,SAAS,EAAE,WAAW;IACtB,gBAAgB,EAAE,gBAAgB;IAClC,wBAAwB,EAAE,wBAAwB;IAClD,QAAQ,EAAE,UAAU;IACpB,uBAAuB,EAAE,uBAAuB;IAChD,uBAAuB,EAAE,uBAAuB;IAChD,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,eAAe;IACvB,eAAe,EAAE,kBAAkB;IACnC,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,cAAc;IAC3B,aAAa,EAAE,iBAAiB;IAChC,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,aAAa;IACzB,eAAe,EAAE,qBAAqB;IACtC,QAAQ,EAAE,cAAc;IACxB,aAAa,EAAE,aAAa;IAC5B,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,YAAY;IACxB,QAAQ,EAAE,aAAa;IACvB,mBAAmB,EAAE,mBAAmB;IACxC,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,aAAa;IACrB,sBAAsB,EAAE,gCAAgC;IACxD,uBAAuB,EAAE,oCAAoC;IAC7D,uBAAuB,EAAE,oCAAoC;IAC7D,MAAM,EAAE,QAAQ;CAChB,CAAC","sourcesContent":["export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n\tanthropic: \"Anthropic\",\n\t\"amazon-bedrock\": \"Amazon Bedrock\",\n\t\"azure-openai-responses\": \"Azure OpenAI Responses\",\n\tcerebras: \"Cerebras\",\n\t\"cloudflare-ai-gateway\": \"Cloudflare AI Gateway\",\n\t\"cloudflare-workers-ai\": \"Cloudflare Workers AI\",\n\tdeepseek: \"DeepSeek\",\n\tfireworks: \"Fireworks\",\n\tgoogle: \"Google Gemini\",\n\t\"google-vertex\": \"Google Vertex AI\",\n\tgroq: \"Groq\",\n\thuggingface: \"Hugging Face\",\n\t\"kimi-coding\": \"Kimi For Coding\",\n\tmistral: \"Mistral\",\n\tminimax: \"MiniMax\",\n\t\"minimax-cn\": \"MiniMax (China)\",\n\tmoonshotai: \"Moonshot AI\",\n\t\"moonshotai-cn\": \"Moonshot AI (China)\",\n\topencode: \"OpenCode Zen\",\n\t\"opencode-go\": \"OpenCode Go\",\n\topenai: \"OpenAI\",\n\topenrouter: \"OpenRouter\",\n\ttogether: \"Together AI\",\n\t\"vercel-ai-gateway\": \"Vercel AI Gateway\",\n\txai: \"xAI\",\n\tzai: \"ZAI\",\n\txiaomi: \"Xiaomi MiMo\",\n\t\"xiaomi-token-plan-cn\": \"Xiaomi MiMo Token Plan (China)\",\n\t\"xiaomi-token-plan-ams\": \"Xiaomi MiMo Token Plan (Amsterdam)\",\n\t\"xiaomi-token-plan-sgp\": \"Xiaomi MiMo Token Plan (Singapore)\",\n\tnvidia: \"NVIDIA\",\n};\n"]}
|
|
@@ -24,6 +24,10 @@ interface ProfileDetector {
|
|
|
24
24
|
interface ModeConfig {
|
|
25
25
|
/** Tool names that bypass the permission gate in this mode */
|
|
26
26
|
auto_allow?: string[];
|
|
27
|
+
/** Tool names available in this mode (if set, only these tools are active) */
|
|
28
|
+
enabled_tools?: string[];
|
|
29
|
+
/** Allowed write paths in this mode (glob patterns, only applies if write/edit is enabled) */
|
|
30
|
+
allowed_write_paths?: string[];
|
|
27
31
|
}
|
|
28
32
|
interface ProfileConfig {
|
|
29
33
|
/** Tool names to activate for this profile */
|
|
@@ -41,6 +45,24 @@ export interface HooConfig {
|
|
|
41
45
|
/** Ordered list of file-marker → profile mappings for auto-detection */
|
|
42
46
|
profile_detectors?: ProfileDetector[];
|
|
43
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Deep-merges a project-local config on top of the global config.
|
|
50
|
+
*
|
|
51
|
+
* Merge rules:
|
|
52
|
+
* - Scalars (active_mode, active_profile): project wins if set
|
|
53
|
+
* - modes[x].auto_allow: union of global + project arrays
|
|
54
|
+
* - modes[x].allowed_write_paths: union of global + project arrays
|
|
55
|
+
* - modes[x].enabled_tools: project wins if set, else falls back to global
|
|
56
|
+
* - profiles[x].enabled_tools: project wins if set, else falls back to global
|
|
57
|
+
* - profile_detectors: project list is prepended so project markers are checked first
|
|
58
|
+
*/
|
|
59
|
+
export declare function mergeConfigs(global: HooConfig, project: HooConfig): HooConfig;
|
|
60
|
+
/**
|
|
61
|
+
* Reads the global config and optionally overlays the project-local config at
|
|
62
|
+
* `./.hoocode/config.json`. Project values win on all scalar fields; arrays are
|
|
63
|
+
* unioned (see mergeConfigs for full rules).
|
|
64
|
+
*/
|
|
65
|
+
export declare function readMergedConfig(cwd: string): HooConfig;
|
|
44
66
|
export declare function setupPermissionGate(pi: ExtensionAPI): void;
|
|
45
67
|
export interface McpServerConfig {
|
|
46
68
|
/** Unique server identifier used as prefix for registered tool names */
|
|
@@ -67,7 +89,7 @@ export declare function resolveProfile(config: HooConfig, cwd: string): string;
|
|
|
67
89
|
* Each present layer is joined with a `---` separator.
|
|
68
90
|
*/
|
|
69
91
|
export declare function buildSystemPrompt(mode: string, profile: string, cwd: string): string | undefined;
|
|
70
|
-
interface PlanSections {
|
|
92
|
+
export interface PlanSections {
|
|
71
93
|
goal?: string;
|
|
72
94
|
filesToModify?: string;
|
|
73
95
|
newFiles?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hoo-core.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/hoo-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AASH,OAAO,KAAK,EAKX,YAAY,EAMZ,MAAM,gCAAgC,CAAC;AAcxC,UAAU,eAAe;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,UAAU;IACnB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,UAAU,aAAa;IACtB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACzB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACzC,0EAAwE;IACxE,iBAAiB,CAAC,EAAE,eAAe,EAAE,CAAC;CACtC;AAwGD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAiC1D;AAgBD,MAAM,WAAW,eAAe;IAC/B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAuGD,wBAAgB,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA4FrD;AAyBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA8BhG;AAMD,UAAU,YAAY;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,CA2BnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAwBlE;AAMD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA4J1D;AAMD,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAItD","sourcesContent":["/**\n * hoo-core — HooCode built-in core extension\n *\n * A. Permission Gate — prompts before bash/write/edit; checks modes.{mode}.auto_allow\n * from the merged (global + project) config; persists \"always\"\n * choices back to the global config\n * B. MCP Server Loader — discovers ~/.hoocode/mcp-servers and ./.hoocode/mcp-servers JSON\n * configs, connects via JSON-RPC 2.0, registers server tools\n * C. Mode + Profile — resolves active mode (ask/plan/build/agent/debug) and profile\n * (default/data/devops/…), merges system prompt from three template\n * layers, filters active tools, and exposes /mode, /profile,\n * /plan, and /approve commands\n *\n * Config merge order (lowest → highest priority):\n * 1. ~/.hoocode/agent/hoo-config.json (global defaults)\n * 2. ./.hoocode/config.json (project overrides — scalars win; arrays union)\n * 3. profile_detectors from project prepend global list (project markers checked first)\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { type Static, Type } from \"typebox\";\nimport type {\n\tAgentToolResult,\n\tAgentToolUpdateCallback,\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tSessionStartEvent,\n\tToolCallEvent,\n\tToolCallEventResult,\n} from \"../../core/extensions/types.js\";\nimport { isToolCallEventType } from \"../../core/extensions/types.js\";\n\n// ============================================================================\n// Shared paths\n// ============================================================================\n\nconst HOOCODE_DIR = join(homedir(), \".hoocode\");\nconst GLOBAL_CONFIG_PATH = join(HOOCODE_DIR, \"agent\", \"hoo-config.json\");\n\n// ============================================================================\n// Config types\n// ============================================================================\n\ninterface ProfileDetector {\n\tmarker: string;\n\tprofile: string;\n}\n\ninterface ModeConfig {\n\t/** Tool names that bypass the permission gate in this mode */\n\tauto_allow?: string[];\n}\n\ninterface ProfileConfig {\n\t/** Tool names to activate for this profile */\n\tenabled_tools?: string[];\n}\n\nexport interface HooConfig {\n\t/** Manually-pinned active mode (overrides default \"build\") */\n\tactive_mode?: string;\n\t/** Manually-pinned active profile (overrides auto-detection) */\n\tactive_profile?: string;\n\t/** Per-mode configuration keyed by mode name */\n\tmodes?: Record<string, ModeConfig>;\n\t/** Per-profile configuration keyed by profile name */\n\tprofiles?: Record<string, ProfileConfig>;\n\t/** Ordered list of file-marker → profile mappings for auto-detection */\n\tprofile_detectors?: ProfileDetector[];\n}\n\n// ============================================================================\n// Config I/O and merging\n// ============================================================================\n\nfunction 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\nfunction writeConfig(config: HooConfig): void {\n\tconst dir = join(HOOCODE_DIR, \"agent\");\n\tif (!existsSync(dir)) mkdirSync(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 * - Scalars (active_mode, active_profile): project wins if set\n * - modes[x].auto_allow: union of global + project arrays\n * - profiles[x].enabled_tools: project wins if set, else falls back to global\n * - profile_detectors: project list is prepended so project markers are checked first\n */\nfunction 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\tif (project.active_profile !== undefined) merged.active_profile = project.active_profile;\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};\n\t\t}\n\t}\n\n\tif (project.profiles) {\n\t\tmerged.profiles = { ...(global.profiles ?? {}) };\n\t\tfor (const [profile, projectCfg] of Object.entries(project.profiles)) {\n\t\t\tmerged.profiles[profile] = {\n\t\t\t\t...(global.profiles?.[profile] ?? {}),\n\t\t\t\t...projectCfg,\n\t\t\t};\n\t\t}\n\t}\n\n\tif (project.profile_detectors) {\n\t\t// Project detectors are prepended: project-specific markers are checked first\n\t\tmerged.profile_detectors = [...project.profile_detectors, ...(global.profile_detectors ?? [])];\n\t}\n\n\treturn merged;\n}\n\n/**\n * Reads the global config and optionally overlays the project-local config at\n * `./.hoocode/config.json`. Project values win on all scalar fields; arrays are\n * unioned (see mergeConfigs for full rules).\n */\nfunction readMergedConfig(cwd: string): HooConfig {\n\tconst global = readConfig();\n\tconst projectPath = join(cwd, \".hoocode\", \"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\n// ============================================================================\n// A. Permission Gate\n// ============================================================================\n\nconst GATED_TOOLS = new Set([\"bash\", \"write\", \"edit\"]);\n\nfunction describeTool(event: ToolCallEvent): string {\n\tif (isToolCallEventType(\"bash\", event)) {\n\t\treturn `$ ${event.input.command.replace(/\\s+/g, \" \").slice(0, 100)}`;\n\t}\n\tif (isToolCallEventType(\"edit\", event)) {\n\t\tconst p = (event.input as { file_path?: string }).file_path ?? \"(unknown)\";\n\t\treturn `edit ${p}`;\n\t}\n\tif (isToolCallEventType(\"write\", event)) {\n\t\tconst p = (event.input as { file_path?: string }).file_path ?? \"(unknown)\";\n\t\treturn `write ${p}`;\n\t}\n\treturn event.toolName;\n}\n\nexport function setupPermissionGate(pi: ExtensionAPI): void {\n\tpi.on(\"tool_call\", async (event: ToolCallEvent, ctx: ExtensionContext): Promise<ToolCallEventResult | undefined> => {\n\t\tif (!GATED_TOOLS.has(event.toolName) || !ctx.hasUI) return;\n\n\t\t// Use the merged config so project-local auto_allow entries are respected\n\t\tconst config = readMergedConfig(ctx.cwd);\n\t\tconst mode = config.active_mode ?? \"build\";\n\t\tconst autoAllow = config.modes?.[mode]?.auto_allow ?? [];\n\t\tif (autoAllow.includes(event.toolName)) return;\n\n\t\tconst choice = await ctx.ui.select(`Allow: ${describeTool(event)}`, [\n\t\t\t\"Yes (once)\",\n\t\t\t\"No (block)\",\n\t\t\t\"Always (add to auto-allow for this mode)\",\n\t\t]);\n\n\t\tif (!choice || choice.startsWith(\"No\")) {\n\t\t\treturn { block: true, reason: \"Denied by permission gate\" };\n\t\t}\n\n\t\tif (choice.startsWith(\"Always\")) {\n\t\t\t// Write \"always\" choices to the global config only\n\t\t\tconst latest = readConfig();\n\t\t\tconst currentMode = latest.active_mode ?? \"build\";\n\t\t\tlatest.modes ??= {};\n\t\t\tlatest.modes[currentMode] ??= {};\n\t\t\tlatest.modes[currentMode].auto_allow = Array.from(\n\t\t\t\tnew Set([...(latest.modes[currentMode].auto_allow ?? []), event.toolName]),\n\t\t\t);\n\t\t\twriteConfig(latest);\n\t\t\tctx.ui.notify(`\"${event.toolName}\" added to auto-allow for mode \"${currentMode}\"`, \"info\");\n\t\t}\n\t});\n}\n\n// ============================================================================\n// B. MCP Server Loader\n// ============================================================================\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn */\n\tcommand: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown): Promise<unknown>;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n\nfunction spawnMcpServer(config: McpServerConfig): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tpending.set(id, { resolve, reject });\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\treturn {\n\t\trpc,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = spawnMcpServer(config);\n\tmcpConnections.set(config.name, conn);\n\n\tawait conn.rpc(\"initialize\", {\n\t\tprotocolVersion: \"2024-11-05\",\n\t\tcapabilities: { tools: {} },\n\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t});\n\n\tconst toolsResult = (await conn.rpc(\"tools/list\", {})) as {\n\t\ttools?: McpToolDef[];\n\t};\n\treturn { conn, tools: toolsResult.tools ?? [] };\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || !serverConfig.command) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\t\tconst schema = buildMcpSchema(tool);\n\t\t\t\t\t\tconst capturedServer = serverConfig.name;\n\t\t\t\t\t\tconst capturedTool = tool.name;\n\n\t\t\t\t\t\tpi.registerTool({\n\t\t\t\t\t\t\tname: toolName,\n\t\t\t\t\t\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\t\t\t\t\t\tdescription: tool.description,\n\t\t\t\t\t\t\tparameters: schema,\n\t\t\t\t\t\t\tasync execute(\n\t\t\t\t\t\t\t\t_toolCallId: string,\n\t\t\t\t\t\t\t\tparams: Static<typeof schema>,\n\t\t\t\t\t\t\t\tsignal: AbortSignal,\n\t\t\t\t\t\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t\t\t\t\t\t): Promise<AgentToolResult<undefined>> {\n\t\t\t\t\t\t\t\tconst activeConn = mcpConnections.get(capturedServer);\n\t\t\t\t\t\t\t\tif (!activeConn) {\n\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\t\ttext: `MCP server \"${capturedServer}\" is not connected`,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\t\t\t\t\tactiveConn.rpc(\"tools/call\", {\n\t\t\t\t\t\t\t\t\t\tname: capturedTool,\n\t\t\t\t\t\t\t\t\t\targuments: params,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\tabortPromise,\n\t\t\t\t\t\t\t\t]);\n\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"})`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n}\n\n// ============================================================================\n// C. Mode + Profile System\n// ============================================================================\n\nconst DEFAULT_MODE = \"build\";\nconst DEFAULT_PROFILE = \"default\";\n\n/**\n * Returns true if `marker` matches something in `cwd`.\n * Plain markers use existsSync. Glob markers (containing `*`) scan the\n * immediate directory entries — only one level, no recursion needed for\n * common cases like `*.sql` or `k8s/`.\n */\nfunction markerExists(cwd: string, marker: string): boolean {\n\tif (!marker.includes(\"*\")) return existsSync(join(cwd, marker));\n\tconst suffix = marker.replace(/^\\*/, \"\");\n\ttry {\n\t\treturn readdirSync(cwd).some((entry) => entry.endsWith(suffix));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Resolves which profile should be active.\n * Priority: config override → file-marker detection → \"default\"\n */\nexport function resolveProfile(config: HooConfig, cwd: string): string {\n\tif (config.active_profile) return config.active_profile;\n\tfor (const detector of config.profile_detectors ?? []) {\n\t\tif (markerExists(cwd, detector.marker)) return detector.profile;\n\t}\n\treturn DEFAULT_PROFILE;\n}\n\n/**\n * Merges the system prompt from up to three layers (lowest → highest priority):\n * 1. ~/.hoocode/templates/modes/{mode}/system.md (mode behaviour)\n * 2. ~/.hoocode/templates/profiles/{profile}/context.md (domain context; skipped for \"default\")\n * 3. ./.hoocode/agents.md (project-local override; appended last)\n *\n * Each present layer is joined with a `---` separator.\n */\nexport function buildSystemPrompt(mode: string, profile: string, cwd: string): string | undefined {\n\tconst layers: string[] = [];\n\n\tfunction tryRead(path: string): string | undefined {\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst text = readFileSync(path, \"utf8\").trim();\n\t\t\treturn text || undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t// Layer 1: mode system prompt (~/.hoocode/modes/{mode}/system.md)\n\tconst modePrompt = tryRead(join(HOOCODE_DIR, \"modes\", mode, \"system.md\"));\n\tif (modePrompt) layers.push(modePrompt);\n\n\t// Layer 2: profile context — omit for \"default\" (no extra domain constraints)\n\t// (~/.hoocode/profiles/{profile}/context.md)\n\tif (profile !== DEFAULT_PROFILE) {\n\t\tconst profileContext = tryRead(join(HOOCODE_DIR, \"profiles\", profile, \"context.md\"));\n\t\tif (profileContext) layers.push(profileContext);\n\t}\n\n\t// Layer 3: project-local agents.md — appended after mode + profile so it can\n\t// extend or override them for this specific repo\n\tconst projectOverride = tryRead(join(cwd, \".hoocode\", \"agents.md\"));\n\tif (projectOverride) layers.push(projectOverride);\n\n\treturn layers.length > 0 ? layers.join(\"\\n\\n---\\n\\n\") : undefined;\n}\n\n// ============================================================================\n// Plan file: section parsing and step-by-step execution message\n// ============================================================================\n\ninterface PlanSections {\n\tgoal?: string;\n\tfilesToModify?: string;\n\tnewFiles?: string;\n\ttests?: string;\n\tverification?: string;\n\t/** Original full text, used as fallback if no sections parsed */\n\traw: string;\n}\n\n/**\n * Parses `.hoocode/plan.md` into named sections.\n *\n * Recognises both ATX headings (`## Goal`) and bold labels (`**Goal**`).\n * Section names matched (case-insensitive): Goal, Files to modify, New files,\n * Tests, Verification.\n */\nexport function parsePlanSections(planContent: string): PlanSections {\n\tconst result: PlanSections = { raw: planContent };\n\n\t// Match `## Heading text` or `**Heading text**` followed by content until\n\t// the next heading of the same style.\n\tconst sectionPattern =\n\t\t/^(?:#{1,3}\\s+(.+?)|(?:\\*\\*(.+?)\\*\\*))\\s*\\n([\\s\\S]*?)(?=(?:^#{1,3}\\s+|\\*\\*[^*\\n]+\\*\\*\\s*\\n)|$)/gm;\n\n\tfor (const match of planContent.matchAll(sectionPattern)) {\n\t\tconst heading = (match[1] ?? match[2] ?? \"\").toLowerCase().trim();\n\t\tconst content = match[3].trim();\n\t\tif (!content) continue;\n\n\t\tif (/^goal/.test(heading)) {\n\t\t\tresult.goal = content;\n\t\t} else if (/files?\\s+to\\s+modif|^modif/.test(heading)) {\n\t\t\tresult.filesToModify = content;\n\t\t} else if (/new\\s+files?/.test(heading)) {\n\t\t\tresult.newFiles = content;\n\t\t} else if (/^tests?/.test(heading)) {\n\t\t\tresult.tests = content;\n\t\t} else if (/^verif/.test(heading)) {\n\t\t\tresult.verification = content;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Builds the user message sent to the agent when `/approve` is run.\n *\n * If the plan has recognisable sections, each is presented as a numbered step\n * so the agent works through them sequentially. Otherwise the raw plan is used.\n *\n * Execution order:\n * 1. Modify existing files\n * 2. Create new files\n * 3. Update / add tests\n * 4. Run verification commands\n */\nexport function buildApproveMessage(sections: PlanSections): string {\n\tconst steps: string[] = [];\n\n\tif (sections.goal) {\n\t\tsteps.push(`**Goal:** ${sections.goal}`);\n\t}\n\tif (sections.filesToModify) {\n\t\tsteps.push(`**Step 1 — Modify existing files:**\\n${sections.filesToModify}`);\n\t}\n\tif (sections.newFiles) {\n\t\tsteps.push(`**Step 2 — Create new files:**\\n${sections.newFiles}`);\n\t}\n\tif (sections.tests) {\n\t\tsteps.push(`**Step 3 — Update tests:**\\n${sections.tests}`);\n\t}\n\tif (sections.verification) {\n\t\tsteps.push(`**Step 4 — Verify:**\\n${sections.verification}`);\n\t}\n\n\tif (steps.length === 0) {\n\t\treturn `Execute the following plan:\\n\\n${sections.raw}`;\n\t}\n\n\treturn `Execute this plan step by step. Complete each step fully before moving to the next.\\n\\n${steps.join(\"\\n\\n\")}`;\n}\n\n// ============================================================================\n// C. setupModeAndProfile\n// ============================================================================\n\nexport function setupModeAndProfile(pi: ExtensionAPI): void {\n\tlet cachedMode = DEFAULT_MODE;\n\tlet cachedProfile = DEFAULT_PROFILE;\n\tlet cachedSystemPrompt: string | undefined;\n\n\t// ── session_start ─────────────────────────────────────────────────────────\n\t// Config resolution order:\n\t// 1. Read global config (~/.hoocode/agent/hoo-config.json)\n\t// 2. Read project config (./.hoocode/config.json) if present\n\t// 3. Merge — project scalars win; arrays are unioned; project detectors prepend\n\t// 4. Re-resolve active_mode and active_profile from the merged result\n\n\tpi.on(\"session_start\", (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// Steps 1–3: merge global + project configs\n\t\tconst config = readMergedConfig(ctx.cwd);\n\n\t\t// Step 4: resolve mode and profile from the merged config\n\t\tcachedMode = config.active_mode ?? DEFAULT_MODE;\n\t\tcachedProfile = resolveProfile(config, ctx.cwd);\n\t\tcachedSystemPrompt = buildSystemPrompt(cachedMode, cachedProfile, ctx.cwd);\n\n\t\t// Update footer with active mode/profile\n\t\tif (ctx.hasUI) {\n\t\t\tctx.ui.setModeProfile(cachedMode, cachedProfile);\n\t\t}\n\n\t\t// Apply tool filter defined by the active profile\n\t\tconst profileCfg = config.profiles?.[cachedProfile];\n\t\tif (profileCfg?.enabled_tools && profileCfg.enabled_tools.length > 0) {\n\t\t\tpi.setActiveTools(profileCfg.enabled_tools);\n\t\t}\n\t});\n\n\t// ── before_agent_start ────────────────────────────────────────────────────\n\n\tpi.on(\"before_agent_start\", (event: BeforeAgentStartEvent): BeforeAgentStartEventResult | undefined => {\n\t\tif (!cachedSystemPrompt) return;\n\t\treturn {\n\t\t\tsystemPrompt:\n\t\t\t\t`${event.systemPrompt}\\n\\n` +\n\t\t\t\t`<!-- hoo-core: mode=${cachedMode} profile=${cachedProfile} -->\\n` +\n\t\t\t\tcachedSystemPrompt,\n\t\t};\n\t});\n\n\t// ── /mode command ─────────────────────────────────────────────────────────\n\n\tconst KNOWN_MODES = [\"ask\", \"plan\", \"build\", \"agent\", \"debug\"];\n\n\tpi.registerCommand(\"mode\", {\n\t\tdescription: \"Switch active mode. Usage: /mode <ask|plan|build|agent|debug>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tKNOWN_MODES.filter((m) => m.startsWith(prefix)).map((m) => ({ value: m, label: m })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tif (!name) {\n\t\t\t\tctx.ui.notify(`Active mode: ${cachedMode}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = name === DEFAULT_MODE ? undefined : name;\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Mode set to \"${name}\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /profile command ──────────────────────────────────────────────────────\n\n\tpi.registerCommand(\"profile\", {\n\t\tdescription: \"Switch active profile. Usage: /profile <name>\",\n\t\tgetArgumentCompletions: (prefix: string) => {\n\t\t\t// Show profiles from the merged config so project-local profiles appear\n\t\t\tconst config = readMergedConfig(\".\");\n\t\t\tconst names = Object.keys(config.profiles ?? {});\n\t\t\tconst suggestions = [DEFAULT_PROFILE, ...names.filter((n) => n !== DEFAULT_PROFILE)];\n\t\t\treturn suggestions.filter((n) => n.startsWith(prefix)).map((n) => ({ value: n, label: n }));\n\t\t},\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tif (!name) {\n\t\t\t\tctx.ui.notify(`Active profile: ${cachedProfile}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_profile = name === DEFAULT_PROFILE ? undefined : name;\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Profile set to \"${name}\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /plan command (shorthand for /mode plan) ──────────────────────────────\n\n\tpi.registerCommand(\"plan\", {\n\t\tdescription: \"Switch to plan mode. Shorthand for /mode plan.\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = \"plan\";\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Mode set to \"plan\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /approve command ──────────────────────────────────────────────────────\n\t// Reads .hoocode/plan.md, parses it into named sections (Goal, Files to\n\t// modify, New files, Tests, Verification), switches to build mode, then\n\t// injects a step-by-step execution message into the new session.\n\n\tpi.registerCommand(\"approve\", {\n\t\tdescription: \"Approve the current plan and switch to build mode to execute it.\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tif (cachedMode !== \"plan\") {\n\t\t\t\tctx.ui.notify(`/approve is only available in plan mode (current mode: \"${cachedMode}\")`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Read ./.hoocode/plan.md written by the agent during plan mode\n\t\t\tconst planPath = join(ctx.cwd, \".hoocode\", \"plan.md\");\n\t\t\tlet approveMessage: string | undefined;\n\n\t\t\tif (existsSync(planPath)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst raw = readFileSync(planPath, \"utf8\").trim();\n\t\t\t\t\tif (raw) {\n\t\t\t\t\t\tconst sections = parsePlanSections(raw);\n\t\t\t\t\t\tapproveMessage = buildApproveMessage(sections);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\tctx.ui.notify(`Could not read .hoocode/plan.md`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Switch global config to build mode\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = \"build\";\n\t\t\twriteConfig(config);\n\n\t\t\tif (approveMessage) {\n\t\t\t\t// Open a new build-mode session and deliver the parsed plan as the\n\t\t\t\t// first user message so the agent starts executing immediately\n\t\t\t\tawait ctx.newSession({\n\t\t\t\t\twithSession: async (replacedCtx) => {\n\t\t\t\t\t\tawait replacedCtx.sendUserMessage(approveMessage!, { deliverAs: \"followUp\" });\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tctx.ui.notify(`Switched to build mode. No .hoocode/plan.md found — describe what to build.`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t}\n\t\t},\n\t});\n}\n\n// ============================================================================\n// Extension entry point\n// ============================================================================\n\nexport default function hooCore(pi: ExtensionAPI): void {\n\tsetupPermissionGate(pi);\n\tsetupMcpLoader(pi);\n\tsetupModeAndProfile(pi);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"hoo-core.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/hoo-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AASH,OAAO,KAAK,EAKX,YAAY,EAMZ,MAAM,gCAAgC,CAAC;AAcxC,UAAU,eAAe;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,UAAU;IACnB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,8FAA8F;IAC9F,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,UAAU,aAAa;IACtB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACzB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACzC,0EAAwE;IACxE,iBAAiB,CAAC,EAAE,eAAe,EAAE,CAAC;CACtC;AAoBD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,GAAG,SAAS,CAyC7E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAUvD;AAyCD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAiD1D;AAgBD,MAAM,WAAW,eAAe;IAC/B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAuGD,wBAAgB,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA4FrD;AAyBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA8BhG;AAMD,MAAM,WAAW,YAAY;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,CA2BnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAwBlE;AAMD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA+J1D;AAMD,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAItD","sourcesContent":["/**\n * hoo-core — HooCode built-in core extension\n *\n * A. Permission Gate — prompts before bash/write/edit; checks modes.{mode}.auto_allow\n * from the merged (global + project) config; persists \"always\"\n * choices back to the global config\n * B. MCP Server Loader — discovers ~/.hoocode/mcp-servers and ./.hoocode/mcp-servers JSON\n * configs, connects via JSON-RPC 2.0, registers server tools\n * C. Mode + Profile — resolves active mode (ask/plan/build/agent/debug) and profile\n * (default/data/devops/…), merges system prompt from three template\n * layers, filters active tools, and exposes /mode, /profile,\n * /plan, and /approve commands\n *\n * Config merge order (lowest → highest priority):\n * 1. ~/.hoocode/agent/hoo-config.json (global defaults)\n * 2. ./.hoocode/config.json (project overrides — scalars win; arrays union)\n * 3. profile_detectors from project prepend global list (project markers checked first)\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { type Static, Type } from \"typebox\";\nimport type {\n\tAgentToolResult,\n\tAgentToolUpdateCallback,\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tSessionStartEvent,\n\tToolCallEvent,\n\tToolCallEventResult,\n} from \"../../core/extensions/types.js\";\nimport { isToolCallEventType } from \"../../core/extensions/types.js\";\n\n// ============================================================================\n// Shared paths\n// ============================================================================\n\nconst HOOCODE_DIR = join(homedir(), \".hoocode\");\nconst GLOBAL_CONFIG_PATH = join(HOOCODE_DIR, \"agent\", \"hoo-config.json\");\n\n// ============================================================================\n// Config types\n// ============================================================================\n\ninterface ProfileDetector {\n\tmarker: string;\n\tprofile: string;\n}\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/** Allowed write paths in this mode (glob patterns, only applies if write/edit is enabled) */\n\tallowed_write_paths?: string[];\n}\n\ninterface ProfileConfig {\n\t/** Tool names to activate for this profile */\n\tenabled_tools?: string[];\n}\n\nexport interface HooConfig {\n\t/** Manually-pinned active mode (overrides default \"build\") */\n\tactive_mode?: string;\n\t/** Manually-pinned active profile (overrides auto-detection) */\n\tactive_profile?: string;\n\t/** Per-mode configuration keyed by mode name */\n\tmodes?: Record<string, ModeConfig>;\n\t/** Per-profile configuration keyed by profile name */\n\tprofiles?: Record<string, ProfileConfig>;\n\t/** Ordered list of file-marker → profile mappings for auto-detection */\n\tprofile_detectors?: ProfileDetector[];\n}\n\n// ============================================================================\n// Config I/O and merging\n// ============================================================================\n\nfunction 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\nfunction writeConfig(config: HooConfig): void {\n\tconst dir = join(HOOCODE_DIR, \"agent\");\n\tif (!existsSync(dir)) mkdirSync(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 * - Scalars (active_mode, active_profile): 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 * - profiles[x].enabled_tools: project wins if set, else falls back to global\n * - profile_detectors: project list is prepended so project markers are checked first\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\tif (project.active_profile !== undefined) merged.active_profile = project.active_profile;\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};\n\t\t}\n\t}\n\n\tif (project.profiles) {\n\t\tmerged.profiles = { ...(global.profiles ?? {}) };\n\t\tfor (const [profile, projectCfg] of Object.entries(project.profiles)) {\n\t\t\tmerged.profiles[profile] = {\n\t\t\t\t...(global.profiles?.[profile] ?? {}),\n\t\t\t\t...projectCfg,\n\t\t\t};\n\t\t}\n\t}\n\n\tif (project.profile_detectors) {\n\t\t// Project detectors are prepended: project-specific markers are checked first\n\t\tmerged.profile_detectors = [...project.profile_detectors, ...(global.profile_detectors ?? [])];\n\t}\n\n\treturn merged;\n}\n\n/**\n * Reads the global config and optionally overlays the project-local config at\n * `./.hoocode/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\", \"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\n// ============================================================================\n// A. Permission Gate\n// ============================================================================\n\nconst GATED_TOOLS = new Set([\"bash\", \"write\", \"edit\"]);\n\n/**\n * Checks if a file path matches any of the allowed patterns.\n * Supports glob patterns with * and exact paths.\n */\nfunction matchesAllowedPath(filePath: string, allowedPatterns: string[]): boolean {\n\tif (allowedPatterns.length === 0) return true;\n\tfor (const pattern of allowedPatterns) {\n\t\t// Exact match\n\t\tif (pattern === filePath) return true;\n\t\t// Glob pattern matching for *\n\t\tif (pattern.includes(\"*\")) {\n\t\t\tconst regex = new RegExp(`^${pattern.replace(/\\*/g, \".*\")}$`);\n\t\t\tif (regex.test(filePath)) return true;\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction describeTool(event: ToolCallEvent): string {\n\tif (isToolCallEventType(\"bash\", event)) {\n\t\treturn `$ ${event.input.command.replace(/\\s+/g, \" \").slice(0, 100)}`;\n\t}\n\tif (isToolCallEventType(\"edit\", event)) {\n\t\tconst p = (event.input as { file_path?: string }).file_path ?? \"(unknown)\";\n\t\treturn `edit ${p}`;\n\t}\n\tif (isToolCallEventType(\"write\", event)) {\n\t\tconst p = (event.input as { file_path?: string }).file_path ?? \"(unknown)\";\n\t\treturn `write ${p}`;\n\t}\n\treturn event.toolName;\n}\n\nexport function setupPermissionGate(pi: ExtensionAPI): void {\n\tpi.on(\"tool_call\", async (event: ToolCallEvent, ctx: ExtensionContext): Promise<ToolCallEventResult | undefined> => {\n\t\tif (!GATED_TOOLS.has(event.toolName) || !ctx.hasUI) return;\n\n\t\t// Use the merged config so project-local auto_allow entries are respected\n\t\tconst config = readMergedConfig(ctx.cwd);\n\t\tconst mode = config.active_mode ?? \"build\";\n\t\tconst modeCfg = config.modes?.[mode];\n\t\tconst autoAllow = modeCfg?.auto_allow ?? [];\n\n\t\t// Check allowed_write_paths for write/edit operations\n\t\tif ((event.toolName === \"write\" || event.toolName === \"edit\") && modeCfg?.allowed_write_paths) {\n\t\t\tconst filePath = (event.input as { file_path?: string }).file_path ?? \"\";\n\t\t\tif (!matchesAllowedPath(filePath, modeCfg.allowed_write_paths)) {\n\t\t\t\treturn {\n\t\t\t\t\tblock: true,\n\t\t\t\t\treason:\n\t\t\t\t\t\t`Mode \"${mode}\" only allows writes to: ${modeCfg.allowed_write_paths.join(\", \")}. ` +\n\t\t\t\t\t\t`Attempted to ${event.toolName}: ${filePath}. ` +\n\t\t\t\t\t\t`Switch to \"/mode build\" or \"/mode agent\" to modify source files.`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif (autoAllow.includes(event.toolName)) return;\n\n\t\tconst choice = await ctx.ui.select(`Allow: ${describeTool(event)}`, [\n\t\t\t\"Yes (once)\",\n\t\t\t\"No (block)\",\n\t\t\t\"Always (add to auto-allow for this mode)\",\n\t\t]);\n\n\t\tif (!choice || choice.startsWith(\"No\")) {\n\t\t\treturn { block: true, reason: \"Denied by permission gate\" };\n\t\t}\n\n\t\tif (choice.startsWith(\"Always\")) {\n\t\t\t// Write \"always\" choices to the global config only\n\t\t\tconst latest = readConfig();\n\t\t\tconst currentMode = latest.active_mode ?? \"build\";\n\t\t\tlatest.modes ??= {};\n\t\t\tlatest.modes[currentMode] ??= {};\n\t\t\tlatest.modes[currentMode].auto_allow = Array.from(\n\t\t\t\tnew Set([...(latest.modes[currentMode].auto_allow ?? []), event.toolName]),\n\t\t\t);\n\t\t\twriteConfig(latest);\n\t\t\tctx.ui.notify(`\"${event.toolName}\" added to auto-allow for mode \"${currentMode}\"`, \"info\");\n\t\t}\n\t});\n}\n\n// ============================================================================\n// B. MCP Server Loader\n// ============================================================================\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn */\n\tcommand: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown): Promise<unknown>;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n\nfunction spawnMcpServer(config: McpServerConfig): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tpending.set(id, { resolve, reject });\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\treturn {\n\t\trpc,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = spawnMcpServer(config);\n\tmcpConnections.set(config.name, conn);\n\n\tawait conn.rpc(\"initialize\", {\n\t\tprotocolVersion: \"2024-11-05\",\n\t\tcapabilities: { tools: {} },\n\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t});\n\n\tconst toolsResult = (await conn.rpc(\"tools/list\", {})) as {\n\t\ttools?: McpToolDef[];\n\t};\n\treturn { conn, tools: toolsResult.tools ?? [] };\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || !serverConfig.command) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\t\tconst schema = buildMcpSchema(tool);\n\t\t\t\t\t\tconst capturedServer = serverConfig.name;\n\t\t\t\t\t\tconst capturedTool = tool.name;\n\n\t\t\t\t\t\tpi.registerTool({\n\t\t\t\t\t\t\tname: toolName,\n\t\t\t\t\t\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\t\t\t\t\t\tdescription: tool.description,\n\t\t\t\t\t\t\tparameters: schema,\n\t\t\t\t\t\t\tasync execute(\n\t\t\t\t\t\t\t\t_toolCallId: string,\n\t\t\t\t\t\t\t\tparams: Static<typeof schema>,\n\t\t\t\t\t\t\t\tsignal: AbortSignal,\n\t\t\t\t\t\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t\t\t\t\t\t): Promise<AgentToolResult<undefined>> {\n\t\t\t\t\t\t\t\tconst activeConn = mcpConnections.get(capturedServer);\n\t\t\t\t\t\t\t\tif (!activeConn) {\n\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\t\ttext: `MCP server \"${capturedServer}\" is not connected`,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\t\t\t\t\tactiveConn.rpc(\"tools/call\", {\n\t\t\t\t\t\t\t\t\t\tname: capturedTool,\n\t\t\t\t\t\t\t\t\t\targuments: params,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\tabortPromise,\n\t\t\t\t\t\t\t\t]);\n\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"})`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n}\n\n// ============================================================================\n// C. Mode + Profile System\n// ============================================================================\n\nconst DEFAULT_MODE = \"build\";\nconst DEFAULT_PROFILE = \"default\";\n\n/**\n * Returns true if `marker` matches something in `cwd`.\n * Plain markers use existsSync. Glob markers (containing `*`) scan the\n * immediate directory entries — only one level, no recursion needed for\n * common cases like `*.sql` or `k8s/`.\n */\nfunction markerExists(cwd: string, marker: string): boolean {\n\tif (!marker.includes(\"*\")) return existsSync(join(cwd, marker));\n\tconst suffix = marker.replace(/^\\*/, \"\");\n\ttry {\n\t\treturn readdirSync(cwd).some((entry) => entry.endsWith(suffix));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Resolves which profile should be active.\n * Priority: config override → file-marker detection → \"default\"\n */\nexport function resolveProfile(config: HooConfig, cwd: string): string {\n\tif (config.active_profile) return config.active_profile;\n\tfor (const detector of config.profile_detectors ?? []) {\n\t\tif (markerExists(cwd, detector.marker)) return detector.profile;\n\t}\n\treturn DEFAULT_PROFILE;\n}\n\n/**\n * Merges the system prompt from up to three layers (lowest → highest priority):\n * 1. ~/.hoocode/templates/modes/{mode}/system.md (mode behaviour)\n * 2. ~/.hoocode/templates/profiles/{profile}/context.md (domain context; skipped for \"default\")\n * 3. ./.hoocode/agents.md (project-local override; appended last)\n *\n * Each present layer is joined with a `---` separator.\n */\nexport function buildSystemPrompt(mode: string, profile: string, cwd: string): string | undefined {\n\tconst layers: string[] = [];\n\n\tfunction tryRead(path: string): string | undefined {\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst text = readFileSync(path, \"utf8\").trim();\n\t\t\treturn text || undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t// Layer 1: mode system prompt (~/.hoocode/modes/{mode}/system.md)\n\tconst modePrompt = tryRead(join(HOOCODE_DIR, \"modes\", mode, \"system.md\"));\n\tif (modePrompt) layers.push(modePrompt);\n\n\t// Layer 2: profile context — omit for \"default\" (no extra domain constraints)\n\t// (~/.hoocode/profiles/{profile}/context.md)\n\tif (profile !== DEFAULT_PROFILE) {\n\t\tconst profileContext = tryRead(join(HOOCODE_DIR, \"profiles\", profile, \"context.md\"));\n\t\tif (profileContext) layers.push(profileContext);\n\t}\n\n\t// Layer 3: project-local agents.md — appended after mode + profile so it can\n\t// extend or override them for this specific repo\n\tconst projectOverride = tryRead(join(cwd, \".hoocode\", \"agents.md\"));\n\tif (projectOverride) layers.push(projectOverride);\n\n\treturn layers.length > 0 ? layers.join(\"\\n\\n---\\n\\n\") : undefined;\n}\n\n// ============================================================================\n// Plan file: section parsing and step-by-step execution message\n// ============================================================================\n\nexport interface PlanSections {\n\tgoal?: string;\n\tfilesToModify?: string;\n\tnewFiles?: string;\n\ttests?: string;\n\tverification?: string;\n\t/** Original full text, used as fallback if no sections parsed */\n\traw: string;\n}\n\n/**\n * Parses `.hoocode/plan.md` into named sections.\n *\n * Recognises both ATX headings (`## Goal`) and bold labels (`**Goal**`).\n * Section names matched (case-insensitive): Goal, Files to modify, New files,\n * Tests, Verification.\n */\nexport function parsePlanSections(planContent: string): PlanSections {\n\tconst result: PlanSections = { raw: planContent };\n\n\t// Match `## Heading text` or `**Heading text**` followed by content until\n\t// the next heading of the same style.\n\tconst sectionPattern =\n\t\t/^(?:#{1,3}\\s+(.+?)|(?:\\*\\*(.+?)\\*\\*))\\s*\\n([\\s\\S]*?)(?=(?:^#{1,3}\\s+|\\*\\*[^*\\n]+\\*\\*\\s*\\n)|$)/gm;\n\n\tfor (const match of planContent.matchAll(sectionPattern)) {\n\t\tconst heading = (match[1] ?? match[2] ?? \"\").toLowerCase().trim();\n\t\tconst content = match[3].trim();\n\t\tif (!content) continue;\n\n\t\tif (/^goal/.test(heading)) {\n\t\t\tresult.goal = content;\n\t\t} else if (/files?\\s+to\\s+modif|^modif/.test(heading)) {\n\t\t\tresult.filesToModify = content;\n\t\t} else if (/new\\s+files?/.test(heading)) {\n\t\t\tresult.newFiles = content;\n\t\t} else if (/^tests?/.test(heading)) {\n\t\t\tresult.tests = content;\n\t\t} else if (/^verif/.test(heading)) {\n\t\t\tresult.verification = content;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Builds the user message sent to the agent when `/approve` is run.\n *\n * If the plan has recognisable sections, each is presented as a numbered step\n * so the agent works through them sequentially. Otherwise the raw plan is used.\n *\n * Execution order:\n * 1. Modify existing files\n * 2. Create new files\n * 3. Update / add tests\n * 4. Run verification commands\n */\nexport function buildApproveMessage(sections: PlanSections): string {\n\tconst steps: string[] = [];\n\n\tif (sections.goal) {\n\t\tsteps.push(`**Goal:** ${sections.goal}`);\n\t}\n\tif (sections.filesToModify) {\n\t\tsteps.push(`**Step 1 — Modify existing files:**\\n${sections.filesToModify}`);\n\t}\n\tif (sections.newFiles) {\n\t\tsteps.push(`**Step 2 — Create new files:**\\n${sections.newFiles}`);\n\t}\n\tif (sections.tests) {\n\t\tsteps.push(`**Step 3 — Update tests:**\\n${sections.tests}`);\n\t}\n\tif (sections.verification) {\n\t\tsteps.push(`**Step 4 — Verify:**\\n${sections.verification}`);\n\t}\n\n\tif (steps.length === 0) {\n\t\treturn `Execute the following plan:\\n\\n${sections.raw}`;\n\t}\n\n\treturn `Execute this plan step by step. Complete each step fully before moving to the next.\\n\\n${steps.join(\"\\n\\n\")}`;\n}\n\n// ============================================================================\n// C. setupModeAndProfile\n// ============================================================================\n\nexport function setupModeAndProfile(pi: ExtensionAPI): void {\n\tlet cachedMode = DEFAULT_MODE;\n\tlet cachedProfile = DEFAULT_PROFILE;\n\tlet cachedSystemPrompt: string | undefined;\n\n\t// ── session_start ─────────────────────────────────────────────────────────\n\t// Config resolution order:\n\t// 1. Read global config (~/.hoocode/agent/hoo-config.json)\n\t// 2. Read project config (./.hoocode/config.json) if present\n\t// 3. Merge — project scalars win; arrays are unioned; project detectors prepend\n\t// 4. Re-resolve active_mode and active_profile from the merged result\n\n\tpi.on(\"session_start\", (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// Steps 1–3: merge global + project configs\n\t\tconst config = readMergedConfig(ctx.cwd);\n\n\t\t// Step 4: resolve mode and profile from the merged config\n\t\tcachedMode = config.active_mode ?? DEFAULT_MODE;\n\t\tcachedProfile = resolveProfile(config, ctx.cwd);\n\t\tcachedSystemPrompt = buildSystemPrompt(cachedMode, cachedProfile, ctx.cwd);\n\n\t\t// Update footer with active mode/profile\n\t\tif (ctx.hasUI) {\n\t\t\tctx.ui.setModeProfile(cachedMode, cachedProfile);\n\t\t}\n\n\t\t// Apply tool filter: mode enabled_tools takes priority, then profile\n\t\tconst modeCfg = config.modes?.[cachedMode];\n\t\tconst profileCfg = config.profiles?.[cachedProfile];\n\t\tif (modeCfg?.enabled_tools && modeCfg.enabled_tools.length > 0) {\n\t\t\tpi.setActiveTools(modeCfg.enabled_tools);\n\t\t} else if (profileCfg?.enabled_tools && profileCfg.enabled_tools.length > 0) {\n\t\t\tpi.setActiveTools(profileCfg.enabled_tools);\n\t\t}\n\t});\n\n\t// ── before_agent_start ────────────────────────────────────────────────────\n\n\tpi.on(\"before_agent_start\", (event: BeforeAgentStartEvent): BeforeAgentStartEventResult | undefined => {\n\t\tif (!cachedSystemPrompt) return;\n\t\treturn {\n\t\t\tsystemPrompt:\n\t\t\t\t`${event.systemPrompt}\\n\\n` +\n\t\t\t\t`<!-- hoo-core: mode=${cachedMode} profile=${cachedProfile} -->\\n` +\n\t\t\t\tcachedSystemPrompt,\n\t\t};\n\t});\n\n\t// ── /mode command ─────────────────────────────────────────────────────────\n\n\tconst KNOWN_MODES = [\"ask\", \"plan\", \"build\", \"agent\", \"debug\"];\n\n\tpi.registerCommand(\"mode\", {\n\t\tdescription: \"Switch active mode. Usage: /mode <ask|plan|build|agent|debug>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tKNOWN_MODES.filter((m) => m.startsWith(prefix)).map((m) => ({ value: m, label: m })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tif (!name) {\n\t\t\t\tctx.ui.notify(`Active mode: ${cachedMode}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = name === DEFAULT_MODE ? undefined : name;\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Mode set to \"${name}\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /profile command ──────────────────────────────────────────────────────\n\n\tpi.registerCommand(\"profile\", {\n\t\tdescription: \"Switch active profile. Usage: /profile <name>\",\n\t\tgetArgumentCompletions: (prefix: string) => {\n\t\t\t// Show profiles from the merged config so project-local profiles appear\n\t\t\tconst config = readMergedConfig(\".\");\n\t\t\tconst names = Object.keys(config.profiles ?? {});\n\t\t\tconst suggestions = [DEFAULT_PROFILE, ...names.filter((n) => n !== DEFAULT_PROFILE)];\n\t\t\treturn suggestions.filter((n) => n.startsWith(prefix)).map((n) => ({ value: n, label: n }));\n\t\t},\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tif (!name) {\n\t\t\t\tctx.ui.notify(`Active profile: ${cachedProfile}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_profile = name === DEFAULT_PROFILE ? undefined : name;\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Profile set to \"${name}\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /plan command (shorthand for /mode plan) ──────────────────────────────\n\n\tpi.registerCommand(\"plan\", {\n\t\tdescription: \"Switch to plan mode. Shorthand for /mode plan.\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = \"plan\";\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Mode set to \"plan\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /approve command ──────────────────────────────────────────────────────\n\t// Reads .hoocode/plan.md, parses it into named sections (Goal, Files to\n\t// modify, New files, Tests, Verification), switches to build mode, then\n\t// injects a step-by-step execution message into the new session.\n\n\tpi.registerCommand(\"approve\", {\n\t\tdescription: \"Approve the current plan and switch to build mode to execute it.\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tif (cachedMode !== \"plan\") {\n\t\t\t\tctx.ui.notify(`/approve is only available in plan mode (current mode: \"${cachedMode}\")`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Read ./.hoocode/plan.md written by the agent during plan mode\n\t\t\tconst planPath = join(ctx.cwd, \".hoocode\", \"plan.md\");\n\t\t\tlet approveMessage: string | undefined;\n\n\t\t\tif (existsSync(planPath)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst raw = readFileSync(planPath, \"utf8\").trim();\n\t\t\t\t\tif (raw) {\n\t\t\t\t\t\tconst sections = parsePlanSections(raw);\n\t\t\t\t\t\tapproveMessage = buildApproveMessage(sections);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\tctx.ui.notify(`Could not read .hoocode/plan.md`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Switch global config to build mode\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = \"build\";\n\t\t\twriteConfig(config);\n\n\t\t\tif (approveMessage) {\n\t\t\t\t// Open a new build-mode session and deliver the parsed plan as the\n\t\t\t\t// first user message so the agent starts executing immediately\n\t\t\t\tawait ctx.newSession({\n\t\t\t\t\twithSession: async (replacedCtx) => {\n\t\t\t\t\t\tawait replacedCtx.sendUserMessage(approveMessage!, { deliverAs: \"followUp\" });\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tctx.ui.notify(`Switched to build mode. No .hoocode/plan.md found — describe what to build.`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t}\n\t\t},\n\t});\n}\n\n// ============================================================================\n// Extension entry point\n// ============================================================================\n\nexport default function hooCore(pi: ExtensionAPI): void {\n\tsetupPermissionGate(pi);\n\tsetupMcpLoader(pi);\n\tsetupModeAndProfile(pi);\n}\n"]}
|
|
@@ -52,10 +52,12 @@ function writeConfig(config) {
|
|
|
52
52
|
* Merge rules:
|
|
53
53
|
* - Scalars (active_mode, active_profile): project wins if set
|
|
54
54
|
* - modes[x].auto_allow: union of global + project arrays
|
|
55
|
+
* - modes[x].allowed_write_paths: union of global + project arrays
|
|
56
|
+
* - modes[x].enabled_tools: project wins if set, else falls back to global
|
|
55
57
|
* - profiles[x].enabled_tools: project wins if set, else falls back to global
|
|
56
58
|
* - profile_detectors: project list is prepended so project markers are checked first
|
|
57
59
|
*/
|
|
58
|
-
function mergeConfigs(global, project) {
|
|
60
|
+
export function mergeConfigs(global, project) {
|
|
59
61
|
const merged = { ...global };
|
|
60
62
|
if (project.active_mode !== undefined)
|
|
61
63
|
merged.active_mode = project.active_mode;
|
|
@@ -70,6 +72,10 @@ function mergeConfigs(global, project) {
|
|
|
70
72
|
...projectCfg,
|
|
71
73
|
// Union both auto_allow lists so project can extend, not just replace
|
|
72
74
|
auto_allow: Array.from(new Set([...(globalCfg.auto_allow ?? []), ...(projectCfg.auto_allow ?? [])])),
|
|
75
|
+
// Union allowed_write_paths so project can extend
|
|
76
|
+
allowed_write_paths: Array.from(new Set([...(globalCfg.allowed_write_paths ?? []), ...(projectCfg.allowed_write_paths ?? [])])),
|
|
77
|
+
// enabled_tools: project wins if set, else falls back to global
|
|
78
|
+
enabled_tools: projectCfg.enabled_tools ?? globalCfg.enabled_tools,
|
|
73
79
|
};
|
|
74
80
|
}
|
|
75
81
|
}
|
|
@@ -93,7 +99,7 @@ function mergeConfigs(global, project) {
|
|
|
93
99
|
* `./.hoocode/config.json`. Project values win on all scalar fields; arrays are
|
|
94
100
|
* unioned (see mergeConfigs for full rules).
|
|
95
101
|
*/
|
|
96
|
-
function readMergedConfig(cwd) {
|
|
102
|
+
export function readMergedConfig(cwd) {
|
|
97
103
|
const global = readConfig();
|
|
98
104
|
const projectPath = join(cwd, ".hoocode", "config.json");
|
|
99
105
|
if (!existsSync(projectPath))
|
|
@@ -110,6 +116,26 @@ function readMergedConfig(cwd) {
|
|
|
110
116
|
// A. Permission Gate
|
|
111
117
|
// ============================================================================
|
|
112
118
|
const GATED_TOOLS = new Set(["bash", "write", "edit"]);
|
|
119
|
+
/**
|
|
120
|
+
* Checks if a file path matches any of the allowed patterns.
|
|
121
|
+
* Supports glob patterns with * and exact paths.
|
|
122
|
+
*/
|
|
123
|
+
function matchesAllowedPath(filePath, allowedPatterns) {
|
|
124
|
+
if (allowedPatterns.length === 0)
|
|
125
|
+
return true;
|
|
126
|
+
for (const pattern of allowedPatterns) {
|
|
127
|
+
// Exact match
|
|
128
|
+
if (pattern === filePath)
|
|
129
|
+
return true;
|
|
130
|
+
// Glob pattern matching for *
|
|
131
|
+
if (pattern.includes("*")) {
|
|
132
|
+
const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
|
|
133
|
+
if (regex.test(filePath))
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
113
139
|
function describeTool(event) {
|
|
114
140
|
if (isToolCallEventType("bash", event)) {
|
|
115
141
|
return `$ ${event.input.command.replace(/\s+/g, " ").slice(0, 100)}`;
|
|
@@ -131,7 +157,20 @@ export function setupPermissionGate(pi) {
|
|
|
131
157
|
// Use the merged config so project-local auto_allow entries are respected
|
|
132
158
|
const config = readMergedConfig(ctx.cwd);
|
|
133
159
|
const mode = config.active_mode ?? "build";
|
|
134
|
-
const
|
|
160
|
+
const modeCfg = config.modes?.[mode];
|
|
161
|
+
const autoAllow = modeCfg?.auto_allow ?? [];
|
|
162
|
+
// Check allowed_write_paths for write/edit operations
|
|
163
|
+
if ((event.toolName === "write" || event.toolName === "edit") && modeCfg?.allowed_write_paths) {
|
|
164
|
+
const filePath = event.input.file_path ?? "";
|
|
165
|
+
if (!matchesAllowedPath(filePath, modeCfg.allowed_write_paths)) {
|
|
166
|
+
return {
|
|
167
|
+
block: true,
|
|
168
|
+
reason: `Mode "${mode}" only allows writes to: ${modeCfg.allowed_write_paths.join(", ")}. ` +
|
|
169
|
+
`Attempted to ${event.toolName}: ${filePath}. ` +
|
|
170
|
+
`Switch to "/mode build" or "/mode agent" to modify source files.`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
135
174
|
if (autoAllow.includes(event.toolName))
|
|
136
175
|
return;
|
|
137
176
|
const choice = await ctx.ui.select(`Allow: ${describeTool(event)}`, [
|
|
@@ -482,9 +521,13 @@ export function setupModeAndProfile(pi) {
|
|
|
482
521
|
if (ctx.hasUI) {
|
|
483
522
|
ctx.ui.setModeProfile(cachedMode, cachedProfile);
|
|
484
523
|
}
|
|
485
|
-
// Apply tool filter
|
|
524
|
+
// Apply tool filter: mode enabled_tools takes priority, then profile
|
|
525
|
+
const modeCfg = config.modes?.[cachedMode];
|
|
486
526
|
const profileCfg = config.profiles?.[cachedProfile];
|
|
487
|
-
if (
|
|
527
|
+
if (modeCfg?.enabled_tools && modeCfg.enabled_tools.length > 0) {
|
|
528
|
+
pi.setActiveTools(modeCfg.enabled_tools);
|
|
529
|
+
}
|
|
530
|
+
else if (profileCfg?.enabled_tools && profileCfg.enabled_tools.length > 0) {
|
|
488
531
|
pi.setActiveTools(profileCfg.enabled_tools);
|
|
489
532
|
}
|
|
490
533
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hoo-core.js","sourceRoot":"","sources":["../../../src/extensions/core/hoo-core.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * hoo-core — HooCode built-in core extension\n *\n * A. Permission Gate — prompts before bash/write/edit; checks modes.{mode}.auto_allow\n * from the merged (global + project) config; persists \"always\"\n * choices back to the global config\n * B. MCP Server Loader — discovers ~/.hoocode/mcp-servers and ./.hoocode/mcp-servers JSON\n * configs, connects via JSON-RPC 2.0, registers server tools\n * C. Mode + Profile — resolves active mode (ask/plan/build/agent/debug) and profile\n * (default/data/devops/…), merges system prompt from three template\n * layers, filters active tools, and exposes /mode, /profile,\n * /plan, and /approve commands\n *\n * Config merge order (lowest → highest priority):\n * 1. ~/.hoocode/agent/hoo-config.json (global defaults)\n * 2. ./.hoocode/config.json (project overrides — scalars win; arrays union)\n * 3. profile_detectors from project prepend global list (project markers checked first)\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { type Static, Type } from \"typebox\";\nimport type {\n\tAgentToolResult,\n\tAgentToolUpdateCallback,\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tSessionStartEvent,\n\tToolCallEvent,\n\tToolCallEventResult,\n} from \"../../core/extensions/types.js\";\nimport { isToolCallEventType } from \"../../core/extensions/types.js\";\n\n// ============================================================================\n// Shared paths\n// ============================================================================\n\nconst HOOCODE_DIR = join(homedir(), \".hoocode\");\nconst GLOBAL_CONFIG_PATH = join(HOOCODE_DIR, \"agent\", \"hoo-config.json\");\n\n// ============================================================================\n// Config types\n// ============================================================================\n\ninterface ProfileDetector {\n\tmarker: string;\n\tprofile: string;\n}\n\ninterface ModeConfig {\n\t/** Tool names that bypass the permission gate in this mode */\n\tauto_allow?: string[];\n}\n\ninterface ProfileConfig {\n\t/** Tool names to activate for this profile */\n\tenabled_tools?: string[];\n}\n\nexport interface HooConfig {\n\t/** Manually-pinned active mode (overrides default \"build\") */\n\tactive_mode?: string;\n\t/** Manually-pinned active profile (overrides auto-detection) */\n\tactive_profile?: string;\n\t/** Per-mode configuration keyed by mode name */\n\tmodes?: Record<string, ModeConfig>;\n\t/** Per-profile configuration keyed by profile name */\n\tprofiles?: Record<string, ProfileConfig>;\n\t/** Ordered list of file-marker → profile mappings for auto-detection */\n\tprofile_detectors?: ProfileDetector[];\n}\n\n// ============================================================================\n// Config I/O and merging\n// ============================================================================\n\nfunction 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\nfunction writeConfig(config: HooConfig): void {\n\tconst dir = join(HOOCODE_DIR, \"agent\");\n\tif (!existsSync(dir)) mkdirSync(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 * - Scalars (active_mode, active_profile): project wins if set\n * - modes[x].auto_allow: union of global + project arrays\n * - profiles[x].enabled_tools: project wins if set, else falls back to global\n * - profile_detectors: project list is prepended so project markers are checked first\n */\nfunction 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\tif (project.active_profile !== undefined) merged.active_profile = project.active_profile;\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};\n\t\t}\n\t}\n\n\tif (project.profiles) {\n\t\tmerged.profiles = { ...(global.profiles ?? {}) };\n\t\tfor (const [profile, projectCfg] of Object.entries(project.profiles)) {\n\t\t\tmerged.profiles[profile] = {\n\t\t\t\t...(global.profiles?.[profile] ?? {}),\n\t\t\t\t...projectCfg,\n\t\t\t};\n\t\t}\n\t}\n\n\tif (project.profile_detectors) {\n\t\t// Project detectors are prepended: project-specific markers are checked first\n\t\tmerged.profile_detectors = [...project.profile_detectors, ...(global.profile_detectors ?? [])];\n\t}\n\n\treturn merged;\n}\n\n/**\n * Reads the global config and optionally overlays the project-local config at\n * `./.hoocode/config.json`. Project values win on all scalar fields; arrays are\n * unioned (see mergeConfigs for full rules).\n */\nfunction readMergedConfig(cwd: string): HooConfig {\n\tconst global = readConfig();\n\tconst projectPath = join(cwd, \".hoocode\", \"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\n// ============================================================================\n// A. Permission Gate\n// ============================================================================\n\nconst GATED_TOOLS = new Set([\"bash\", \"write\", \"edit\"]);\n\nfunction describeTool(event: ToolCallEvent): string {\n\tif (isToolCallEventType(\"bash\", event)) {\n\t\treturn `$ ${event.input.command.replace(/\\s+/g, \" \").slice(0, 100)}`;\n\t}\n\tif (isToolCallEventType(\"edit\", event)) {\n\t\tconst p = (event.input as { file_path?: string }).file_path ?? \"(unknown)\";\n\t\treturn `edit ${p}`;\n\t}\n\tif (isToolCallEventType(\"write\", event)) {\n\t\tconst p = (event.input as { file_path?: string }).file_path ?? \"(unknown)\";\n\t\treturn `write ${p}`;\n\t}\n\treturn event.toolName;\n}\n\nexport function setupPermissionGate(pi: ExtensionAPI): void {\n\tpi.on(\"tool_call\", async (event: ToolCallEvent, ctx: ExtensionContext): Promise<ToolCallEventResult | undefined> => {\n\t\tif (!GATED_TOOLS.has(event.toolName) || !ctx.hasUI) return;\n\n\t\t// Use the merged config so project-local auto_allow entries are respected\n\t\tconst config = readMergedConfig(ctx.cwd);\n\t\tconst mode = config.active_mode ?? \"build\";\n\t\tconst autoAllow = config.modes?.[mode]?.auto_allow ?? [];\n\t\tif (autoAllow.includes(event.toolName)) return;\n\n\t\tconst choice = await ctx.ui.select(`Allow: ${describeTool(event)}`, [\n\t\t\t\"Yes (once)\",\n\t\t\t\"No (block)\",\n\t\t\t\"Always (add to auto-allow for this mode)\",\n\t\t]);\n\n\t\tif (!choice || choice.startsWith(\"No\")) {\n\t\t\treturn { block: true, reason: \"Denied by permission gate\" };\n\t\t}\n\n\t\tif (choice.startsWith(\"Always\")) {\n\t\t\t// Write \"always\" choices to the global config only\n\t\t\tconst latest = readConfig();\n\t\t\tconst currentMode = latest.active_mode ?? \"build\";\n\t\t\tlatest.modes ??= {};\n\t\t\tlatest.modes[currentMode] ??= {};\n\t\t\tlatest.modes[currentMode].auto_allow = Array.from(\n\t\t\t\tnew Set([...(latest.modes[currentMode].auto_allow ?? []), event.toolName]),\n\t\t\t);\n\t\t\twriteConfig(latest);\n\t\t\tctx.ui.notify(`\"${event.toolName}\" added to auto-allow for mode \"${currentMode}\"`, \"info\");\n\t\t}\n\t});\n}\n\n// ============================================================================\n// B. MCP Server Loader\n// ============================================================================\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn */\n\tcommand: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown): Promise<unknown>;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n\nfunction spawnMcpServer(config: McpServerConfig): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tpending.set(id, { resolve, reject });\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\treturn {\n\t\trpc,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = spawnMcpServer(config);\n\tmcpConnections.set(config.name, conn);\n\n\tawait conn.rpc(\"initialize\", {\n\t\tprotocolVersion: \"2024-11-05\",\n\t\tcapabilities: { tools: {} },\n\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t});\n\n\tconst toolsResult = (await conn.rpc(\"tools/list\", {})) as {\n\t\ttools?: McpToolDef[];\n\t};\n\treturn { conn, tools: toolsResult.tools ?? [] };\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || !serverConfig.command) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\t\tconst schema = buildMcpSchema(tool);\n\t\t\t\t\t\tconst capturedServer = serverConfig.name;\n\t\t\t\t\t\tconst capturedTool = tool.name;\n\n\t\t\t\t\t\tpi.registerTool({\n\t\t\t\t\t\t\tname: toolName,\n\t\t\t\t\t\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\t\t\t\t\t\tdescription: tool.description,\n\t\t\t\t\t\t\tparameters: schema,\n\t\t\t\t\t\t\tasync execute(\n\t\t\t\t\t\t\t\t_toolCallId: string,\n\t\t\t\t\t\t\t\tparams: Static<typeof schema>,\n\t\t\t\t\t\t\t\tsignal: AbortSignal,\n\t\t\t\t\t\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t\t\t\t\t\t): Promise<AgentToolResult<undefined>> {\n\t\t\t\t\t\t\t\tconst activeConn = mcpConnections.get(capturedServer);\n\t\t\t\t\t\t\t\tif (!activeConn) {\n\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\t\ttext: `MCP server \"${capturedServer}\" is not connected`,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\t\t\t\t\tactiveConn.rpc(\"tools/call\", {\n\t\t\t\t\t\t\t\t\t\tname: capturedTool,\n\t\t\t\t\t\t\t\t\t\targuments: params,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\tabortPromise,\n\t\t\t\t\t\t\t\t]);\n\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"})`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n}\n\n// ============================================================================\n// C. Mode + Profile System\n// ============================================================================\n\nconst DEFAULT_MODE = \"build\";\nconst DEFAULT_PROFILE = \"default\";\n\n/**\n * Returns true if `marker` matches something in `cwd`.\n * Plain markers use existsSync. Glob markers (containing `*`) scan the\n * immediate directory entries — only one level, no recursion needed for\n * common cases like `*.sql` or `k8s/`.\n */\nfunction markerExists(cwd: string, marker: string): boolean {\n\tif (!marker.includes(\"*\")) return existsSync(join(cwd, marker));\n\tconst suffix = marker.replace(/^\\*/, \"\");\n\ttry {\n\t\treturn readdirSync(cwd).some((entry) => entry.endsWith(suffix));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Resolves which profile should be active.\n * Priority: config override → file-marker detection → \"default\"\n */\nexport function resolveProfile(config: HooConfig, cwd: string): string {\n\tif (config.active_profile) return config.active_profile;\n\tfor (const detector of config.profile_detectors ?? []) {\n\t\tif (markerExists(cwd, detector.marker)) return detector.profile;\n\t}\n\treturn DEFAULT_PROFILE;\n}\n\n/**\n * Merges the system prompt from up to three layers (lowest → highest priority):\n * 1. ~/.hoocode/templates/modes/{mode}/system.md (mode behaviour)\n * 2. ~/.hoocode/templates/profiles/{profile}/context.md (domain context; skipped for \"default\")\n * 3. ./.hoocode/agents.md (project-local override; appended last)\n *\n * Each present layer is joined with a `---` separator.\n */\nexport function buildSystemPrompt(mode: string, profile: string, cwd: string): string | undefined {\n\tconst layers: string[] = [];\n\n\tfunction tryRead(path: string): string | undefined {\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst text = readFileSync(path, \"utf8\").trim();\n\t\t\treturn text || undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t// Layer 1: mode system prompt (~/.hoocode/modes/{mode}/system.md)\n\tconst modePrompt = tryRead(join(HOOCODE_DIR, \"modes\", mode, \"system.md\"));\n\tif (modePrompt) layers.push(modePrompt);\n\n\t// Layer 2: profile context — omit for \"default\" (no extra domain constraints)\n\t// (~/.hoocode/profiles/{profile}/context.md)\n\tif (profile !== DEFAULT_PROFILE) {\n\t\tconst profileContext = tryRead(join(HOOCODE_DIR, \"profiles\", profile, \"context.md\"));\n\t\tif (profileContext) layers.push(profileContext);\n\t}\n\n\t// Layer 3: project-local agents.md — appended after mode + profile so it can\n\t// extend or override them for this specific repo\n\tconst projectOverride = tryRead(join(cwd, \".hoocode\", \"agents.md\"));\n\tif (projectOverride) layers.push(projectOverride);\n\n\treturn layers.length > 0 ? layers.join(\"\\n\\n---\\n\\n\") : undefined;\n}\n\n// ============================================================================\n// Plan file: section parsing and step-by-step execution message\n// ============================================================================\n\ninterface PlanSections {\n\tgoal?: string;\n\tfilesToModify?: string;\n\tnewFiles?: string;\n\ttests?: string;\n\tverification?: string;\n\t/** Original full text, used as fallback if no sections parsed */\n\traw: string;\n}\n\n/**\n * Parses `.hoocode/plan.md` into named sections.\n *\n * Recognises both ATX headings (`## Goal`) and bold labels (`**Goal**`).\n * Section names matched (case-insensitive): Goal, Files to modify, New files,\n * Tests, Verification.\n */\nexport function parsePlanSections(planContent: string): PlanSections {\n\tconst result: PlanSections = { raw: planContent };\n\n\t// Match `## Heading text` or `**Heading text**` followed by content until\n\t// the next heading of the same style.\n\tconst sectionPattern =\n\t\t/^(?:#{1,3}\\s+(.+?)|(?:\\*\\*(.+?)\\*\\*))\\s*\\n([\\s\\S]*?)(?=(?:^#{1,3}\\s+|\\*\\*[^*\\n]+\\*\\*\\s*\\n)|$)/gm;\n\n\tfor (const match of planContent.matchAll(sectionPattern)) {\n\t\tconst heading = (match[1] ?? match[2] ?? \"\").toLowerCase().trim();\n\t\tconst content = match[3].trim();\n\t\tif (!content) continue;\n\n\t\tif (/^goal/.test(heading)) {\n\t\t\tresult.goal = content;\n\t\t} else if (/files?\\s+to\\s+modif|^modif/.test(heading)) {\n\t\t\tresult.filesToModify = content;\n\t\t} else if (/new\\s+files?/.test(heading)) {\n\t\t\tresult.newFiles = content;\n\t\t} else if (/^tests?/.test(heading)) {\n\t\t\tresult.tests = content;\n\t\t} else if (/^verif/.test(heading)) {\n\t\t\tresult.verification = content;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Builds the user message sent to the agent when `/approve` is run.\n *\n * If the plan has recognisable sections, each is presented as a numbered step\n * so the agent works through them sequentially. Otherwise the raw plan is used.\n *\n * Execution order:\n * 1. Modify existing files\n * 2. Create new files\n * 3. Update / add tests\n * 4. Run verification commands\n */\nexport function buildApproveMessage(sections: PlanSections): string {\n\tconst steps: string[] = [];\n\n\tif (sections.goal) {\n\t\tsteps.push(`**Goal:** ${sections.goal}`);\n\t}\n\tif (sections.filesToModify) {\n\t\tsteps.push(`**Step 1 — Modify existing files:**\\n${sections.filesToModify}`);\n\t}\n\tif (sections.newFiles) {\n\t\tsteps.push(`**Step 2 — Create new files:**\\n${sections.newFiles}`);\n\t}\n\tif (sections.tests) {\n\t\tsteps.push(`**Step 3 — Update tests:**\\n${sections.tests}`);\n\t}\n\tif (sections.verification) {\n\t\tsteps.push(`**Step 4 — Verify:**\\n${sections.verification}`);\n\t}\n\n\tif (steps.length === 0) {\n\t\treturn `Execute the following plan:\\n\\n${sections.raw}`;\n\t}\n\n\treturn `Execute this plan step by step. Complete each step fully before moving to the next.\\n\\n${steps.join(\"\\n\\n\")}`;\n}\n\n// ============================================================================\n// C. setupModeAndProfile\n// ============================================================================\n\nexport function setupModeAndProfile(pi: ExtensionAPI): void {\n\tlet cachedMode = DEFAULT_MODE;\n\tlet cachedProfile = DEFAULT_PROFILE;\n\tlet cachedSystemPrompt: string | undefined;\n\n\t// ── session_start ─────────────────────────────────────────────────────────\n\t// Config resolution order:\n\t// 1. Read global config (~/.hoocode/agent/hoo-config.json)\n\t// 2. Read project config (./.hoocode/config.json) if present\n\t// 3. Merge — project scalars win; arrays are unioned; project detectors prepend\n\t// 4. Re-resolve active_mode and active_profile from the merged result\n\n\tpi.on(\"session_start\", (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// Steps 1–3: merge global + project configs\n\t\tconst config = readMergedConfig(ctx.cwd);\n\n\t\t// Step 4: resolve mode and profile from the merged config\n\t\tcachedMode = config.active_mode ?? DEFAULT_MODE;\n\t\tcachedProfile = resolveProfile(config, ctx.cwd);\n\t\tcachedSystemPrompt = buildSystemPrompt(cachedMode, cachedProfile, ctx.cwd);\n\n\t\t// Update footer with active mode/profile\n\t\tif (ctx.hasUI) {\n\t\t\tctx.ui.setModeProfile(cachedMode, cachedProfile);\n\t\t}\n\n\t\t// Apply tool filter defined by the active profile\n\t\tconst profileCfg = config.profiles?.[cachedProfile];\n\t\tif (profileCfg?.enabled_tools && profileCfg.enabled_tools.length > 0) {\n\t\t\tpi.setActiveTools(profileCfg.enabled_tools);\n\t\t}\n\t});\n\n\t// ── before_agent_start ────────────────────────────────────────────────────\n\n\tpi.on(\"before_agent_start\", (event: BeforeAgentStartEvent): BeforeAgentStartEventResult | undefined => {\n\t\tif (!cachedSystemPrompt) return;\n\t\treturn {\n\t\t\tsystemPrompt:\n\t\t\t\t`${event.systemPrompt}\\n\\n` +\n\t\t\t\t`<!-- hoo-core: mode=${cachedMode} profile=${cachedProfile} -->\\n` +\n\t\t\t\tcachedSystemPrompt,\n\t\t};\n\t});\n\n\t// ── /mode command ─────────────────────────────────────────────────────────\n\n\tconst KNOWN_MODES = [\"ask\", \"plan\", \"build\", \"agent\", \"debug\"];\n\n\tpi.registerCommand(\"mode\", {\n\t\tdescription: \"Switch active mode. Usage: /mode <ask|plan|build|agent|debug>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tKNOWN_MODES.filter((m) => m.startsWith(prefix)).map((m) => ({ value: m, label: m })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tif (!name) {\n\t\t\t\tctx.ui.notify(`Active mode: ${cachedMode}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = name === DEFAULT_MODE ? undefined : name;\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Mode set to \"${name}\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /profile command ──────────────────────────────────────────────────────\n\n\tpi.registerCommand(\"profile\", {\n\t\tdescription: \"Switch active profile. Usage: /profile <name>\",\n\t\tgetArgumentCompletions: (prefix: string) => {\n\t\t\t// Show profiles from the merged config so project-local profiles appear\n\t\t\tconst config = readMergedConfig(\".\");\n\t\t\tconst names = Object.keys(config.profiles ?? {});\n\t\t\tconst suggestions = [DEFAULT_PROFILE, ...names.filter((n) => n !== DEFAULT_PROFILE)];\n\t\t\treturn suggestions.filter((n) => n.startsWith(prefix)).map((n) => ({ value: n, label: n }));\n\t\t},\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tif (!name) {\n\t\t\t\tctx.ui.notify(`Active profile: ${cachedProfile}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_profile = name === DEFAULT_PROFILE ? undefined : name;\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Profile set to \"${name}\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /plan command (shorthand for /mode plan) ──────────────────────────────\n\n\tpi.registerCommand(\"plan\", {\n\t\tdescription: \"Switch to plan mode. Shorthand for /mode plan.\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = \"plan\";\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Mode set to \"plan\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /approve command ──────────────────────────────────────────────────────\n\t// Reads .hoocode/plan.md, parses it into named sections (Goal, Files to\n\t// modify, New files, Tests, Verification), switches to build mode, then\n\t// injects a step-by-step execution message into the new session.\n\n\tpi.registerCommand(\"approve\", {\n\t\tdescription: \"Approve the current plan and switch to build mode to execute it.\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tif (cachedMode !== \"plan\") {\n\t\t\t\tctx.ui.notify(`/approve is only available in plan mode (current mode: \"${cachedMode}\")`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Read ./.hoocode/plan.md written by the agent during plan mode\n\t\t\tconst planPath = join(ctx.cwd, \".hoocode\", \"plan.md\");\n\t\t\tlet approveMessage: string | undefined;\n\n\t\t\tif (existsSync(planPath)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst raw = readFileSync(planPath, \"utf8\").trim();\n\t\t\t\t\tif (raw) {\n\t\t\t\t\t\tconst sections = parsePlanSections(raw);\n\t\t\t\t\t\tapproveMessage = buildApproveMessage(sections);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\tctx.ui.notify(`Could not read .hoocode/plan.md`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Switch global config to build mode\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = \"build\";\n\t\t\twriteConfig(config);\n\n\t\t\tif (approveMessage) {\n\t\t\t\t// Open a new build-mode session and deliver the parsed plan as the\n\t\t\t\t// first user message so the agent starts executing immediately\n\t\t\t\tawait ctx.newSession({\n\t\t\t\t\twithSession: async (replacedCtx) => {\n\t\t\t\t\t\tawait replacedCtx.sendUserMessage(approveMessage!, { deliverAs: \"followUp\" });\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tctx.ui.notify(`Switched to build mode. No .hoocode/plan.md found — describe what to build.`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t}\n\t\t},\n\t});\n}\n\n// ============================================================================\n// Extension entry point\n// ============================================================================\n\nexport default function hooCore(pi: ExtensionAPI): void {\n\tsetupPermissionGate(pi);\n\tsetupMcpLoader(pi);\n\tsetupModeAndProfile(pi);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"hoo-core.js","sourceRoot":"","sources":["../../../src/extensions/core/hoo-core.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * hoo-core — HooCode built-in core extension\n *\n * A. Permission Gate — prompts before bash/write/edit; checks modes.{mode}.auto_allow\n * from the merged (global + project) config; persists \"always\"\n * choices back to the global config\n * B. MCP Server Loader — discovers ~/.hoocode/mcp-servers and ./.hoocode/mcp-servers JSON\n * configs, connects via JSON-RPC 2.0, registers server tools\n * C. Mode + Profile — resolves active mode (ask/plan/build/agent/debug) and profile\n * (default/data/devops/…), merges system prompt from three template\n * layers, filters active tools, and exposes /mode, /profile,\n * /plan, and /approve commands\n *\n * Config merge order (lowest → highest priority):\n * 1. ~/.hoocode/agent/hoo-config.json (global defaults)\n * 2. ./.hoocode/config.json (project overrides — scalars win; arrays union)\n * 3. profile_detectors from project prepend global list (project markers checked first)\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { type Static, Type } from \"typebox\";\nimport type {\n\tAgentToolResult,\n\tAgentToolUpdateCallback,\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tSessionStartEvent,\n\tToolCallEvent,\n\tToolCallEventResult,\n} from \"../../core/extensions/types.js\";\nimport { isToolCallEventType } from \"../../core/extensions/types.js\";\n\n// ============================================================================\n// Shared paths\n// ============================================================================\n\nconst HOOCODE_DIR = join(homedir(), \".hoocode\");\nconst GLOBAL_CONFIG_PATH = join(HOOCODE_DIR, \"agent\", \"hoo-config.json\");\n\n// ============================================================================\n// Config types\n// ============================================================================\n\ninterface ProfileDetector {\n\tmarker: string;\n\tprofile: string;\n}\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/** Allowed write paths in this mode (glob patterns, only applies if write/edit is enabled) */\n\tallowed_write_paths?: string[];\n}\n\ninterface ProfileConfig {\n\t/** Tool names to activate for this profile */\n\tenabled_tools?: string[];\n}\n\nexport interface HooConfig {\n\t/** Manually-pinned active mode (overrides default \"build\") */\n\tactive_mode?: string;\n\t/** Manually-pinned active profile (overrides auto-detection) */\n\tactive_profile?: string;\n\t/** Per-mode configuration keyed by mode name */\n\tmodes?: Record<string, ModeConfig>;\n\t/** Per-profile configuration keyed by profile name */\n\tprofiles?: Record<string, ProfileConfig>;\n\t/** Ordered list of file-marker → profile mappings for auto-detection */\n\tprofile_detectors?: ProfileDetector[];\n}\n\n// ============================================================================\n// Config I/O and merging\n// ============================================================================\n\nfunction 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\nfunction writeConfig(config: HooConfig): void {\n\tconst dir = join(HOOCODE_DIR, \"agent\");\n\tif (!existsSync(dir)) mkdirSync(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 * - Scalars (active_mode, active_profile): 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 * - profiles[x].enabled_tools: project wins if set, else falls back to global\n * - profile_detectors: project list is prepended so project markers are checked first\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\tif (project.active_profile !== undefined) merged.active_profile = project.active_profile;\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};\n\t\t}\n\t}\n\n\tif (project.profiles) {\n\t\tmerged.profiles = { ...(global.profiles ?? {}) };\n\t\tfor (const [profile, projectCfg] of Object.entries(project.profiles)) {\n\t\t\tmerged.profiles[profile] = {\n\t\t\t\t...(global.profiles?.[profile] ?? {}),\n\t\t\t\t...projectCfg,\n\t\t\t};\n\t\t}\n\t}\n\n\tif (project.profile_detectors) {\n\t\t// Project detectors are prepended: project-specific markers are checked first\n\t\tmerged.profile_detectors = [...project.profile_detectors, ...(global.profile_detectors ?? [])];\n\t}\n\n\treturn merged;\n}\n\n/**\n * Reads the global config and optionally overlays the project-local config at\n * `./.hoocode/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\", \"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\n// ============================================================================\n// A. Permission Gate\n// ============================================================================\n\nconst GATED_TOOLS = new Set([\"bash\", \"write\", \"edit\"]);\n\n/**\n * Checks if a file path matches any of the allowed patterns.\n * Supports glob patterns with * and exact paths.\n */\nfunction matchesAllowedPath(filePath: string, allowedPatterns: string[]): boolean {\n\tif (allowedPatterns.length === 0) return true;\n\tfor (const pattern of allowedPatterns) {\n\t\t// Exact match\n\t\tif (pattern === filePath) return true;\n\t\t// Glob pattern matching for *\n\t\tif (pattern.includes(\"*\")) {\n\t\t\tconst regex = new RegExp(`^${pattern.replace(/\\*/g, \".*\")}$`);\n\t\t\tif (regex.test(filePath)) return true;\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction describeTool(event: ToolCallEvent): string {\n\tif (isToolCallEventType(\"bash\", event)) {\n\t\treturn `$ ${event.input.command.replace(/\\s+/g, \" \").slice(0, 100)}`;\n\t}\n\tif (isToolCallEventType(\"edit\", event)) {\n\t\tconst p = (event.input as { file_path?: string }).file_path ?? \"(unknown)\";\n\t\treturn `edit ${p}`;\n\t}\n\tif (isToolCallEventType(\"write\", event)) {\n\t\tconst p = (event.input as { file_path?: string }).file_path ?? \"(unknown)\";\n\t\treturn `write ${p}`;\n\t}\n\treturn event.toolName;\n}\n\nexport function setupPermissionGate(pi: ExtensionAPI): void {\n\tpi.on(\"tool_call\", async (event: ToolCallEvent, ctx: ExtensionContext): Promise<ToolCallEventResult | undefined> => {\n\t\tif (!GATED_TOOLS.has(event.toolName) || !ctx.hasUI) return;\n\n\t\t// Use the merged config so project-local auto_allow entries are respected\n\t\tconst config = readMergedConfig(ctx.cwd);\n\t\tconst mode = config.active_mode ?? \"build\";\n\t\tconst modeCfg = config.modes?.[mode];\n\t\tconst autoAllow = modeCfg?.auto_allow ?? [];\n\n\t\t// Check allowed_write_paths for write/edit operations\n\t\tif ((event.toolName === \"write\" || event.toolName === \"edit\") && modeCfg?.allowed_write_paths) {\n\t\t\tconst filePath = (event.input as { file_path?: string }).file_path ?? \"\";\n\t\t\tif (!matchesAllowedPath(filePath, modeCfg.allowed_write_paths)) {\n\t\t\t\treturn {\n\t\t\t\t\tblock: true,\n\t\t\t\t\treason:\n\t\t\t\t\t\t`Mode \"${mode}\" only allows writes to: ${modeCfg.allowed_write_paths.join(\", \")}. ` +\n\t\t\t\t\t\t`Attempted to ${event.toolName}: ${filePath}. ` +\n\t\t\t\t\t\t`Switch to \"/mode build\" or \"/mode agent\" to modify source files.`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif (autoAllow.includes(event.toolName)) return;\n\n\t\tconst choice = await ctx.ui.select(`Allow: ${describeTool(event)}`, [\n\t\t\t\"Yes (once)\",\n\t\t\t\"No (block)\",\n\t\t\t\"Always (add to auto-allow for this mode)\",\n\t\t]);\n\n\t\tif (!choice || choice.startsWith(\"No\")) {\n\t\t\treturn { block: true, reason: \"Denied by permission gate\" };\n\t\t}\n\n\t\tif (choice.startsWith(\"Always\")) {\n\t\t\t// Write \"always\" choices to the global config only\n\t\t\tconst latest = readConfig();\n\t\t\tconst currentMode = latest.active_mode ?? \"build\";\n\t\t\tlatest.modes ??= {};\n\t\t\tlatest.modes[currentMode] ??= {};\n\t\t\tlatest.modes[currentMode].auto_allow = Array.from(\n\t\t\t\tnew Set([...(latest.modes[currentMode].auto_allow ?? []), event.toolName]),\n\t\t\t);\n\t\t\twriteConfig(latest);\n\t\t\tctx.ui.notify(`\"${event.toolName}\" added to auto-allow for mode \"${currentMode}\"`, \"info\");\n\t\t}\n\t});\n}\n\n// ============================================================================\n// B. MCP Server Loader\n// ============================================================================\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn */\n\tcommand: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown): Promise<unknown>;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n\nfunction spawnMcpServer(config: McpServerConfig): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tpending.set(id, { resolve, reject });\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\treturn {\n\t\trpc,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = spawnMcpServer(config);\n\tmcpConnections.set(config.name, conn);\n\n\tawait conn.rpc(\"initialize\", {\n\t\tprotocolVersion: \"2024-11-05\",\n\t\tcapabilities: { tools: {} },\n\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t});\n\n\tconst toolsResult = (await conn.rpc(\"tools/list\", {})) as {\n\t\ttools?: McpToolDef[];\n\t};\n\treturn { conn, tools: toolsResult.tools ?? [] };\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || !serverConfig.command) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\t\tconst schema = buildMcpSchema(tool);\n\t\t\t\t\t\tconst capturedServer = serverConfig.name;\n\t\t\t\t\t\tconst capturedTool = tool.name;\n\n\t\t\t\t\t\tpi.registerTool({\n\t\t\t\t\t\t\tname: toolName,\n\t\t\t\t\t\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\t\t\t\t\t\tdescription: tool.description,\n\t\t\t\t\t\t\tparameters: schema,\n\t\t\t\t\t\t\tasync execute(\n\t\t\t\t\t\t\t\t_toolCallId: string,\n\t\t\t\t\t\t\t\tparams: Static<typeof schema>,\n\t\t\t\t\t\t\t\tsignal: AbortSignal,\n\t\t\t\t\t\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t\t\t\t\t\t): Promise<AgentToolResult<undefined>> {\n\t\t\t\t\t\t\t\tconst activeConn = mcpConnections.get(capturedServer);\n\t\t\t\t\t\t\t\tif (!activeConn) {\n\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\t\ttext: `MCP server \"${capturedServer}\" is not connected`,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\t\t\t\t\tactiveConn.rpc(\"tools/call\", {\n\t\t\t\t\t\t\t\t\t\tname: capturedTool,\n\t\t\t\t\t\t\t\t\t\targuments: params,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\tabortPromise,\n\t\t\t\t\t\t\t\t]);\n\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"})`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n}\n\n// ============================================================================\n// C. Mode + Profile System\n// ============================================================================\n\nconst DEFAULT_MODE = \"build\";\nconst DEFAULT_PROFILE = \"default\";\n\n/**\n * Returns true if `marker` matches something in `cwd`.\n * Plain markers use existsSync. Glob markers (containing `*`) scan the\n * immediate directory entries — only one level, no recursion needed for\n * common cases like `*.sql` or `k8s/`.\n */\nfunction markerExists(cwd: string, marker: string): boolean {\n\tif (!marker.includes(\"*\")) return existsSync(join(cwd, marker));\n\tconst suffix = marker.replace(/^\\*/, \"\");\n\ttry {\n\t\treturn readdirSync(cwd).some((entry) => entry.endsWith(suffix));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Resolves which profile should be active.\n * Priority: config override → file-marker detection → \"default\"\n */\nexport function resolveProfile(config: HooConfig, cwd: string): string {\n\tif (config.active_profile) return config.active_profile;\n\tfor (const detector of config.profile_detectors ?? []) {\n\t\tif (markerExists(cwd, detector.marker)) return detector.profile;\n\t}\n\treturn DEFAULT_PROFILE;\n}\n\n/**\n * Merges the system prompt from up to three layers (lowest → highest priority):\n * 1. ~/.hoocode/templates/modes/{mode}/system.md (mode behaviour)\n * 2. ~/.hoocode/templates/profiles/{profile}/context.md (domain context; skipped for \"default\")\n * 3. ./.hoocode/agents.md (project-local override; appended last)\n *\n * Each present layer is joined with a `---` separator.\n */\nexport function buildSystemPrompt(mode: string, profile: string, cwd: string): string | undefined {\n\tconst layers: string[] = [];\n\n\tfunction tryRead(path: string): string | undefined {\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst text = readFileSync(path, \"utf8\").trim();\n\t\t\treturn text || undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t// Layer 1: mode system prompt (~/.hoocode/modes/{mode}/system.md)\n\tconst modePrompt = tryRead(join(HOOCODE_DIR, \"modes\", mode, \"system.md\"));\n\tif (modePrompt) layers.push(modePrompt);\n\n\t// Layer 2: profile context — omit for \"default\" (no extra domain constraints)\n\t// (~/.hoocode/profiles/{profile}/context.md)\n\tif (profile !== DEFAULT_PROFILE) {\n\t\tconst profileContext = tryRead(join(HOOCODE_DIR, \"profiles\", profile, \"context.md\"));\n\t\tif (profileContext) layers.push(profileContext);\n\t}\n\n\t// Layer 3: project-local agents.md — appended after mode + profile so it can\n\t// extend or override them for this specific repo\n\tconst projectOverride = tryRead(join(cwd, \".hoocode\", \"agents.md\"));\n\tif (projectOverride) layers.push(projectOverride);\n\n\treturn layers.length > 0 ? layers.join(\"\\n\\n---\\n\\n\") : undefined;\n}\n\n// ============================================================================\n// Plan file: section parsing and step-by-step execution message\n// ============================================================================\n\nexport interface PlanSections {\n\tgoal?: string;\n\tfilesToModify?: string;\n\tnewFiles?: string;\n\ttests?: string;\n\tverification?: string;\n\t/** Original full text, used as fallback if no sections parsed */\n\traw: string;\n}\n\n/**\n * Parses `.hoocode/plan.md` into named sections.\n *\n * Recognises both ATX headings (`## Goal`) and bold labels (`**Goal**`).\n * Section names matched (case-insensitive): Goal, Files to modify, New files,\n * Tests, Verification.\n */\nexport function parsePlanSections(planContent: string): PlanSections {\n\tconst result: PlanSections = { raw: planContent };\n\n\t// Match `## Heading text` or `**Heading text**` followed by content until\n\t// the next heading of the same style.\n\tconst sectionPattern =\n\t\t/^(?:#{1,3}\\s+(.+?)|(?:\\*\\*(.+?)\\*\\*))\\s*\\n([\\s\\S]*?)(?=(?:^#{1,3}\\s+|\\*\\*[^*\\n]+\\*\\*\\s*\\n)|$)/gm;\n\n\tfor (const match of planContent.matchAll(sectionPattern)) {\n\t\tconst heading = (match[1] ?? match[2] ?? \"\").toLowerCase().trim();\n\t\tconst content = match[3].trim();\n\t\tif (!content) continue;\n\n\t\tif (/^goal/.test(heading)) {\n\t\t\tresult.goal = content;\n\t\t} else if (/files?\\s+to\\s+modif|^modif/.test(heading)) {\n\t\t\tresult.filesToModify = content;\n\t\t} else if (/new\\s+files?/.test(heading)) {\n\t\t\tresult.newFiles = content;\n\t\t} else if (/^tests?/.test(heading)) {\n\t\t\tresult.tests = content;\n\t\t} else if (/^verif/.test(heading)) {\n\t\t\tresult.verification = content;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Builds the user message sent to the agent when `/approve` is run.\n *\n * If the plan has recognisable sections, each is presented as a numbered step\n * so the agent works through them sequentially. Otherwise the raw plan is used.\n *\n * Execution order:\n * 1. Modify existing files\n * 2. Create new files\n * 3. Update / add tests\n * 4. Run verification commands\n */\nexport function buildApproveMessage(sections: PlanSections): string {\n\tconst steps: string[] = [];\n\n\tif (sections.goal) {\n\t\tsteps.push(`**Goal:** ${sections.goal}`);\n\t}\n\tif (sections.filesToModify) {\n\t\tsteps.push(`**Step 1 — Modify existing files:**\\n${sections.filesToModify}`);\n\t}\n\tif (sections.newFiles) {\n\t\tsteps.push(`**Step 2 — Create new files:**\\n${sections.newFiles}`);\n\t}\n\tif (sections.tests) {\n\t\tsteps.push(`**Step 3 — Update tests:**\\n${sections.tests}`);\n\t}\n\tif (sections.verification) {\n\t\tsteps.push(`**Step 4 — Verify:**\\n${sections.verification}`);\n\t}\n\n\tif (steps.length === 0) {\n\t\treturn `Execute the following plan:\\n\\n${sections.raw}`;\n\t}\n\n\treturn `Execute this plan step by step. Complete each step fully before moving to the next.\\n\\n${steps.join(\"\\n\\n\")}`;\n}\n\n// ============================================================================\n// C. setupModeAndProfile\n// ============================================================================\n\nexport function setupModeAndProfile(pi: ExtensionAPI): void {\n\tlet cachedMode = DEFAULT_MODE;\n\tlet cachedProfile = DEFAULT_PROFILE;\n\tlet cachedSystemPrompt: string | undefined;\n\n\t// ── session_start ─────────────────────────────────────────────────────────\n\t// Config resolution order:\n\t// 1. Read global config (~/.hoocode/agent/hoo-config.json)\n\t// 2. Read project config (./.hoocode/config.json) if present\n\t// 3. Merge — project scalars win; arrays are unioned; project detectors prepend\n\t// 4. Re-resolve active_mode and active_profile from the merged result\n\n\tpi.on(\"session_start\", (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// Steps 1–3: merge global + project configs\n\t\tconst config = readMergedConfig(ctx.cwd);\n\n\t\t// Step 4: resolve mode and profile from the merged config\n\t\tcachedMode = config.active_mode ?? DEFAULT_MODE;\n\t\tcachedProfile = resolveProfile(config, ctx.cwd);\n\t\tcachedSystemPrompt = buildSystemPrompt(cachedMode, cachedProfile, ctx.cwd);\n\n\t\t// Update footer with active mode/profile\n\t\tif (ctx.hasUI) {\n\t\t\tctx.ui.setModeProfile(cachedMode, cachedProfile);\n\t\t}\n\n\t\t// Apply tool filter: mode enabled_tools takes priority, then profile\n\t\tconst modeCfg = config.modes?.[cachedMode];\n\t\tconst profileCfg = config.profiles?.[cachedProfile];\n\t\tif (modeCfg?.enabled_tools && modeCfg.enabled_tools.length > 0) {\n\t\t\tpi.setActiveTools(modeCfg.enabled_tools);\n\t\t} else if (profileCfg?.enabled_tools && profileCfg.enabled_tools.length > 0) {\n\t\t\tpi.setActiveTools(profileCfg.enabled_tools);\n\t\t}\n\t});\n\n\t// ── before_agent_start ────────────────────────────────────────────────────\n\n\tpi.on(\"before_agent_start\", (event: BeforeAgentStartEvent): BeforeAgentStartEventResult | undefined => {\n\t\tif (!cachedSystemPrompt) return;\n\t\treturn {\n\t\t\tsystemPrompt:\n\t\t\t\t`${event.systemPrompt}\\n\\n` +\n\t\t\t\t`<!-- hoo-core: mode=${cachedMode} profile=${cachedProfile} -->\\n` +\n\t\t\t\tcachedSystemPrompt,\n\t\t};\n\t});\n\n\t// ── /mode command ─────────────────────────────────────────────────────────\n\n\tconst KNOWN_MODES = [\"ask\", \"plan\", \"build\", \"agent\", \"debug\"];\n\n\tpi.registerCommand(\"mode\", {\n\t\tdescription: \"Switch active mode. Usage: /mode <ask|plan|build|agent|debug>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tKNOWN_MODES.filter((m) => m.startsWith(prefix)).map((m) => ({ value: m, label: m })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tif (!name) {\n\t\t\t\tctx.ui.notify(`Active mode: ${cachedMode}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = name === DEFAULT_MODE ? undefined : name;\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Mode set to \"${name}\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /profile command ──────────────────────────────────────────────────────\n\n\tpi.registerCommand(\"profile\", {\n\t\tdescription: \"Switch active profile. Usage: /profile <name>\",\n\t\tgetArgumentCompletions: (prefix: string) => {\n\t\t\t// Show profiles from the merged config so project-local profiles appear\n\t\t\tconst config = readMergedConfig(\".\");\n\t\t\tconst names = Object.keys(config.profiles ?? {});\n\t\t\tconst suggestions = [DEFAULT_PROFILE, ...names.filter((n) => n !== DEFAULT_PROFILE)];\n\t\t\treturn suggestions.filter((n) => n.startsWith(prefix)).map((n) => ({ value: n, label: n }));\n\t\t},\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tif (!name) {\n\t\t\t\tctx.ui.notify(`Active profile: ${cachedProfile}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_profile = name === DEFAULT_PROFILE ? undefined : name;\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Profile set to \"${name}\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /plan command (shorthand for /mode plan) ──────────────────────────────\n\n\tpi.registerCommand(\"plan\", {\n\t\tdescription: \"Switch to plan mode. Shorthand for /mode plan.\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = \"plan\";\n\t\t\twriteConfig(config);\n\t\t\tctx.ui.notify(`Mode set to \"plan\" — reloading…`, \"info\");\n\t\t\tawait ctx.reload();\n\t\t},\n\t});\n\n\t// ── /approve command ──────────────────────────────────────────────────────\n\t// Reads .hoocode/plan.md, parses it into named sections (Goal, Files to\n\t// modify, New files, Tests, Verification), switches to build mode, then\n\t// injects a step-by-step execution message into the new session.\n\n\tpi.registerCommand(\"approve\", {\n\t\tdescription: \"Approve the current plan and switch to build mode to execute it.\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tif (cachedMode !== \"plan\") {\n\t\t\t\tctx.ui.notify(`/approve is only available in plan mode (current mode: \"${cachedMode}\")`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Read ./.hoocode/plan.md written by the agent during plan mode\n\t\t\tconst planPath = join(ctx.cwd, \".hoocode\", \"plan.md\");\n\t\t\tlet approveMessage: string | undefined;\n\n\t\t\tif (existsSync(planPath)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst raw = readFileSync(planPath, \"utf8\").trim();\n\t\t\t\t\tif (raw) {\n\t\t\t\t\t\tconst sections = parsePlanSections(raw);\n\t\t\t\t\t\tapproveMessage = buildApproveMessage(sections);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\tctx.ui.notify(`Could not read .hoocode/plan.md`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Switch global config to build mode\n\t\t\tconst config = readConfig();\n\t\t\tconfig.active_mode = \"build\";\n\t\t\twriteConfig(config);\n\n\t\t\tif (approveMessage) {\n\t\t\t\t// Open a new build-mode session and deliver the parsed plan as the\n\t\t\t\t// first user message so the agent starts executing immediately\n\t\t\t\tawait ctx.newSession({\n\t\t\t\t\twithSession: async (replacedCtx) => {\n\t\t\t\t\t\tawait replacedCtx.sendUserMessage(approveMessage!, { deliverAs: \"followUp\" });\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tctx.ui.notify(`Switched to build mode. No .hoocode/plan.md found — describe what to build.`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t}\n\t\t},\n\t});\n}\n\n// ============================================================================\n// Extension entry point\n// ============================================================================\n\nexport default function hooCore(pi: ExtensionAPI): void {\n\tsetupPermissionGate(pi);\n\tsetupMcpLoader(pi);\n\tsetupModeAndProfile(pi);\n}\n"]}
|