Browse Source

Code reviewer

Alvin Unreal 2 months ago
parent
commit
66e2c07b24

+ 33 - 12
README.md

@@ -32,6 +32,7 @@ The plugin follows a "Hub and Spoke" model:
 | **frontend-ui-ux** | Designer | Visual changes, CSS/styling, and React/Vue component polish. |
 | **document-writer** | Scribe | Technical documentation, READMEs, and inline code comments. |
 | **multimodal-looker** | Visionary | Analyzing screenshots, wireframes, or UI designs. |
+| **code-simplicity-reviewer** | Minimalist | Ruthless code simplification and YAGNI principle enforcement. |
 
 ---
 
@@ -52,21 +53,36 @@ The plugin provides three core tools to manage asynchronous work:
 
 You can customize the behavior of the plugin via the OpenCode configuration.
 
-### Customizing Agents
-In your `.opencode/config.json` (or via the plugin's config hook), you can override models or prompts:
+### Customizing & Adding Agents
+You can customize built-in agents or add entirely new ones in your `oh-my-opencode-lite.json` file.
+
+#### Adding a New Agent
+Simply define a `custom_agents` array. The Orchestrator will automatically learn when to use your new agent based on its `description`.
 
 ```json
 {
-  "oh-my-opencode-lite": {
-    "agents": {
-      "oracle": {
-        "model": "claude-3-5-sonnet",
-        "temperature": 0,
-        "prompt_append": "Always prioritize security in your reviews."
-      }
-    },
-    "disabled_agents": ["multimodal-looker"]
-  }
+  "custom_agents": [
+    {
+      "name": "sql-expert",
+      "description": "Schema design, query optimization, and complex joins",
+      "prompt": "You are a Senior DBA. Analyze the schema and provide optimized SQL...",
+      "model": "openai/gpt-4.5"
+    }
+  ]
+}
+```
+
+#### Overriding Built-in Agents
+```json
+{
+  "agents": {
+    "oracle": {
+      "model": "claude-3-5-sonnet",
+      "temperature": 0,
+      "prompt_append": "Always prioritize security in your reviews."
+    }
+  },
+  "disabled_agents": ["multimodal-looker"]
 }
 ```
 
@@ -74,6 +90,11 @@ In your `.opencode/config.json` (or via the plugin's config hook), you can overr
 
 ## 👨‍💻 Development
 
+### Configuration Files
+The plugin looks for configuration in two places (and merges them):
+1.  **User Global**: `~/.config/opencode/oh-my-opencode-lite.json` (or OS equivalent)
+2.  **Project Local**: `./.opencode/oh-my-opencode-lite.json`
+
 ### Getting Started
 1.  **Install dependencies**:
     ```bash

+ 1 - 0
src/agents/document-writer.ts

@@ -4,6 +4,7 @@ import type { AgentDefinition } from "./orchestrator";
 export function createDocumentWriterAgent(model: string): AgentDefinition {
   return {
     name: "document-writer",
+    description: "Technical documentation and READMEs",
     config: {
       model,
       temperature: 0.3,

+ 1 - 0
src/agents/explore.ts

@@ -4,6 +4,7 @@ import type { AgentDefinition } from "./orchestrator";
 export function createExploreAgent(model: string): AgentDefinition {
   return {
     name: "explore",
+    description: "Fast codebase search and pattern matching",
     config: {
       model,
       temperature: 0.1,

+ 1 - 0
src/agents/frontend.ts

@@ -4,6 +4,7 @@ import type { AgentDefinition } from "./orchestrator";
 export function createFrontendAgent(model: string): AgentDefinition {
   return {
     name: "frontend-ui-ux-engineer",
+    description: "UI/UX implementation and visual changes",
     config: {
       model,
       temperature: 0.7,

+ 44 - 17
src/agents/index.ts

@@ -7,44 +7,71 @@ import { createExploreAgent } from "./explore";
 import { createFrontendAgent } from "./frontend";
 import { createDocumentWriterAgent } from "./document-writer";
 import { createMultimodalAgent } from "./multimodal";
+import { createSimplicityReviewerAgent } from "./simplicity-reviewer";
 
 export type { AgentDefinition } from "./orchestrator";
 
 type AgentFactory = (model: string) => AgentDefinition;
 
-const AGENT_FACTORIES: Record<AgentName, AgentFactory> = {
-  orchestrator: createOrchestratorAgent,
+const SUBAGENT_FACTORIES: Omit<Record<AgentName, AgentFactory>, "orchestrator"> = {
   oracle: createOracleAgent,
   librarian: createLibrarianAgent,
   explore: createExploreAgent,
   "frontend-ui-ux-engineer": createFrontendAgent,
   "document-writer": createDocumentWriterAgent,
   "multimodal-looker": createMultimodalAgent,
+  "code-simplicity-reviewer": createSimplicityReviewerAgent,
 };
 
 export function createAgents(config?: PluginConfig): AgentDefinition[] {
   const disabledAgents = new Set(config?.disabled_agents ?? []);
   const agentOverrides = config?.agents ?? {};
 
-  return Object.entries(AGENT_FACTORIES)
-    .filter(([name]) => !disabledAgents.has(name))
-    .map(([name, factory]) => {
-      const override = agentOverrides[name];
-      const model = override?.model ?? DEFAULT_MODELS[name as AgentName];
-      const agent = factory(model);
+  // 1. Gather all sub-agent proto-definitions (built-in + custom)
+  const protoSubAgents: AgentDefinition[] = [
+    ...Object.entries(SUBAGENT_FACTORIES).map(([name, factory]) => {
+      const model = DEFAULT_MODELS[name as AgentName];
+      return factory(model);
+    }),
+    ...(config?.custom_agents ?? []).map((ca) => ({
+      name: ca.name,
+      description: ca.description,
+      config: {
+        model: ca.model ?? "anthropic/claude-sonnet-4-5",
+        temperature: ca.temperature ?? 0.1,
+        system: ca.prompt,
+      },
+    })),
+  ];
 
-      if (override?.temperature !== undefined) {
-        agent.config.temperature = override.temperature;
+  // 2. Apply common filtering and overrides
+  const allSubAgents = protoSubAgents
+    .filter((a) => !disabledAgents.has(a.name))
+    .map((agent) => {
+      const override = agentOverrides[agent.name];
+      if (override) {
+        if (override.model) agent.config.model = override.model;
+        if (override.temperature !== undefined) agent.config.temperature = override.temperature;
+        if (override.prompt) agent.config.system = override.prompt;
+        if (override.prompt_append)
+          agent.config.system = `${agent.config.system}\n\n${override.prompt_append}`;
       }
-      if (override?.prompt) {
-        agent.config.system = override.prompt;
-      }
-      if (override?.prompt_append) {
-        agent.config.system = `${agent.config.system}\n\n${override.prompt_append}`;
-      }
-
       return agent;
     });
+
+  // 3. Create Orchestrator (with its own overrides)
+  const orchestratorModel =
+    agentOverrides["orchestrator"]?.model ?? DEFAULT_MODELS["orchestrator"];
+  const orchestrator = createOrchestratorAgent(orchestratorModel, allSubAgents);
+  const oOverride = agentOverrides["orchestrator"];
+  if (oOverride) {
+    if (oOverride.temperature !== undefined) orchestrator.config.temperature = oOverride.temperature;
+    if (oOverride.prompt) orchestrator.config.system = oOverride.prompt;
+    if (oOverride.prompt_append)
+      orchestrator.config.system = `${orchestrator.config.system}\n\n${oOverride.prompt_append}`;
+  }
+
+  return [orchestrator, ...allSubAgents];
 }
 
 export function getAgentConfigs(config?: PluginConfig): Record<string, AgentConfig> {

+ 1 - 0
src/agents/librarian.ts

@@ -4,6 +4,7 @@ import type { AgentDefinition } from "./orchestrator";
 export function createLibrarianAgent(model: string): AgentDefinition {
   return {
     name: "librarian",
+    description: "External documentation and library research",
     config: {
       model,
       temperature: 0.1,

+ 1 - 0
src/agents/multimodal.ts

@@ -4,6 +4,7 @@ import type { AgentDefinition } from "./orchestrator";
 export function createMultimodalAgent(model: string): AgentDefinition {
   return {
     name: "multimodal-looker",
+    description: "Image and UI analysis",
     config: {
       model,
       temperature: 0.1,

+ 1 - 0
src/agents/oracle.ts

@@ -4,6 +4,7 @@ import type { AgentDefinition } from "./orchestrator";
 export function createOracleAgent(model: string): AgentDefinition {
   return {
     name: "oracle",
+    description: "Architecture, debugging, and code review",
     config: {
       model,
       temperature: 0.1,

+ 16 - 15
src/agents/orchestrator.ts

@@ -2,21 +2,29 @@ import type { AgentConfig } from "@opencode-ai/sdk";
 
 export interface AgentDefinition {
   name: string;
+  description: string;
   config: AgentConfig;
 }
 
-export function createOrchestratorAgent(model: string): AgentDefinition {
+export function createOrchestratorAgent(model: string, subAgents: AgentDefinition[]): AgentDefinition {
+  const agentTable = subAgents
+    .map((a) => `| @${a.name} | ${a.description} |`)
+    .join("\n");
+
+  const prompt = ORCHESTRATOR_PROMPT_TEMPLATE.replace("{{AGENT_TABLE}}", agentTable);
+
   return {
     name: "orchestrator",
+    description: "AI coding orchestrator with access to specialized subagents",
     config: {
       model,
       temperature: 0.1,
-      system: ORCHESTRATOR_PROMPT,
+      system: prompt,
     },
   };
 }
 
-const ORCHESTRATOR_PROMPT = `<Role>
+const ORCHESTRATOR_PROMPT_TEMPLATE = `<Role>
 You are an AI coding orchestrator with access to specialized subagents.
 
 **Core Competencies**:
@@ -27,13 +35,9 @@ You are an AI coding orchestrator with access to specialized subagents.
 </Role>
 
 <Subagents>
-| Agent | Purpose | When to Use |
-|-------|---------|-------------|
-| @oracle | Architecture, debugging, code review | Complex decisions, after 2+ failed attempts |
-| @librarian | Docs, GitHub examples, library research | External library questions |
-| @explore | Fast codebase grep | "Find X", "Where is Y", codebase patterns |
-| @frontend-ui-ux-engineer | UI/UX implementation | Visual/styling changes |
-| @document-writer | Technical documentation | README, API docs |
+| Agent | Purpose / When to Use |
+|-------|-----------------------|
+{{AGENT_TABLE}}
 </Subagents>
 
 <Delegation>
@@ -47,11 +51,8 @@ background_task(agent="librarian", prompt="How does library X handle Y")
 \`\`\`
 
 ## When to Delegate
-- Frontend visual work → frontend-ui-ux-engineer
-- Documentation → document-writer  
-- Research → librarian (background)
-- Codebase search → explore (background, fire multiple)
-- Complex architecture → oracle (consult first)
+- Use the subagent most relevant to the task description.
+- Use background tasks for research or search while you continue working.
 </Delegation>
 
 <Workflow>

+ 93 - 0
src/agents/simplicity-reviewer.ts

@@ -0,0 +1,93 @@
+import type { AgentDefinition } from "./orchestrator";
+
+export function createSimplicityReviewerAgent(model: string): AgentDefinition {
+  return {
+    name: "code-simplicity-reviewer",
+    description: "Ruthless code simplification and YAGNI principle enforcement",
+    config: {
+      model,
+      temperature: 0.1,
+      system: SIMPLICITY_REVIEWER_PROMPT,
+    },
+  };
+}
+
+const SIMPLICITY_REVIEWER_PROMPT = `You are a code simplicity expert specializing in minimalism and the YAGNI (You Aren't Gonna Need It) principle. Your mission is to ruthlessly simplify code while maintaining functionality and clarity.
+
+When reviewing code, you will:
+
+1. **Analyze Every Line**: Question the necessity of each line of code. If it doesn't directly contribute to the current requirements, flag it for removal.
+
+2. **Simplify Complex Logic**: 
+   - Break down complex conditionals into simpler forms
+   - Replace clever code with obvious code
+   - Eliminate nested structures where possible
+   - Use early returns to reduce indentation
+
+3. **Remove Redundancy**:
+   - Identify duplicate error checks
+   - Find repeated patterns that can be consolidated
+   - Eliminate defensive programming that adds no value
+   - Remove commented-out code
+
+4. **Challenge Abstractions**:
+   - Question every interface, base class, and abstraction layer
+   - Recommend inlining code that's only used once
+   - Suggest removing premature generalizations
+   - Identify over-engineered solutions
+
+5. **Apply YAGNI Rigorously**:
+   - Remove features not explicitly required now
+   - Eliminate extensibility points without clear use cases
+   - Question generic solutions for specific problems
+   - Remove "just in case" code
+
+6. **Optimize for Readability**:
+   - Prefer self-documenting code over comments
+   - Use descriptive names instead of explanatory comments
+   - Simplify data structures to match actual usage
+   - Make the common case obvious
+
+Your review process:
+
+1. First, identify the core purpose of the code
+2. List everything that doesn't directly serve that purpose
+3. For each complex section, propose a simpler alternative
+4. Create a prioritized list of simplification opportunities
+5. Estimate the lines of code that can be removed
+
+Output format:
+
+\`\`\`markdown
+## Simplification Analysis
+
+### Core Purpose
+[Clearly state what this code actually needs to do]
+
+### Unnecessary Complexity Found
+- [Specific issue with line numbers/file]
+- [Why it's unnecessary]
+- [Suggested simplification]
+
+### Code to Remove
+- [File:lines] - [Reason]
+- [Estimated LOC reduction: X]
+
+### Simplification Recommendations
+1. [Most impactful change]
+   - Current: [brief description]
+   - Proposed: [simpler alternative]
+   - Impact: [LOC saved, clarity improved]
+
+### YAGNI Violations
+- [Feature/abstraction that isn't needed]
+- [Why it violates YAGNI]
+- [What to do instead]
+
+### Final Assessment
+Total potential LOC reduction: X%
+Complexity score: [High/Medium/Low]
+Recommended action: [Proceed with simplifications/Minor tweaks only/Already minimal]
+\`\`\`
+
+Remember: Perfect is the enemy of good. The simplest code that works is often the best code. Every line of code is a liability - it can have bugs, needs maintenance, and adds cognitive load. Your job is to minimize these liabilities while preserving functionality.`;

+ 6 - 6
src/config/loader.ts

@@ -73,18 +73,18 @@ export function loadPluginConfig(directory: string): PluginConfig {
       ...config,
       ...projectConfig,
       agents: deepMerge(config.agents, projectConfig.agents),
+      custom_agents: [
+        ...new Map([
+          ...(config.custom_agents ?? []).map((a) => [a.name, a] as const),
+          ...(projectConfig.custom_agents ?? []).map((a) => [a.name, a] as const),
+        ]).values(),
+      ],
       disabled_agents: [
         ...new Set([
           ...(config.disabled_agents ?? []),
           ...(projectConfig.disabled_agents ?? []),
         ]),
       ],
-      disabled_hooks: [
-        ...new Set([
-          ...(config.disabled_hooks ?? []),
-          ...(projectConfig.disabled_hooks ?? []),
-        ]),
-      ],
     };
   }
 

+ 10 - 2
src/config/schema.ts

@@ -14,8 +14,14 @@ export type AgentConfig = z.infer<typeof AgentConfigSchema>;
 // Main plugin config
 export const PluginConfigSchema = z.object({
   agents: z.record(z.string(), AgentConfigSchema).optional(),
+  custom_agents: z.array(z.object({
+    name: z.string(),
+    description: z.string(),
+    prompt: z.string(),
+    model: z.string().optional(),
+    temperature: z.number().optional(),
+  })).optional(),
   disabled_agents: z.array(z.string()).optional(),
-  disabled_hooks: z.array(z.string()).optional(),
 });
 
 export type PluginConfig = z.infer<typeof PluginConfigSchema>;
@@ -28,7 +34,8 @@ export type AgentName =
   | "explore"
   | "frontend-ui-ux-engineer"
   | "document-writer"
-  | "multimodal-looker";
+  | "multimodal-looker"
+  | "code-simplicity-reviewer";
 
 export const DEFAULT_MODELS: Record<AgentName, string> = {
   orchestrator: "anthropic/claude-sonnet-4-5",
@@ -38,4 +45,5 @@ export const DEFAULT_MODELS: Record<AgentName, string> = {
   "frontend-ui-ux-engineer": "google/gemini-2.5-pro",
   "document-writer": "google/gemini-2.5-pro",
   "multimodal-looker": "google/gemini-2.5-flash",
+  "code-simplicity-reviewer": "anthropic/claude-sonnet-4-5",
 };