Explorar o código

Tunes prompts especially fixer (#217)

* Tunes prompts especially fixer

* Fix config unset

* tunes
Alvin hai 4 meses
pai
achega
cdc2ccae02

+ 16 - 26
src/agents/orchestrator.ts

@@ -31,12 +31,14 @@ You are an AI coding orchestrator that optimizes for quality, speed, cost, and r
 
 @explorer
 - Role: Parallel search specialist for discovering unknowns across the codebase
+- Stats: 3x faster codebase search than orchestrator, 1/2 cost of orchestrator
 - Capabilities: Glob, grep, AST queries to locate files, symbols, patterns
 - **Delegate when:** Need to discover what exists before planning • Parallel searches speed discovery • Need summarized map vs full contents • Broad/uncertain scope
 - **Don't delegate when:** Know the path and need actual content • Need full file anyway • Single specific lookup • About to edit the file
 
 @librarian
 - Role: Authoritative source for current library docs and API references
+- Stats: 10x better finding up-to-date library docs than orchestrator, 1/2 cost of orchestrator
 - Capabilities: Fetches latest official docs, examples, API signatures, version-specific behavior via grep_app MCP
 - **Delegate when:** Libraries with frequent API changes (React, Next.js, AI SDKs) • Complex APIs needing official examples (ORMs, auth) • Version-specific behavior matters • Unfamiliar library • Edge cases or advanced features • Nuanced best practices
 - **Don't delegate when:** Standard usage you're confident about (\`Array.map()\`, \`fetch()\`) • Simple stable APIs • General programming knowledge • Info already in conversation • Built-in language features
@@ -44,6 +46,7 @@ You are an AI coding orchestrator that optimizes for quality, speed, cost, and r
 
 @oracle
 - Role: Strategic advisor for high-stakes decisions and persistent problems
+- Stats: 10x better decisions maker, problem solver, investigator than orchestrator, 0.8x speed of orchestrator, same cost.
 - Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging
 - Tools/Constraints: Slow, expensive, high-quality—use sparingly when thoroughness beats speed
 - **Delegate when:** Major architectural decisions with long-term impact • Problems persisting after 2+ fix attempts • High-risk multi-system refactors • Costly trade-offs (performance vs maintainability) • Complex debugging with unclear root cause • Security/scalability/data integrity decisions • Genuinely uncertain and cost of wrong choice is high
@@ -52,22 +55,23 @@ You are an AI coding orchestrator that optimizes for quality, speed, cost, and r
 
 @designer
 - Role: UI/UX specialist for intentional, polished experiences
+- Stats: 10x better UI/UX than orchestrator
 - Capabilities: Visual direction, interactions, responsive layouts, design systems with aesthetic intent
 - **Delegate when:** User-facing interfaces needing polish • Responsive layouts • UX-critical components (forms, nav, dashboards) • Visual consistency systems • Animations/micro-interactions • Landing/marketing pages • Refining functional→delightful
 - **Don't delegate when:** Backend/logic with no visual • Quick prototypes where design doesn't matter yet
 - **Rule of thumb:** Users see it and polish matters? → @designer. Headless/functional? → yourself.
 
 @fixer
-- Role: Fast, parallel execution specialist for well-defined tasks
-- Capabilities: Efficient implementation when spec and context are clear
+- Role: Fast execution specialist for well-defined tasks, which empowers orchestrator with parallel, speedy executions
+- Stats: 2x faster code edits, 1/2 cost of orchestrator, 0.8x quality of orchestrator
 - Tools/Constraints: Execution-focused—no research, no architectural decisions
-- **Delegate when:** Clearly specified with known approach • 3+ independent parallel tasks • Straightforward but time-consuming • Solid plan needing execution • Repetitive multi-location changes • Overhead < time saved by parallelization
-- **Don't delegate when:** Needs discovery/research/decisions • Single small change (<20 lines, one file) • Unclear requirements needing iteration • Explaining > doing • Tight integration with your current work • Sequential dependencies
-- **Parallelization:** 3+ independent tasks → spawn multiple @fixers. 1-2 simple tasks → do yourself.
-- **Rule of thumb:** Explaining > doing? → yourself. Can split to parallel streams? → multiple @fixers.
+- **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer
+- **Don't delegate when:** Needs discovery/research/decisions • Single small change (<20 lines, one file) • Unclear requirements needing iteration • Explaining to fixer > doing • Tight integration with your current work • Sequential dependencies
+- **Rule of thumb:** Explaining > doing? → yourself. Orchestrator paths selection is vastly improved by Fixer. eg it can reduce overall speed if Orchestrator splits what's usually a single task into multiple subtasks and parallelize it with fixer.
 
 @council
 - Role: Multi-LLM consensus engine for high-confidence answers
+- Stats: 3x slower than orchestrator, 3x or more cost of orchestrator
 - Capabilities: Runs multiple models in parallel, synthesizes their responses via a council master
 - **Delegate when:** Critical decisions needing diverse model perspectives • High-stakes architectural choices where consensus reduces risk • Ambiguous problems where multi-model disagreement is informative • Security-sensitive design reviews
 - **Don't delegate when:** Straightforward tasks you're confident about • Speed matters more than confidence • Single-model answer is sufficient • Routine implementation work
@@ -81,19 +85,14 @@ You are an AI coding orchestrator that optimizes for quality, speed, cost, and r
 ## 1. Understand
 Parse request: explicit requirements + implicit needs.
 
-## 2. Path Analysis
+## 2. Path Selection
 Evaluate approach by: quality, speed, cost, reliability.
 Choose the path that optimizes all four.
 
 ## 3. Delegation Check
 **STOP. Review specialists before acting.**
 
-Each specialist delivers 10x results in their domain:
-- @explorer → Parallel discovery when you need to find unknowns, not read knowns
-- @librarian → Complex/evolving APIs where docs prevent errors, not basic usage
-- @oracle → High-stakes decisions where wrong choice is costly, not routine calls
-- @designer → User-facing experiences where polish matters, not internal logic
-- @fixer → Parallel execution of clear specs, not explaining trivial changes
+!!! Review available agents and delegation rules. Decide whether to delegate or do it yourself. !!!
 
 **Delegation efficiency:**
 - Reference paths/lines, don't paste files (\`src/app.ts:42\` not full contents)
@@ -101,21 +100,16 @@ Each specialist delivers 10x results in their domain:
 - Brief user on delegation goal before each call
 - Skip delegation if overhead ≥ doing it yourself
 
-**Fixer parallelization:**
-- 3+ independent tasks? Spawn multiple @fixers simultaneously
-- 1-2 simple tasks? Do it yourself
-- Sequential dependencies? Handle serially or do yourself
-
-## 4. Parallelize
-Can tasks run simultaneously?
+## 4. Split and Parallelize
+Can tasks be split into subtasks and run in parallel?
 - Multiple @explorer searches across different domains?
 - @explorer + @librarian research in parallel?
-- Multiple @fixer instances for independent changes?
+- Multiple @fixer instances for faster, scoped implementation?
 
 Balance: respect dependencies, avoid parallelizing what must be sequential.
 
 ## 5. Execute
-1. Break complex tasks into todos if needed
+1. Break complex tasks into todos
 2. Fire parallel research/implementation
 3. Delegate to specialists or do it yourself based on step 3
 4. Integrate results
@@ -127,10 +121,6 @@ Balance: respect dependencies, avoid parallelizing what must be sequential.
 - Confirm specialists completed successfully
 - Verify solution meets requirements
 
-## Agent Role Mapping
-When a workflow calls for an **implementer** subagent: dispatch \`@fixer\`. Fixer has enforced constraints (no research, no delegation, structured output) that match the implementer role exactly.
-When a workflow calls for a **reviewer** subagent: dispatch \`@oracle\`. Oracle has the depth for architectural review and access to code review skills.
-
 </Workflow>
 
 <Communication>

+ 18 - 0
src/cli/paths.test.ts

@@ -9,6 +9,7 @@ import {
   getConfigDir,
   getConfigJson,
   getConfigJsonc,
+  getConfigSearchDirs,
   getExistingConfigPath,
   getLiteConfig,
   getOpenCodeConfigPaths,
@@ -44,6 +45,23 @@ describe('paths', () => {
     expect(getConfigDir()).toBe(expected);
   });
 
+  test('getConfigSearchDirs() returns custom dir first, then default dir', () => {
+    process.env.OPENCODE_CONFIG_DIR = '/custom/directory';
+    process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
+
+    expect(getConfigSearchDirs()).toEqual([
+      '/custom/directory',
+      '/tmp/xdg-config/opencode',
+    ]);
+  });
+
+  test('getConfigSearchDirs() de-duplicates identical dirs', () => {
+    process.env.OPENCODE_CONFIG_DIR = '/tmp/xdg-config/opencode';
+    process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
+
+    expect(getConfigSearchDirs()).toEqual(['/tmp/xdg-config/opencode']);
+  });
+
   test('getOpenCodeConfigPaths() returns both json and jsonc paths', () => {
     process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
     expect(getOpenCodeConfigPaths()).toEqual([

+ 17 - 0
src/cli/paths.ts

@@ -32,6 +32,23 @@ export function getConfigDir(): string {
   return getDefaultOpenCodeConfigDir();
 }
 
+/**
+ * Get OpenCode config directories in read/search order.
+ *
+ * Resolution order:
+ * 1. OPENCODE_CONFIG_DIR (if set)
+ * 2. XDG_CONFIG_HOME/opencode or ~/.config/opencode
+ *
+ * Duplicate entries are removed.
+ */
+export function getConfigSearchDirs(): string[] {
+  const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
+
+  return dirs.filter((dir, index): dir is string => {
+    return Boolean(dir) && dirs.indexOf(dir) === index;
+  });
+}
+
 export function getOpenCodeConfigPaths(): string[] {
   const configDir = getDefaultOpenCodeConfigDir();
   return [join(configDir, 'opencode.json'), join(configDir, 'opencode.jsonc')];

+ 5 - 0
src/config/constants.ts

@@ -78,6 +78,11 @@ export const FALLBACK_FAILOVER_TIMEOUT_MS = 15_000;
 // Subagent depth limits
 export const DEFAULT_MAX_SUBAGENT_DEPTH = 3;
 
+// Workflow reminders
+export const PHASE_REMINDER_TEXT = `Recall Workflow Rules:
+Understand → build the best path (delegated based on Agent rules, split and parallelized as much as possible) → execute → verify.
+If delegating, launch the specialist in the same turn you mention it.`;
+
 // Tmux pane spawn delay (ms) — gives TmuxSessionManager time to create pane
 export const TMUX_SPAWN_DELAY_MS = 500;
 

+ 58 - 16
src/config/loader.test.ts

@@ -178,6 +178,30 @@ describe('loadPluginConfig', () => {
 
     fs.rmSync(customDir, { recursive: true, force: true });
   });
+
+  test('falls back to default user config dir when OPENCODE_CONFIG_DIR has no config', () => {
+    const customDir = fs.mkdtempSync(
+      path.join(os.tmpdir(), 'omc-opencode-config-empty-'),
+    );
+    process.env.OPENCODE_CONFIG_DIR = customDir;
+
+    const defaultConfigDir = path.join(userConfigDir, 'opencode');
+    fs.mkdirSync(defaultConfigDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(defaultConfigDir, 'oh-my-opencode-slim.json'),
+      JSON.stringify({
+        agents: { oracle: { model: 'fallback/default-config' } },
+      }),
+    );
+
+    const projectDir = path.join(tempDir, 'project');
+    fs.mkdirSync(projectDir, { recursive: true });
+
+    const config = loadPluginConfig(projectDir);
+    expect(config.agents?.oracle?.model).toBe('fallback/default-config');
+
+    fs.rmSync(customDir, { recursive: true, force: true });
+  });
 });
 
 describe('deepMerge behavior', () => {
@@ -989,14 +1013,15 @@ describe('loadAgentPrompt', () => {
 
     // Use a unique agent name and check for it specifically
     const originalReadFileSync = fs.readFileSync;
-    const readSpy = spyOn(fs, 'readFileSync').mockImplementation(
-      (p: any, o: any) => {
-        if (typeof p === 'string' && p.includes('error-agent.md')) {
-          throw new Error('Read error');
-        }
-        return originalReadFileSync(p, o);
-      },
-    );
+    const readSpy = spyOn(fs, 'readFileSync').mockImplementation(((
+      ...args: Parameters<typeof fs.readFileSync>
+    ) => {
+      const [p] = args;
+      if (typeof p === 'string' && p.includes('error-agent.md')) {
+        throw new Error('Read error');
+      }
+      return originalReadFileSync(...args);
+    }) as typeof fs.readFileSync);
 
     try {
       const result = loadAgentPrompt('error-agent');
@@ -1084,14 +1109,15 @@ describe('loadAgentPrompt', () => {
 
     const consoleWarnSpy = spyOn(console, 'warn');
     const originalReadFileSync = fs.readFileSync;
-    const readSpy = spyOn(fs, 'readFileSync').mockImplementation(
-      (p: any, o: any) => {
-        if (typeof p === 'string' && p === presetPromptPath) {
-          throw new Error('Preset read error');
-        }
-        return originalReadFileSync(p, o);
-      },
-    );
+    const readSpy = spyOn(fs, 'readFileSync').mockImplementation(((
+      ...args: Parameters<typeof fs.readFileSync>
+    ) => {
+      const [p] = args;
+      if (typeof p === 'string' && p === presetPromptPath) {
+        throw new Error('Preset read error');
+      }
+      return originalReadFileSync(...args);
+    }) as typeof fs.readFileSync);
 
     try {
       const result = loadAgentPrompt('oracle', 'test');
@@ -1136,4 +1162,20 @@ describe('loadAgentPrompt', () => {
 
     fs.rmSync(customDir, { recursive: true, force: true });
   });
+
+  test('falls back to default prompt dir when OPENCODE_CONFIG_DIR has no prompt', () => {
+    const customDir = fs.mkdtempSync(
+      path.join(os.tmpdir(), 'omc-prompt-config-empty-'),
+    );
+    process.env.OPENCODE_CONFIG_DIR = customDir;
+
+    const promptsDir = path.join(tempDir, 'opencode', 'oh-my-opencode-slim');
+    fs.mkdirSync(promptsDir, { recursive: true });
+    fs.writeFileSync(path.join(promptsDir, 'oracle.md'), 'fallback prompt');
+
+    const result = loadAgentPrompt('oracle');
+    expect(result.prompt).toBe('fallback prompt');
+
+    fs.rmSync(customDir, { recursive: true, force: true });
+  });
 });

+ 25 - 7
src/config/loader.ts

@@ -1,7 +1,7 @@
 import * as fs from 'node:fs';
 import * as path from 'node:path';
 import { stripJsonComments } from '../cli/config-io';
-import { getConfigDir } from '../cli/paths';
+import { getConfigDir, getConfigSearchDirs } from '../cli/paths';
 import { type PluginConfig, PluginConfigSchema } from './schema';
 
 const PROMPTS_DIR_NAME = 'oh-my-opencode-slim';
@@ -66,6 +66,20 @@ function findConfigPath(basePath: string): string | null {
   return null;
 }
 
+function findConfigPathInDirs(
+  configDirs: string[],
+  baseName: string,
+): string | null {
+  for (const configDir of configDirs) {
+    const configPath = findConfigPath(path.join(configDir, baseName));
+    if (configPath) {
+      return configPath;
+    }
+  }
+
+  return null;
+}
+
 /**
  * Recursively merge two objects, with override values taking precedence.
  * For nested objects, merges recursively. For arrays and primitives, override replaces base.
@@ -121,7 +135,10 @@ function deepMerge<T extends Record<string, unknown>>(
  * @returns Merged plugin configuration (empty object if no configs found)
  */
 export function loadPluginConfig(directory: string): PluginConfig {
-  const userConfigBasePath = path.join(getConfigDir(), 'oh-my-opencode-slim');
+  const userConfigPath = findConfigPathInDirs(
+    getConfigSearchDirs(),
+    'oh-my-opencode-slim',
+  );
 
   const projectConfigBasePath = path.join(
     directory,
@@ -130,7 +147,6 @@ export function loadPluginConfig(directory: string): PluginConfig {
   );
 
   // Find existing config files (preferring .jsonc over .json)
-  const userConfigPath = findConfigPath(userConfigBasePath);
   const projectConfigPath = findConfigPath(projectConfigBasePath);
 
   let config: PluginConfig = userConfigPath
@@ -197,10 +213,12 @@ export function loadAgentPrompt(
 } {
   const presetDirName =
     preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : undefined;
-  const promptsDir = path.join(getConfigDir(), PROMPTS_DIR_NAME);
-  const promptSearchDirs = presetDirName
-    ? [path.join(promptsDir, presetDirName), promptsDir]
-    : [promptsDir];
+  const promptSearchDirs = getConfigSearchDirs().flatMap((configDir) => {
+    const promptsDir = path.join(configDir, PROMPTS_DIR_NAME);
+    return presetDirName
+      ? [path.join(promptsDir, presetDirName), promptsDir]
+      : [promptsDir];
+  });
   const result: { prompt?: string; appendPrompt?: string } = {};
 
   const readFirstPrompt = (

+ 1 - 1
src/hooks/index.ts

@@ -8,4 +8,4 @@ export {
 } from './foreground-fallback';
 export { createJsonErrorRecoveryHook } from './json-error-recovery';
 export { createPhaseReminderHook } from './phase-reminder';
-export { createPostReadNudgeHook } from './post-read-nudge';
+export { createPostFileToolNudgeHook } from './post-file-tool-nudge';

+ 2 - 3
src/hooks/phase-reminder/index.ts

@@ -8,11 +8,10 @@
  *
  * Uses experimental.chat.messages.transform so it doesn't show in UI.
  */
+import { PHASE_REMINDER_TEXT } from '../../config/constants';
 import { SLIM_INTERNAL_INITIATOR_MARKER } from '../../utils';
 
-export const PHASE_REMINDER = `<reminder>Recall Workflow Rules:
-Understand → find the best path (delegate based on rules and parallelize independent work) → execute → verify.
-If delegating, launch the specialist in the same turn you mention it.</reminder>`;
+export const PHASE_REMINDER = `<reminder>${PHASE_REMINDER_TEXT}</reminder>`;
 
 interface MessageInfo {
   role: string;

+ 19 - 0
src/hooks/post-file-tool-nudge/codemap.md

@@ -0,0 +1,19 @@
+# src/hooks/post-file-tool-nudge/
+
+<!-- Explorer: Fill in this section with architectural understanding -->
+
+## Responsibility
+
+Provide a lightweight safety net that reminds agents to stay in the delegation workflow whenever they read or write project files. When the Read or Write tool emits output, this hook appends a standardized nudge so that follow-up work is routed to the right specialists instead of being solved immediately.
+
+## Design
+
+Exports a single factory (`createPostFileToolNudgeHook`) that returns a handler map for the `tool.execute.after` event. The hook keeps a shared `NUDGE` constant and enforces a policy guard for Read/Write tool names before mutating the `output`. This keeps the implementation focused and pluggable inside the broader hook registry.
+
+## Flow
+
+The hook is instantiated once and registered with the hook system, which invokes `tool.execute.after` after every tool call. When a Read or Write tool completes, the hook sees the input metadata, verifies the tool name, and appends the reminder text to `output.output`, thereby altering the user-facing response before it bubbles back to the agent.
+
+## Integration
+
+Plugged into the global hook registry, this module intercepts every tool response via the `tool.execute.after` lifecycle event. It directly touches only Read/Write tool output objects so there are no downstream dependencies, but the appended reminder influences all downstream consumers who display file-tool results.

+ 42 - 0
src/hooks/post-file-tool-nudge/index.ts

@@ -0,0 +1,42 @@
+/**
+ * Post-tool nudge - appends a delegation reminder after file reads/writes.
+ * Catches the "inspect/edit files → implement myself" anti-pattern.
+ */
+
+import { PHASE_REMINDER_TEXT } from '../../config/constants';
+
+const NUDGE = `\n\n---\n${PHASE_REMINDER_TEXT}`;
+
+interface ToolExecuteAfterInput {
+  tool: string;
+  sessionID?: string;
+  callID?: string;
+}
+
+interface ToolExecuteAfterOutput {
+  title: string;
+  output: string;
+  metadata: Record<string, unknown>;
+}
+
+export function createPostFileToolNudgeHook() {
+  return {
+    'tool.execute.after': async (
+      input: ToolExecuteAfterInput,
+      output: ToolExecuteAfterOutput,
+    ): Promise<void> => {
+      // Only nudge for Read/Write tools
+      if (
+        input.tool !== 'Read' &&
+        input.tool !== 'read' &&
+        input.tool !== 'Write' &&
+        input.tool !== 'write'
+      ) {
+        return;
+      }
+
+      // Append the nudge
+      output.output = output.output + NUDGE;
+    },
+  };
+}

+ 0 - 19
src/hooks/post-read-nudge/codemap.md

@@ -1,19 +0,0 @@
-# src/hooks/post-read-nudge/
-
-<!-- Explorer: Fill in this section with architectural understanding -->
-
-## Responsibility
-
-Provide a lightweight safety net that reminds agents to stay in the delegation workflow whenever they read project files. When the Read tool emits output, this hook appends a standardized nudge so that follow-up work is routed to the right specialists instead of being solved immediately.
-
-## Design
-
-Exports a single factory (`createPostReadNudgeHook`) that returns a handler map for the `tool.execute.after` event. The hook keeps a shared `NUDGE` constant and enforces a policy guard (`input.tool === 'Read' || input.tool === 'read'`) before mutating the `output`. This keeps the implementation focused and pluggable inside the broader hook registry.
-
-## Flow
-
-The hook is instantiated once and registered with the hook system, which invokes `tool.execute.after` after every tool call. When the Read tool completes, the hook sees the input metadata, verifies the tool name, and appends the reminder text to `output.output`, thereby altering the user-facing response before it bubbles back to the agent.
-
-## Integration
-
-Plugged into the global hook registry, this module intercepts every tool response via the `tool.execute.after` lifecycle event. It directly touches only the Read tool’s output object so there are no downstream dependencies, but the appended reminder influences all downstream consumers who display read results.

+ 0 - 36
src/hooks/post-read-nudge/index.ts

@@ -1,36 +0,0 @@
-/**
- * Post-Read nudge - appends a delegation reminder after file reads.
- * Catches the "read files → implement myself" anti-pattern.
- */
-
-const NUDGE =
-  '\n\n---\nWorkflow Reminder: delegate based on rules; If mentioning a specialist, launch it in this same turn.';
-
-interface ToolExecuteAfterInput {
-  tool: string;
-  sessionID?: string;
-  callID?: string;
-}
-
-interface ToolExecuteAfterOutput {
-  title: string;
-  output: string;
-  metadata: Record<string, unknown>;
-}
-
-export function createPostReadNudgeHook() {
-  return {
-    'tool.execute.after': async (
-      input: ToolExecuteAfterInput,
-      output: ToolExecuteAfterOutput,
-    ): Promise<void> => {
-      // Only nudge for Read tool
-      if (input.tool !== 'Read' && input.tool !== 'read') {
-        return;
-      }
-
-      // Append the nudge
-      output.output = output.output + NUDGE;
-    },
-  };
-}

+ 5 - 5
src/index.ts

@@ -10,7 +10,7 @@ import {
   createDelegateTaskRetryHook,
   createJsonErrorRecoveryHook,
   createPhaseReminderHook,
-  createPostReadNudgeHook,
+  createPostFileToolNudgeHook,
   ForegroundFallbackManager,
 } from './hooks';
 import { createBuiltinMcps } from './mcp';
@@ -124,8 +124,8 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
   // Initialize phase reminder hook for workflow compliance
   const phaseReminderHook = createPhaseReminderHook();
 
-  // Initialize post-read nudge hook
-  const postReadNudgeHook = createPostReadNudgeHook();
+  // Initialize post-file-tool nudge hook
+  const postFileToolNudgeHook = createPostFileToolNudgeHook();
 
   const chatHeadersHook = createChatHeadersHook(ctx);
 
@@ -387,7 +387,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
     'experimental.chat.messages.transform':
       phaseReminderHook['experimental.chat.messages.transform'],
 
-    // Post-tool hooks: retry guidance for delegation errors + post-read nudge
+    // Post-tool hooks: retry guidance for delegation errors + file-tool nudge
     'tool.execute.after': async (input, output) => {
       await delegateTaskRetryHook['tool.execute.after'](
         input as { tool: string },
@@ -407,7 +407,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         },
       );
 
-      await postReadNudgeHook['tool.execute.after'](
+      await postFileToolNudgeHook['tool.execute.after'](
         input as {
           tool: string;
           sessionID?: string;