diff --git a/flake.lock b/flake.lock index fd549db..615b141 100644 --- a/flake.lock +++ b/flake.lock @@ -98,11 +98,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1760524057, - "narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=", + "lastModified": 1760878510, + "narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5", + "rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67", "type": "github" }, "original": { @@ -114,11 +114,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1760423683, - "narHash": "sha256-Tb+NYuJhWZieDZUxN6PgglB16yuqBYQeMJyYBGCXlt8=", + "lastModified": 1761016216, + "narHash": "sha256-G/iC4t/9j/52i/nm+0/4ybBmAF4hzR8CNHC75qEhjHo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "a493e93b4a259cd9fea8073f89a7ed9b1c5a1da2", + "rev": "481cf557888e05d3128a76f14c76397b7d7cc869", "type": "github" }, "original": { diff --git a/nate-work/modules/home-manager/home.nix b/nate-work/modules/home-manager/home.nix index 91d9902..e1b7486 100644 --- a/nate-work/modules/home-manager/home.nix +++ b/nate-work/modules/home-manager/home.nix @@ -56,6 +56,7 @@ python3 nodejs_24 cmake + gh # AI unstable.claude-code unstable.opencode diff --git a/shared/linked-dotfiles/opencode/.ignore b/shared/linked-dotfiles/opencode/.ignore new file mode 100644 index 0000000..a860310 --- /dev/null +++ b/shared/linked-dotfiles/opencode/.ignore @@ -0,0 +1 @@ +node_modules/** diff --git a/shared/linked-dotfiles/opencode/PLUGIN_REFERENCE.md b/shared/linked-dotfiles/opencode/PLUGIN_REFERENCE.md new file mode 100644 index 0000000..876e662 --- /dev/null +++ b/shared/linked-dotfiles/opencode/PLUGIN_REFERENCE.md @@ -0,0 +1,442 @@ +# OpenCode Plugin Development Reference + +## Overview + +OpenCode plugins are JavaScript/TypeScript modules that extend OpenCode's functionality by hooking into various events and customizing behavior. Plugins can add custom tools, modify LLM parameters, handle authentication, intercept tool execution, and respond to system events. + +## Plugin Locations + +Plugins are automatically loaded from: +1. **Project-local**: `.opencode/plugin/` directory in your project +2. **Global**: `~/.config/opencode/plugin/` directory + +**Note**: Local plugin files are auto-discovered and do NOT need to be listed in `opencode.jsonc`'s `plugin` array. The `plugin` array is only for npm package plugins. + +## Basic Plugin Structure + +### File Format + +Plugins are `.js` or `.ts` files that export one or more plugin functions: + +```javascript +export const MyPlugin = async ({ project, client, $, directory, worktree }) => { + // Initialization code here + console.log("Plugin initialized"); + + return { + // Hook implementations + }; +}; +``` + +### Plugin Context (Input Parameters) + +Every plugin function receives a context object with: + +| Parameter | Type | Description | +|-----------|------|-------------| +| `project` | `Project` | Current project information | +| `directory` | `string` | Current working directory | +| `worktree` | `string` | Git worktree path | +| `client` | `OpencodeClient` | OpenCode SDK client for API access | +| `$` | `BunShell` | Bun's shell API for executing commands | + +### Plugin Return Value (Hooks) + +Plugins return an object containing hook implementations. All hooks are optional. + +## Available Hooks + +### 1. `event` Hook + +Respond to OpenCode system events. + +```javascript +event: async ({ event }) => { + if (event.type === "session.idle") { + // OpenCode is waiting for user input + } +} +``` + +**Common Event Types:** +- `session.idle` - Session is waiting for user input +- `session.start` - Session has started +- `session.end` - Session has ended +- Additional events available via SDK + +### 2. `config` Hook + +React to configuration changes. + +```javascript +config: async (config) => { + console.log("Config:", config.model, config.theme); +} +``` + +### 3. `tool` Hook + +Add custom tools that the LLM can call. + +```javascript +import { tool } from "@opencode-ai/plugin"; + +return { + tool: { + mytool: tool({ + description: "Description shown to LLM", + args: { + query: tool.schema.string().describe("Query parameter"), + count: tool.schema.number().optional().describe("Optional count") + }, + async execute(args, context) { + // context contains: { agent, sessionID, messageID } + return `Result: ${args.query}`; + } + }) + } +}; +``` + +**Tool Schema Types** (using Zod): +- `tool.schema.string()` +- `tool.schema.number()` +- `tool.schema.boolean()` +- `tool.schema.object({ ... })` +- `tool.schema.array(...)` +- `.optional()` - Make parameter optional +- `.describe("...")` - Add description for LLM + +### 4. `auth` Hook + +Provide custom authentication methods. + +```javascript +auth: { + provider: "my-service", + methods: [ + { + type: "api", + label: "API Key" + }, + { + type: "oauth", + label: "OAuth Login", + authorize: async () => ({ + url: "https://...", + instructions: "Login instructions", + method: "auto", + callback: async () => ({ + type: "success", + key: "token" + }) + }) + } + ] +} +``` + +### 5. `chat.message` Hook + +Called when a new user message is received. + +```javascript +"chat.message": async ({}, output) => { + console.log("Message:", output.message.text); + console.log("Parts:", output.parts); +} +``` + +### 6. `chat.params` Hook + +Modify parameters sent to the LLM. + +```javascript +"chat.params": async (input, output) => { + // input: { model, provider, message } + output.temperature = 0.7; + output.topP = 0.95; + output.options = { /* custom options */ }; +} +``` + +### 7. `permission.ask` Hook + +Intercept permission requests. + +```javascript +"permission.ask": async (permission, output) => { + if (permission.tool === "bash" && permission.args.command.includes("rm")) { + output.status = "deny"; // or "allow" or "ask" + } +} +``` + +### 8. `tool.execute.before` Hook + +Intercept tool execution before it runs. + +```javascript +"tool.execute.before": async (input, output) => { + // input: { tool, sessionID, callID } + // output: { args } + + if (input.tool === "read" && output.args.filePath.includes(".env")) { + throw new Error("Cannot read .env files"); + } +} +``` + +### 9. `tool.execute.after` Hook + +Modify tool execution results. + +```javascript +"tool.execute.after": async (input, output) => { + // input: { tool, sessionID, callID } + // output: { title, output, metadata } + + console.log(`Tool ${input.tool} returned:`, output.output); +} +``` + +## Using the OpenCode SDK Client + +The `client` parameter provides full API access: + +### Common Operations + +```javascript +// Get current project +const project = await client.project.current(); + +// List sessions +const sessions = await client.session.list(); + +// Read a file +const content = await client.file.read({ + query: { path: "src/index.ts" } +}); + +// Search for text +const matches = await client.find.text({ + query: { pattern: "TODO" } +}); + +// Show toast notification +await client.tui.showToast({ + body: { message: "Task complete", variant: "success" } +}); + +// Send a prompt +await client.session.prompt({ + path: { id: sessionID }, + body: { + model: { providerID: "anthropic", modelID: "claude-3-5-sonnet-20241022" }, + parts: [{ type: "text", text: "Hello!" }] + } +}); +``` + +### Available SDK Methods + +**App**: `app.log()`, `app.agents()` +**Project**: `project.list()`, `project.current()` +**Sessions**: `session.list()`, `session.get()`, `session.create()`, `session.delete()`, etc. +**Files**: `file.read()`, `file.status()`, `find.text()`, `find.files()`, `find.symbols()` +**TUI**: `tui.appendPrompt()`, `tui.showToast()`, `tui.openHelp()`, etc. +**Config**: `config.get()`, `config.providers()` +**Events**: `event.subscribe()` (for event streaming) + +See full SDK documentation: https://opencode.ai/docs/sdk/ + +## Using Bun Shell (`$`) + +Execute shell commands easily: + +```javascript +// Simple command +await $`notify-send "Hello"`; + +// Get output +const text = await $`git status`.text(); +const json = await $`hyprctl clients -j`.json(); + +// Command with variables +const file = "test.txt"; +await $`cat ${file}`; + +// Array of arguments +const args = ["notify-send", "-u", "normal", "Title", "Body"]; +await $`${args}`; +``` + +## Complete Example: Notification Plugin + +```javascript +export const NotificationPlugin = async ({ project, client, $, directory, worktree }) => { + console.log("Notification plugin initialized"); + + return { + // Send notification when OpenCode is idle + event: async ({ event }) => { + if (event.type === "session.idle") { + const pid = process.pid; + const iconPath = `${process.env.HOME}/.config/opencode/icon.png`; + + try { + // Get window info from Hyprland + const clientsJson = await $`hyprctl clients -j`.text(); + const clients = JSON.parse(clientsJson); + const window = clients.find(c => c.pid === pid); + + // Send notification with action + const result = await $`notify-send -a "OpenCode" -u normal -i ${iconPath} -A focus=Focus "OpenCode Ready" "Waiting for input in ${directory}"`.text(); + + // Handle action click + if (result.trim() === "focus" && window?.address) { + await $`hyprctl dispatch focuswindow address:${window.address}`; + } + } catch (error) { + console.error("Notification error:", error); + } + } + }, + + // Add custom tool + tool: { + notify: tool({ + description: "Send a system notification", + args: { + message: tool.schema.string().describe("Notification message"), + urgency: tool.schema.enum(["low", "normal", "critical"]).optional() + }, + async execute(args) { + await $`notify-send -u ${args.urgency || "normal"} "OpenCode" ${args.message}`; + return "Notification sent"; + } + }) + }, + + // Modify LLM parameters + "chat.params": async (input, output) => { + // Lower temperature for code-focused tasks + if (input.message.text?.includes("refactor") || input.message.text?.includes("bug")) { + output.temperature = 0.3; + } + }, + + // Prevent dangerous operations + "tool.execute.before": async (input, output) => { + if (input.tool === "bash" && output.args.command.includes("rm -rf")) { + throw new Error("Dangerous command blocked by plugin"); + } + } + }; +}; +``` + +## TypeScript Support + +For type-safe plugins, import types from the plugin package: + +```typescript +import type { Plugin } from "@opencode-ai/plugin"; + +export const MyPlugin: Plugin = async (ctx) => { + return { + // Type-safe hook implementations + }; +}; +``` + +## Best Practices + +1. **Error Handling**: Always wrap risky operations in try-catch blocks +2. **Async Operations**: All hook functions should be async +3. **Console Logging**: Use `console.log()` for debugging - visible in OpenCode logs +4. **Resource Cleanup**: Clean up resources when plugins are reloaded +5. **Minimal Processing**: Keep hooks fast to avoid blocking OpenCode +6. **Security**: Validate inputs, especially in custom tools +7. **Documentation**: Add clear descriptions to custom tools for the LLM + +## Common Use Cases + +### System Integration +- Send desktop notifications (Linux, macOS, Windows) +- Integrate with window managers (Hyprland, i3, etc.) +- System clipboard operations +- File system watching + +### Development Workflow +- Run tests on code changes +- Format code automatically +- Update documentation +- Git operations + +### LLM Enhancement +- Add domain-specific tools +- Custom prompt preprocessing +- Response filtering +- Context augmentation + +### Security & Compliance +- Block dangerous commands +- Prevent access to sensitive files +- Audit tool usage +- Rate limiting + +## Debugging + +1. **View Logs**: Run OpenCode with debug output + ```bash + G_MESSAGES_DEBUG=all opencode + ``` + +2. **Console Logging**: Use `console.log()`, `console.error()` in plugins + +3. **Test Independently**: Test shell commands and SDK calls outside plugins first + +4. **Hot Reload**: Plugins are reloaded when files change (in development mode) + +## Related Documentation + +- [OpenCode Plugins](https://opencode.ai/docs/plugins/) +- [OpenCode SDK](https://opencode.ai/docs/sdk/) +- [Custom Tools](https://opencode.ai/docs/custom-tools/) +- [Bun Shell API](https://bun.sh/docs/runtime/shell) +- [Zod Documentation](https://zod.dev/) + +## Template Plugin + +```javascript +/** + * Template Plugin + * Description: What this plugin does + */ +export const TemplatePlugin = async ({ project, client, $, directory, worktree }) => { + // Initialization + console.log("Template plugin initialized"); + + // You can store state here + let pluginState = {}; + + return { + // Implement the hooks you need + event: async ({ event }) => { + // Handle events + }, + + tool: { + // Add custom tools + }, + + "chat.params": async (input, output) => { + // Modify LLM parameters + }, + + // ... other hooks as needed + }; +}; +``` diff --git a/shared/linked-dotfiles/opencode/agent/review.md b/shared/linked-dotfiles/opencode/agent/review.md new file mode 100644 index 0000000..f02b327 --- /dev/null +++ b/shared/linked-dotfiles/opencode/agent/review.md @@ -0,0 +1,19 @@ +--- +description: Reviews code for quality and best practices +mode: subagent +model: anthropic/claude-sonnet-4-20250514 +temperature: 0.1 +tools: + write: false + edit: false + bash: false +--- + +You are in code review mode. Focus on: + +- Code quality and best practices +- Potential bugs and edge cases +- Performance implications +- Security considerations + +Provide constructive feedback without making direct changes. diff --git a/shared/linked-dotfiles/opencode/command/skills.md b/shared/linked-dotfiles/opencode/command/skills.md new file mode 100644 index 0000000..8e399c5 --- /dev/null +++ b/shared/linked-dotfiles/opencode/command/skills.md @@ -0,0 +1,15 @@ +--- +description: List available skills +--- + +Do not add any commentary, explanation, or additional text. + +--- + +# Available Skills + +!`find "$HOME/.config/opencode/skills" -maxdepth 2 -name "SKILL.md" -type f -exec sh -c 'dir=$(dirname "{}"); name=$(basename "$dir"); echo ""; echo "## $name"; echo ""; head -20 "{}" | grep "^description:" | head -1 | sed "s/^description: //"' \;` + +--- + +To invoke a skill, use the corresponding `skills_*` tool (e.g., `skills_create_skill`, `skills_go_pr_review`). diff --git a/shared/linked-dotfiles/opencode/opencode.jsonc b/shared/linked-dotfiles/opencode/opencode.jsonc index b43ce5c..f6c44d1 100644 --- a/shared/linked-dotfiles/opencode/opencode.jsonc +++ b/shared/linked-dotfiles/opencode/opencode.jsonc @@ -1,16 +1,11 @@ { "$schema": "https://opencode.ai/config.json", + // Theme configuration "theme": "catppuccin", + "model": "anthropic/claude-sonnet-4-5", "autoupdate": false, - "model": "claude-sonnet-4-5", - "small_model": "claude-haiku-4-5", - "mcp": { - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp", - "headers": { - "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" - } - } - } + "plugin": [ + // "skills" + // "swaync-notifications" + ] } \ No newline at end of file diff --git a/shared/linked-dotfiles/opencode/opencode.png b/shared/linked-dotfiles/opencode/opencode.png new file mode 100644 index 0000000..d7f1c9e Binary files /dev/null and b/shared/linked-dotfiles/opencode/opencode.png differ diff --git a/shared/linked-dotfiles/opencode/package-lock.json b/shared/linked-dotfiles/opencode/package-lock.json new file mode 100644 index 0000000..206220a --- /dev/null +++ b/shared/linked-dotfiles/opencode/package-lock.json @@ -0,0 +1,138 @@ +{ + "name": "opencode", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@opencode-ai/plugin": "0.15.2", + "gray-matter": "^4.0.3" + } + }, + "node_modules/@opencode-ai/plugin": { + "version": "0.15.2", + "dependencies": { + "@opencode-ai/sdk": "0.15.2", + "zod": "4.1.8" + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "0.15.2" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/zod": { + "version": "4.1.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/shared/linked-dotfiles/opencode/plugin/skills.js b/shared/linked-dotfiles/opencode/plugin/skills.js new file mode 100644 index 0000000..043fe3a --- /dev/null +++ b/shared/linked-dotfiles/opencode/plugin/skills.js @@ -0,0 +1,160 @@ +/** + * OpenCode Skills Plugin (Local Version) + * + * Implements Anthropic's Agent Skills Specification (v1.0) for OpenCode. + * + * Modified to: + * - Only scan ~/.config/opencode/skills/ directory + * - Provide minimal logging (one-line summary) + * + * Original: https://github.com/malhashemi/opencode-skills + */ +import { tool } from "@opencode-ai/plugin"; +import matter from "gray-matter"; +import { Glob } from "bun"; +import { join, dirname, basename, relative, sep } from "path"; +import { z } from "zod"; +import os from "os"; + +const SkillFrontmatterSchema = z.object({ + name: z.string() + .regex(/^[a-z0-9-]+$/, "Name must be lowercase alphanumeric with hyphens") + .min(1, "Name cannot be empty"), + description: z.string() + .min(20, "Description must be at least 20 characters for discoverability"), + license: z.string().optional(), + "allowed-tools": z.array(z.string()).optional(), + metadata: z.record(z.string()).optional() +}); + +function generateToolName(skillPath, baseDir) { + const rel = relative(baseDir, skillPath); + const dirPath = dirname(rel); + const components = dirPath.split(sep).filter(c => c !== "."); + return "skills_" + components.join("_").replace(/-/g, "_"); +} + +async function parseSkill(skillPath, baseDir) { + try { + const content = await Bun.file(skillPath).text(); + const { data, content: markdown } = matter(content); + + let frontmatter; + try { + frontmatter = SkillFrontmatterSchema.parse(data); + } + catch (error) { + return null; + } + + const skillDir = basename(dirname(skillPath)); + if (frontmatter.name !== skillDir) { + return null; + } + + const toolName = generateToolName(skillPath, baseDir); + + return { + name: frontmatter.name, + fullPath: dirname(skillPath), + toolName, + description: frontmatter.description, + allowedTools: frontmatter["allowed-tools"], + metadata: frontmatter.metadata, + license: frontmatter.license, + content: markdown.trim(), + path: skillPath + }; + } + catch (error) { + return null; + } +} + +async function discoverSkills(basePath) { + const skills = []; + + try { + const glob = new Glob("**/SKILL.md"); + for await (const match of glob.scan({ + cwd: basePath, + absolute: true + })) { + const skill = await parseSkill(match, basePath); + if (skill) { + skills.push(skill); + } + } + } + catch (error) { + // Directory doesn't exist, return empty array + } + + return skills; +} + +export const SkillsPlugin = async (ctx) => { + const xdgConfigHome = process.env.XDG_CONFIG_HOME; + const configSkillsPath = xdgConfigHome + ? join(xdgConfigHome, "opencode/skills") + : join(os.homedir(), ".config/opencode/skills"); + + const skills = await discoverSkills(configSkillsPath); + + if (skills.length > 0) { + console.log(`Skills loaded: ${skills.map(s => s.name).join(", ")}`); + } + + const tools = {}; + for (const skill of skills) { + tools[skill.toolName] = tool({ + description: skill.description, + args: {}, + async execute(args, toolCtx) { + return `# ⚠️ SKILL EXECUTION INSTRUCTIONS ⚠️ + +**SKILL NAME:** ${skill.name} +**SKILL DIRECTORY:** ${skill.fullPath}/ + +## EXECUTION WORKFLOW: + +**STEP 1: PLAN THE WORK** +Before executing this skill, use the \`todowrite\` tool to create a todo list of the main tasks described in the skill content below. +- Parse the skill instructions carefully +- Identify the key tasks and steps required +- Create todos with status "pending" and appropriate priority levels +- This helps track progress and ensures nothing is missed + +**STEP 2: EXECUTE THE SKILL** +Follow the skill instructions below, marking todos as "in_progress" when starting a task and "completed" when done. +Use \`todowrite\` to update task statuses as you work through them. + +## PATH RESOLUTION RULES (READ CAREFULLY): + +All file paths mentioned below are relative to the SKILL DIRECTORY shown above. + +**Examples:** +- If the skill mentions \`scripts/init_skill.py\`, the full path is: \`${skill.fullPath}/scripts/init_skill.py\` +- If the skill mentions \`references/docs.md\`, the full path is: \`${skill.fullPath}/references/docs.md\` +- If the skill mentions \`assets/template.html\`, the full path is: \`${skill.fullPath}/assets/template.html\` + +**IMPORTANT:** Always prepend \`${skill.fullPath}/\` to any relative path mentioned in the skill content below. + +--- + +# SKILL CONTENT: + +${skill.content} + +--- + +**Remember:** +1. All relative paths in the skill content above are relative to: \`${skill.fullPath}/\` +2. Update your todo list as you progress through the skill tasks +`; + } + }); + } + + return { tool: tools }; +}; diff --git a/shared/linked-dotfiles/opencode/plugin/swaync-notifications.js b/shared/linked-dotfiles/opencode/plugin/swaync-notifications.js new file mode 100644 index 0000000..66ac51f --- /dev/null +++ b/shared/linked-dotfiles/opencode/plugin/swaync-notifications.js @@ -0,0 +1,68 @@ +export const SwayNotificationCenter = async ({ project, client, $, directory, worktree }) => { + console.log("SwayNC notification plugin initialized"); + + return { + event: async ({ event }) => { + if (event.type === "session.idle") { + const pid = process.pid; + const dir = directory || worktree || "unknown"; + const iconPath = `${process.env.HOME}/.config/opencode/opencode.png`; + + try { + const clientsJson = await $`hyprctl clients -j`.text(); + const clients = JSON.parse(clientsJson); + + const opencodeWindow = clients.find(c => + c.pid === pid || + (c.title && c.title.toLowerCase().includes("opencode")) + ); + + const windowAddress = opencodeWindow?.address || ""; + + const notifyCmd = [ + "notify-send", + "-a", "OpenCode", + "-u", "normal", + "-i", iconPath, + "-h", `string:x-opencode-window:${windowAddress}`, + "-h", `string:x-opencode-dir:${dir}`, + "-A", `focus=Focus Window`, + "OpenCode Ready", + `Waiting for input\nDirectory: ${dir}` + ]; + + if (windowAddress) { + // Run notify-send as detached background process + import("child_process").then(({ spawn }) => { + const child = spawn(notifyCmd[0], notifyCmd.slice(1), { + detached: true, + stdio: 'ignore' + }); + child.unref(); + + // Handle the response in the background + let output = ''; + if (child.stdout) { + child.stdout.on('data', (data) => { + output += data.toString(); + }); + child.on('close', () => { + if (output.trim() === "focus" && windowAddress) { + $`hyprctl dispatch focuswindow address:${windowAddress}`.catch(() => {}); + } + }); + } + }).catch(() => {}); + } else { + // Run without action button, no need to wait + $`${notifyCmd.filter(arg => !arg.startsWith('focus'))}`.catch(() => {}); + } + } catch (error) { + console.error("Notification error:", error); + + await $`notify-send -a "OpenCode" -u normal -i ${iconPath} "OpenCode Ready" "Waiting for input in ${dir}"`; + } + } + }, + }; +}; diff --git a/shared/linked-dotfiles/opencode/skills/create-skill/SKILL.md b/shared/linked-dotfiles/opencode/skills/create-skill/SKILL.md new file mode 100644 index 0000000..5926dee --- /dev/null +++ b/shared/linked-dotfiles/opencode/skills/create-skill/SKILL.md @@ -0,0 +1,858 @@ +--- +name: create-skill +description: Use when creating new skills, editing existing skills, or planning skill architecture - provides comprehensive guidance on skill structure, discoverability, token efficiency, and best practices for writing skills that AI can find and use effectively +--- + +# Creating Skills + +## Overview + +Skills are reference guides for proven techniques, patterns, or tools. Good skills are concise, well-structured, discoverable, and help AI instances find and apply effective approaches. + +**Core principle:** Only add context AI doesn't already have. Challenge every token - assume Claude is smart and knows standard practices. + +## When to Create a Skill + +**Create when:** +- Technique wasn't intuitively obvious to you +- You'd reference this again across projects +- Pattern applies broadly (not project-specific) +- Others would benefit from this knowledge + +**Don't create for:** +- One-off solutions +- Standard practices well-documented elsewhere +- Project-specific conventions (put in CLAUDE.md instead) +- Obvious or trivial information + +## Skill Types + +### Technique +Concrete method with steps to follow + +**Examples:** condition-based-waiting, root-cause-tracing, defensive-programming + +**Test with:** Application scenarios, variation scenarios, missing information tests + +### Pattern +Way of thinking about problems + +**Examples:** flatten-with-flags, reducing-complexity, information-hiding + +**Test with:** Recognition scenarios, application scenarios, counter-examples + +### Reference +API docs, syntax guides, tool documentation + +**Examples:** API documentation, command references, library guides + +**Test with:** Retrieval scenarios, application scenarios, gap testing + +## Skill Structure Requirements + +### Directory Layout +``` +skill-name/ +├── SKILL.md # Required: Main skill file with frontmatter +├── scripts/ # Optional: Executable code +├── references/ # Optional: Documentation to load as needed +└── assets/ # Optional: Files used in output +``` + +### Naming Conventions +- **Directory name**: lowercase with hyphens only (e.g., `my-skill`) +- **Frontmatter name**: must exactly match directory name +- **Tool name**: auto-generated as `skills_{directory_name}` with underscores +- **Use gerund form (verb + -ing)**: `processing-pdfs`, `analyzing-data`, `creating-skills` +- **Avoid vague names**: "Helper", "Utils", "Tools" + +## Frontmatter Requirements + +### Required Fields +```yaml +--- +name: skill-name +description: Use when [specific triggers/symptoms] - [what it does and how it helps] +--- +``` + +### Constraints +- **Max 1024 characters total** for frontmatter +- **Only `name` and `description`** fields supported +- **Name**: letters, numbers, hyphens only (no parentheses, special chars) +- **Description target**: under 500 characters if possible + +### Writing Effective Descriptions + +**Critical for discovery:** AI reads description to decide which skills to load. + +**Format:** Start with "Use when..." to focus on triggering conditions + +**Include:** +- Concrete triggers, symptoms, and situations that signal this skill applies +- Describe the *problem* not *language-specific symptoms* (unless skill is tech-specific) +- Technology-agnostic triggers unless skill is technology-specific +- What the skill does and how it helps + +**Always write in third person** (injected into system prompt): + +**Good examples:** +```yaml +description: Use when tests have race conditions, timing dependencies, or pass/fail inconsistently - replaces arbitrary timeouts with condition polling for reliable async tests + +description: Use when using React Router and handling authentication redirects - provides patterns for protected routes and auth state management + +description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction. +``` + +**Bad examples:** +```yaml +# Too abstract, no triggers +description: For async testing + +# First person +description: I can help you with async tests when they're flaky + +# Vague, no specifics +description: Helps with documents +``` + +## Core Principles + +### Concise is Key + +Context window is shared with everything else. Only add what AI doesn't already know. + +**Challenge each piece of information:** +- "Does Claude really need this explanation?" +- "Can I assume Claude knows this?" +- "Does this paragraph justify its token cost?" + +**Good (concise - ~50 tokens):** +````markdown +## Extract PDF text + +Use pdfplumber for text extraction: + +```python +import pdfplumber + +with pdfplumber.open("file.pdf") as pdf: + text = pdf.pages[0].extract_text() +``` +```` + +**Bad (verbose - ~150 tokens):** +```markdown +## Extract PDF text + +PDF (Portable Document Format) files are a common file format that contains text, images, and other content. To extract text from a PDF, you'll need to use a library. There are many libraries available for PDF processing, but we recommend pdfplumber because it's easy to use and handles most cases well. First, you'll need to install it using pip. Then you can use the code below... +``` + +### Set Appropriate Degrees of Freedom + +Match specificity to task fragility and variability. + +**Analogy:** Think of AI as a robot exploring a path: +- **Narrow bridge with cliffs**: Provide specific guardrails and exact instructions (low freedom) +- **Open field with no hazards**: Give general direction and trust AI to find best route (high freedom) + +**High freedom** (text-based instructions): + +Use when multiple approaches are valid, decisions depend on context, heuristics guide approach. + +```markdown +## Code review process + +1. Analyze the code structure and organization +2. Check for potential bugs or edge cases +3. Suggest improvements for readability and maintainability +4. Verify adherence to project conventions +``` + +**Medium freedom** (pseudocode or scripts with parameters): + +Use when a preferred pattern exists, some variation is acceptable, configuration affects behavior. + +````markdown +## Generate report + +Use this template and customize as needed: + +```python +def generate_report(data, format="markdown", include_charts=True): + # Process data + # Generate output in specified format + # Optionally include visualizations +``` +```` + +**Low freedom** (specific scripts, few or no parameters): + +Use when operations are fragile and error-prone, consistency is critical, specific sequence must be followed. + +````markdown +## Database migration + +Run exactly this script: + +```bash +python scripts/migrate.py --verify --backup +``` + +Do not modify the command or add additional flags. +```` + +## Content Structure + +### Recommended Template + +```markdown +# Skill Title + +Brief overview of the skill's purpose (1-2 sentences with core principle). + +## When to Use This Skill + +List specific symptoms and use cases: +- Use case 1 +- Use case 2 + +**When NOT to use:** +- Counter-example 1 +- Counter-example 2 + +## Core Pattern (for techniques/patterns) + +Before/after code comparison OR quick reference table for scanning + +## Quick Reference + +Table or bullets for common operations + +## Implementation + +Step-by-step guidance (inline for simple, link to files for complex) + +## Common Mistakes + +What goes wrong + fixes + +## Real-World Impact (optional) + +Concrete results showing effectiveness +``` + +### Progressive Disclosure + +SKILL.md serves as overview that points to detailed materials as needed. + +**Keep SKILL.md under 500 lines for optimal performance** + +**Pattern 1: High-level guide with references** + +````markdown +--- +name: pdf-processing +description: Extracts text and tables from PDF files, fills forms, merges documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction. +--- + +# PDF Processing + +## Quick start + +Extract text with pdfplumber: +```python +import pdfplumber +with pdfplumber.open("file.pdf") as pdf: + text = pdf.pages[0].extract_text() +``` + +## Advanced features + +**Form filling**: See `references/forms.md` for complete guide +**API reference**: See `references/api.md` for all methods +**Examples**: See `references/examples.md` for common patterns +```` + +**Pattern 2: Domain-specific organization** + +Keep token usage low by organizing content so AI loads only relevant domains. + +``` +bigquery-skill/ +├── SKILL.md (overview and navigation) +└── references/ + ├── finance.md (revenue, billing metrics) + ├── sales.md (opportunities, pipeline) + ├── product.md (API usage, features) + └── marketing.md (campaigns, attribution) +``` + +**Pattern 3: Conditional details** + +Show basic content inline, link to advanced content: + +```markdown +# DOCX Processing + +## Creating documents + +Use docx-js for new documents. See `references/docx-js.md`. + +## Editing documents + +For simple edits, modify the XML directly. + +**For tracked changes**: See `references/redlining.md` +**For OOXML details**: See `references/ooxml.md` +``` + +**Avoid deeply nested references** - keep all reference files one level deep from SKILL.md. AI may partially read nested files, resulting in incomplete information. + +**Table of contents for long references** - Files >100 lines need TOC at top to enable previewing scope. + +## Skill Discovery Optimization + +Future AI needs to FIND your skill. Optimize for discovery. + +### Keyword Coverage + +Use words AI would search for: +- **Error messages**: "Hook timed out", "ENOTEMPTY", "race condition" +- **Symptoms**: "flaky", "hanging", "zombie", "pollution" +- **Synonyms**: "timeout/hang/freeze", "cleanup/teardown/afterEach" +- **Tools**: Actual commands, library names, file types + +### Descriptive Naming + +**Use active voice, verb-first (gerund form):** +- ✅ `creating-skills` not `skill-creation` +- ✅ `testing-async-code` not `async-test-helpers` +- ✅ `processing-pdfs` not `pdf-processor` + +### Token Efficiency + +**Target word counts:** +- Getting-started workflows: <150 words each +- Frequently-loaded skills: <200 words total +- Other skills: <500 words (still be concise) + +**Techniques:** + +**Move details to tool help:** +```bash +# ❌ BAD: Document all flags in SKILL.md +search-conversations supports --text, --both, --after DATE, --before DATE, --limit N + +# ✅ GOOD: Reference --help +search-conversations supports multiple modes and filters. Run --help for details. +``` + +**Use cross-references:** +```markdown +# ❌ BAD: Repeat workflow details +When searching, dispatch subagent with template... +[20 lines of repeated instructions] + +# ✅ GOOD: Reference other skill +Always use subagents (50-100x context savings). Use skill-name for workflow. +``` + +**Compress examples:** +```markdown +# ❌ BAD: Verbose (42 words) +your human partner: "How did we handle authentication errors in React Router before?" +You: I'll search past conversations for React Router authentication patterns. +[Dispatch subagent with search query: "React Router authentication error handling 401"] + +# ✅ GOOD: Minimal (20 words) +Partner: "How did we handle auth errors in React Router?" +You: Searching... +[Dispatch subagent → synthesis] +``` + +**Verification:** +```bash +wc -w skills/path/SKILL.md +``` + +### Cross-Referencing Other Skills + +Use skill name only, with explicit requirement markers: +- ✅ Good: `**REQUIRED:** Use skill-name-here` +- ✅ Good: `**REQUIRED BACKGROUND:** You MUST understand skill-name-here` +- ❌ Bad: `See skills/testing/test-driven-development` (unclear if required) + +**Why no @ links:** Force-loads files immediately, consuming context before needed. + +### Discovery Workflow + +How AI finds and uses your skill: + +1. **Encounters problem** ("tests are flaky") +2. **Searches descriptions** (keyword matching) +3. **Finds SKILL** (description matches symptoms) +4. **Scans overview** (is this relevant?) +5. **Reads patterns** (quick reference table) +6. **Loads example** (only when implementing) + +**Optimize for this flow** - put searchable terms early and often. + +## Workflows and Feedback Loops + +### Use Workflows for Complex Tasks + +Break complex operations into clear, sequential steps. Provide checklists AI can copy and check off. + +**Example 1: Research synthesis workflow** (no code): + +````markdown +## Research synthesis workflow + +Copy this checklist and track your progress: + +``` +Research Progress: +- [ ] Step 1: Read all source documents +- [ ] Step 2: Identify key themes +- [ ] Step 3: Cross-reference claims +- [ ] Step 4: Create structured summary +- [ ] Step 5: Verify citations +``` + +**Step 1: Read all source documents** + +Review each document in the `sources/` directory. Note the main arguments and supporting evidence. + +**Step 2: Identify key themes** + +Look for patterns across sources. What themes appear repeatedly? Where do sources agree or disagree? + +[Continue with detailed steps...] +```` + +**Example 2: PDF form filling workflow** (with code): + +````markdown +## PDF form filling workflow + +Copy this checklist and check off items as you complete them: + +``` +Task Progress: +- [ ] Step 1: Analyze the form (run analyze_form.py) +- [ ] Step 2: Create field mapping (edit fields.json) +- [ ] Step 3: Validate mapping (run validate_fields.py) +- [ ] Step 4: Fill the form (run fill_form.py) +- [ ] Step 5: Verify output (run verify_output.py) +``` + +**Step 1: Analyze the form** + +Run: `python scripts/analyze_form.py input.pdf` + +[Continue with detailed steps...] +```` + +### Implement Feedback Loops + +**Common pattern:** Run validator → fix errors → repeat + +**Example: Document editing process** + +```markdown +## Document editing process + +1. Make your edits to `word/document.xml` +2. **Validate immediately**: `python ooxml/scripts/validate.py unpacked_dir/` +3. If validation fails: + - Review the error message carefully + - Fix the issues in the XML + - Run validation again +4. **Only proceed when validation passes** +5. Rebuild: `python ooxml/scripts/pack.py unpacked_dir/ output.docx` +6. Test the output document +``` + +## Code Examples + +**One excellent example beats many mediocre ones** + +Choose most relevant language: +- Testing techniques → TypeScript/JavaScript +- System debugging → Shell/Python +- Data processing → Python + +**Good example characteristics:** +- Complete and runnable +- Well-commented explaining WHY +- From real scenario +- Shows pattern clearly +- Ready to adapt (not generic template) + +**Don't:** +- Implement in 5+ languages +- Create fill-in-the-blank templates +- Write contrived examples + +## Common Patterns + +### Template Pattern + +Provide templates for output format. Match strictness to needs. + +**For strict requirements:** + +````markdown +## Report structure + +ALWAYS use this exact template structure: + +```markdown +# [Analysis Title] + +## Executive summary +[One-paragraph overview of key findings] + +## Key findings +- Finding 1 with supporting data +- Finding 2 with supporting data + +## Recommendations +1. Specific actionable recommendation +2. Specific actionable recommendation +``` +```` + +**For flexible guidance:** + +````markdown +## Report structure + +Here is a sensible default format, but use your best judgment: + +```markdown +# [Analysis Title] + +## Executive summary +[Overview] + +## Key findings +[Adapt sections based on what you discover] +``` + +Adjust sections as needed for the specific analysis type. +```` + +### Examples Pattern + +For skills where output quality depends on seeing examples: + +````markdown +## Commit message format + +Generate commit messages following these examples: + +**Example 1:** +Input: Added user authentication with JWT tokens +Output: +``` +feat(auth): implement JWT-based authentication + +Add login endpoint and token validation middleware +``` + +**Example 2:** +Input: Fixed bug where dates displayed incorrectly in reports +Output: +``` +fix(reports): correct date formatting in timezone conversion + +Use UTC timestamps consistently across report generation +``` + +Follow this style: type(scope): brief description, then detailed explanation. +```` + +### Conditional Workflow Pattern + +Guide through decision points: + +```markdown +## Document modification workflow + +1. Determine the modification type: + + **Creating new content?** → Follow "Creation workflow" below + **Editing existing content?** → Follow "Editing workflow" below + +2. Creation workflow: + - Use docx-js library + - Build document from scratch + - Export to .docx format + +3. Editing workflow: + - Unpack existing document + - Modify XML directly + - Validate after each change + - Repack when complete +``` + +## Flowchart Usage + +**Use flowcharts ONLY for:** +- Non-obvious decision points +- Process loops where you might stop too early +- "When to use A vs B" decisions + +**Never use flowcharts for:** +- Reference material → Use tables, lists +- Code examples → Use markdown blocks +- Linear instructions → Use numbered lists + +See `references/graphviz-conventions.dot` for graphviz style rules. + +## Content Guidelines + +### Avoid Time-Sensitive Information + +Don't include information that will become outdated. + +**Bad (time-sensitive):** +```markdown +If you're doing this before August 2025, use the old API. +After August 2025, use the new API. +``` + +**Good (old patterns section):** +```markdown +## Current method + +Use the v2 API endpoint: `api.example.com/v2/messages` + +## Old patterns + +
+Legacy v1 API (deprecated 2025-08) + +The v1 API used: `api.example.com/v1/messages` + +This endpoint is no longer supported. +
+``` + +### Use Consistent Terminology + +Choose one term and use it throughout: + +**Good - Consistent:** +- Always "API endpoint" +- Always "field" +- Always "extract" + +**Bad - Inconsistent:** +- Mix "API endpoint", "URL", "API route", "path" +- Mix "field", "box", "element", "control" + +## File Organization + +### Self-Contained Skill +``` +defense-in-depth/ + SKILL.md # Everything inline +``` +When: All content fits, no heavy reference needed + +### Skill with Reusable Tool +``` +condition-based-waiting/ + SKILL.md # Overview + patterns + example.ts # Working helpers to adapt +``` +When: Tool is reusable code, not just narrative + +### Skill with Heavy Reference +``` +pptx/ + SKILL.md # Overview + workflows + references/ + pptxgenjs.md # 600 lines API reference + ooxml.md # 500 lines XML structure + scripts/ # Executable tools +``` +When: Reference material too large for inline + +## Anti-Patterns + +### ❌ Narrative Example +"In session 2025-10-03, we found empty projectDir caused..." + +**Why bad:** Too specific, not reusable + +### ❌ Multi-Language Dilution +example-js.js, example-py.py, example-go.go + +**Why bad:** Mediocre quality, maintenance burden + +### ❌ Code in Flowcharts +```dot +step1 [label="import fs"]; +step2 [label="read file"]; +``` + +**Why bad:** Can't copy-paste, hard to read + +### ❌ Generic Labels +helper1, helper2, step3, pattern4 + +**Why bad:** Labels should have semantic meaning + +## Evaluation and Iteration + +### Build Evaluations First + +Create evaluations BEFORE writing extensive documentation. + +**Evaluation-driven development:** + +1. **Identify gaps**: Run tasks without skill, document failures +2. **Create evaluations**: Build 3+ scenarios testing these gaps +3. **Establish baseline**: Measure performance without skill +4. **Write minimal instructions**: Create just enough to pass evaluations +5. **Iterate**: Execute evaluations, compare baseline, refine + +### Develop Skills Iteratively + +**Creating new skill:** + +1. **Complete task without skill**: Work through problem, notice what context you repeatedly provide +2. **Identify reusable pattern**: What context would be useful for similar tasks? +3. **Ask AI to create skill**: "Create a skill that captures this pattern we just used" +4. **Review for conciseness**: Challenge unnecessary explanations +5. **Improve information architecture**: Organize content effectively +6. **Test on similar tasks**: Use skill with fresh AI instance +7. **Iterate based on observation**: Refine based on what worked/didn't + +**Iterating on existing skill:** + +1. **Use skill in real workflows**: Give AI actual tasks +2. **Observe behavior**: Note struggles, successes, unexpected choices +3. **Request improvements**: Share observations with AI helper +4. **Review suggestions**: Consider reorganization, stronger language, restructuring +5. **Apply and test**: Update skill, test again +6. **Repeat based on usage**: Continue observe → refine cycle + +## Creating a New Skill + +### Step 1: Choose Location +- **Project-specific**: `.opencode/skills/skill-name/` +- **Global**: `~/.opencode/skills/skill-name/` + +### Step 2: Create Directory Structure +```bash +mkdir -p .opencode/skills/skill-name +mkdir -p .opencode/skills/skill-name/references # if needed +mkdir -p .opencode/skills/skill-name/scripts # if needed +``` + +### Step 3: Create SKILL.md with Frontmatter + +Follow requirements in Frontmatter Requirements section above. + +### Step 4: Write Skill Content + +Structure content following Content Structure section above. + +### Step 5: Add Supporting Files + +Organize by type: +- `scripts/`: Executable code the skill might run +- `references/`: Documentation to reference +- `assets/`: Templates, configs, or output files + +### Step 6: Validate + +Check that: +- Directory name matches frontmatter `name` field +- Description is at least 20 characters +- Name uses only lowercase letters, numbers, and hyphens +- YAML frontmatter is valid +- Supporting file paths are relative, not absolute +- Word count appropriate for skill type + +### Step 7: Restart OpenCode + +Skills are loaded at startup. Restart OpenCode to register your new skill. + +## Path Resolution + +When referencing files in SKILL.md, use relative paths: + +```markdown +Read the API docs in `references/api.md` +Run `scripts/deploy.sh` for deployment +``` + +The Agent will resolve these relative to the skill directory automatically. + +## Skill Creation Checklist + +**Planning:** +- [ ] Identified gaps or patterns worth capturing +- [ ] Determined skill type (Technique, Pattern, or Reference) +- [ ] Created evaluation scenarios +- [ ] Established baseline without skill + +**Structure:** +- [ ] Directory created in correct location +- [ ] Directory name is lowercase with hyphens +- [ ] Name uses gerund form (verb + -ing) if applicable +- [ ] SKILL.md file created +- [ ] Frontmatter includes required fields (name, description) +- [ ] Name in frontmatter matches directory name exactly +- [ ] Description starts with "Use when..." and includes triggers +- [ ] Description written in third person +- [ ] Description under 500 characters + +**Content:** +- [ ] Overview with core principle (1-2 sentences) +- [ ] "When to Use" section with symptoms and counter-examples +- [ ] Quick reference table or bullets +- [ ] Clear, actionable steps +- [ ] Common mistakes section +- [ ] One excellent code example (not multi-language) +- [ ] Keywords throughout for search +- [ ] Consistent terminology +- [ ] No time-sensitive information +- [ ] Appropriate degree of freedom + +**Progressive Disclosure:** +- [ ] SKILL.md under 500 lines +- [ ] Supporting files in subdirectories if needed +- [ ] References one level deep (not nested) +- [ ] Table of contents for files >100 lines +- [ ] File references use relative paths + +**Token Efficiency:** +- [ ] Challenged every paragraph for necessity +- [ ] Word count appropriate for skill type +- [ ] Compressed examples where possible +- [ ] Cross-references instead of repetition +- [ ] No generic or obvious explanations + +**Testing:** +- [ ] Evaluations pass with skill present +- [ ] Tested on similar tasks with fresh AI instance +- [ ] Observed and refined based on usage +- [ ] Skill appears in tool list as `skills_{name}` + +**Deployment:** +- [ ] OpenCode restarted to load new skill +- [ ] Verified skill is discoverable +- [ ] Documented in project if applicable + +## Reference Files + +- `references/graphviz-conventions.dot`: Flowchart style guide and conventions +- `references/persuasion-principles.md`: Psychology for effective skill design diff --git a/shared/linked-dotfiles/opencode/skills/create-skill/references/graphviz-conventions.dot b/shared/linked-dotfiles/opencode/skills/create-skill/references/graphviz-conventions.dot new file mode 100644 index 0000000..f02d8d9 --- /dev/null +++ b/shared/linked-dotfiles/opencode/skills/create-skill/references/graphviz-conventions.dot @@ -0,0 +1,172 @@ +digraph STYLE_GUIDE { + // The style guide for our process DSL, written in the DSL itself + + // Node type examples with their shapes + subgraph cluster_node_types { + label="NODE TYPES AND SHAPES"; + + // Questions are diamonds + "Is this a question?" [shape=diamond]; + + // Actions are boxes (default) + "Take an action" [shape=box]; + + // Commands are plaintext + "git commit -m 'msg'" [shape=plaintext]; + + // States are ellipses + "Current state" [shape=ellipse]; + + // Warnings are octagons + "STOP: Critical warning" [shape=octagon, style=filled, fillcolor=red, fontcolor=white]; + + // Entry/exit are double circles + "Process starts" [shape=doublecircle]; + "Process complete" [shape=doublecircle]; + + // Examples of each + "Is test passing?" [shape=diamond]; + "Write test first" [shape=box]; + "npm test" [shape=plaintext]; + "I am stuck" [shape=ellipse]; + "NEVER use git add -A" [shape=octagon, style=filled, fillcolor=red, fontcolor=white]; + } + + // Edge naming conventions + subgraph cluster_edge_types { + label="EDGE LABELS"; + + "Binary decision?" [shape=diamond]; + "Yes path" [shape=box]; + "No path" [shape=box]; + + "Binary decision?" -> "Yes path" [label="yes"]; + "Binary decision?" -> "No path" [label="no"]; + + "Multiple choice?" [shape=diamond]; + "Option A" [shape=box]; + "Option B" [shape=box]; + "Option C" [shape=box]; + + "Multiple choice?" -> "Option A" [label="condition A"]; + "Multiple choice?" -> "Option B" [label="condition B"]; + "Multiple choice?" -> "Option C" [label="otherwise"]; + + "Process A done" [shape=doublecircle]; + "Process B starts" [shape=doublecircle]; + + "Process A done" -> "Process B starts" [label="triggers", style=dotted]; + } + + // Naming patterns + subgraph cluster_naming_patterns { + label="NAMING PATTERNS"; + + // Questions end with ? + "Should I do X?"; + "Can this be Y?"; + "Is Z true?"; + "Have I done W?"; + + // Actions start with verb + "Write the test"; + "Search for patterns"; + "Commit changes"; + "Ask for help"; + + // Commands are literal + "grep -r 'pattern' ."; + "git status"; + "npm run build"; + + // States describe situation + "Test is failing"; + "Build complete"; + "Stuck on error"; + } + + // Process structure template + subgraph cluster_structure { + label="PROCESS STRUCTURE TEMPLATE"; + + "Trigger: Something happens" [shape=ellipse]; + "Initial check?" [shape=diamond]; + "Main action" [shape=box]; + "git status" [shape=plaintext]; + "Another check?" [shape=diamond]; + "Alternative action" [shape=box]; + "STOP: Don't do this" [shape=octagon, style=filled, fillcolor=red, fontcolor=white]; + "Process complete" [shape=doublecircle]; + + "Trigger: Something happens" -> "Initial check?"; + "Initial check?" -> "Main action" [label="yes"]; + "Initial check?" -> "Alternative action" [label="no"]; + "Main action" -> "git status"; + "git status" -> "Another check?"; + "Another check?" -> "Process complete" [label="ok"]; + "Another check?" -> "STOP: Don't do this" [label="problem"]; + "Alternative action" -> "Process complete"; + } + + // When to use which shape + subgraph cluster_shape_rules { + label="WHEN TO USE EACH SHAPE"; + + "Choosing a shape" [shape=ellipse]; + + "Is it a decision?" [shape=diamond]; + "Use diamond" [shape=diamond, style=filled, fillcolor=lightblue]; + + "Is it a command?" [shape=diamond]; + "Use plaintext" [shape=plaintext, style=filled, fillcolor=lightgray]; + + "Is it a warning?" [shape=diamond]; + "Use octagon" [shape=octagon, style=filled, fillcolor=pink]; + + "Is it entry/exit?" [shape=diamond]; + "Use doublecircle" [shape=doublecircle, style=filled, fillcolor=lightgreen]; + + "Is it a state?" [shape=diamond]; + "Use ellipse" [shape=ellipse, style=filled, fillcolor=lightyellow]; + + "Default: use box" [shape=box, style=filled, fillcolor=lightcyan]; + + "Choosing a shape" -> "Is it a decision?"; + "Is it a decision?" -> "Use diamond" [label="yes"]; + "Is it a decision?" -> "Is it a command?" [label="no"]; + "Is it a command?" -> "Use plaintext" [label="yes"]; + "Is it a command?" -> "Is it a warning?" [label="no"]; + "Is it a warning?" -> "Use octagon" [label="yes"]; + "Is it a warning?" -> "Is it entry/exit?" [label="no"]; + "Is it entry/exit?" -> "Use doublecircle" [label="yes"]; + "Is it entry/exit?" -> "Is it a state?" [label="no"]; + "Is it a state?" -> "Use ellipse" [label="yes"]; + "Is it a state?" -> "Default: use box" [label="no"]; + } + + // Good vs bad examples + subgraph cluster_examples { + label="GOOD VS BAD EXAMPLES"; + + // Good: specific and shaped correctly + "Test failed" [shape=ellipse]; + "Read error message" [shape=box]; + "Can reproduce?" [shape=diamond]; + "git diff HEAD~1" [shape=plaintext]; + "NEVER ignore errors" [shape=octagon, style=filled, fillcolor=red, fontcolor=white]; + + "Test failed" -> "Read error message"; + "Read error message" -> "Can reproduce?"; + "Can reproduce?" -> "git diff HEAD~1" [label="yes"]; + + // Bad: vague and wrong shapes + bad_1 [label="Something wrong", shape=box]; // Should be ellipse (state) + bad_2 [label="Fix it", shape=box]; // Too vague + bad_3 [label="Check", shape=box]; // Should be diamond + bad_4 [label="Run command", shape=box]; // Should be plaintext with actual command + + bad_1 -> bad_2; + bad_2 -> bad_3; + bad_3 -> bad_4; + } +} diff --git a/shared/linked-dotfiles/opencode/skills/create-skill/references/persuasion-principles.md b/shared/linked-dotfiles/opencode/skills/create-skill/references/persuasion-principles.md new file mode 100644 index 0000000..5950d95 --- /dev/null +++ b/shared/linked-dotfiles/opencode/skills/create-skill/references/persuasion-principles.md @@ -0,0 +1,187 @@ +# Persuasion Principles for Skill Design + +## Overview + +AI models respond to the same persuasion principles as humans. Understanding this psychology helps you design more effective skills - not to manipulate, but to ensure critical practices are followed even under pressure. + +**Research foundation:** Meincke et al. (2025) tested 7 persuasion principles with N=28,000 AI conversations. Persuasion techniques more than doubled compliance rates (33% → 72%, p < .001). + +## The Seven Principles + +### 1. Authority +**What it is:** Deference to expertise, credentials, or official sources. + +**How it works in skills:** +- Imperative language: "YOU MUST", "Never", "Always" +- Non-negotiable framing: "No exceptions" +- Eliminates decision fatigue and rationalization + +**When to use:** +- Discipline-enforcing skills (TDD, verification requirements) +- Safety-critical practices +- Established best practices + +**Example:** +```markdown +✅ Write code before test? Delete it. Start over. No exceptions. +❌ Consider writing tests first when feasible. +``` + +### 2. Commitment +**What it is:** Consistency with prior actions, statements, or public declarations. + +**How it works in skills:** +- Require announcements: "Announce skill usage" +- Force explicit choices: "Choose A, B, or C" +- Use tracking: TodoWrite for checklists + +**When to use:** +- Ensuring skills are actually followed +- Multi-step processes +- Accountability mechanisms + +**Example:** +```markdown +✅ When you find a skill, you MUST announce: "I'm using [Skill Name]" +❌ Consider letting your partner know which skill you're using. +``` + +### 3. Scarcity +**What it is:** Urgency from time limits or limited availability. + +**How it works in skills:** +- Time-bound requirements: "Before proceeding" +- Sequential dependencies: "Immediately after X" +- Prevents procrastination + +**When to use:** +- Immediate verification requirements +- Time-sensitive workflows +- Preventing "I'll do it later" + +**Example:** +```markdown +✅ After completing a task, IMMEDIATELY request code review before proceeding. +❌ You can review code when convenient. +``` + +### 4. Social Proof +**What it is:** Conformity to what others do or what's considered normal. + +**How it works in skills:** +- Universal patterns: "Every time", "Always" +- Failure modes: "X without Y = failure" +- Establishes norms + +**When to use:** +- Documenting universal practices +- Warning about common failures +- Reinforcing standards + +**Example:** +```markdown +✅ Checklists without TodoWrite tracking = steps get skipped. Every time. +❌ Some people find TodoWrite helpful for checklists. +``` + +### 5. Unity +**What it is:** Shared identity, "we-ness", in-group belonging. + +**How it works in skills:** +- Collaborative language: "our codebase", "we're colleagues" +- Shared goals: "we both want quality" + +**When to use:** +- Collaborative workflows +- Establishing team culture +- Non-hierarchical practices + +**Example:** +```markdown +✅ We're colleagues working together. I need your honest technical judgment. +❌ You should probably tell me if I'm wrong. +``` + +### 6. Reciprocity +**What it is:** Obligation to return benefits received. + +**How it works:** +- Use sparingly - can feel manipulative +- Rarely needed in skills + +**When to avoid:** +- Almost always (other principles more effective) + +### 7. Liking +**What it is:** Preference for cooperating with those we like. + +**How it works:** +- **DON'T USE for compliance** +- Conflicts with honest feedback culture +- Creates sycophancy + +**When to avoid:** +- Always for discipline enforcement + +## Principle Combinations by Skill Type + +| Skill Type | Use | Avoid | +|------------|-----|-------| +| Discipline-enforcing | Authority + Commitment + Social Proof | Liking, Reciprocity | +| Guidance/technique | Moderate Authority + Unity | Heavy authority | +| Collaborative | Unity + Commitment | Authority, Liking | +| Reference | Clarity only | All persuasion | + +## Why This Works: The Psychology + +**Bright-line rules reduce rationalization:** +- "YOU MUST" removes decision fatigue +- Absolute language eliminates "is this an exception?" questions +- Explicit anti-rationalization counters close specific loopholes + +**Implementation intentions create automatic behavior:** +- Clear triggers + required actions = automatic execution +- "When X, do Y" more effective than "generally do Y" +- Reduces cognitive load on compliance + +**AI models are parahuman:** +- Trained on human text containing these patterns +- Authority language precedes compliance in training data +- Commitment sequences (statement → action) frequently modeled +- Social proof patterns (everyone does X) establish norms + +## Ethical Use + +**Legitimate:** +- Ensuring critical practices are followed +- Creating effective documentation +- Preventing predictable failures + +**Illegitimate:** +- Manipulating for personal gain +- Creating false urgency +- Guilt-based compliance + +**The test:** Would this technique serve the user's genuine interests if they fully understood it? + +## Research Citations + +**Cialdini, R. B. (2021).** *Influence: The Psychology of Persuasion (New and Expanded).* Harper Business. +- Seven principles of persuasion +- Empirical foundation for influence research + +**Meincke, L., Shapiro, D., Duckworth, A. L., Mollick, E., Mollick, L., & Cialdini, R. (2025).** Call Me A Jerk: Persuading AI to Comply with Objectionable Requests. University of Pennsylvania. +- Tested 7 principles with N=28,000 AI conversations +- Compliance increased 33% → 72% with persuasion techniques +- Authority, commitment, scarcity most effective +- Validates parahuman model of AI behavior + +## Quick Reference + +When designing a skill, ask: + +1. **What type is it?** (Discipline vs. guidance vs. reference) +2. **What behavior am I trying to change?** +3. **Which principle(s) apply?** (Usually authority + commitment for discipline) +4. **Am I combining too many?** (Don't use all seven) +5. **Is this ethical?** (Serves user's genuine interests?) diff --git a/shared/linked-dotfiles/opencode/skills/go-pr-review/SKILL.md b/shared/linked-dotfiles/opencode/skills/go-pr-review/SKILL.md new file mode 100644 index 0000000..e261ad4 --- /dev/null +++ b/shared/linked-dotfiles/opencode/skills/go-pr-review/SKILL.md @@ -0,0 +1,217 @@ +--- +name: go-pr-review +description: Reviews pull requests in Go repositories with comprehensive checks including code quality, tests, linting, type checking, and Makefile validation +--- + +# Go Pull Request Review + +This skill performs comprehensive reviews of pull requests in Go repositories, with special attention to projects +using Makefiles for build automation. + +## When to Use This Skill + +Use this skill when: +- A developer requests a PR review for a Go repository +- You need to validate changes before merging +- You want to ensure code quality, test coverage, and build success +- The repository uses a Makefile for build automation + +## Review Process + +### 0. Validate Repository Compatibility + +**IMPORTANT: Before proceeding, verify this is a compatible Go repository.** + +Check for required characteristics: +```bash +# 1. Must be a Go repository +test -f go.mod || echo "ERROR: Not a Go repository (no go.mod found)" + +# 2. Must have a Makefile +test -f Makefile || echo "ERROR: No Makefile found" + +# 3. Check Makefile has test/lint targets +grep -E "^(test|lint|tidy):" Makefile || echo "WARNING: Makefile missing common targets" + +Abort conditions: + +• ❌ No go.mod file exists → This is not a Go repository +• ❌ No Makefile exists → This skill requires Makefile-based workflows +• ⚠️ Makefile missing test/lint targets → Proceed with caution, manual validation required + +If repository is incompatible: Stop execution and inform the user: "This repository doesn't match the requirements +for the go-pr-review skill (requires Go with Makefile). Would you like a standard code review instead?" + +### 1. Gather PR Information + +Fetch the PR details using the gh CLI: + +• Get the PR diff and changed files +• Identify the PR number and branch +• Review the PR description and context + +### 2. Analyze Changed Files + +Review all modified files with focus on: + +• Go code changes: Logic, patterns, error handling +• Test files: Coverage of new functionality +• Documentation: README, AGENTS.md, comments (check for AIDEV- anchors) +• Configuration: go.mod, Makefile, YAML files + +### 3. Check Makefile Commands + +Read the Makefile to identify available validation commands. Common commands include: + +• make lint - Run linters (golangci-lint) +• make tidy - Format and tidy code +• make test-api - Run integration/API tests (most critical for validating) + +### 4. Run Validation Commands + +# Run tests and linting +make lint && make tidy && make test-api + +Check for: + +• Test failures +• Linting errors or warnings +• Build failures +• Documentation generation errors + +### 5. Code Quality Review + +Review code against project standards from AGENTS.md (if exists) and references/go-standards.md: + +Architecture & Design + +• Does the change follow existing patterns? +• Is the business logic in the correct layer? (handlers → service → API clients) +• Are there any breaking changes to API contracts? + +Go Best Practices + +• Proper error handling (don't ignore errors) +• Context propagation for cancellation +• Resource cleanup (defer statements) +• Concurrency safety (mutexes, channels) +• No hardcoded values (use constants or env vars) + +Code Style + +• Follows project conventions (check neighboring files) +• No unnecessary comments (unless documenting complex business logic) +• Lowercase SQL keywords (e.g., select not SELECT) if applicable +• Proper use of existing libraries (check go.mod) + +API Documentation (Swag) - If repository uses Swag + +• All HTTP handlers have complete Swag comments +• Required annotations: @Summary, @Description, @Tags, @Router +• All parameters documented with @Param +• All response codes documented (@Success, @Failure) +• Model references are correct + +Security + +• No secrets or credentials in code +• Input validation on all user inputs +• Proper authentication/authorization checks +• No SQL injection risks (use parameterized queries/stored procedures) + +Testing + +• New functionality has test coverage +• Tests are meaningful, not just for coverage numbers +• Integration tests for API endpoints +• Edge cases are considered + +### 6. Review Anchor Comments + +Search for developer anchor comments: + +rg "AIDEV-(NOTE|TODO|QUESTION):" --type go + +These indicate areas an agent flagged for review. Assess whether: + +• The concern is valid +• The implementation needs improvement + +Typically, the anchor should be removed before merge + +### 7. Provide Structured Feedback + +Organize findings into categories: + +Critical Issues (must fix before merge) + +• Test failures +• Build/lint errors +• Security vulnerabilities +• Breaking changes without versioning + +Suggestions (should consider) + +• Code quality improvements +• Better error messages +• Performance optimizations +• Missing edge case tests + +Observations (nice to have) + +• Refactoring opportunities +• Documentation enhancements +• Code organization improvements + +### 8. Summary and Recommendation + +Provide a clear verdict: + +• ✅ Approved: Ready to merge +• ⚠️ Approved with suggestions: Can merge, but consider improvements +• ❌ Request changes: Must address critical issues + +## Example Review Workflow + +# 0. Validate repository compatibility +test -f go.mod && test -f Makefile && echo "Repository compatible" || echo "Repository incompatible - ABORT" + +# 1. Get PR information +gh pr view 123 --json number,title,body,changedFiles + +# 2. Get the diff +gh pr diff 123 + +# 3. Check Makefile for available commands +cat Makefile + +# 4. Run validations (adjust based on Makefile) +make test-unit +make test-api +make lint +make tidy +make docs + +# 5. Search for anchor comments +rg "AIDEV-" --type go + +# 6. Review changed files +# (Use Read tool for each changed file) + +## Reference Materials + +See references/go-standards.md for detailed Go code quality standards and common issues to watch for. + +## Important Notes + +• Validate repository first - Don't waste time on incompatible repos +• Never modify test files unless explicitly requested +• Never change API contracts - version endpoints instead +• Never alter migration files - data loss risk +• Always verify tests pass before approving +• Check that make commands succeed - this is critical validation +• Look for AIDEV- comments - they highlight areas needing attention +• Validate Swag comments for all new/modified handlers (if applicable) + +Remember: The goal is constructive feedback that improves code quality while respecting the developer's intent and +design decisions. diff --git a/shared/linked-dotfiles/opencode/skills/go-pr-review/references/go-standards.md b/shared/linked-dotfiles/opencode/skills/go-pr-review/references/go-standards.md new file mode 100644 index 0000000..ec096fb --- /dev/null +++ b/shared/linked-dotfiles/opencode/skills/go-pr-review/references/go-standards.md @@ -0,0 +1,248 @@ +# Go Code Quality Standards + +Reference guide for reviewing Go code quality in PRs. + +## Common Issues to Watch For + +### Error Handling + +**Bad:** +```go +result, _ := doSomething() +``` + +**Good:** +```go +result, err := doSomething() +if err != nil { + return fmt.Errorf("failed to do something: %w", err) +} +``` + +### Context Propagation + +**Bad:** +```go +func handleRequest(c *gin.Context) { + result := callExternalAPI() // No timeout +} +``` + +**Good:** +```go +func handleRequest(c *gin.Context) { + ctx := c.Request.Context() + result, err := callExternalAPIWithContext(ctx) +} +``` + +### Resource Cleanup + +**Bad:** +```go +file, _ := os.Open("data.txt") +// Missing Close() +``` + +**Good:** +```go +file, err := os.Open("data.txt") +if err != nil { + return err +} +defer file.Close() +``` + +### Hardcoded Values + +**Bad:** +```go +timeout := 30 * time.Second +apiURL := "https://api.example.com" +``` + +**Good:** +```go +const defaultTimeout = 30 * time.Second + +apiURL := os.Getenv("API_URL") +if apiURL == "" { + return errors.New("API_URL environment variable required") +} +``` + +## Swag Documentation Standards + +### Complete Handler Documentation + +```go +// GetFile godoc +// +// @Summary Retrieve file by ID +// @Description Returns detailed information about a specific file including metadata, permissions, and sharing status +// @Tags files +// @Accept json +// @Produce json +// @Param id path string true "File ID" +// @Param X-Tenant-ID header string true "Tenant identifier" +// @Success 200 {object} model.File +// @Failure 400 {string} string "Invalid file ID" +// @Failure 404 {string} string "File not found" +// @Failure 500 {string} string "Internal server error" +// @Router /v1/files/{id} [get] +func GetFile(c *gin.Context) { + // implementation +} +``` + +### Required Swag Elements + +1. **@Summary** - One line description (< 120 chars) +2. **@Description** - Detailed explanation of behavior +3. **@Tags** - Logical grouping (files, folders, sessions, etc.) +4. **@Accept** - Content-Type accepted (if body expected) +5. **@Produce** - Content-Type returned +6. **@Param** - Document EVERY parameter (path, query, header, body) +7. **@Success** - All successful response codes with types +8. **@Failure** - All error response codes with descriptions +9. **@Router** - Exact route path and HTTP method + +### Common Swag Mistakes + +- Missing `@Param` for headers (X-Tenant-ID, Authorization) +- Incomplete `@Failure` documentation (only documenting 400/500) +- Generic descriptions ("Get file" vs "Retrieve file with metadata and permissions") +- Wrong model references (string vs object) +- Missing `@Accept` on POST/PUT handlers + +## Project-Specific Patterns + +### SQL Style + +Use lowercase SQL keywords: +```go +query := "select id, name from users where active = ?" +``` + +Not: +```go +query := "SELECT id, name FROM users WHERE active = ?" +``` + +### Layer Separation + +- **Handlers** (`/internal/server/`): HTTP routing, request/response mapping +- **Service** (`/internal/service/`): Business logic, validation, orchestration +- **API Clients** (`/internal/api/`): External service communication + +Don't put business logic in handlers or HTTP code in services. + +### Database Operations + +Prefer stored procedures for complex queries: +- Type-safe parameters +- Optimized execution +- Centralized logic + +### Test Organization + +- Unit tests: `/internal/service/*_test.go` +- Integration tests: `/api-tests/*_test.go` +- Helpers: `/api-tests/helpers/*.go` + +## Security Checklist + +- [ ] No secrets in code (use env vars) +- [ ] All user inputs validated +- [ ] Authentication checks on protected endpoints +- [ ] Authorization checks for resource access +- [ ] SQL queries use parameterization (no string concatenation) +- [ ] Sensitive data not logged +- [ ] CORS configured appropriately +- [ ] Rate limiting considered for public endpoints + +## Performance Considerations + +- [ ] Database queries are efficient (no N+1 queries) +- [ ] Appropriate indexes exist for queries +- [ ] Large lists are paginated +- [ ] File uploads handle streaming (not loading entire file in memory) +- [ ] Long operations use SSE for progress updates +- [ ] Context timeouts prevent hanging requests +- [ ] Connection pooling configured properly + +## Code Organization Red Flags + +- God functions (>100 lines) +- Deep nesting (>4 levels) +- Duplicate code (consider extracting helper) +- Magic numbers without constants +- Inconsistent naming conventions +- Missing package documentation +- Circular dependencies between packages + +## Testing Red Flags + +- Tests that don't assert anything +- Tests that sleep/use arbitrary timeouts +- Tests that depend on external services (without mocking) +- Tests that share state (global variables) +- Tests with no cleanup (leaked resources) +- Missing table-driven tests for multiple cases +- No error case testing + +## Documentation Standards + +### Anchor Comments for AI Review + +Use these ONLY during development, remove before merge: +- `// AIDEV-NOTE:` - Important business logic explanation +- `// AIDEV-TODO:` - Incomplete work or needed improvement +- `// AIDEV-QUESTION:` - Uncertainty or need for discussion + +These should be reviewed and removed before PR approval. + +### Regular Comments + +Only add comments when: +- Explaining non-obvious business rules +- Documenting complex algorithms +- Clarifying why (not what) code does something +- Warning about edge cases or gotchas + +Don't comment obvious code: +```go +// Bad: Obvious +// increment counter +counter++ + +// Good: Explains why +// Skip deleted items to maintain accurate active count +if !item.IsDeleted { + counter++ +} +``` + +## Go-Specific Best Practices + +- Use `gofmt` formatting (enforced by `make tidy`) +- Follow [Effective Go](https://golang.org/doc/effective_go.html) guidelines +- Use short variable names in limited scopes (i, err, ok) +- Use descriptive names for package-level and exported items +- Prefer composition over inheritance +- Use interfaces for abstraction, not just for mocking +- Keep interfaces small and focused +- Return errors, don't panic (except in init() for fatal issues) +- Use `context.Context` for cancellation and deadlines +- Avoid global mutable state + +## Makefile Integration + +Always verify the Makefile has these targets and run them: +- `make test` or `make test-unit` - Unit tests must pass +- `make test-api` - Integration tests must pass +- `make lint` - Linting must pass with no errors +- `make tidy` - Code must be properly formatted +- `make docs` - If Swag is used, docs must generate successfully + +Failing any of these is grounds for requesting changes.