diff --git a/README.md b/README.md index 3ce5c4e..abf9dbd 100755 --- a/README.md +++ b/README.md @@ -92,6 +92,16 @@ git clone https://github.com/orkait/hyperstack.git ~/.claude/skills/hyperstack | **rust** | Rust best practices | 4 | 18 practices (good/bad pairs), ownership guide, cheatsheet | | **design-tokens** | Tailwind v4 + OKLCH token system | 7 | 10 token categories, 8 build procedures, color ramp templates | | **ui-ux** | UI/UX design principles | 6 | Typography, color, spacing, elevation, motion, a11y, component patterns | +| **behaviour-analysis** | Interaction & State Audits | 2 | Heuristics, state inventory, edge case sweeps | +| **design-patterns-skill** | Core Programming Principles | 2 | Clean Code, Pragmatic Programmer patterns | +| **engineering-discipline** | Senior SDE-3 Framework | 2 | Architecture reasoning, verification gates | +| **excalidraw** | Architecture Diagrams | 2 | Automated diagram generation guidelines | +| **frame-animator** | Character Animation | 2 | Frame-based tick animation and expressions | +| **golang-design-pattern** | Go Design Patterns | 2 | Patterns adapted for Go's philosophy | +| **pinchtab** | Browser Automation | 2 | Web scraping and browser testing guidelines | +| **react-pro-coder** | Senior React/Next.js SDE | 2 | RSC-first constraints, core web vitals | +| **readme-writer** | Project Documentation | 2 | Evidence-based README generation | +| **security-review** | Security Audits | 2 | OWASP review, vulnerability checklists | --- diff --git a/SKILL.md b/SKILL.md index 133e646..8be2e91 100755 --- a/SKILL.md +++ b/SKILL.md @@ -62,6 +62,28 @@ triggers: - typography scale - color contrast - wcag + - behaviour analysis + - state audit + - nielsen heuristics + - design patterns + - clean code + - engineering discipline + - code review + - architecture diagram + - excalidraw + - frame animation + - golang patterns + - pinchtab automation + - web scraping + - browser testing + - react pro + - rsc constraints + - core web vitals + - readme generation + - project documentation + - security review + - owasp + - vulnerability check activation: mode: fuzzy priority: high @@ -447,3 +469,26 @@ Elevation: 5 levels distinguished by bg-color not just borders; dark mode uses l Motion: exits faster than entrances (subtract 50-100ms); ease-out entering, ease-in exiting, ease-in-out repositioning; never linear; details via ui_ux_get_principle duration-rules Pre-ship: run ui_ux_get_checklist for each domain before shipping a new component or page + +--- + +## Additional Engineering Skills + +Hyperstack also bundles 10 specialized engineering skills that do not rely on standard API references but instead provide comprehensive guidelines, checklists, and principles. + +For each of these skills, you can: +1. List all available documents: `[skill_name]_list_docs()` +2. Get the content of a specific document: `[skill_name]_get_doc({ path: "..." })` (Always start by reading `SKILL.txt`) + +| Skill Prefix | Focus Area | +|--------------|------------| +| `behaviour_analysis` | UI/UX state audits, Nielsen heuristics | +| `design_patterns_skill` | Clean Code, Pragmatic Programmer concepts | +| `engineering_discipline`| Architecture reasoning, verification gates | +| `excalidraw` | Automated architecture diagram generation | +| `frame_animator` | Frame-based tick animation & expressions | +| `golang_design_pattern`| Go-specific implementations of design patterns | +| `pinchtab` | Browser automation, scraping, web testing | +| `react_pro_coder` | Senior Next.js/React constraints, RSC rules | +| `readme_writer` | Evidence-based README generation | +| `security_review` | OWASP audits, vulnerability checklists | diff --git a/src/index.ts b/src/index.ts index e9e7a4e..bfb7606 100755 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,16 @@ import { golangPlugin } from "./plugins/golang/index.js"; import { rustPlugin } from "./plugins/rust/index.js"; import { designTokensPlugin } from "./plugins/design-tokens/index.js"; import { uiUxPlugin } from "./plugins/ui-ux/index.js"; +import { behaviourAnalysisPlugin } from "./plugins/behaviour-analysis/index.js"; +import { designPatternsSkillPlugin } from "./plugins/design-patterns-skill/index.js"; +import { engineeringDisciplinePlugin } from "./plugins/engineering-discipline/index.js"; +import { excalidrawPlugin } from "./plugins/excalidraw/index.js"; +import { frameAnimatorPlugin } from "./plugins/frame-animator/index.js"; +import { golangDesignPatternPlugin } from "./plugins/golang-design-pattern/index.js"; +import { pinchtabPlugin } from "./plugins/pinchtab/index.js"; +import { reactProCoderPlugin } from "./plugins/react-pro-coder/index.js"; +import { readmeWriterPlugin } from "./plugins/readme-writer/index.js"; +import { securityReviewPlugin } from "./plugins/security-review/index.js"; const server = new McpServer({ name: "hyperstack", @@ -28,6 +38,16 @@ loadPlugins(server, [ rustPlugin, designTokensPlugin, uiUxPlugin, + behaviourAnalysisPlugin, + designPatternsSkillPlugin, + engineeringDisciplinePlugin, + excalidrawPlugin, + frameAnimatorPlugin, + golangDesignPatternPlugin, + pinchtabPlugin, + reactProCoderPlugin, + readmeWriterPlugin, + securityReviewPlugin, ]); async function main() { diff --git a/src/plugins/behaviour-analysis/index.ts b/src/plugins/behaviour-analysis/index.ts new file mode 100644 index 0000000..4af17cd --- /dev/null +++ b/src/plugins/behaviour-analysis/index.ts @@ -0,0 +1,3 @@ +import { createStaticSkillPlugin } from "../../shared/static-skill.js"; + +export const behaviourAnalysisPlugin = createStaticSkillPlugin("behaviour-analysis", "The behaviour-analysis skill."); diff --git a/src/plugins/behaviour-analysis/snippets/SKILL.txt b/src/plugins/behaviour-analysis/snippets/SKILL.txt new file mode 100755 index 0000000..e376a30 --- /dev/null +++ b/src/plugins/behaviour-analysis/snippets/SKILL.txt @@ -0,0 +1,162 @@ +--- +name: behaviour-analysis +description: Systematic UI/UX behaviour analysis for interactive applications. Audits every user action, state transition, view mode, and edge case like an experienced QA + UX engineer. Produces a complete interaction matrix with expected vs actual behaviour, finds inconsistencies, dead states, and missing feedback. Use when reviewing UI behaviour, before shipping features, or when something "feels off" but you can't pinpoint why. +compatibility: Requires Read, Grep, Glob, WebSearch tools. Works with any frontend codebase. +metadata: + author: kai + version: "1.0" +--- + +# Behaviour Analysis + +Systematic interaction audit combining UX heuristics, QA state-machine thinking, and developer code-reading. + +## When to Use + +- After implementing a feature with multiple interaction modes +- When the user reports something "doesn't feel right" or "is inconsistent" +- Before shipping — final behavioural review +- When adding a new view mode, action, or state to an existing system + +## Process + +### Phase 1: Inventory (read code, build the map) + +Before judging anything, build a complete picture: + +1. **Identify all state variables** that affect UI behaviour + - Read the store/state management files + - List every piece of state: data, config, transient UI state + - Note which are persisted vs ephemeral + +2. **Identify all user actions** that modify state + - Buttons, clicks, drags, keyboard shortcuts, sliders, toggles + - API calls triggered by actions + - Implicit actions (hover, scroll, resize, mode switch) + +3. **Identify all view modes / display states** + - Tabs, toggles, conditional rendering branches + - How different modes compose (layout mode x view mode x highlight state) + +4. **Identify all feedback mechanisms** + - Visual feedback (highlighting, dimming, borders, badges, glow) + - Textual feedback (labels, counts, status text) + - Animated feedback (transitions, physics, spring effects) + - Absence of feedback (silent failures, no-ops) + +Output: A **state inventory table** and an **action inventory table**. + +### Phase 2: Interaction Matrix (the core analysis) + +Build a matrix: **every action x every relevant state combination**. + +For each cell ask: +- **What should happen?** (expected behaviour — think like a UX designer) +- **What does happen?** (actual behaviour — read the code path) +- **Match?** OK / BUG / UX-ISSUE / MISSING-FEEDBACK + +Structure the matrix by category: + +```markdown +| # | Action | Context/State | Expected | Actual | Status | +|---|--------|---------------|----------|--------|--------| +``` + +Categories to cover: +- **CRUD actions** (create, read, update, delete of primary data) +- **Selection & highlighting** (what gets selected, how, clear) +- **View mode transitions** (switching between modes) +- **Layout mode transitions** (switching layout engines) +- **Configuration changes** (sliders, toggles, settings) +- **Drag & interaction** (drag, hover, click targets) +- **Reset & cleanup** (what gets cleared, what persists) +- **Edge cases** (empty state, max state, conflicting states) + +### Phase 3: Heuristic Audit + +Apply Nielsen's 10 heuristics (adapted for interactive visualizations): + +1. **Visibility of system status** — Does the UI show what's active, selected, loading? +2. **Match between system and real world** — Do labels make sense? Are actions named clearly? +3. **User control and freedom** — Can the user undo/escape from any state? Is there always a way back? +4. **Consistency and standards** — Do similar actions behave the same way everywhere? +5. **Error prevention** — Can the user reach a broken/dead state? +6. **Recognition rather than recall** — Is the current mode/state visible without memorizing? +7. **Flexibility and efficiency** — Are there shortcuts for power users? +8. **Aesthetic and minimalist design** — Is information presented at the right density? +9. **Help users recover from errors** — What happens on API failure, empty results, bad input? +10. **Accessibility** — Keyboard navigation, screen reader, reduced motion? + +Refer to [references/heuristics.md](references/heuristics.md) for detailed questions per heuristic. + +### Phase 4: Edge Case Sweep + +Systematically check: + +**Empty states:** +- No data loaded +- No results +- No highlights active +- Empty search filter results + +**Boundary states:** +- Maximum data (100+ nodes) +- Single node, no edges +- All nodes highlighted +- All sliders at min/max + +**Transition states:** +- Mode switch with active highlights +- Mode switch mid-drag +- Query execution while loading +- Rapid repeated actions (double-click, spam slider) + +**Composition states:** +- Every view mode x every layout mode +- Highlight + search filter active simultaneously +- Collapsed groups + highlighting + path results + +### Phase 5: Report + +Output a structured report: + +```markdown +## State Inventory +[table of all state variables] + +## Action × State Matrix +[full interaction matrix with status] + +## Heuristic Findings +[issues grouped by heuristic, with severity] + +## Edge Cases +[bugs and UX issues found] + +## Verdict +[summary: how many behaviours tested, how many correct, critical issues] +``` + +Severity levels: +- **CRITICAL** — broken functionality, data loss, unreachable state +- **HIGH** — major UX inconsistency, confusing behaviour +- **MEDIUM** — minor inconsistency, missing feedback +- **LOW** — cosmetic, nice-to-have + +## Research Enhancement + +Before starting the analysis, search for: +- Current best practices for the specific UI pattern being analyzed (graph viz, form, dashboard, etc.) +- Known UX patterns for the interaction model (drag-and-drop, force-directed graphs, etc.) +- Accessibility guidelines for the specific component type + +Use findings to set expectations in the matrix — "expected behaviour" should be informed by industry standards, not just gut feeling. + +## Key Principles + +- **Think like a user first** — what would someone expect when they click this? +- **Think like QA second** — what's the worst thing that could happen? +- **Think like a developer third** — read the code to verify, don't assume +- **Every action must have visible feedback** — if clicking something does nothing visibly, that's a bug +- **Every state must be escapable** — the user should never be "stuck" +- **Composition must be tested** — features that work alone often break in combination diff --git a/src/plugins/behaviour-analysis/snippets/references/heuristics.txt b/src/plugins/behaviour-analysis/snippets/references/heuristics.txt new file mode 100755 index 0000000..9d846e0 --- /dev/null +++ b/src/plugins/behaviour-analysis/snippets/references/heuristics.txt @@ -0,0 +1,114 @@ +# Heuristic Evaluation Questions + +Detailed questions per Nielsen's heuristic, adapted for interactive data visualizations and modern web apps. + +## 1. Visibility of System Status + +- Is the current view mode clearly indicated (active tab, highlight, selected state)? +- Is there a loading indicator when async operations run? +- Does the active/selected result show a distinct visual treatment? +- When a filter is active, is it obvious that results are filtered? +- Do sliders/toggles show their current value? +- After dragging a node, is the settled state visually clear? +- Is the current layout mode (dagre/cluster) clearly indicated? + +## 2. Match Between System and Real World + +- Do button labels describe what they DO, not what they ARE? ("Clear" not "X") +- Are view mode names intuitive? ("Live" vs "Results" vs "Highlight" — does a new user understand these?) +- Do edge/node labels match the domain vocabulary? +- Are slider labels clear about what they control? + +## 3. User Control and Freedom + +- Can every highlight be cleared? +- Can every mode switch be reversed? +- Can collapsed groups be re-expanded? +- Can the user undo the last action? +- Is there a "reset to defaults" for settings? +- Can the user escape from every state back to a clean view? + +## 4. Consistency and Standards + +- Do all "clear" actions clear the same scope of state? +- Do all click targets have hover states? +- Are all buttons the same size/style for the same level of importance? +- Does clicking behave the same way on result cards, nodes, edges? +- Do both layout modes support the same view modes identically? +- Are keyboard shortcuts consistent with platform conventions? + +## 5. Error Prevention + +- Can slider values be set to break the layout? +- Can the user reach a state where no nodes are visible and there's no indication why? +- Can rapid clicking cause race conditions? +- Does the UI prevent invalid state combinations? +- Are destructive actions (reset, clear all) confirmed or easily undoable? + +## 6. Recognition Rather Than Recall + +- Is the current state visible at all times (not hidden in a menu)? +- Can the user see which result is highlighted without scrolling the results panel? +- Are collapsed group contents summarized (count, kind)? +- Is the search filter text always visible when active? + +## 7. Flexibility and Efficiency + +- Can power users access functions via keyboard? +- Are there shortcuts for common workflows (run + highlight)? +- Can the user adjust layout parameters without opening a dialog? +- Is the most common action the easiest to perform? + +## 8. Aesthetic and Minimalist Design + +- Are controls only shown when relevant? (dagre sliders hidden in cluster mode) +- Is information density appropriate — not too sparse, not overwhelming? +- Are animations purposeful (communicate state change) or decorative (just pretty)? +- Do hover/highlight effects add information or just noise? + +## 9. Help Users Recover from Errors + +- What happens when the API is unreachable? +- What happens when a query returns an error? +- What happens when the graph data is malformed? +- Are error messages actionable ("server unreachable — is it running?")? +- Can the user retry failed operations? + +## 10. Accessibility + +- Can all interactive elements be reached via keyboard (Tab)? +- Do interactive elements have focus indicators? +- Is there sufficient color contrast for all states? +- Do animations respect `prefers-reduced-motion`? +- Are drag interactions achievable without a mouse? +- Do screen readers announce state changes (highlights, mode switches)? +- Are ARIA labels present on non-text interactive elements? + +## Visualization-Specific Heuristics + +Beyond Nielsen's 10, for data visualizations check: + +### Data-Ink Ratio +- Is every visual element carrying information? +- Can any decoration be removed without losing meaning? + +### Gestalt Principles +- Are related nodes visually grouped (proximity, color, enclosure)? +- Do edges clearly connect their endpoints? +- Is the visual hierarchy clear (important nodes larger/brighter)? + +### Interaction Affordance +- Do draggable things look draggable (cursor change)? +- Do clickable things look clickable (hover effect)? +- Are non-interactive elements clearly non-interactive? + +### State Feedback Latency +- Is feedback immediate (<100ms) for direct manipulation (drag)? +- Is feedback fast (<300ms) for triggered actions (click to highlight)? +- Are long operations (layout compute) shown with progress indication? + +## Sources + +- [Nielsen Norman Group: 10 Usability Heuristics](https://www.nngroup.com/articles/ten-usability-heuristics/) +- [Maze: How to Conduct a Heuristic Evaluation](https://maze.co/guides/usability-testing/heuristic-evaluation/) +- [Adam Fard: Heuristic Evaluation Guide](https://adamfard.com/blog/heuristic-evaluation) diff --git a/src/plugins/design-patterns-skill/index.ts b/src/plugins/design-patterns-skill/index.ts new file mode 100644 index 0000000..def9c39 --- /dev/null +++ b/src/plugins/design-patterns-skill/index.ts @@ -0,0 +1,3 @@ +import { createStaticSkillPlugin } from "../../shared/static-skill.js"; + +export const designPatternsSkillPlugin = createStaticSkillPlugin("design-patterns-skill", "The design-patterns-skill skill."); diff --git a/src/plugins/design-patterns-skill/snippets/SKILL.txt b/src/plugins/design-patterns-skill/snippets/SKILL.txt new file mode 100755 index 0000000..586e78f --- /dev/null +++ b/src/plugins/design-patterns-skill/snippets/SKILL.txt @@ -0,0 +1,124 @@ +--- +name: design-patterns-skill +description: Apply core programming principles and design patterns from Clean Code, The Pragmatic Programmer, Code Complete, Refactoring, and Design Patterns. Use when writing code, reviewing PRs, refactoring, or designing system architecture. +triggers: + - "code review" + - "design pattern" + - "refactor" + - "clean code" + - "SOLID" + - "code quality" + - "architecture design" + - "code generation" +activation: + mode: fuzzy + priority: normal + triggers: + - "code review" + - "design pattern" + - "refactor" + - "clean code" + - "SOLID" + - "code quality" + - "architecture design" + - "code generation" +compatibility: ">=1.0.0" +metadata: + version: "1.0.0" +references: + - references/patterns/readability.md + - references/patterns/simplicity.md + - references/patterns/design-architecture.md + - references/patterns/testing.md + - references/patterns/error-handling.md + - references/patterns/maintainability.md +--- + +# Design Patterns & Programming Principles + +## Overview + +Structured guidance on programming principles and design patterns from foundational software engineering books. Ensures code follows industry-standard practices for readability, maintainability, simplicity, and architectural soundness. + +## When to Apply + +- **Code Generation:** Writing new functions, classes, or modules +- **Code Review:** Evaluating pull requests or existing codebases +- **Refactoring:** Improving code structure and clarity +- **Architecture Design:** Choosing appropriate patterns and abstractions + +--- + +## Core Philosophy + +1. **Readability over cleverness** — Code is read more than written +2. **Simplicity over complexity** — Use the simplest solution that works +3. **Testability by design** — Write code that's easy to test +4. **Incremental improvement** — Leave code better than you found it +5. **Patterns as tools** — Apply patterns when they clarify, not by default + +--- + +## Principle Categories + +### 1. Readability & Clarity +- Descriptive naming, consistent formatting, self-documenting code, small focused functions +- **Reference:** `references/patterns/readability.md` + +### 2. Simplicity & Efficiency +- KISS, DRY, YAGNI +- **Reference:** `references/patterns/simplicity.md` + +### 3. Design & Architecture +- SRP, composition over inheritance, program to interfaces +- Patterns: Factory, Strategy, Observer, Decorator, Adapter, Command, Singleton +- **Reference:** `references/patterns/design-architecture.md` + +### 4. Testing & Quality +- Automated testing, focused assertions, edge case coverage +- **Reference:** `references/patterns/testing.md` + +### 5. Error Handling +- Clear error messages, early validation, proper exception usage +- **Reference:** `references/patterns/error-handling.md` + +### 6. Maintainability +- Boy Scout Rule, continuous refactoring, atomic commits, automation +- **Reference:** `references/patterns/maintainability.md` + +--- + +## AI-Specific Guidance + +When generating or reviewing code, always: +1. Check for AI pitfalls listed in each principle +2. Avoid pattern prediction bias — don't use patterns just because they're common +3. Question generic naming — resist `data`, `temp`, `result` without context +4. Validate edge cases — don't skip error handling +5. Keep functions focused — resist combining unrelated operations +6. Match project conventions — maintain consistency with existing codebase + +--- + +## Quick Reference + +| Situation | Apply | +|-----------|-------| +| Function > 20 lines | Split into smaller functions (SRP) | +| Repeated code blocks | Extract to function/constant (DRY) | +| Complex conditionals | Strategy or State pattern | +| Object creation logic | Factory pattern | +| Cross-cutting concerns | Decorator or Observer pattern | +| Incompatible interfaces | Adapter pattern | +| Need undo/logging | Command pattern | +| Global access point | Singleton (use sparingly) | + +--- + +## Sources + +- *Clean Code* — Robert C. Martin +- *The Pragmatic Programmer* — Andrew Hunt & David Thomas +- *Code Complete* — Steve McConnell +- *Refactoring* — Martin Fowler +- *Design Patterns* — Gang of Four diff --git a/src/plugins/design-patterns-skill/snippets/references/misc/overview.txt b/src/plugins/design-patterns-skill/snippets/references/misc/overview.txt new file mode 100755 index 0000000..4bf7af1 --- /dev/null +++ b/src/plugins/design-patterns-skill/snippets/references/misc/overview.txt @@ -0,0 +1,170 @@ +# Design Patterns & Programming Principles Skill + +A comprehensive Kiro skill that provides structured guidance on programming principles and design patterns from foundational software engineering books. + +## Overview + +This skill encapsulates best practices from: +- *Clean Code* by Robert C. Martin +- *The Pragmatic Programmer* by Andrew Hunt & David Thomas +- *Code Complete* by Steve McConnell +- *Refactoring* by Martin Fowler +- *Design Patterns* by Gang of Four + +## Installation + +### For Workspace (Project-Specific) +```bash +mkdir -p .kiro/skills +cp -r design-patterns .kiro/skills/ +``` + +### For Global (All Projects) +```bash +mkdir -p ~/.kiro/skills +cp -r design-patterns ~/.kiro/skills/ +``` + +## Structure + +``` +design-patterns/ +├── SKILL.md # Main skill definition +├── README.md # This file +└── references/ + ├── readability.md # Naming, formatting, documentation + ├── simplicity.md # KISS, DRY, YAGNI principles + ├── design-architecture.md # SRP, patterns, composition + ├── testing.md # Testing strategies and best practices + ├── error-handling.md # Validation, exceptions, recovery + └── maintainability.md # Refactoring, commits, automation +``` + +## Usage + +The skill activates automatically when: +- Writing new code +- Reviewing pull requests +- Refactoring existing code +- Designing system architecture +- Assisting with AI code generation + +## Principle Categories + +### 1. Readability & Clarity +- Descriptive naming conventions +- Consistent code formatting +- Self-documenting code principles +- Small, focused functions + +### 2. Simplicity & Efficiency +- KISS (Keep It Simple, Stupid) +- DRY (Don't Repeat Yourself) +- YAGNI (You Aren't Gonna Need It) +- Avoiding premature optimization + +### 3. Design & Architecture +- Single Responsibility Principle (SRP) +- Composition over Inheritance +- Program to Interfaces +- Essential Design Patterns: + - Factory Pattern + - Strategy Pattern + - Observer Pattern + - Decorator Pattern + - Adapter Pattern + - Command Pattern + - Singleton Pattern + +### 4. Testing & Quality +- Test-driven development approach +- Focused test assertions +- Test pyramid (unit/integration/e2e) +- Mocking and test doubles + +### 5. Error Handling +- Clear error messages +- Early input validation +- Exception hierarchies +- Recovery strategies + +### 6. Maintainability +- Boy Scout Rule +- Continuous refactoring +- Incremental commits +- Automation and tooling + +## AI-Specific Guidance + +This skill includes specific guidance for AI code generation, helping avoid common pitfalls such as: +- Generic naming (`data`, `temp`, `result`) +- Over-commenting obvious code +- Skipping edge case validation +- Applying patterns unnecessarily +- Creating monolithic functions +- Duplicating code structures + +## Quick Reference Examples + +### Before & After + +**Poor Code:** +```python +def proc(u): + if u['age'] < 13: return False + db.save(u) + email.send(u['email'], 'Welcome') + return True +``` + +**Improved Code:** +```python +def is_eligible_user(user): + return user['age'] >= 13 + +def save_user(user): + db.save(user) + +def send_welcome_email(user): + email.send(user['email'], 'Welcome to the platform') + +def register_user(user): + if not is_eligible_user(user): + raise ValueError('User must be 13 or older') + save_user(user) + send_welcome_email(user) +``` + +## When to Apply + +| Situation | Recommended Principle/Pattern | +|-----------|------------------------------| +| Function > 20 lines | Split using SRP | +| Repeated code blocks | Extract with DRY | +| Complex conditionals | Strategy or State pattern | +| Object creation complexity | Factory pattern | +| Cross-cutting concerns | Decorator or Observer | +| Incompatible interfaces | Adapter pattern | +| Need undo/logging | Command pattern | + +## Contributing + +This skill is structured to be easily extended. To add new principles or patterns: + +1. Update the relevant reference file in `references/` +2. Add a cross-reference in `SKILL.md` +3. Include examples with "Do", "Don't", and "AI Pitfalls" sections + +## License + +This skill is based on principles from publicly available software engineering literature and industry best practices. + +## Additional Resources + +- [The 7 Most Important Software Design Patterns](https://learningdaily.dev/the-7-most-important-software-design-patterns-d60e546afb0e) +- [Refactoring Guru - Design Patterns](https://refactoring.guru/design-patterns) +- [SOLID Principles](https://en.wikipedia.org/wiki/SOLID) + +## Version + +1.0.0 - Initial release diff --git a/src/plugins/design-patterns-skill/snippets/references/patterns/design-architecture.txt b/src/plugins/design-patterns-skill/snippets/references/patterns/design-architecture.txt new file mode 100755 index 0000000..fc1cd6c --- /dev/null +++ b/src/plugins/design-patterns-skill/snippets/references/patterns/design-architecture.txt @@ -0,0 +1,477 @@ +# Design & Architecture Principles + +## Single Responsibility Principle (SRP) + +**Definition:** Each class or module should have only one reason to change. It should encapsulate one cohesive responsibility. + +**Supported by:** *Clean Code*, *The Pragmatic Programmer*, SOLID Principles + +### Examples + +```python +# Bad - Multiple responsibilities +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + def save_to_database(self): + # Database logic + pass + + def send_welcome_email(self): + # Email logic + pass + + def generate_report(self): + # Reporting logic + pass + +# Good - Separated concerns +class User: + def __init__(self, name, email): + self.name = name + self.email = email + +class UserRepository: + def save(self, user): + # Database logic + pass + +class EmailService: + def send_welcome(self, user): + # Email logic + pass + +class UserReportGenerator: + def generate(self, user): + # Reporting logic + pass +``` + +### Do +- Encapsulate related data and behavior +- Separate concerns (data access, business logic, presentation) +- Create cohesive modules +- Make reasons for change explicit + +### Don't +- Mix data access, logic, and UI in one class +- Create "god objects" that do everything +- Couple unrelated functionality + +### AI Pitfalls +- Cramming multiple operations into one class +- Creating utility classes with unrelated methods +- Mixing infrastructure and domain logic + +--- + +## Composition Over Inheritance + +**Definition:** Prefer combining objects to form behavior over creating deep class hierarchies. Favor "has-a" relationships over "is-a". + +**Supported by:** *The Pragmatic Programmer*, *Design Patterns* + +### Examples + +```python +# Bad - Rigid inheritance hierarchy +class Bird: + def fly(self): + return "Flying" + +class Penguin(Bird): + def fly(self): + raise Exception("Penguins cannot fly") + +# Good - Composition with behavior injection +class FlyBehavior: + def fly(self): + pass + +class CanFly(FlyBehavior): + def fly(self): + return "Flying" + +class CannotFly(FlyBehavior): + def fly(self): + return "Cannot fly" + +class Bird: + def __init__(self, fly_behavior): + self.fly_behavior = fly_behavior + + def perform_fly(self): + return self.fly_behavior.fly() + +# Usage +sparrow = Bird(CanFly()) +penguin = Bird(CannotFly()) +``` + +### Do +- Use interfaces or protocols to define contracts +- Inject dependencies and behaviors +- Compose small, focused objects +- Favor delegation over inheritance + +### Don't +- Create deep inheritance hierarchies (>3 levels) +- Inherit just to override behavior +- Use inheritance for code reuse alone +- Force unnatural "is-a" relationships + +### AI Pitfalls +- Defaulting to inheritance for code reuse +- Creating rigid class hierarchies +- Not recognizing when composition is clearer + +--- + +## Program to an Interface, Not an Implementation + +**Definition:** Depend on abstractions (interfaces, protocols) rather than concrete implementations. This enables flexibility and testability. + +**Supported by:** *Design Patterns*, *Code Complete*, Dependency Inversion Principle + +### Examples + +```python +# Bad - Depends on concrete implementation +class OrderProcessor: + def __init__(self): + self.payment = StripePayment() # Hard-coded dependency + + def process(self, order): + self.payment.charge(order.total) + +# Good - Depends on abstraction +class PaymentProcessor: + def charge(self, amount): + raise NotImplementedError + +class StripePayment(PaymentProcessor): + def charge(self, amount): + # Stripe-specific logic + pass + +class PayPalPayment(PaymentProcessor): + def charge(self, amount): + # PayPal-specific logic + pass + +class OrderProcessor: + def __init__(self, payment_processor: PaymentProcessor): + self.payment = payment_processor + + def process(self, order): + self.payment.charge(order.total) + +# Usage - Easy to swap implementations +processor = OrderProcessor(StripePayment()) +# or +processor = OrderProcessor(PayPalPayment()) +``` + +### Do +- Define interfaces for key abstractions +- Inject dependencies via constructors +- Use dependency injection frameworks when appropriate +- Code against contracts, not implementations + +### Don't +- Hard-code concrete class names +- Use `isinstance()` checks to switch behavior +- Create tight coupling to specific implementations + +### AI Pitfalls +- Using fixed class names instead of interfaces +- Not recognizing opportunities for abstraction +- Creating concrete dependencies in constructors + +--- + +## Essential Design Patterns + +### Factory Pattern + +**Purpose:** Delegate object creation to factory methods or classes. Decouples client code from concrete instantiation. + +**Use when:** Object creation is complex or varies based on conditions. + +```python +# Example +class LoggerFactory: + @staticmethod + def get_logger(log_type): + if log_type == "file": + return FileLogger() + elif log_type == "console": + return ConsoleLogger() + elif log_type == "cloud": + return CloudLogger() + else: + raise ValueError(f"Unknown logger type: {log_type}") + +# Usage +logger = LoggerFactory.get_logger("file") +logger.log("Application started") +``` + +### Strategy Pattern + +**Purpose:** Define a family of interchangeable algorithms and make them swappable at runtime. + +**Use when:** You need different behaviors for the same operation. + +```python +# Example +class SortStrategy: + def sort(self, data): + raise NotImplementedError + +class QuickSort(SortStrategy): + def sort(self, data): + # Quick sort implementation + pass + +class MergeSort(SortStrategy): + def sort(self, data): + # Merge sort implementation + pass + +class DataProcessor: + def __init__(self, sort_strategy: SortStrategy): + self.sorter = sort_strategy + + def process(self, data): + sorted_data = self.sorter.sort(data) + return sorted_data + +# Usage +processor = DataProcessor(MergeSort()) +result = processor.process([3, 1, 4, 1, 5]) +``` + +### Observer Pattern + +**Purpose:** Define a one-to-many dependency where changes in one object notify all dependents automatically. + +**Use when:** Multiple objects need to react to state changes. + +```python +# Example +class Subject: + def __init__(self): + self._observers = [] + + def attach(self, observer): + self._observers.append(observer) + + def notify(self, event): + for observer in self._observers: + observer.update(event) + +class Observer: + def update(self, event): + raise NotImplementedError + +class EmailNotifier(Observer): + def update(self, event): + print(f"Sending email for: {event}") + +class SlackNotifier(Observer): + def update(self, event): + print(f"Posting to Slack: {event}") + +# Usage +order_system = Subject() +order_system.attach(EmailNotifier()) +order_system.attach(SlackNotifier()) +order_system.notify("Order #123 shipped") +``` + +### Decorator Pattern + +**Purpose:** Dynamically add responsibilities to objects without modifying their class. + +**Use when:** You need flexible, composable enhancements. + +```python +# Example +class Notifier: + def send(self, message): + raise NotImplementedError + +class BasicNotifier(Notifier): + def send(self, message): + print(f"Basic notification: {message}") + +class NotifierDecorator(Notifier): + def __init__(self, notifier: Notifier): + self._notifier = notifier + + def send(self, message): + self._notifier.send(message) + +class SlackDecorator(NotifierDecorator): + def send(self, message): + super().send(message) + print(f"Also sent to Slack: {message}") + +class EmailDecorator(NotifierDecorator): + def send(self, message): + super().send(message) + print(f"Also sent via email: {message}") + +# Usage - Compose behaviors +notifier = EmailDecorator(SlackDecorator(BasicNotifier())) +notifier.send("System alert") +``` + +### Adapter Pattern + +**Purpose:** Convert one interface into another that clients expect. Enables incompatible interfaces to work together. + +**Use when:** Integrating legacy systems or third-party libraries. + +```python +# Example +class LegacyPrinter: + def print_text(self, text): + print(f"[LEGACY] {text}") + +class ModernPrinter: + def print(self, content): + raise NotImplementedError + +class PrinterAdapter(ModernPrinter): + def __init__(self, legacy_printer: LegacyPrinter): + self.legacy = legacy_printer + + def print(self, content): + self.legacy.print_text(content) + +# Usage +old_printer = LegacyPrinter() +adapter = PrinterAdapter(old_printer) +adapter.print("Hello World") # Uses modern interface, delegates to legacy +``` + +### Command Pattern + +**Purpose:** Encapsulate a request as an object, enabling queuing, logging, or undoable operations. + +**Use when:** You need to queue operations, support undo/redo, or log actions. + +```python +# Example +class Command: + def execute(self): + raise NotImplementedError + + def undo(self): + raise NotImplementedError + +class Light: + def on(self): + print("Light is ON") + + def off(self): + print("Light is OFF") + +class LightOnCommand(Command): + def __init__(self, light: Light): + self.light = light + + def execute(self): + self.light.on() + + def undo(self): + self.light.off() + +class LightOffCommand(Command): + def __init__(self, light: Light): + self.light = light + + def execute(self): + self.light.off() + + def undo(self): + self.light.on() + +# Usage +living_room_light = Light() +light_on = LightOnCommand(living_room_light) +light_on.execute() # Light is ON +light_on.undo() # Light is OFF +``` + +### Singleton Pattern + +**Purpose:** Ensure only one instance of a class exists globally. + +**Use when:** You need a single point of access (e.g., config, logger, connection pool). + +**Caution:** Often overused. Consider dependency injection instead. + +```python +# Example +class Singleton: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + +class ConfigManager(Singleton): + def __init__(self): + if not hasattr(self, 'initialized'): + self.config = {} + self.initialized = True + +# Usage +config1 = ConfigManager() +config2 = ConfigManager() +assert config1 is config2 # Same instance +``` + +--- + +## Pattern Usage Guidelines + +### Do +- Apply patterns when they improve clarity and flexibility +- Choose patterns based on structural fit +- Use patterns to communicate design intent +- Combine patterns when appropriate + +### Don't +- Force patterns into simple code +- Use patterns for the sake of patterns +- Apply patterns without understanding the problem +- Over-abstract with unnecessary pattern layers + +### AI Pitfalls +- Predicting patterns where none are needed +- Misnaming pattern roles (e.g., calling a simple factory a "Factory Pattern") +- Misapplying pattern intent (e.g., Singleton for everything) +- Creating pattern boilerplate without actual benefit + +--- + +## Summary + +Good architecture is: +- **Modular** - clear boundaries and responsibilities +- **Flexible** - uses composition and interfaces +- **Abstract** - depends on contracts, not implementations +- **Pattern-aware** - applies proven solutions appropriately + +When designing systems, ask: +- Does each module have one clear responsibility? +- Can I swap implementations easily? +- Am I using inheritance or composition? +- Does this pattern solve a real structural problem? diff --git a/src/plugins/design-patterns-skill/snippets/references/patterns/error-handling.txt b/src/plugins/design-patterns-skill/snippets/references/patterns/error-handling.txt new file mode 100755 index 0000000..1b1c25f --- /dev/null +++ b/src/plugins/design-patterns-skill/snippets/references/patterns/error-handling.txt @@ -0,0 +1,364 @@ +# Error Handling & Input Validation + +## Handle Errors Clearly + +**Definition:** Use exceptions for unexpected states and provide clear error messages. Fail early and explicitly rather than allowing silent failures. + +**Supported by:** *Code Complete*, *Clean Code*, *The Pragmatic Programmer* + +### Examples + +```python +# Bad - Silent failure +def divide(a, b): + if b == 0: + return None # Caller has to check for None + return a / b + +# Good - Explicit error +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b +``` + +```python +# Bad - Vague error message +def process_user(user): + if not user: + raise Exception("Error") + +# Good - Descriptive error message +def process_user(user): + if user is None: + raise ValueError("User object cannot be None") + if not user.email: + raise ValueError(f"User {user.id} must have a valid email address") +``` + +### Do +- Use exceptions for exceptional conditions +- Provide descriptive error messages +- Include context (what failed, why, what was expected) +- Fail fast - validate inputs early +- Use specific exception types +- Document what exceptions can be raised + +### Don't +- Return None or -1 as error codes +- Swallow exceptions silently +- Use exceptions for control flow +- Provide generic error messages ("Error occurred") +- Catch exceptions you can't handle + +### AI Pitfalls +- Skipping edge-case validation +- Empty except blocks: `except: pass` +- Returning default values instead of raising errors +- Generic exception types instead of specific ones + +--- + +## Validate Inputs Early + +**Definition:** Check preconditions at the entry point of functions. Reject invalid input before processing. + +**Supported by:** *Code Complete*, *Clean Code* + +### Examples + +```python +# Bad - Late validation, partial processing +def register_user(username, email, age): + user = User(username, email, age) + save_to_database(user) + if age < 18: # Too late - already saved! + raise ValueError("User must be 18 or older") + +# Good - Early validation +def register_user(username, email, age): + if not username or len(username) < 3: + raise ValueError("Username must be at least 3 characters") + if not email or '@' not in email: + raise ValueError("Invalid email address") + if age < 18: + raise ValueError("User must be 18 or older") + + user = User(username, email, age) + save_to_database(user) +``` + +### Guard Clauses + +Use guard clauses to validate and exit early: + +```python +# Bad - Nested conditions +def process_order(order): + if order is not None: + if order.items: + if order.total > 0: + # Main logic here + charge_payment(order) + ship_order(order) + +# Good - Guard clauses +def process_order(order): + if order is None: + raise ValueError("Order cannot be None") + if not order.items: + raise ValueError("Order must contain at least one item") + if order.total <= 0: + raise ValueError("Order total must be positive") + + # Main logic - no nesting + charge_payment(order) + ship_order(order) +``` + +### Do +- Validate at function entry +- Use guard clauses to reduce nesting +- Check preconditions explicitly +- Validate types and ranges +- Use type hints and runtime validation + +### Don't +- Defer validation until deep in the logic +- Assume inputs are valid +- Mix validation with business logic + +--- + +## Exception Hierarchy + +**Definition:** Use specific exception types to allow targeted error handling. + +### Examples + +```python +# Bad - Generic exceptions +def fetch_user(user_id): + if user_id < 0: + raise Exception("Invalid ID") + user = db.get(user_id) + if not user: + raise Exception("Not found") + return user + +# Good - Specific exceptions +class InvalidUserIdError(ValueError): + pass + +class UserNotFoundError(LookupError): + pass + +def fetch_user(user_id): + if user_id < 0: + raise InvalidUserIdError(f"User ID must be positive, got {user_id}") + user = db.get(user_id) + if not user: + raise UserNotFoundError(f"User with ID {user_id} not found") + return user + +# Caller can handle specifically +try: + user = fetch_user(user_id) +except InvalidUserIdError as e: + return {"error": "bad_request", "message": str(e)} +except UserNotFoundError as e: + return {"error": "not_found", "message": str(e)} +``` + +### Do +- Create custom exception classes for domain errors +- Inherit from appropriate built-in exceptions +- Use exception hierarchies for related errors +- Document exception types in docstrings + +### Don't +- Raise generic `Exception` or `RuntimeError` +- Create exceptions for every possible error +- Use exceptions for non-exceptional cases + +--- + +## Error Recovery Strategies + +### Retry with Backoff + +```python +import time + +def fetch_with_retry(url, max_attempts=3): + for attempt in range(max_attempts): + try: + return http.get(url) + except TransientError as e: + if attempt == max_attempts - 1: + raise + wait_time = 2 ** attempt # Exponential backoff + time.sleep(wait_time) +``` + +### Fallback Mechanisms + +```python +def get_user_avatar(user_id): + try: + return cdn.fetch_avatar(user_id) + except CDNError: + # Fallback to default avatar + return DEFAULT_AVATAR_URL +``` + +### Circuit Breaker + +```python +class CircuitBreaker: + def __init__(self, failure_threshold=5): + self.failure_count = 0 + self.threshold = failure_threshold + self.state = "closed" # closed, open, half-open + + def call(self, func, *args): + if self.state == "open": + raise CircuitOpenError("Service is temporarily unavailable") + + try: + result = func(*args) + self.on_success() + return result + except Exception as e: + self.on_failure() + raise + + def on_success(self): + self.failure_count = 0 + self.state = "closed" + + def on_failure(self): + self.failure_count += 1 + if self.failure_count >= self.threshold: + self.state = "open" +``` + +--- + +## Logging vs. Exceptions + +**Definition:** Log for diagnostics, use exceptions for control flow. + +### When to Log + +```python +# Log operational info +logger.info(f"Processing order {order_id}") + +# Log warnings for recoverable issues +logger.warning(f"Slow query detected: {duration}ms") + +# Log errors with context +try: + process_payment(order) +except PaymentError as e: + logger.error(f"Payment failed for order {order.id}", exc_info=True) + raise # Re-raise after logging +``` + +### When to Raise Exceptions + +```python +# Invalid input - exception +def set_age(age): + if age < 0 or age > 150: + raise ValueError(f"Invalid age: {age}") + +# Business rule violation - exception +def withdraw(account, amount): + if account.balance < amount: + raise InsufficientFundsError(f"Balance: {account.balance}, requested: {amount}") + +# Operational issue - log + exception +def connect_to_database(): + try: + return db.connect() + except ConnectionError as e: + logger.error("Database connection failed", exc_info=True) + raise DatabaseUnavailableError("Cannot connect to database") from e +``` + +### Do +- Log context before re-raising +- Include exception traceback in logs +- Use structured logging for searchability +- Set appropriate log levels + +### Don't +- Log and swallow exceptions +- Log sensitive data (passwords, tokens) +- Over-log routine operations + +--- + +## Error Messages Best Practices + +### Good Error Messages + +**What went wrong:** +``` +"Invalid email address: 'user@domain' - missing top-level domain" +``` + +**What was expected:** +``` +"Order total must be positive, got -50.00" +``` + +**How to fix it:** +``` +"File not found: '/data/input.csv'. Check that the file exists and path is correct." +``` + +**Actionable context:** +``` +"User authentication failed: Invalid API key. Please check your credentials in the dashboard." +``` + +### Bad Error Messages + +``` +"Error" # Too vague +"Something went wrong" # Unhelpful +"Invalid input" # Missing details +"Error code: 42" # No explanation +``` + +### Do +- Explain what failed and why +- Include actual vs. expected values +- Suggest corrective actions +- Avoid technical jargon for user-facing errors +- Use clear, plain language + +### Don't +- Expose internal implementation details to end users +- Include stack traces in user-facing messages +- Use codes without explanations +- Be condescending ("You entered invalid data") + +--- + +## Summary + +Effective error handling: +- **Fails fast** - Validates early and explicitly +- **Provides clarity** - Error messages explain what and why +- **Uses exceptions correctly** - For exceptional conditions only +- **Enables recovery** - Appropriate retry and fallback strategies + +When handling errors, ask: +- Have I validated all inputs? +- Will the error message help someone fix the issue? +- Am I using the right exception type? +- Should this be logged, raised, or both? diff --git a/src/plugins/design-patterns-skill/snippets/references/patterns/maintainability.txt b/src/plugins/design-patterns-skill/snippets/references/patterns/maintainability.txt new file mode 100755 index 0000000..a085889 --- /dev/null +++ b/src/plugins/design-patterns-skill/snippets/references/patterns/maintainability.txt @@ -0,0 +1,548 @@ +# Maintainability & Best Practices + +## Boy Scout Rule + +**Definition:** "Leave the code better than you found it." Make small improvements whenever you touch existing code. + +**Supported by:** *Clean Code*, *The Pragmatic Programmer* + +### Examples + +```python +# Before - Existing code you're modifying +def calc(a, b): + return a + b + +# After - Improved while making your change +def calculate_sum(a, b): + """Return the sum of two numbers.""" + return a + b +``` + +```python +# Before - Adding a feature to messy code +def processUser(u): + # Check age + if u.age<18:return False + db.save(u) + return True + +# After - Clean up while you're here +def process_user(user): + """Register an eligible user.""" + if not is_eligible_user(user): + return False + save_user(user) + return True + +def is_eligible_user(user): + return user.age >= 18 +``` + +### Do +- Improve variable names +- Extract magic numbers to constants +- Add missing docstrings +- Fix formatting inconsistencies +- Remove dead code +- Simplify complex conditions + +### Don't +- Make unrelated large refactors +- Change behavior without tests +- Add hacks or workarounds +- Ignore obvious issues ("not my code") + +### AI Pitfalls +- Regenerating dirty code without improvements +- Not suggesting cleanup opportunities +- Adding to technical debt instead of reducing it + +--- + +## Continuous Refactoring + +**Definition:** Improve code structure regularly through small, safe changes backed by tests. Refactoring should be ongoing, not a separate phase. + +**Supported by:** *Refactoring*, *Code Complete*, *Clean Code* + +### Common Refactorings + +**Extract Method** +```python +# Before +def process_order(order): + # Validate + if not order.items: + raise ValueError("Empty order") + + # Calculate total + total = 0 + for item in order.items: + total += item.price * item.quantity + + # Apply discount + if order.customer.is_premium: + total *= 0.9 + + return total + +# After +def process_order(order): + validate_order(order) + total = calculate_total(order) + return apply_discount(total, order.customer) + +def validate_order(order): + if not order.items: + raise ValueError("Empty order") + +def calculate_total(order): + return sum(item.price * item.quantity for item in order.items) + +def apply_discount(total, customer): + if customer.is_premium: + return total * 0.9 + return total +``` + +**Extract Variable** +```python +# Before +if (user.age >= 18 and user.has_verified_email and user.account_status == 'active'): + grant_access() + +# After +is_adult = user.age >= 18 +has_verified_email = user.has_verified_email +is_active = user.account_status == 'active' + +if is_adult and has_verified_email and is_active: + grant_access() +``` + +**Rename** +```python +# Before +def fn(x, y): + return x * y + +# After +def calculate_area(width, height): + return width * height +``` + +**Replace Magic Numbers** +```python +# Before +def calculate_price(quantity): + if quantity > 100: + return quantity * 9.99 * 0.85 + return quantity * 9.99 + +# After +UNIT_PRICE = 9.99 +BULK_DISCOUNT = 0.85 +BULK_THRESHOLD = 100 + +def calculate_price(quantity): + price = quantity * UNIT_PRICE + if quantity > BULK_THRESHOLD: + price *= BULK_DISCOUNT + return price +``` + +### Refactoring Workflow + +1. **Ensure tests pass** - Start with green tests +2. **Make one change** - Small, focused refactor +3. **Run tests** - Verify behavior unchanged +4. **Commit** - Save working state +5. **Repeat** - Iterate on improvements + +### Do +- Refactor in small steps +- Run tests after each change +- Commit frequently +- Use IDE refactoring tools +- Keep behavior identical + +### Don't +- Refactor without tests +- Mix refactoring with feature work +- Make multiple changes at once +- Skip running tests +- Delay commits + +### AI Pitfalls +- Suggesting large refactors without incremental steps +- Omitting test runs between changes +- Changing behavior during refactoring + +--- + +## Version Control & Incremental Work + +**Definition:** Commit code in logical, testable chunks. Each commit should represent a complete, working unit of change. + +**Supported by:** *Refactoring*, *The Pragmatic Programmer*, Agile practices + +### Good Commit Practices + +**Atomic Commits** +``` +✓ "Add user email validation" +✓ "Extract payment processing to service" +✓ "Fix off-by-one error in pagination" + +✗ "Fixed stuff" +✗ "WIP" +✗ "Updated files" +``` + +**Commit Messages** +``` +# Good - Imperative mood, clear intent +Add password strength validation + +Implement validation rules: +- Minimum 8 characters +- At least one uppercase letter +- At least one number +- At least one special character + +Closes #123 + +# Bad +fixed login +``` + +### Commit Workflow + +```bash +# 1. Make a focused change +# 2. Run tests +pytest + +# 3. Review changes +git diff + +# 4. Stage related files +git add user_validator.py tests/test_validator.py + +# 5. Commit with clear message +git commit -m "Add email format validation" + +# 6. Repeat for next logical change +``` + +### Do +- Commit working, tested code +- Write descriptive commit messages +- Keep commits focused and atomic +- Use branches for features +- Commit frequently + +### Don't +- Commit broken code +- Mix unrelated changes in one commit +- Skip commit messages +- Commit sensitive data (API keys, passwords) +- Leave uncommitted changes overnight + +### AI Pitfalls +- Generating large changes without guiding commit boundaries +- Not suggesting logical commit points +- Creating code that can't be committed incrementally + +--- + +## Code Reviews + +**Definition:** Systematic examination of code changes by peers to catch issues, share knowledge, and maintain quality. + +### Review Checklist + +**Correctness** +- Does it solve the stated problem? +- Are edge cases handled? +- Is error handling appropriate? +- Are there off-by-one errors or race conditions? + +**Design** +- Is it in the right place? +- Does it follow existing patterns? +- Is complexity warranted? +- Could it be simpler? + +**Readability** +- Are names clear? +- Is logic easy to follow? +- Are comments helpful (not redundant)? +- Is formatting consistent? + +**Testing** +- Are tests included? +- Do tests cover edge cases? +- Are tests readable and maintainable? + +**Security** +- Is input validated? +- Are secrets hardcoded? +- Are SQL queries parameterized? +- Is authentication/authorization correct? + +### Review Etiquette + +**As Reviewer** +``` +✓ "Consider extracting this to a helper function for reusability" +✓ "Could we add a test for the empty list case?" +✓ "This is clever! Can we add a comment explaining the algorithm?" + +✗ "This is terrible" +✗ "Why didn't you just..." +✗ "Obviously this is wrong" +``` + +**As Author** +- Respond to all feedback +- Ask for clarification +- Explain non-obvious decisions +- Be open to suggestions +- Thank reviewers + +### Do +- Review promptly +- Focus on substance over style +- Suggest improvements, don't demand +- Automate style checks +- Learn from reviews you receive + +### Don't +- Approve without reading +- Nitpick trivial issues +- Review your own PRs +- Take criticism personally +- Skip review for "small" changes + +--- + +## Automation and Tooling + +**Definition:** Automate repetitive tasks and use tools to maintain consistency and quality. + +**Supported by:** *The Pragmatic Programmer*, *Clean Code* + +### Essential Tools + +**Linters** - Catch common mistakes +```bash +# Python +pylint myapp/ +flake8 myapp/ + +# JavaScript +eslint src/ + +# Go +golangci-lint run +``` + +**Formatters** - Maintain consistent style +```bash +# Python +black myapp/ + +# JavaScript +prettier --write src/ + +# Rust +rustfmt src/ +``` + +**Type Checkers** - Catch type errors +```bash +# Python +mypy myapp/ + +# TypeScript +tsc --noEmit + +# Flow +flow check +``` + +**Test Runners** - Verify behavior +```bash +# Python +pytest + +# JavaScript +jest + +# Go +go test ./... +``` + +### Continuous Integration + +```yaml +# .github/workflows/ci.yml +name: CI +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: pip install -r requirements.txt + - name: Lint + run: flake8 . + - name: Type check + run: mypy . + - name: Test + run: pytest --cov + - name: Security scan + run: bandit -r . +``` + +### Pre-commit Hooks + +```bash +# .pre-commit-config.yaml +repos: + - repo: https://github.com/psf/black + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + hooks: + - id: flake8 + - repo: https://github.com/pre-commit/pre-commit-hooks + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml +``` + +### Do +- Integrate tools into workflow +- Run checks locally before pushing +- Fail builds on violations +- Configure tools consistently +- Update tools regularly + +### Don't +- Rely on manual checks +- Ignore tool warnings +- Skip tools for "quick fixes" +- Disable checks without good reason + +### AI Pitfalls +- Producing code that doesn't pass linting +- Ignoring type annotations +- Generating code incompatible with project tools + +--- + +## Documentation + +**Definition:** Provide context and explanations where code alone isn't sufficient. + +### What to Document + +**APIs and Public Interfaces** +```python +def calculate_shipping_cost(weight_kg: float, destination: str) -> float: + """Calculate shipping cost based on weight and destination. + + Args: + weight_kg: Package weight in kilograms (must be positive) + destination: ISO 3166-1 alpha-2 country code + + Returns: + Shipping cost in USD + + Raises: + ValueError: If weight is negative or destination is invalid + + Example: + >>> calculate_shipping_cost(2.5, 'US') + 12.50 + """ +``` + +**Complex Algorithms** +```python +def dijkstra(graph, start): + """Find shortest paths using Dijkstra's algorithm. + + Time complexity: O((V + E) log V) where V is vertices, E is edges + Space complexity: O(V) + + See: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + """ +``` + +**Non-Obvious Decisions** +```python +# Using MD5 for cache keys only - NOT for security +# MD5 is fast and collision-resistant enough for this use case +cache_key = hashlib.md5(url.encode()).hexdigest() +``` + +**Setup and Configuration** +```markdown +# README.md + +## Installation + +pip install -r requirements.txt + +## Configuration + +Set environment variables: +- `DATABASE_URL`: PostgreSQL connection string +- `API_KEY`: Third-party service API key + +## Running + +python app.py +``` + +### Don't Document + +- Obvious code (let code be self-documenting) +- Implementation details that change frequently +- Duplicated information available elsewhere + +### Do +- Keep docs close to code +- Update docs with code changes +- Use examples liberally +- Link to external references + +### Don't +- Let docs become stale +- Over-document simple code +- Duplicate info across files + +--- + +## Summary + +Maintainable code: +- **Improves incrementally** - Boy Scout Rule +- **Refactors continuously** - Small, safe improvements +- **Commits logically** - Atomic, tested changes +- **Automates quality** - Linters, formatters, CI/CD +- **Documents appropriately** - Context where needed + +When maintaining code, ask: +- Can I improve this while I'm here? +- Is this change small and safe? +- Should I commit now? +- Are my tools catching issues? +- Does this need documentation? diff --git a/src/plugins/design-patterns-skill/snippets/references/patterns/readability.txt b/src/plugins/design-patterns-skill/snippets/references/patterns/readability.txt new file mode 100755 index 0000000..edf85e0 --- /dev/null +++ b/src/plugins/design-patterns-skill/snippets/references/patterns/readability.txt @@ -0,0 +1,195 @@ +# Readability & Clarity Principles + +## Descriptive Naming + +**Definition:** Use clear, meaningful names for variables, functions, classes, etc., so code reads like natural language. Avoid vague, abbreviated, or encoded names. Good names explain intent without requiring comments. + +**Supported by:** *Clean Code*, *Code Complete* + +### Examples + +```python +# Bad +def calc(a, b): + return a * b + 3 + +# Good +def calculate_rectangle_area(width, height): + margin = 3 + return width * height + margin +``` + +### Do +- Use nouns for data structures and variables +- Use verbs for functions and methods +- Use consistent domain terminology +- Make names pronounceable and searchable +- Use solution/problem domain names + +### Don't +- Use single-letter names (except loop counters in small scopes) +- Create misleading names +- Use encodings or prefixes (Hungarian notation) +- Use abbreviations unless universally known +- Mix naming conventions in the same scope + +### AI Pitfalls +- Repeating generic names like `data`, `temp`, `foo`, `result` +- Inconsistent naming across similar concepts +- Using placeholder names and forgetting to rename +- Over-shortening meaningful names for brevity + +--- + +## Consistent Style & Formatting + +**Definition:** Follow a uniform coding style and project conventions. Consistency aids readability and reduces cognitive load. + +**Supported by:** *Clean Code*, *Code Complete* + +### Examples + +```javascript +// Bad - Inconsistent spacing, braces, indentation +if(x>0){ +y= x+10; + console.log(y);} + +// Good - Consistent formatting +if (x > 0) { + let result = x + 10; + console.log(result); +} +``` + +### Do +- Stick to one brace style (K&R, Allman, etc.) +- Use consistent indentation (2 or 4 spaces, never mix tabs/spaces) +- Follow language conventions (PEP 8 for Python, Airbnb for JS) +- Maintain consistent line length (80-120 characters) +- Use automated formatters (Prettier, Black, rustfmt) + +### Don't +- Mix different formatting styles in one file +- Ignore project linting rules +- Use inconsistent whitespace +- Create overly long lines + +### AI Pitfalls +- Producing inconsistent formatting across code blocks +- Mixing indentation styles +- Ignoring existing project formatting conventions + +--- + +## Self-Documenting Code (Minimize Comments) + +**Definition:** Write code so its intent is clear from the code itself. Comments should explain *why*, not *what*. + +**Supported by:** *Clean Code*, *Code Complete* + +### Examples + +```python +# Bad - Redundant comment +# Increment i by 1 +i = i + 1 + +# Good - No comment needed +i = i + 1 + +# Acceptable - Explains business rule +# Block access for users under minimum age requirement +if user.age < 13: + block_access() + +# Good - Explains non-obvious why +# Using exponential backoff to avoid API rate limits +retry_delay = base_delay * (2 ** attempt_count) +``` + +### Do +- Use clear naming and logic structure +- Comment complex algorithms or business rules +- Explain performance optimizations +- Document API contracts and side effects +- Add TODO comments for future work (with ticket IDs) + +### Don't +- Write comments that restate the code +- Leave commented-out code +- Write misleading or outdated comments +- Use comments to fix bad naming + +### AI Pitfalls +- Over-commenting obvious operations +- Leaving stale or contradictory comments +- Using comments instead of refactoring unclear code + +--- + +## Small Functions & Single Responsibility + +**Definition:** Functions and methods should do one thing and do it well. Small, cohesive units are easier to understand, test, and maintain. + +**Supported by:** *Clean Code*, *Code Complete* + +### Examples + +```python +# Bad - Function does too many things +def update_user(data): + validate(data) + update_database(data) + send_email(data) + log_activity(data) + invalidate_cache(data) + +# Good - Separated concerns +def update_user(data): + validated_data = validate(data) + save_user(validated_data) + notify_user(validated_data) + +def save_user(data): + update_database(data) + invalidate_cache(data) + +def notify_user(data): + send_email(data) + log_activity(data) +``` + +### Do +- Keep functions under 20-30 lines when possible +- Extract helper functions for complex logic +- Use descriptive function names that indicate purpose +- Limit function parameters (ideally ≤ 3) +- Make one level of abstraction per function + +### Don't +- Combine unrelated operations +- Create deeply nested logic +- Use flag arguments to control behavior +- Write functions that both query and modify state + +### AI Pitfalls +- Creating monolithic functions with multiple responsibilities +- Over-fragmenting into excessive tiny functions +- Mixing abstraction levels within one function +- Generating functions that modify global state unexpectedly + +--- + +## Summary + +Readable code is: +- **Self-explanatory** through naming +- **Consistent** in style and structure +- **Minimal in comments** - code speaks for itself +- **Small and focused** - easy to understand at a glance + +When writing or reviewing code, ask: +- Can I understand this without the author present? +- Would I want to debug this at 2 AM? +- Does this follow the team's conventions? diff --git a/src/plugins/design-patterns-skill/snippets/references/patterns/simplicity.txt b/src/plugins/design-patterns-skill/snippets/references/patterns/simplicity.txt new file mode 100755 index 0000000..f0533d2 --- /dev/null +++ b/src/plugins/design-patterns-skill/snippets/references/patterns/simplicity.txt @@ -0,0 +1,279 @@ +# Simplicity & Efficiency Principles + +## KISS (Keep It Simple, Stupid) + +**Definition:** Use the simplest solution that solves the problem. Avoid unnecessary complexity, over-engineering, or premature optimization. + +**Supported by:** *Clean Code*, *The Pragmatic Programmer* + +### Examples + +```javascript +// Bad - Unnecessary abstraction +class SingleValueContainer { + constructor(value) { + this.values = [value]; + } + add(value) { + this.values.push(value); + } + getValue() { + return this.values[0]; + } +} + +// Good - Use built-in features +let numbers = [5]; +numbers.push(7); +let firstNumber = numbers[0]; +``` + +```python +# Bad - Over-complicated +def is_even(n): + return True if n % 2 == 0 else False + +# Good - Direct and clear +def is_even(n): + return n % 2 == 0 +``` + +### Do +- Use language built-ins and standard libraries +- Choose clear, direct solutions +- Optimize only when profiling shows need +- Prefer composition of simple parts +- Write code for the current requirement + +### Don't +- Create abstractions without clear benefit +- Add complexity for hypothetical future needs +- Use clever tricks that obscure intent +- Build custom solutions when standard ones exist + +### AI Pitfalls +- Using classes or design patterns unnecessarily +- Creating abstractions for single-use code +- Over-complicating simple conditional logic +- Generating enterprise patterns for simple scripts + +--- + +## DRY (Don't Repeat Yourself) + +**Definition:** Eliminate duplicated code and logic. Every piece of knowledge should have a single, authoritative representation. + +**Supported by:** *The Pragmatic Programmer*, *Clean Code* + +### Examples + +```python +# Bad - Duplicated logic +def circle_area(radius): + return 3.14159 * radius * radius + +def quarter_circle_area(radius): + return 3.14159 * radius * radius / 4 + +def sphere_volume(radius): + return (4/3) * 3.14159 * radius * radius * radius + +# Good - Extracted constant and reused logic +PI = 3.14159 + +def circle_area(radius): + return PI * radius ** 2 + +def quarter_circle_area(radius): + return circle_area(radius) / 4 + +def sphere_volume(radius): + return (4/3) * PI * radius ** 3 +``` + +```javascript +// Bad - Repeated validation +function createUser(name, email) { + if (!email.includes('@')) throw Error('Invalid email'); + // ... +} + +function updateEmail(userId, email) { + if (!email.includes('@')) throw Error('Invalid email'); + // ... +} + +// Good - Extracted validation +function validateEmail(email) { + if (!email.includes('@')) { + throw Error('Invalid email'); + } +} + +function createUser(name, email) { + validateEmail(email); + // ... +} + +function updateEmail(userId, email) { + validateEmail(email); + // ... +} +``` + +### Do +- Extract common logic into functions +- Use constants for repeated values +- Abstract similar patterns +- Share code across modules appropriately +- Keep abstractions at the right level + +### Don't +- Copy-paste code blocks +- Duplicate business rules +- Repeat validation logic +- Hard-code the same values multiple times +- Create premature abstractions (see Rule of Three) + +### Rule of Three +Wait until you see duplication **three times** before abstracting. Two instances might be coincidental; three suggests a pattern. + +### AI Pitfalls +- Producing repeated code structures from pattern prediction +- Duplicating similar functions instead of parameterizing +- Repeating validation or error handling logic +- Not recognizing when to extract shared utilities + +--- + +## YAGNI (You Aren't Gonna Need It) + +**Definition:** Don't implement features or infrastructure until you actually need them. Avoid speculative development. + +**Supported by:** *The Pragmatic Programmer*, Extreme Programming (XP) + +### Examples + +```python +# Bad - Building for hypothetical futures +def process_order(order): + prepare_invoice(order) + apply_future_discount_system(order) # Not used yet + schedule_loyalty_rewards(order) # Not needed now + prepare_for_blockchain_audit(order) # Speculative + +# Good - Only what's needed now +def process_order(order): + prepare_invoice(order) + charge_payment(order) + ship_order(order) +``` + +```javascript +// Bad - Over-engineered configuration +class DatabaseConfig { + constructor() { + this.primaryHost = 'localhost'; + this.replicaHosts = []; // Not using replication + this.shardingStrategy = null; // Not sharding + this.cacheLayer = null; // No cache yet + } +} + +// Good - Current requirements only +class DatabaseConfig { + constructor(host) { + this.host = host; + } +} +``` + +### Do +- Write code for current, known requirements +- Add features when they're actually requested +- Keep infrastructure minimal +- Refactor when new needs emerge +- Trust that future changes will be manageable + +### Don't +- Build "just in case" features +- Create extensibility points without use cases +- Add configuration for hypothetical scenarios +- Implement features before they're specified + +### AI Pitfalls +- Generating code for unspecified future features +- Adding unnecessary configuration options +- Creating extensibility hooks without current need +- Building infrastructure beyond MVP scope + +--- + +## Premature Optimization + +**Definition:** Don't optimize until you have evidence of a performance problem. Clarity and correctness come first. + +**Supported by:** *The Pragmatic Programmer*, Donald Knuth's famous quote + +> "Premature optimization is the root of all evil" — Donald Knuth + +### Examples + +```python +# Bad - Premature optimization +def find_user(user_id): + # Using complex caching before knowing if it's needed + cache_key = f"user:{user_id}:v2" + if cache_key in cache: + return deserialize(decompress(cache[cache_key])) + user = db.query(user_id) + cache[cache_key] = compress(serialize(user)) + return user + +# Good - Start simple, optimize if needed +def find_user(user_id): + return db.query(user_id) + +# Later, if profiling shows this is slow: +def find_user(user_id): + cached = cache.get(f"user:{user_id}") + if cached: + return cached + user = db.query(user_id) + cache.set(f"user:{user_id}", user) + return user +``` + +### Do +- Write clear, correct code first +- Profile before optimizing +- Optimize only proven bottlenecks +- Measure impact of optimizations +- Document why optimizations were made + +### Don't +- Sacrifice readability for unmeasured performance +- Optimize without profiling data +- Use complex algorithms for small datasets +- Cache everything "just in case" + +### AI Pitfalls +- Adding caching layers without justification +- Using complex data structures for simple cases +- Micro-optimizing at the expense of clarity + +--- + +## Summary + +Simple code is: +- **Direct** - solves the problem at hand +- **DRY** - has no unnecessary duplication +- **Minimal** - contains only what's needed now +- **Clear** - prioritizes readability over premature optimization + +When writing code, ask: +- Is this the simplest approach that works? +- Am I repeating myself? +- Do I actually need this now? +- Am I optimizing based on evidence? diff --git a/src/plugins/design-patterns-skill/snippets/references/patterns/testing.txt b/src/plugins/design-patterns-skill/snippets/references/patterns/testing.txt new file mode 100755 index 0000000..6ddc615 --- /dev/null +++ b/src/plugins/design-patterns-skill/snippets/references/patterns/testing.txt @@ -0,0 +1,309 @@ +# Testing & Quality Principles + +## Write Automated Tests Early + +**Definition:** Use tests to guide design, prevent regressions, and validate behavior. Testing should be part of the development process, not an afterthought. + +**Supported by:** *Refactoring*, *The Pragmatic Programmer*, Test-Driven Development (TDD) + +### Examples + +```python +# Test-first approach +def test_calculate_discount(): + # Arrange + price = 100 + discount_percent = 10 + + # Act + result = calculate_discount(price, discount_percent) + + # Assert + assert result == 90 + +def calculate_discount(price, discount_percent): + return price * (1 - discount_percent / 100) +``` + +```python +# Test edge cases +def test_user_age_validation(): + assert is_adult(18) == True + assert is_adult(17) == False + assert is_adult(0) == False + assert is_adult(150) == True # No upper bound check yet + +def is_adult(age): + return age >= 18 +``` + +### Do +- Write tests before or alongside code +- Test edge cases and boundary conditions +- Test business logic thoroughly +- Use descriptive test names +- Keep tests fast and independent +- Use test fixtures and setup/teardown appropriately + +### Don't +- Skip tests for "simple" code +- Test implementation details instead of behavior +- Write brittle tests that break on refactoring +- Ignore failing tests +- Write tests that depend on external state + +### AI Pitfalls +- Missing tests entirely +- Writing overly broad test functions +- Not testing edge cases or error paths +- Creating tests with vague assertions + +--- + +## One Assert Per Test (Focus) + +**Definition:** Keep tests focused on a single behavior or scenario. This makes failures easy to diagnose. + +**Supported by:** *Clean Code*, TDD best practices + +### Examples + +```python +# Bad - Multiple unrelated assertions +def test_user(): + user = User("Alice", 25) + assert user.name == "Alice" + assert user.age == 25 + assert user.is_adult() == True + assert user.can_vote() == True + assert user.get_greeting() == "Hello, Alice" + +# Good - Focused tests +def test_user_name_is_set_correctly(): + user = User("Alice", 25) + assert user.name == "Alice" + +def test_user_age_is_set_correctly(): + user = User("Alice", 25) + assert user.age == 25 + +def test_user_is_adult_when_age_18_or_above(): + user = User("Alice", 25) + assert user.is_adult() == True + +def test_user_is_not_adult_when_age_below_18(): + user = User("Bob", 17) + assert user.is_adult() == False +``` + +### Guideline Exceptions + +Multiple assertions are acceptable when: +- Testing object state after a single operation +- Verifying related properties of one concept +- Testing list/collection contents + +```python +# Acceptable - Related assertions on same concept +def test_order_creation(): + order = Order(items=[item1, item2]) + assert len(order.items) == 2 + assert order.total == 50.00 + assert order.status == OrderStatus.PENDING +``` + +### Do +- Use test names to describe expected behavior +- Group related tests in test classes +- Use parametrized tests for similar scenarios +- Make test intent crystal clear + +### Don't +- Group many checks together +- Test multiple behaviors in one test +- Create generic test names like `test_user()` + +### AI Pitfalls +- Combining multiple assertions in one test function +- Creating catch-all test functions +- Not using descriptive test names + +--- + +## Test Coverage Guidelines + +**Definition:** Aim for meaningful coverage of critical paths, not just high percentages. Focus on business logic, edge cases, and failure modes. + +### What to Test + +**High Priority:** +- Business logic and algorithms +- Input validation and error handling +- State transitions +- Integration points +- Security-critical code + +**Medium Priority:** +- Data transformations +- Configuration handling +- User-facing features + +**Low Priority:** +- Trivial getters/setters +- Framework-generated code +- External library wrappers + +### Coverage Anti-Patterns + +```python +# Bad - Testing for coverage, not correctness +def test_add(): + add(2, 3) # No assertion! + +# Good - Test actual behavior +def test_add_returns_sum(): + result = add(2, 3) + assert result == 5 +``` + +### Do +- Focus on critical code paths +- Test public interfaces, not private methods +- Use code coverage as a guide, not a goal +- Write tests that catch real bugs + +### Don't +- Aim for 100% coverage blindly +- Test trivial code just for metrics +- Ignore untested critical paths + +--- + +## Test Pyramid + +**Definition:** Balance different types of tests - many unit tests, fewer integration tests, even fewer end-to-end tests. + +``` + /\ + / \ Few E2E tests (slow, brittle) + /____\ + / \ More integration tests (moderate speed) + /________\ + / \ Many unit tests (fast, isolated) +``` + +### Unit Tests +- Test individual functions/classes in isolation +- Fast execution (milliseconds) +- Mock external dependencies +- High count (hundreds to thousands) + +### Integration Tests +- Test interactions between components +- Moderate speed (seconds) +- Use real dependencies where practical +- Medium count (dozens to hundreds) + +### End-to-End Tests +- Test complete user workflows +- Slow execution (minutes) +- Test through actual UI/API +- Low count (handful to dozens) + +### Do +- Rely primarily on unit tests +- Use integration tests for critical paths +- Reserve E2E tests for key user journeys + +### Don't +- Over-rely on E2E tests +- Skip unit tests in favor of integration tests +- Test everything through the UI + +--- + +## Test Quality Checklist + +Good tests are: + +- **Fast** - Run in milliseconds +- **Isolated** - No shared state or order dependency +- **Repeatable** - Same result every time +- **Self-validating** - Pass/fail is clear +- **Timely** - Written close to code + +### Do +- Use test fixtures for setup +- Clean up resources in teardown +- Use meaningful test data +- Avoid test interdependence + +### Don't +- Rely on external services without mocks +- Use production data +- Write flaky tests +- Commit commented-out tests + +--- + +## Mocking & Test Doubles + +**Definition:** Use test doubles (mocks, stubs, fakes) to isolate the code under test. + +### Types of Test Doubles + +**Stub** - Returns canned responses +```python +class StubPaymentGateway: + def charge(self, amount): + return {"status": "success", "transaction_id": "123"} +``` + +**Mock** - Verifies interactions +```python +def test_order_charges_payment(): + mock_gateway = Mock() + processor = OrderProcessor(mock_gateway) + processor.process(order) + mock_gateway.charge.assert_called_once_with(100.00) +``` + +**Fake** - Simplified working implementation +```python +class FakeDatabase: + def __init__(self): + self.data = {} + + def save(self, key, value): + self.data[key] = value + + def get(self, key): + return self.data.get(key) +``` + +### Do +- Mock external dependencies (APIs, databases, file systems) +- Use dependency injection to enable mocking +- Verify behavior, not implementation +- Keep mocks simple + +### Don't +- Mock everything (test real code when possible) +- Create complex mock hierarchies +- Over-specify mock expectations + +--- + +## Summary + +Effective testing: +- **Guides design** - Tests drive better architecture +- **Prevents regressions** - Catches bugs early +- **Documents behavior** - Tests are living specifications +- **Enables refactoring** - Confidence to improve code + +When writing tests, ask: +- Does this test verify actual behavior? +- Will this test catch real bugs? +- Is this test easy to understand and maintain? +- Can this test run quickly and reliably? diff --git a/src/plugins/engineering-discipline/index.ts b/src/plugins/engineering-discipline/index.ts new file mode 100644 index 0000000..8c9e80a --- /dev/null +++ b/src/plugins/engineering-discipline/index.ts @@ -0,0 +1,3 @@ +import { createStaticSkillPlugin } from "../../shared/static-skill.js"; + +export const engineeringDisciplinePlugin = createStaticSkillPlugin("engineering-discipline", "The engineering-discipline skill."); diff --git a/src/plugins/engineering-discipline/snippets/SKILL.txt b/src/plugins/engineering-discipline/snippets/SKILL.txt new file mode 100755 index 0000000..eb0465a --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/SKILL.txt @@ -0,0 +1,157 @@ +--- +name: engineering-discipline +description: Apply senior-level software engineering discipline including design patterns, SOLID principles, architectural reasoning, systematic verification, and safety gates. Use when writing production code, complex features, reviewing code, refactoring systems, or when engineering rigor and correctness are required. Supports both quick reference lookup and full step-by-step process mode. +triggers: + - "build production code" + - "design architecture" + - "code review" + - "refactor" + - "engineering rigor" + - "production feature" + - "complex system" + - "safety gates" + - "design pattern" + - "SOLID principles" +activation: + mode: fuzzy + priority: normal + triggers: + - "build production code" + - "design architecture" + - "code review" + - "refactor" + - "engineering rigor" + - "production feature" + - "complex system" + - "safety gates" + - "design pattern" + - "SOLID principles" +compatibility: ">=2.0.0" +metadata: + version: "2.0.0" +references: + - references/patterns/readability.md + - references/patterns/simplicity.md + - references/patterns/design-architecture.md + - references/patterns/testing.md + - references/patterns/error-handling.md + - references/patterns/maintainability.md + - references/architecture/task-classification.md + - references/architecture/architecture-reasoning.md + - references/architecture/verification-gates.md + - references/architecture/negative-doubt.md + - references/architecture/output-format.md +--- + +# Engineering Discipline - Senior SDE-3 Framework + +## Overview + +Two operating modes: +- **Quick Reference**: Direct lookup of patterns, principles, naming conventions +- **Process Mode**: Full 8-step engineering workflow for complex/production features + +--- + +## Core Philosophy + +**You are not an autocomplete engine. You are an engineering constraint solver.** + +- **Correctness over speed** — Preserve invariants, prevent bugs +- **Architecture over syntax** — Think in layers before coding +- **Long-term maintainability** — Optimize for change velocity +- **Explicit over implicit** — No hidden assumptions +- **Tests lock behavior** — No refactor without tests +- **Patterns require justification** — No pattern without named force + +--- + +## Quick Reference Index + +### Patterns & Principles +- **Readability & Clarity** → `references/patterns/readability.md` +- **Simplicity & Efficiency** → `references/patterns/simplicity.md` +- **Design & Architecture** → `references/patterns/design-architecture.md` +- **Testing & Quality** → `references/patterns/testing.md` +- **Error Handling** → `references/patterns/error-handling.md` +- **Maintainability** → `references/patterns/maintainability.md` + +### Architecture & Process +- **Task Classification** → `references/architecture/task-classification.md` +- **Architecture Reasoning** → `references/architecture/architecture-reasoning.md` +- **Verification Gates** → `references/architecture/verification-gates.md` +- **Negative Doubt Bias** → `references/architecture/negative-doubt.md` +- **Standard Output Format** → `references/architecture/output-format.md` + +--- + +## Process Mode: 8-Step Framework + +### Step 0: Environment Gate ⛔ +Verify runtime, package manager, dependencies. **Do NOT proceed without valid environment.** + +### Step 1: Task Classification 🏷️ +Classify as exactly one: New feature | Refactor (behavior preserved) | Bug fix | Review/audit | Documentation only. +**If unclear → STOP and request clarification.** + +### Step 2: Load Engineering Constraints 📋 +Hard rules: clear naming, single responsibility, explicit module boundaries, no circular dependencies, folder structure reflects architecture, tests before refactor, YAGNI, patterns only when forces are named. + +### Step 3: Architecture-First Reasoning 🏗️ +Reason in strict order: +1. Responsibilities → 2. Invariants → 3. Dependency Direction → 4. Module Boundaries → 5. Public APIs → 6. Folder Structure → 7. Files → 8. Functions → 9. Syntax + +**Never skip layers. If you start at syntax, you'll build wrong.** + +### Step 4: Behavior & Invariants 🔒 +State observable behavior, invariants (input, state, ordering), and public vs private APIs. +**If refactoring and behavior not test-locked → STOP.** + +### Step 5: Pattern Gate 🚧 +Use design pattern **only if**: force is stated, invariant it protects is stated, simpler alternatives rejected. +**No force → no pattern.** + +### Step 6: Code Generation Rules ⚙️ +- Prefer deletion over abstraction +- No `utils/`, `common/`, `shared/` without ownership +- One reason to change per file +- Explicit public API per module +- Flat over deep structures +- No global state without justification + +### Step 7: Tests Are Part of Output 🧪 +If behavior exists: tests must exist, tests define invariants, refactors require tests first. +**No tests → no refactor.** + +### Step 8: Negative Doubt Routine 🔍 +Self-verification: (1) list 5 failure modes, (2) falsify assumptions, (3) verify invariants enforced, (4) audit dependencies, (5) try simpler alternative, (6) add failure-mode tests, (7) revise if issues found, (8) log findings. +**If critical issue unaddressed → HARD STOP.** + +--- + +## When to Use Each Section + +| Situation | Reference | +|-----------|-----------| +| Need pattern advice | `references/patterns/` | +| Building complex feature | Full Process Mode (Steps 0–8) | +| Quick naming question | `references/patterns/readability.md` | +| Refactoring code | Process Mode + `references/patterns/maintainability.md` | +| Code review | Process Mode Step 4 + `references/patterns/testing.md` | +| Error handling unclear | `references/patterns/error-handling.md` | +| Architecture decisions | `references/architecture/architecture-reasoning.md` | +| Standard response format | `references/architecture/output-format.md` | + +--- + +## Critical Reminders + +**Non-Negotiable Rules:** +1. ⛔ No refactor without tests +2. ⛔ No pattern without named force +3. ⛔ No circular dependencies +4. ⛔ No assumptions without disclosure +5. ⛔ No global state without justification +6. ⛔ No proceeding with ambiguous requirements + +**If something cannot be done safely → Say so and explain why.** diff --git a/src/plugins/engineering-discipline/snippets/references/architecture/architecture-reasoning.txt b/src/plugins/engineering-discipline/snippets/references/architecture/architecture-reasoning.txt new file mode 100755 index 0000000..f23d14d --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/architecture/architecture-reasoning.txt @@ -0,0 +1,374 @@ +# Architecture-First Reasoning + +## Core Principle + +**Think in layers of abstraction before writing syntax.** + +Code is the final output of architectural decisions, not the starting point. + +## The Reasoning Ladder (Never Skip Steps) + +### 1. Responsibilities + +**Question:** What does this component *do*? + +Define in terms of: +- Domain concepts (User, Order, Payment) +- Actions (authenticate, validate, process) +- Boundaries (what it does NOT do) + +**Example:** +``` +UserService responsibilities: +✓ Create user accounts +✓ Authenticate users +✓ Update user profiles +✗ Send emails (NotificationService) +✗ Store data (UserRepository) +``` + +**Anti-Pattern:** Defining by implementation ("uses database", "calls API") + +### 2. Invariants + +**Question:** What must *always* be true? + +**Types of Invariants:** + +**State Invariants:** +```python +# User age must be 0-150 +assert 0 <= user.age <= 150 + +# Order total must equal sum of items +assert order.total == sum(item.price * item.qty for item in order.items) +``` + +**Ordering Invariants:** +```python +# Payment must occur before shipping +assert order.payment_status == 'paid' before order.ship() +``` + +**Input Invariants:** +```python +# Email must contain @ symbol +assert '@' in email +``` + +**Document Early:** Write invariants before code. + +### 3. Dependency Direction + +**Question:** What depends on what? + +**Rules:** +- High-level modules depend on abstractions, not implementations +- Dependencies point inward (toward domain core) +- No circular dependencies + +**Dependency Graph Example:** +``` +Controller → Service → Repository + ↓ + DTO/Model + +NOT: +Repository → Service (wrong direction) +Service ←→ Controller (circular) +``` + +**Test:** Can you replace a dependency without changing dependents? + +### 4. Module Boundaries + +**Question:** What is public vs private? + +**Public API:** +- Exported functions/classes +- Documented contracts +- Stable interfaces + +**Private Implementation:** +- Internal helpers +- Implementation details +- Subject to change + +**Example:** +```python +# user_service.py (public API) +def create_user(name: str, email: str) -> User: + """Public: Create a new user""" + _validate_email(email) # private + return _persist_user(name, email) # private + +# Private helpers (not exported) +def _validate_email(email: str): + ... + +def _persist_user(name: str, email: str): + ... +``` + +### 5. Public APIs + +**Question:** How do consumers interact with this? + +**API Design Checklist:** +- [ ] Clear function names (verbs for actions) +- [ ] Typed parameters (what goes in) +- [ ] Typed returns (what comes out) +- [ ] Error cases documented +- [ ] Idempotency specified (if relevant) +- [ ] Side effects stated + +**Example:** +```python +def transfer_funds( + from_account: AccountId, + to_account: AccountId, + amount: Decimal +) -> TransferResult: + """ + Transfer funds between accounts. + + Returns: + TransferResult with transaction ID + + Raises: + InsufficientFundsError: If from_account balance < amount + AccountNotFoundError: If either account doesn't exist + + Side Effects: + - Debits from_account + - Credits to_account + - Creates transaction record + + Idempotency: Safe to retry with same parameters + """ +``` + +### 6. Folder Structure + +**Question:** How is code organized on disk? + +**Principles:** +- Structure reflects architecture +- Co-locate related files +- Separate by layer or domain (choose one) + +**By Layer:** +``` +src/ +├── controllers/ +├── services/ +├── repositories/ +└── models/ +``` + +**By Domain (Preferred for larger systems):** +``` +src/ +├── users/ +│ ├── user_controller.py +│ ├── user_service.py +│ ├── user_repository.py +│ └── user_model.py +├── orders/ +│ ├── order_controller.py +│ ├── order_service.py +│ └── ... +``` + +**Anti-Patterns:** +- `utils/` (too vague) +- `common/` (unclear ownership) +- `shared/` (becomes dumping ground) + +If you need shared code: +- Create specific modules: `validation/`, `auth/`, `formatting/` + +### 7. Files + +**Question:** What goes in each file? + +**One Reason to Change:** +``` +✓ user_repository.py (changes when data access changes) +✓ user_validator.py (changes when validation rules change) + +✗ user_helpers.py (changes for multiple unrelated reasons) +``` + +**Naming:** +- Nouns for classes: `UserRepository`, `PaymentProcessor` +- Verbs for modules: `authenticate.py`, `validate.py` + +### 8. Functions + +**Question:** What are the atomic operations? + +**Function Design:** +- Single responsibility +- One level of abstraction +- Clear inputs/outputs +- Minimal side effects + +**Abstraction Levels:** +```python +# High level (orchestration) +def process_order(order): + validate_order(order) + charge_payment(order) + ship_order(order) + send_confirmation(order) + +# Mid level (business logic) +def validate_order(order): + check_inventory(order.items) + verify_address(order.shipping_address) + +# Low level (implementation) +def check_inventory(items): + for item in items: + if stock[item.id] < item.quantity: + raise OutOfStockError(item) +``` + +**Don't mix levels:** +```python +# Bad - mixing levels +def process_order(order): + # High level + validate_order(order) + # Low level - doesn't belong here + for item in order.items: + if stock[item.id] < item.quantity: + raise OutOfStockError(item) +``` + +### 9. Syntax + +**Question:** How is this expressed in code? + +**Only now** do you write actual implementation. + +If you start here, you'll build the wrong thing correctly. + +## Reasoning Example: Payment System + +**1. Responsibilities** +- PaymentService: Process payments +- PaymentGateway: Communicate with external processor +- PaymentRepository: Store payment records + +**2. Invariants** +- Payment amount must be positive +- Payment must have valid payment method +- Payment cannot be processed twice (idempotency) + +**3. Dependency Direction** +``` +PaymentController → PaymentService → PaymentGateway (interface) + → PaymentRepository +``` + +**4. Module Boundaries** +- Public: `PaymentService.process_payment()` +- Private: Gateway communication details, retry logic + +**5. Public API** +```python +def process_payment( + amount: Decimal, + payment_method: PaymentMethod +) -> PaymentResult +``` + +**6. Folder Structure** +``` +payments/ +├── payment_service.py +├── payment_gateway.py +├── payment_repository.py +└── payment_models.py +``` + +**7. Files** +- `payment_service.py` - Business logic +- `payment_gateway.py` - External integration +- `payment_repository.py` - Data persistence + +**8. Functions** +```python +def process_payment(...) +def validate_payment_method(...) +def charge_payment_gateway(...) +def record_payment(...) +``` + +**9. Syntax** +*Now* write the Python code. + +## Forcing Function: Can You Answer These? + +Before writing code, answer: + +1. **What responsibilities does each component have?** +2. **What invariants must hold?** +3. **What depends on what? (Draw the graph)** +4. **What's public vs private?** +5. **How do consumers use this?** +6. **Where does this live in the folder structure?** +7. **What files exist and why?** +8. **What functions are needed?** + +If any answer is "I don't know" → **Stop. Don't guess.** + +## Anti-Pattern: Bottom-Up Thinking + +```python +# Wrong: Starting with syntax +def process_payment(amount, method): + # Wait, what should this do? + # Who calls this? + # What if amount is negative? + # Where does this go? +``` + +## Correct: Top-Down Thinking + +1. Responsibility: Process payment transactions +2. Invariant: amount > 0, method is valid +3. Depends on: PaymentGateway, PaymentRepository +4. Public API: `process_payment(amount, method) -> result` +5. Returns: Success/failure with transaction ID +6. Now write the code + +## Verification Questions + +After designing, ask: + +- **Can I explain this to a team member?** +- **Are dependencies testable/mockable?** +- **Can I change implementation without breaking consumers?** +- **Is the folder structure obvious?** +- **Would a new developer know where to add features?** + +If "no" to any → redesign before coding. + +## Summary + +**Architecture First = Correctness** + +Syntax is cheap. Wrong architecture is expensive. + +Spend time thinking before writing. + +**Ladder Enforcement:** +``` +Responsibilities → Invariants → Dependencies → Boundaries → APIs → +Folders → Files → Functions → Syntax +``` + +Skip a step = wrong design. diff --git a/src/plugins/engineering-discipline/snippets/references/architecture/negative-doubt.txt b/src/plugins/engineering-discipline/snippets/references/architecture/negative-doubt.txt new file mode 100755 index 0000000..1b95ce1 --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/architecture/negative-doubt.txt @@ -0,0 +1,427 @@ +# Negative Doubt Bias (Self-Verification) + +## Purpose + +**Actively seek ways the solution can fail** before finalizing. + +This is a systematic routine to find bugs, edge cases, and flaws that optimistic reasoning misses. + +## When to Run + +**After producing any candidate solution, before finalizing.** + +This applies to: +- Architecture designs +- Code implementations +- Refactoring plans +- API designs + +## The Routine (8 Steps) + +### Step 1: Fail-Seeking Pass + +**Goal:** List concrete failure modes. + +**Process:** +Generate 5 ways the solution can fail: +1. **Bugs** - Logic errors, off-by-one, null pointers +2. **Edge Cases** - Empty input, max values, special characters +3. **Performance** - O(n²) when n is large, memory leaks +4. **Security** - SQL injection, XSS, unauthorized access +5. **Maintainability** - Hard to change, tight coupling, unclear intent + +**For each failure mode, produce a minimal counterexample:** + +```python +# Solution: Calculate average +def average(numbers): + return sum(numbers) / len(numbers) + +# Fail-seeking: +# 1. Empty list → ZeroDivisionError +# 2. Non-numeric values → TypeError +# 3. Very large list → Memory overflow (unlikely but possible) +# 4. List with None values → TypeError in sum() +# 5. Integer division issues (Python 2) → Not applicable in Python 3 + +# Counterexamples: +average([]) # Fails: ZeroDivisionError +average([1, "two"]) # Fails: TypeError +average([1, None, 3]) # Fails: TypeError +``` + +### Step 2: Assumption Falsification + +**Goal:** Challenge every assumption. + +**Process:** +For each assumption: +1. Identify the assumption +2. Try to falsify it mentally +3. Find input/ordering/environment that breaks it + +**Example:** + +```python +def process_user(user): + """ + Assumptions: + 1. user is not None + 2. user.email is a string + 3. user.email contains '@' + 4. Database is available + """ + +# Falsification: +# Assumption 1: What if user=None? → Add guard +# Assumption 2: What if user.email=123? → Add type check +# Assumption 3: What if email="invalid"? → Add validation +# Assumption 4: What if DB is down? → Add retry/error handling + +# Updated code: +def process_user(user): + if user is None: + raise ValueError("User cannot be None") + if not isinstance(user.email, str): + raise TypeError("Email must be string") + if '@' not in user.email: + raise ValueError("Invalid email format") + + try: + save_to_db(user) + except DatabaseError as e: + log_error(e) + raise ProcessingError("Failed to save user") from e +``` + +### Step 3: Invariant Check + +**Goal:** Verify invariants are enforced. + +**Process:** +For each declared invariant: +1. Check if code enforces it +2. Add guards if missing +3. Add tests to verify + +**Example:** + +```python +# Invariant: Order total must equal sum of item prices + +class Order: + def __init__(self, items): + self.items = items + self.total = self._calculate_total() + + def _calculate_total(self): + return sum(item.price * item.quantity for item in self.items) + + def add_item(self, item): + self.items.append(item) + self.total = self._calculate_total() # Enforce invariant + + def validate(self): + # Runtime check + expected = sum(item.price * item.quantity for item in self.items) + if self.total != expected: + raise InvariantViolation("Order total mismatch") +``` + +**Test invariant:** +```python +def test_order_total_matches_items(): + order = Order([Item(price=10, quantity=2)]) + assert order.total == 20 + + order.add_item(Item(price=5, quantity=1)) + assert order.total == 25 # Invariant still holds +``` + +### Step 4: Dependency & Boundary Audit + +**Goal:** Verify clean architecture. + +**Checklist:** +- [ ] No circular dependencies introduced +- [ ] Module public surface is minimal +- [ ] Private internals not exposed +- [ ] Dependencies point in correct direction + +**Process:** + +**Draw dependency graph:** +``` +Module A → Module B + → Module C +Module C → Module D + +Any cycles? (e.g., B → A) +If YES → Refactor to break cycle +``` + +**Check public API:** +```python +# user_service.py + +# Public (should be exported) +def create_user(...): pass +def get_user(...): pass + +# Private (should NOT be exported) +def _validate_email(...): pass +def _hash_password(...): pass +``` + +**If consumers reach internals:** +```python +# WRONG: Consumer importing private function +from user_service import _validate_email + +# RIGHT: Add to public API or create facade +from validation import validate_email # Moved to proper module +``` + +### Step 5: Simpler-Alternative Challenge + +**Goal:** Find simpler solution that preserves behavior. + +**Process:** +1. Attempt to rewrite in 2-5 lines +2. If successful and acceptable, prefer it +3. If not, document why complexity is needed + +**Example:** + +```python +# Original (10 lines) +class UserValidator: + def __init__(self): + self.rules = [] + + def add_rule(self, rule): + self.rules.append(rule) + + def validate(self, user): + for rule in self.rules: + rule.check(user) + +# Simpler alternative (2 lines) +def validate_user(user, rules): + for rule in rules: + rule.check(user) + +# Decision: Use simpler version unless: +# - Need to cache rules +# - Need to add/remove rules dynamically +# - Need stateful validation +``` + +**Document if keeping complexity:** +```python +# Using class instead of function because: +# - Rules need to be cached and reused +# - Validators compose with other validators +# - Need to maintain validation state across calls +``` + +### Step 6: Test Injection + +**Goal:** Add tests for each failure mode. + +**Process:** +For each failure mode from Step 1: +1. Write test that would fail before fix +2. Verify test fails +3. Fix code +4. Verify test passes + +**Example:** + +```python +# Failure mode: Empty list causes ZeroDivisionError + +# Test (should fail initially) +def test_average_empty_list(): + with pytest.raises(ValueError): + average([]) + +# Fix code +def average(numbers): + if not numbers: + raise ValueError("Cannot calculate average of empty list") + return sum(numbers) / len(numbers) + +# Now test passes +``` + +### Step 7: Decision Revision + +**Goal:** Update design based on findings. + +**Process:** +If Steps 1-6 found issues: +1. Update architecture +2. Update code +3. Update tests +4. **Run routine again** (one more iteration) + +**Stopping Condition:** +- Routine finds no new issues, OR +- Issues are documented as known limitations + +### Step 8: Negative Doubt Log + +**Goal:** Document verification process. + +**Template:** + +```markdown +## Negative Doubt Log + +### Failure Modes Discovered +1. Empty input causes ZeroDivisionError +2. Non-numeric values cause TypeError +3. Large lists may cause memory issues + +### Tests Added +- test_average_empty_list() +- test_average_non_numeric() +- test_average_large_list() (performance test) + +### Assumptions Changed +Before: Assumed input is always non-empty +After: Explicitly validate input or document precondition + +### Design Changes +- Added input validation +- Added error handling +- Added defensive checks + +### Remaining Issues +- Very large lists (>10M items) may be slow + Decision: Document as known limitation, optimize if needed + +### Final Status +✓ All critical issues addressed +⚠ Performance limitation documented +``` + +--- + +## Example: Complete Negative Doubt Routine + +**Original Code:** +```python +def transfer_funds(from_account, to_account, amount): + from_account.balance -= amount + to_account.balance += amount +``` + +**Step 1: Fail-Seeking** +1. Insufficient funds → Negative balance +2. Concurrent transfers → Race condition +3. Non-existent accounts → AttributeError +4. Negative amount → Allows theft +5. Same account transfer → No-op but still processes + +**Step 2: Assumption Falsification** +- Assumption: from_account.balance >= amount + Falsified: What if balance < amount? +- Assumption: Accounts exist + Falsified: What if from_account is None? + +**Step 3: Invariant Check** +- Invariant: Total money in system stays constant + Enforcement: Missing! Need transaction or rollback + +**Step 4: Dependency Audit** +- Direct balance manipulation → Breaks encapsulation +- Should use account methods + +**Step 5: Simpler Alternative** +```python +# No simpler alternative exists while preserving safety +# Complexity needed for atomicity and validation +``` + +**Step 6: Test Injection** +```python +def test_insufficient_funds(): + account = Account(balance=50) + with pytest.raises(InsufficientFundsError): + transfer_funds(account, other, 100) + +def test_negative_amount(): + with pytest.raises(ValueError): + transfer_funds(from_acc, to_acc, -50) +``` + +**Step 7: Revised Design** +```python +def transfer_funds(from_account, to_account, amount): + if from_account is None or to_account is None: + raise ValueError("Accounts cannot be None") + if amount <= 0: + raise ValueError("Amount must be positive") + if from_account == to_account: + return # No-op + if from_account.balance < amount: + raise InsufficientFundsError() + + # Atomic transaction + with transaction(): + from_account.withdraw(amount) + to_account.deposit(amount) +``` + +**Step 8: Negative Doubt Log** +``` +Failure Modes: 5 found, all addressed +Tests Added: 6 new tests +Assumptions Changed: Added explicit guards +Design Changes: Added transaction support, validation +Final Status: ✓ Ready +``` + +--- + +## Hard Stop Condition + +**If any critical issue remains unaddressed:** + +**DO NOT finalize.** + +Return: +1. Revised design +2. Unmet requirements +3. Why they're unmet +4. What's needed to address them + +**Example:** +``` +HARD STOP + +Issue: Race condition in concurrent transfers +Status: NOT ADDRESSED +Reason: Requires database-level locking or transaction isolation +Needed: Database transaction support or pessimistic locking +Recommendation: Do not deploy without addressing +``` + +--- + +## Summary + +**Negative Doubt Bias is NOT optional.** + +**Default stance:** Your solution has bugs. Find them. + +Run this routine: +1. After every design +2. After every implementation +3. Before every review + +**It's faster to find bugs now than in production.** + +Pessimism in development = Reliability in production. diff --git a/src/plugins/engineering-discipline/snippets/references/architecture/output-format.txt b/src/plugins/engineering-discipline/snippets/references/architecture/output-format.txt new file mode 100755 index 0000000..c7e706f --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/architecture/output-format.txt @@ -0,0 +1,49 @@ +# Standard Output Format + +Always structure Process Mode responses as: + +```markdown +## 1. Task Classification +[Feature | Refactor | Bug | Review | Docs] + +## 2. Assumptions +- Input assumptions +- State assumptions +- Environment assumptions + +## 3. Architecture / Design +- Responsibilities +- Invariants +- Dependencies +- Module boundaries + +## 4. Public APIs +- Function signatures +- Input/output contracts +- Error cases + +## 5. Code +[Implementation] + +## 6. Tests +[Test cases covering happy path, edge cases, errors] + +## 7. Risks & Trade-offs +- What can fail +- Performance considerations +- Maintainability impact + +## 8. Negative Doubt Log (Process Mode only) +- Failure modes discovered +- Tests added +- Assumptions changed +- Final decision changes +``` + +## Quick Reference Mode Format + +For direct pattern/principle lookups, respond concisely: +- State the answer directly +- Reference the relevant principle file +- Provide 2–3 supporting points +- Note trade-offs if relevant diff --git a/src/plugins/engineering-discipline/snippets/references/architecture/task-classification.txt b/src/plugins/engineering-discipline/snippets/references/architecture/task-classification.txt new file mode 100755 index 0000000..c3ecdbe --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/architecture/task-classification.txt @@ -0,0 +1,129 @@ +# Task Classification Framework + +## Purpose + +Before writing any code, classify the task to determine the appropriate engineering approach. This prevents mismatched solutions and establishes clear success criteria. + +## Classification Categories + +### 1. New Feature + +**Characteristics:** +- Adds new capability to the system +- Introduces new entry points or APIs +- May require new dependencies or infrastructure + +**Checklist:** +- [ ] Requirements clearly defined +- [ ] Success criteria established +- [ ] Dependencies identified +- [ ] Architecture impacts assessed +- [ ] Test strategy defined + +**Process:** +1. Define public API first +2. Identify module boundaries +3. Plan dependency direction +4. Design data flow +5. Implement with tests +6. Document behavior + +--- + +### 2. Refactor (Behavior Preserved) + +**Characteristics:** +- Changes internal structure +- **Zero** behavior change +- Improves maintainability, readability, or performance +- Must have tests locking existing behavior + +**Critical Rule:** No refactor without tests. If tests don't exist, the first step is writing them, not refactoring. + +**Process:** +1. **Verify tests exist** - If no tests, write them first +2. Make small, safe changes +3. Run tests after each change +4. Commit incrementally +5. Verify no behavior change + +--- + +### 3. Bug Fix + +**Characteristics:** +- Corrects incorrect behavior +- Has observable failure mode +- Should have reproduction test +- May require root cause analysis + +**Process:** +1. Write failing test that reproduces bug +2. Identify root cause (not just symptom) +3. Implement minimal fix +4. Verify test now passes +5. Check for similar bugs elsewhere +6. Document root cause and fix + +--- + +### 4. Review / Audit + +**Characteristics:** +- Evaluates existing code +- Identifies issues, risks, or improvements +- Does not modify code +- Produces recommendations + +**Process:** +1. Define review scope and criteria +2. Check against engineering principles +3. Identify risks and violations +4. Assess severity and impact +5. Provide prioritized recommendations +6. Suggest concrete improvements + +--- + +### 5. Documentation Only + +**Process:** +1. Identify what needs documentation +2. Determine appropriate level of detail +3. Use examples liberally +4. Link to related docs +5. Keep close to code +6. Verify accuracy + +--- + +## Classification Decision Tree + +``` +Is code changing? +├─ No → Documentation Only +└─ Yes + ├─ Does it add new capability? + │ └─ Yes → New Feature + └─ No + ├─ Does it fix incorrect behavior? + │ └─ Yes → Bug Fix + └─ No + └─ Does it change structure without behavior change? + └─ Yes → Refactor +``` + +## When Classification is Unclear + +**Stop and clarify:** +- Request missing requirements +- Ask about success criteria +- Identify ambiguities +- Confirm behavior expectations +- Define scope boundaries + +**Never proceed with unclear classification.** + +## Golden Rule + +**If you can't classify it, don't start it.** diff --git a/src/plugins/engineering-discipline/snippets/references/architecture/verification-gates.txt b/src/plugins/engineering-discipline/snippets/references/architecture/verification-gates.txt new file mode 100755 index 0000000..d81ae37 --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/architecture/verification-gates.txt @@ -0,0 +1,484 @@ +# Verification Gates & Safety Checks + +## Purpose + +Systematic checkpoints that prevent common engineering failures. Each gate must pass before proceeding. + +## Gate 0: Environment Verification + +**Before any reasoning or code generation.** + +### Checklist +- [ ] Runtime/language version specified +- [ ] Package manager identified +- [ ] Dependencies listed +- [ ] Build tool available +- [ ] Test framework present + +### Actions + +**If missing:** +```bash +# Example: Python project +python --version # Check runtime +pip list # Check installed packages +cat requirements.txt # Check dependencies +pytest --version # Check test framework +``` + +**Do NOT proceed without:** +1. Verified runtime environment +2. Required dependencies installed or listed +3. Build/test tools available + +**Output:** +``` +Environment Status: +✓ Python 3.11 +✓ Dependencies: requirements.txt present +✓ Test framework: pytest installed +✓ Proceed: YES +``` + +--- + +## Gate 1: Behavior Lock (Refactors Only) + +**Trigger:** Any refactoring task + +### Rule + +**NO REFACTOR WITHOUT PASSING TESTS** + +### Verification + +```python +# Step 1: Run existing tests +pytest tests/ + +# Step 2: Verify they pass +# All tests green? → Proceed +# Tests fail? → Fix first, then refactor +# No tests? → Write tests first +``` + +### If No Tests Exist + +**Write tests that lock current behavior:** + +```python +# Test current behavior (even if ugly) +def test_current_user_creation(): + # Lock the current behavior + user = create_user("Alice", "alice@example.com") + assert user.name == "Alice" + assert user.email == "alice@example.com" + assert user.created_at is not None +``` + +**Then refactor:** +```python +# Refactor internals +# Tests still pass? Good. +# Tests fail? Rollback and fix. +``` + +### Anti-Pattern + +```python +# WRONG: Refactoring without tests +"Let me clean this up..." +# Changes internal structure +# No tests to verify behavior preserved +# Introduces bugs silently +``` + +--- + +## Gate 2: Pattern Justification + +**Trigger:** Using any design pattern + +### Rule + +**Use pattern ONLY if:** +1. The force it resolves is named +2. The invariant it protects is stated +3. Simpler alternatives were rejected + +### Template + +``` +Pattern: [Factory | Strategy | Observer | etc.] + +Force Resolved: +- [Specific problem this solves] + +Invariant Protected: +- [What must remain true] + +Alternatives Rejected: +- Simple function: [Why insufficient] +- [Other alternative]: [Why insufficient] + +Justification: +- [Why this pattern is necessary] +``` + +### Example: Factory Pattern + +``` +Pattern: Factory + +Force Resolved: +- Object creation logic varies by user type (free, premium, enterprise) +- Don't want to expose creation complexity to clients + +Invariant Protected: +- All users must have valid email before creation +- Premium users must have payment method + +Alternatives Rejected: +- Simple constructor: Insufficient - creation logic too complex +- Multiple constructors: Insufficient - doesn't enforce validation order + +Justification: +- Factory centralizes validation and encapsulates creation complexity +``` + +### No Force → No Pattern + +```python +# WRONG: Pattern without justification +class UserFactory: # Why factory? + def create(self, name): + return User(name) # Just a wrapper + +# RIGHT: Simple function +def create_user(name): + return User(name) +``` + +--- + +## Gate 3: Circular Dependency Check + +**Trigger:** Adding new dependencies + +### Verification + +**Draw dependency graph:** +``` +A → B → C + ↓ + D + +Is there a cycle? (A → B → ... → A) +If YES → STOP, redesign +If NO → Proceed +``` + +### Detection + +```python +# WRONG: Circular dependency +# user_service.py +from order_service import OrderService + +class UserService: + def get_user_orders(self, user_id): + return OrderService().get_orders(user_id) + +# order_service.py +from user_service import UserService # CIRCULAR! + +class OrderService: + def get_user_for_order(self, order_id): + return UserService().get_user(...) +``` + +### Fix Strategies + +**1. Extract common interface:** +```python +# models.py +class User: + pass + +class Order: + pass + +# user_service.py (depends on models) +# order_service.py (depends on models) +# No circular dependency +``` + +**2. Dependency Inversion:** +```python +# Define interface in high-level module +class IOrderRepository: + def get_orders(self, user_id): pass + +# Inject dependency +class UserService: + def __init__(self, order_repo: IOrderRepository): + self.orders = order_repo +``` + +--- + +## Gate 4: Public API Minimization + +**Trigger:** Before finalizing module interface + +### Rule + +**Expose only what's necessary.** + +### Checklist + +- [ ] Every public function has clear purpose +- [ ] Private helpers are actually private +- [ ] No implementation leakage +- [ ] Public API is documented + +### Example + +```python +# user_service.py + +# Public API (exported) +def create_user(name: str, email: str) -> User: + """Create a new user account.""" + _validate_email(email) + return _save_user(name, email) + +# Private (not exported) +def _validate_email(email: str): + if '@' not in email: + raise ValueError("Invalid email") + +def _save_user(name: str, email: str): + # Implementation detail + pass +``` + +### Anti-Pattern: Everything Public + +```python +# WRONG: Exposing internals +class UserService: + def create_user(self): pass + def validate_email(self): pass # Should be private + def save_to_db(self): pass # Should be private + def format_name(self): pass # Should be private +``` + +--- + +## Gate 5: Assumption Disclosure + +**Trigger:** Before finalizing any design/code + +### Rule + +**All assumptions must be explicit.** + +### Categories + +**Input Assumptions:** +```python +def calculate_discount(price: float) -> float: + """ + Assumptions: + - price is positive + - price is in USD + - customer is authenticated + """ +``` + +**State Assumptions:** +```python +def ship_order(order: Order): + """ + Assumptions: + - order.payment_status == 'paid' + - order.items is non-empty + - order.shipping_address is valid + """ +``` + +**Ordering Assumptions:** +```python +def finalize_checkout(): + """ + Assumptions: + - validate_cart() called first + - process_payment() called before this + """ +``` + +**Environment Assumptions:** +```python +def upload_to_s3(file_path: str): + """ + Assumptions: + - AWS credentials configured + - S3 bucket exists + - Network connectivity available + """ +``` + +### Verification + +Turn assumptions into **guards or tests:** + +```python +def calculate_discount(price: float) -> float: + # Guard against assumptions + if price <= 0: + raise ValueError("Price must be positive") + if not is_authenticated(): + raise AuthError("User must be authenticated") + + return price * 0.9 +``` + +--- + +## Gate 6: Test Coverage + +**Trigger:** Before marking code complete + +### Requirements + +**For each function:** +- [ ] Happy path tested +- [ ] Edge cases tested +- [ ] Error cases tested + +### Edge Cases to Test + +```python +def divide(a: float, b: float) -> float: + return a / b + +# Tests needed: +# - Normal case: divide(10, 2) == 5 +# - Zero divisor: divide(10, 0) → raises error +# - Negative numbers: divide(-10, 2) == -5 +# - Very large numbers: divide(1e100, 1e50) +# - Very small numbers: divide(0.0001, 0.0002) +``` + +### Coverage is NOT Enough + +```python +# 100% coverage, but bad test +def test_add(): + add(2, 3) # No assertion! Test passes but verifies nothing +``` + +**Good test:** +```python +def test_add_returns_sum(): + result = add(2, 3) + assert result == 5 +``` + +--- + +## Gate 7: Code Generation Rules + +**Trigger:** When writing implementation + +### Rules + +**Deletion over Abstraction:** +```python +# Prefer deleting unused code +# over abstracting "just in case" +``` + +**No Generic Folders:** +```python +# WRONG +utils/ +common/ +shared/ +helpers/ + +# RIGHT +validation/ +authentication/ +formatting/ +``` + +**One Reason to Change:** +```python +# user_manager.py +# ✓ Changes when user management logic changes +# ✗ Changes when email logic, DB logic, AND user logic change +``` + +**Explicit Public API:** +```python +# __init__.py +from .user_service import create_user, get_user +# Only these are public +``` + +**Flat over Deep:** +```python +# WRONG: Excessive nesting +src/features/users/services/implementations/user_service_impl.py + +# RIGHT: Flat structure +src/users/user_service.py +``` + +**No Global State Without Justification:** +```python +# WRONG: Hidden global state +_cached_users = {} # Who manages this? When is it invalidated? + +# RIGHT: Explicit state management +class UserCache: + def __init__(self): + self._cache = {} + + def get(self, user_id): + return self._cache.get(user_id) +``` + +--- + +## Gate Enforcement Checklist + +Before finalizing any work: + +- [ ] **Gate 0:** Environment verified +- [ ] **Gate 1:** Tests pass (if refactoring) +- [ ] **Gate 2:** Patterns justified +- [ ] **Gate 3:** No circular dependencies +- [ ] **Gate 4:** Public API minimal +- [ ] **Gate 5:** Assumptions disclosed +- [ ] **Gate 6:** Tests cover edge cases +- [ ] **Gate 7:** Code generation rules followed + +**Any gate fails → Stop and fix.** + +--- + +## Summary + +Gates are **hard stops**, not suggestions. + +Bypassing gates leads to: +- Broken refactors (no tests) +- Over-engineered code (unjustified patterns) +- Tangled dependencies (circular refs) +- Brittle APIs (exposed internals) +- Hidden bugs (undisclosed assumptions) + +**Respect the gates.** diff --git a/src/plugins/engineering-discipline/snippets/references/misc/overview.txt b/src/plugins/engineering-discipline/snippets/references/misc/overview.txt new file mode 100755 index 0000000..5f29465 --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/misc/overview.txt @@ -0,0 +1,450 @@ +# Engineering Discipline Skill + +A comprehensive Kiro skill combining design patterns, architectural reasoning, and systematic engineering verification for building production-quality software. + +## Overview + +This skill provides **two complementary modes**: + +### 🔍 Quick Reference Mode +Fast lookup for design patterns, principles, and best practices. + +**Use when:** +- Looking up specific patterns +- Checking naming conventions +- Quick code review questions +- Learning about principles + +### ⚙️ Process Mode +Full engineering workflow with verification gates for complex systems. + +**Use when:** +- Building production features +- Designing critical systems +- Security-sensitive code +- Complex architectures requiring rigor + +## Installation + +### Workspace Installation (Project-Specific) +```bash +mkdir -p .kiro/skills +cp -r engineering-discipline .kiro/skills/ +``` + +### Global Installation (All Projects) +```bash +mkdir -p ~/.kiro/skills +cp -r engineering-discipline ~/.kiro/skills/ +``` + +## Structure + +``` +engineering-discipline/ +├── SKILL.md # Main skill with mode routing +├── README.md # This file +└── references/ + ├── principles/ # Design patterns & best practices + │ ├── readability.md # Naming, formatting, documentation + │ ├── simplicity.md # KISS, DRY, YAGNI + │ ├── design-architecture.md # Patterns, SRP, composition + │ ├── testing.md # TDD, test pyramid, mocking + │ ├── error-handling.md # Validation, exceptions + │ └── maintainability.md # Refactoring, commits, tooling + └── process/ # Engineering workflow + ├── task-classification.md # Classify before coding + ├── architecture-reasoning.md # 9-layer thinking hierarchy + ├── verification-gates.md # Safety checkpoints + └── negative-doubt.md # Adversarial verification +``` + +## Quick Reference Mode Examples + +### Pattern Lookup +``` +User: "What pattern should I use for swappable payment processors?" + +Response: "Use the Strategy Pattern. It allows you to encapsulate +different payment algorithms (Stripe, PayPal, etc.) and make them +interchangeable at runtime..." +``` + +### Principle Check +``` +User: "Is this function too long?" + +Response: "Yes. At 45 lines with mixed abstraction levels, it violates +the Single Responsibility Principle. Consider extracting..." +``` + +### Best Practice +``` +User: "How should I name this variable?" + +Response: "Use a descriptive noun that indicates purpose. Instead of +'data', use 'user_records' or 'payment_transactions'..." +``` + +## Process Mode Examples + +### Building a System +``` +User: "Build a rate limiting service for our API" + +Response: +**Task Classification:** New Feature + +**Architecture:** +Responsibilities: +- RateLimiter: Track request counts per user +- Storage: Persist counter state +- Middleware: Intercept requests and enforce limits + +Invariants: +- Counter never negative +- Counter resets at time boundary +- Max requests per window enforced + +Dependencies: +RateLimiter → Storage (interface) +Middleware → RateLimiter + +[Full 9-layer architecture...] + +**Code:** [Implementation] + +**Tests:** [Comprehensive test suite] + +**Negative Doubt Log:** +- Failure mode: Race condition → Added atomic increment +- Failure mode: Time drift → Used monotonic clock +... +``` + +## Principle Categories + +### 1. Readability & Clarity +**Goal:** Code that reads like natural language + +- Descriptive naming (nouns for data, verbs for actions) +- Consistent formatting and style +- Self-documenting code (minimal comments) +- Small, focused functions (< 20 lines ideal) + +**Key Quote:** "Code is read 10x more than it's written" + +### 2. Simplicity & Efficiency +**Goal:** Minimal viable abstraction + +- KISS: Simplest solution that works +- DRY: No duplicated logic +- YAGNI: Build only what's needed now +- Defer optimization until profiling proves need + +**Key Quote:** "Premature optimization is the root of all evil" + +### 3. Design & Architecture +**Goal:** Modular, flexible, testable systems + +- Single Responsibility Principle +- Composition over Inheritance +- Depend on interfaces, not implementations +- 7 Essential Patterns: + - Factory, Strategy, Observer + - Decorator, Adapter, Command, Singleton + +**Key Quote:** "Architecture enables change velocity" + +### 4. Testing & Quality +**Goal:** Behavior locked by tests + +- Write tests first or alongside code +- Test pyramid: Many unit, fewer integration, minimal e2e +- One assertion per test (focused) +- Mock external dependencies + +**Key Quote:** "No refactor without tests" + +### 5. Error Handling +**Goal:** Fail fast and clearly + +- Validate inputs at entry points +- Use specific exception types +- Provide actionable error messages +- Guard clauses to reduce nesting + +**Key Quote:** "Explicit is better than implicit" + +### 6. Maintainability +**Goal:** Code that's easy to change + +- Boy Scout Rule: Leave it better +- Continuous small refactors +- Atomic commits with clear messages +- Automate quality checks (linting, formatting) + +**Key Quote:** "Technical debt compounds like financial debt" + +## Process Mode Workflow + +### Step 0: Environment Gate ⚠️ +**ALWAYS FIRST** + +Verify: +- Language version +- Package manager +- Dependencies +- Development tools + +### Step 1: Task Classification +Classify as ONE of: +- New Feature +- Refactor (behavior preserved) +- Bug Fix +- Review/Audit +- Documentation + +**If unclear → STOP** + +### Step 2: Load Engineering Constraints +Apply principles as hard rules: +- Single responsibility +- No circular dependencies +- Tests before refactoring +- No speculative features +- Patterns need stated forces + +### Step 3: Architecture-First Reasoning +Think in 9 layers (never skip): + +1. Responsibilities +2. Invariants +3. Dependencies +4. Module boundaries +5. Public APIs +6. Folder structure +7. Files +8. Functions +9. Syntax + +### Step 4: Behavior & Invariants +Document: +- Observable behavior +- Input/output contracts +- State invariants +- Ordering requirements + +### Step 5: Pattern Gate +Use pattern ONLY if: +- Force is stated +- Simpler alternative rejected +- Invariant being protected is clear + +### Step 6: Implementation +Generate code following: +- Explicit boundaries +- Minimal public surface +- No utils/common without ownership +- Flat structures over deep nesting + +### Step 7: Test-Driven +Include: +- Unit tests for logic +- Integration tests for dependencies +- Edge case coverage +- Error path tests + +### Step 8: Negative Doubt Routine +Adversarial verification: + +1. List 5 failure modes +2. Falsify assumptions +3. Check invariant enforcement +4. Audit dependencies +5. Try simpler alternative +6. Inject failure tests +7. Revise design +8. Document findings +9. Hard stop if unsafe + +### Step 9: Assumptions Disclosure +Always state: +- Input assumptions +- State invariants +- Ordering guarantees +- Non-goals + +## Mode Detection + +### Quick Reference Triggers +- "What pattern for...?" +- "How should I...?" +- "Best practice..." +- Pattern/principle names +- Short, focused questions + +### Process Mode Triggers +- "Build [system]" +- "Design [architecture]" +- "Production code for..." +- Keywords: critical, secure, complex +- Multi-component systems + +## Quick Decision Tables + +### When to Use Design Patterns? + +| Need | Pattern | Alternative | +|------|---------|-------------| +| Swap implementations | Strategy | If/else (for 2-3 variants) | +| Complex object creation | Factory | Direct constructor (simple objects) | +| Event notification | Observer | Direct calls (1-2 listeners) | +| Add capabilities | Decorator | Subclassing (stable hierarchy) | +| Interface mismatch | Adapter | Refactor interfaces | +| Undo/logging | Command | Direct methods (no history needed) | +| Single instance | Singleton | Dependency injection | + +### When to Refactor vs Rewrite? + +| Tests Exist? | Code Quality | Action | +|--------------|--------------|--------| +| ✓ Yes | Poor structure | Incremental refactor | +| ✗ No | Poor structure | Write tests → refactor | +| ✗ No | Fundamentally wrong | Rewrite with TDD | +| ✓ Yes | Security flaw | Fix → add regression test | + +### When to Add Abstraction? + +| Condition | Add? | Reason | +|-----------|------|--------| +| Duplicated in 3+ places | Yes | DRY principle | +| Future variation anticipated | No | YAGNI - wait for actual need | +| 2+ implementations exist | Yes | Interface for polymorphism | +| 1 implementation, simple | No | KISS - keep it simple | + +## Anti-Patterns Caught by This Skill + +### Common AI Code Generation Issues +- ❌ Generic variable names (`data`, `temp`, `result`) +- ❌ Over-commenting obvious code +- ❌ Skipping input validation +- ❌ Applying patterns without justification +- ❌ Monolithic functions (100+ lines) +- ❌ Copy-pasted code with minor changes +- ❌ Missing error handling + +### Engineering Anti-Patterns +- ❌ Coding before defining architecture +- ❌ Refactoring without tests +- ❌ Circular dependencies +- ❌ God objects (do everything) +- ❌ Premature optimization +- ❌ Unclear module boundaries +- ❌ Vague variable names + +## Example Usage + +### Quick Mode: Pattern Question +```bash +kiro "When should I use the Observer pattern instead of direct callbacks?" + +# Response includes: +# - Forces that justify Observer +# - When callbacks are simpler +# - Code example of both +# - Trade-offs +``` + +### Process Mode: Build Feature +```bash +kiro "Build authentication service with JWT tokens for production API" + +# Response includes: +# - Task classification (New Feature) +# - Architecture (9 layers) +# - Dependencies (interfaces) +# - Public API contracts +# - Full implementation +# - Comprehensive tests +# - Negative doubt log +# - Security considerations +``` + +## Verification Gates + +| Gate | Checks | Hard Stop If | +|------|--------|--------------| +| 0. Environment | Runtime, tools, deps | Missing required tools | +| 1. Requirements | Clarity, scope | Ambiguous or vague | +| 2. Architecture | Dependencies, boundaries | Circular deps | +| 3. Patterns | Justified forces | No force stated | +| 4. Tests | Coverage, edge cases | Critical paths untested | +| 5. Quality | Linting, formatting | Violations present | +| 6. Security | Validation, secrets | Vulnerabilities found | +| 7. Performance | Benchmarks (if critical) | Requirements not met | + +## Benefits + +### For Individual Developers +- Faster pattern selection +- Fewer bugs through verification +- Better architecture decisions +- Clearer code review criteria + +### For Teams +- Consistent engineering practices +- Shared vocabulary (patterns) +- Reduced technical debt +- Faster onboarding + +### For Production Systems +- Higher reliability (gates catch issues) +- Better maintainability (clear structure) +- Easier debugging (explicit invariants) +- Safer refactoring (tests lock behavior) + +## When NOT to Use Full Process Mode + +**Use lightweight reference mode for:** +- Prototypes and experiments +- Personal scripts +- Learning exercises +- Throwaway code + +**But always state:** "This is a prototype, skipping gates X, Y, Z" + +## Sources & Credits + +### Design Patterns & Principles +- *Clean Code* - Robert C. Martin +- *The Pragmatic Programmer* - Hunt & Thomas +- *Code Complete* - Steve McConnell +- *Refactoring* - Martin Fowler +- *Design Patterns* - Gang of Four + +### Engineering Process +- Senior SDE-3 industry practices +- Production systems methodology +- Safety-critical software engineering + +## Philosophy + +> **You are not an autocomplete engine.** +> **You are an engineering constraint solver.** + +This skill treats engineering as a discipline of **preserving correctness** through: +- Explicit constraints +- Verifiable invariants +- Systematic reasoning +- Adversarial verification + +Code is the output, not the input, of engineering. + +## Version + +2.0.0 - Combined design patterns + ProCoder engineering process + +## License + +Based on publicly available software engineering literature and industry best practices. diff --git a/src/plugins/engineering-discipline/snippets/references/patterns/design-architecture.txt b/src/plugins/engineering-discipline/snippets/references/patterns/design-architecture.txt new file mode 100755 index 0000000..fc1cd6c --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/patterns/design-architecture.txt @@ -0,0 +1,477 @@ +# Design & Architecture Principles + +## Single Responsibility Principle (SRP) + +**Definition:** Each class or module should have only one reason to change. It should encapsulate one cohesive responsibility. + +**Supported by:** *Clean Code*, *The Pragmatic Programmer*, SOLID Principles + +### Examples + +```python +# Bad - Multiple responsibilities +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + def save_to_database(self): + # Database logic + pass + + def send_welcome_email(self): + # Email logic + pass + + def generate_report(self): + # Reporting logic + pass + +# Good - Separated concerns +class User: + def __init__(self, name, email): + self.name = name + self.email = email + +class UserRepository: + def save(self, user): + # Database logic + pass + +class EmailService: + def send_welcome(self, user): + # Email logic + pass + +class UserReportGenerator: + def generate(self, user): + # Reporting logic + pass +``` + +### Do +- Encapsulate related data and behavior +- Separate concerns (data access, business logic, presentation) +- Create cohesive modules +- Make reasons for change explicit + +### Don't +- Mix data access, logic, and UI in one class +- Create "god objects" that do everything +- Couple unrelated functionality + +### AI Pitfalls +- Cramming multiple operations into one class +- Creating utility classes with unrelated methods +- Mixing infrastructure and domain logic + +--- + +## Composition Over Inheritance + +**Definition:** Prefer combining objects to form behavior over creating deep class hierarchies. Favor "has-a" relationships over "is-a". + +**Supported by:** *The Pragmatic Programmer*, *Design Patterns* + +### Examples + +```python +# Bad - Rigid inheritance hierarchy +class Bird: + def fly(self): + return "Flying" + +class Penguin(Bird): + def fly(self): + raise Exception("Penguins cannot fly") + +# Good - Composition with behavior injection +class FlyBehavior: + def fly(self): + pass + +class CanFly(FlyBehavior): + def fly(self): + return "Flying" + +class CannotFly(FlyBehavior): + def fly(self): + return "Cannot fly" + +class Bird: + def __init__(self, fly_behavior): + self.fly_behavior = fly_behavior + + def perform_fly(self): + return self.fly_behavior.fly() + +# Usage +sparrow = Bird(CanFly()) +penguin = Bird(CannotFly()) +``` + +### Do +- Use interfaces or protocols to define contracts +- Inject dependencies and behaviors +- Compose small, focused objects +- Favor delegation over inheritance + +### Don't +- Create deep inheritance hierarchies (>3 levels) +- Inherit just to override behavior +- Use inheritance for code reuse alone +- Force unnatural "is-a" relationships + +### AI Pitfalls +- Defaulting to inheritance for code reuse +- Creating rigid class hierarchies +- Not recognizing when composition is clearer + +--- + +## Program to an Interface, Not an Implementation + +**Definition:** Depend on abstractions (interfaces, protocols) rather than concrete implementations. This enables flexibility and testability. + +**Supported by:** *Design Patterns*, *Code Complete*, Dependency Inversion Principle + +### Examples + +```python +# Bad - Depends on concrete implementation +class OrderProcessor: + def __init__(self): + self.payment = StripePayment() # Hard-coded dependency + + def process(self, order): + self.payment.charge(order.total) + +# Good - Depends on abstraction +class PaymentProcessor: + def charge(self, amount): + raise NotImplementedError + +class StripePayment(PaymentProcessor): + def charge(self, amount): + # Stripe-specific logic + pass + +class PayPalPayment(PaymentProcessor): + def charge(self, amount): + # PayPal-specific logic + pass + +class OrderProcessor: + def __init__(self, payment_processor: PaymentProcessor): + self.payment = payment_processor + + def process(self, order): + self.payment.charge(order.total) + +# Usage - Easy to swap implementations +processor = OrderProcessor(StripePayment()) +# or +processor = OrderProcessor(PayPalPayment()) +``` + +### Do +- Define interfaces for key abstractions +- Inject dependencies via constructors +- Use dependency injection frameworks when appropriate +- Code against contracts, not implementations + +### Don't +- Hard-code concrete class names +- Use `isinstance()` checks to switch behavior +- Create tight coupling to specific implementations + +### AI Pitfalls +- Using fixed class names instead of interfaces +- Not recognizing opportunities for abstraction +- Creating concrete dependencies in constructors + +--- + +## Essential Design Patterns + +### Factory Pattern + +**Purpose:** Delegate object creation to factory methods or classes. Decouples client code from concrete instantiation. + +**Use when:** Object creation is complex or varies based on conditions. + +```python +# Example +class LoggerFactory: + @staticmethod + def get_logger(log_type): + if log_type == "file": + return FileLogger() + elif log_type == "console": + return ConsoleLogger() + elif log_type == "cloud": + return CloudLogger() + else: + raise ValueError(f"Unknown logger type: {log_type}") + +# Usage +logger = LoggerFactory.get_logger("file") +logger.log("Application started") +``` + +### Strategy Pattern + +**Purpose:** Define a family of interchangeable algorithms and make them swappable at runtime. + +**Use when:** You need different behaviors for the same operation. + +```python +# Example +class SortStrategy: + def sort(self, data): + raise NotImplementedError + +class QuickSort(SortStrategy): + def sort(self, data): + # Quick sort implementation + pass + +class MergeSort(SortStrategy): + def sort(self, data): + # Merge sort implementation + pass + +class DataProcessor: + def __init__(self, sort_strategy: SortStrategy): + self.sorter = sort_strategy + + def process(self, data): + sorted_data = self.sorter.sort(data) + return sorted_data + +# Usage +processor = DataProcessor(MergeSort()) +result = processor.process([3, 1, 4, 1, 5]) +``` + +### Observer Pattern + +**Purpose:** Define a one-to-many dependency where changes in one object notify all dependents automatically. + +**Use when:** Multiple objects need to react to state changes. + +```python +# Example +class Subject: + def __init__(self): + self._observers = [] + + def attach(self, observer): + self._observers.append(observer) + + def notify(self, event): + for observer in self._observers: + observer.update(event) + +class Observer: + def update(self, event): + raise NotImplementedError + +class EmailNotifier(Observer): + def update(self, event): + print(f"Sending email for: {event}") + +class SlackNotifier(Observer): + def update(self, event): + print(f"Posting to Slack: {event}") + +# Usage +order_system = Subject() +order_system.attach(EmailNotifier()) +order_system.attach(SlackNotifier()) +order_system.notify("Order #123 shipped") +``` + +### Decorator Pattern + +**Purpose:** Dynamically add responsibilities to objects without modifying their class. + +**Use when:** You need flexible, composable enhancements. + +```python +# Example +class Notifier: + def send(self, message): + raise NotImplementedError + +class BasicNotifier(Notifier): + def send(self, message): + print(f"Basic notification: {message}") + +class NotifierDecorator(Notifier): + def __init__(self, notifier: Notifier): + self._notifier = notifier + + def send(self, message): + self._notifier.send(message) + +class SlackDecorator(NotifierDecorator): + def send(self, message): + super().send(message) + print(f"Also sent to Slack: {message}") + +class EmailDecorator(NotifierDecorator): + def send(self, message): + super().send(message) + print(f"Also sent via email: {message}") + +# Usage - Compose behaviors +notifier = EmailDecorator(SlackDecorator(BasicNotifier())) +notifier.send("System alert") +``` + +### Adapter Pattern + +**Purpose:** Convert one interface into another that clients expect. Enables incompatible interfaces to work together. + +**Use when:** Integrating legacy systems or third-party libraries. + +```python +# Example +class LegacyPrinter: + def print_text(self, text): + print(f"[LEGACY] {text}") + +class ModernPrinter: + def print(self, content): + raise NotImplementedError + +class PrinterAdapter(ModernPrinter): + def __init__(self, legacy_printer: LegacyPrinter): + self.legacy = legacy_printer + + def print(self, content): + self.legacy.print_text(content) + +# Usage +old_printer = LegacyPrinter() +adapter = PrinterAdapter(old_printer) +adapter.print("Hello World") # Uses modern interface, delegates to legacy +``` + +### Command Pattern + +**Purpose:** Encapsulate a request as an object, enabling queuing, logging, or undoable operations. + +**Use when:** You need to queue operations, support undo/redo, or log actions. + +```python +# Example +class Command: + def execute(self): + raise NotImplementedError + + def undo(self): + raise NotImplementedError + +class Light: + def on(self): + print("Light is ON") + + def off(self): + print("Light is OFF") + +class LightOnCommand(Command): + def __init__(self, light: Light): + self.light = light + + def execute(self): + self.light.on() + + def undo(self): + self.light.off() + +class LightOffCommand(Command): + def __init__(self, light: Light): + self.light = light + + def execute(self): + self.light.off() + + def undo(self): + self.light.on() + +# Usage +living_room_light = Light() +light_on = LightOnCommand(living_room_light) +light_on.execute() # Light is ON +light_on.undo() # Light is OFF +``` + +### Singleton Pattern + +**Purpose:** Ensure only one instance of a class exists globally. + +**Use when:** You need a single point of access (e.g., config, logger, connection pool). + +**Caution:** Often overused. Consider dependency injection instead. + +```python +# Example +class Singleton: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + +class ConfigManager(Singleton): + def __init__(self): + if not hasattr(self, 'initialized'): + self.config = {} + self.initialized = True + +# Usage +config1 = ConfigManager() +config2 = ConfigManager() +assert config1 is config2 # Same instance +``` + +--- + +## Pattern Usage Guidelines + +### Do +- Apply patterns when they improve clarity and flexibility +- Choose patterns based on structural fit +- Use patterns to communicate design intent +- Combine patterns when appropriate + +### Don't +- Force patterns into simple code +- Use patterns for the sake of patterns +- Apply patterns without understanding the problem +- Over-abstract with unnecessary pattern layers + +### AI Pitfalls +- Predicting patterns where none are needed +- Misnaming pattern roles (e.g., calling a simple factory a "Factory Pattern") +- Misapplying pattern intent (e.g., Singleton for everything) +- Creating pattern boilerplate without actual benefit + +--- + +## Summary + +Good architecture is: +- **Modular** - clear boundaries and responsibilities +- **Flexible** - uses composition and interfaces +- **Abstract** - depends on contracts, not implementations +- **Pattern-aware** - applies proven solutions appropriately + +When designing systems, ask: +- Does each module have one clear responsibility? +- Can I swap implementations easily? +- Am I using inheritance or composition? +- Does this pattern solve a real structural problem? diff --git a/src/plugins/engineering-discipline/snippets/references/patterns/error-handling.txt b/src/plugins/engineering-discipline/snippets/references/patterns/error-handling.txt new file mode 100755 index 0000000..1b1c25f --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/patterns/error-handling.txt @@ -0,0 +1,364 @@ +# Error Handling & Input Validation + +## Handle Errors Clearly + +**Definition:** Use exceptions for unexpected states and provide clear error messages. Fail early and explicitly rather than allowing silent failures. + +**Supported by:** *Code Complete*, *Clean Code*, *The Pragmatic Programmer* + +### Examples + +```python +# Bad - Silent failure +def divide(a, b): + if b == 0: + return None # Caller has to check for None + return a / b + +# Good - Explicit error +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b +``` + +```python +# Bad - Vague error message +def process_user(user): + if not user: + raise Exception("Error") + +# Good - Descriptive error message +def process_user(user): + if user is None: + raise ValueError("User object cannot be None") + if not user.email: + raise ValueError(f"User {user.id} must have a valid email address") +``` + +### Do +- Use exceptions for exceptional conditions +- Provide descriptive error messages +- Include context (what failed, why, what was expected) +- Fail fast - validate inputs early +- Use specific exception types +- Document what exceptions can be raised + +### Don't +- Return None or -1 as error codes +- Swallow exceptions silently +- Use exceptions for control flow +- Provide generic error messages ("Error occurred") +- Catch exceptions you can't handle + +### AI Pitfalls +- Skipping edge-case validation +- Empty except blocks: `except: pass` +- Returning default values instead of raising errors +- Generic exception types instead of specific ones + +--- + +## Validate Inputs Early + +**Definition:** Check preconditions at the entry point of functions. Reject invalid input before processing. + +**Supported by:** *Code Complete*, *Clean Code* + +### Examples + +```python +# Bad - Late validation, partial processing +def register_user(username, email, age): + user = User(username, email, age) + save_to_database(user) + if age < 18: # Too late - already saved! + raise ValueError("User must be 18 or older") + +# Good - Early validation +def register_user(username, email, age): + if not username or len(username) < 3: + raise ValueError("Username must be at least 3 characters") + if not email or '@' not in email: + raise ValueError("Invalid email address") + if age < 18: + raise ValueError("User must be 18 or older") + + user = User(username, email, age) + save_to_database(user) +``` + +### Guard Clauses + +Use guard clauses to validate and exit early: + +```python +# Bad - Nested conditions +def process_order(order): + if order is not None: + if order.items: + if order.total > 0: + # Main logic here + charge_payment(order) + ship_order(order) + +# Good - Guard clauses +def process_order(order): + if order is None: + raise ValueError("Order cannot be None") + if not order.items: + raise ValueError("Order must contain at least one item") + if order.total <= 0: + raise ValueError("Order total must be positive") + + # Main logic - no nesting + charge_payment(order) + ship_order(order) +``` + +### Do +- Validate at function entry +- Use guard clauses to reduce nesting +- Check preconditions explicitly +- Validate types and ranges +- Use type hints and runtime validation + +### Don't +- Defer validation until deep in the logic +- Assume inputs are valid +- Mix validation with business logic + +--- + +## Exception Hierarchy + +**Definition:** Use specific exception types to allow targeted error handling. + +### Examples + +```python +# Bad - Generic exceptions +def fetch_user(user_id): + if user_id < 0: + raise Exception("Invalid ID") + user = db.get(user_id) + if not user: + raise Exception("Not found") + return user + +# Good - Specific exceptions +class InvalidUserIdError(ValueError): + pass + +class UserNotFoundError(LookupError): + pass + +def fetch_user(user_id): + if user_id < 0: + raise InvalidUserIdError(f"User ID must be positive, got {user_id}") + user = db.get(user_id) + if not user: + raise UserNotFoundError(f"User with ID {user_id} not found") + return user + +# Caller can handle specifically +try: + user = fetch_user(user_id) +except InvalidUserIdError as e: + return {"error": "bad_request", "message": str(e)} +except UserNotFoundError as e: + return {"error": "not_found", "message": str(e)} +``` + +### Do +- Create custom exception classes for domain errors +- Inherit from appropriate built-in exceptions +- Use exception hierarchies for related errors +- Document exception types in docstrings + +### Don't +- Raise generic `Exception` or `RuntimeError` +- Create exceptions for every possible error +- Use exceptions for non-exceptional cases + +--- + +## Error Recovery Strategies + +### Retry with Backoff + +```python +import time + +def fetch_with_retry(url, max_attempts=3): + for attempt in range(max_attempts): + try: + return http.get(url) + except TransientError as e: + if attempt == max_attempts - 1: + raise + wait_time = 2 ** attempt # Exponential backoff + time.sleep(wait_time) +``` + +### Fallback Mechanisms + +```python +def get_user_avatar(user_id): + try: + return cdn.fetch_avatar(user_id) + except CDNError: + # Fallback to default avatar + return DEFAULT_AVATAR_URL +``` + +### Circuit Breaker + +```python +class CircuitBreaker: + def __init__(self, failure_threshold=5): + self.failure_count = 0 + self.threshold = failure_threshold + self.state = "closed" # closed, open, half-open + + def call(self, func, *args): + if self.state == "open": + raise CircuitOpenError("Service is temporarily unavailable") + + try: + result = func(*args) + self.on_success() + return result + except Exception as e: + self.on_failure() + raise + + def on_success(self): + self.failure_count = 0 + self.state = "closed" + + def on_failure(self): + self.failure_count += 1 + if self.failure_count >= self.threshold: + self.state = "open" +``` + +--- + +## Logging vs. Exceptions + +**Definition:** Log for diagnostics, use exceptions for control flow. + +### When to Log + +```python +# Log operational info +logger.info(f"Processing order {order_id}") + +# Log warnings for recoverable issues +logger.warning(f"Slow query detected: {duration}ms") + +# Log errors with context +try: + process_payment(order) +except PaymentError as e: + logger.error(f"Payment failed for order {order.id}", exc_info=True) + raise # Re-raise after logging +``` + +### When to Raise Exceptions + +```python +# Invalid input - exception +def set_age(age): + if age < 0 or age > 150: + raise ValueError(f"Invalid age: {age}") + +# Business rule violation - exception +def withdraw(account, amount): + if account.balance < amount: + raise InsufficientFundsError(f"Balance: {account.balance}, requested: {amount}") + +# Operational issue - log + exception +def connect_to_database(): + try: + return db.connect() + except ConnectionError as e: + logger.error("Database connection failed", exc_info=True) + raise DatabaseUnavailableError("Cannot connect to database") from e +``` + +### Do +- Log context before re-raising +- Include exception traceback in logs +- Use structured logging for searchability +- Set appropriate log levels + +### Don't +- Log and swallow exceptions +- Log sensitive data (passwords, tokens) +- Over-log routine operations + +--- + +## Error Messages Best Practices + +### Good Error Messages + +**What went wrong:** +``` +"Invalid email address: 'user@domain' - missing top-level domain" +``` + +**What was expected:** +``` +"Order total must be positive, got -50.00" +``` + +**How to fix it:** +``` +"File not found: '/data/input.csv'. Check that the file exists and path is correct." +``` + +**Actionable context:** +``` +"User authentication failed: Invalid API key. Please check your credentials in the dashboard." +``` + +### Bad Error Messages + +``` +"Error" # Too vague +"Something went wrong" # Unhelpful +"Invalid input" # Missing details +"Error code: 42" # No explanation +``` + +### Do +- Explain what failed and why +- Include actual vs. expected values +- Suggest corrective actions +- Avoid technical jargon for user-facing errors +- Use clear, plain language + +### Don't +- Expose internal implementation details to end users +- Include stack traces in user-facing messages +- Use codes without explanations +- Be condescending ("You entered invalid data") + +--- + +## Summary + +Effective error handling: +- **Fails fast** - Validates early and explicitly +- **Provides clarity** - Error messages explain what and why +- **Uses exceptions correctly** - For exceptional conditions only +- **Enables recovery** - Appropriate retry and fallback strategies + +When handling errors, ask: +- Have I validated all inputs? +- Will the error message help someone fix the issue? +- Am I using the right exception type? +- Should this be logged, raised, or both? diff --git a/src/plugins/engineering-discipline/snippets/references/patterns/maintainability.txt b/src/plugins/engineering-discipline/snippets/references/patterns/maintainability.txt new file mode 100755 index 0000000..a085889 --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/patterns/maintainability.txt @@ -0,0 +1,548 @@ +# Maintainability & Best Practices + +## Boy Scout Rule + +**Definition:** "Leave the code better than you found it." Make small improvements whenever you touch existing code. + +**Supported by:** *Clean Code*, *The Pragmatic Programmer* + +### Examples + +```python +# Before - Existing code you're modifying +def calc(a, b): + return a + b + +# After - Improved while making your change +def calculate_sum(a, b): + """Return the sum of two numbers.""" + return a + b +``` + +```python +# Before - Adding a feature to messy code +def processUser(u): + # Check age + if u.age<18:return False + db.save(u) + return True + +# After - Clean up while you're here +def process_user(user): + """Register an eligible user.""" + if not is_eligible_user(user): + return False + save_user(user) + return True + +def is_eligible_user(user): + return user.age >= 18 +``` + +### Do +- Improve variable names +- Extract magic numbers to constants +- Add missing docstrings +- Fix formatting inconsistencies +- Remove dead code +- Simplify complex conditions + +### Don't +- Make unrelated large refactors +- Change behavior without tests +- Add hacks or workarounds +- Ignore obvious issues ("not my code") + +### AI Pitfalls +- Regenerating dirty code without improvements +- Not suggesting cleanup opportunities +- Adding to technical debt instead of reducing it + +--- + +## Continuous Refactoring + +**Definition:** Improve code structure regularly through small, safe changes backed by tests. Refactoring should be ongoing, not a separate phase. + +**Supported by:** *Refactoring*, *Code Complete*, *Clean Code* + +### Common Refactorings + +**Extract Method** +```python +# Before +def process_order(order): + # Validate + if not order.items: + raise ValueError("Empty order") + + # Calculate total + total = 0 + for item in order.items: + total += item.price * item.quantity + + # Apply discount + if order.customer.is_premium: + total *= 0.9 + + return total + +# After +def process_order(order): + validate_order(order) + total = calculate_total(order) + return apply_discount(total, order.customer) + +def validate_order(order): + if not order.items: + raise ValueError("Empty order") + +def calculate_total(order): + return sum(item.price * item.quantity for item in order.items) + +def apply_discount(total, customer): + if customer.is_premium: + return total * 0.9 + return total +``` + +**Extract Variable** +```python +# Before +if (user.age >= 18 and user.has_verified_email and user.account_status == 'active'): + grant_access() + +# After +is_adult = user.age >= 18 +has_verified_email = user.has_verified_email +is_active = user.account_status == 'active' + +if is_adult and has_verified_email and is_active: + grant_access() +``` + +**Rename** +```python +# Before +def fn(x, y): + return x * y + +# After +def calculate_area(width, height): + return width * height +``` + +**Replace Magic Numbers** +```python +# Before +def calculate_price(quantity): + if quantity > 100: + return quantity * 9.99 * 0.85 + return quantity * 9.99 + +# After +UNIT_PRICE = 9.99 +BULK_DISCOUNT = 0.85 +BULK_THRESHOLD = 100 + +def calculate_price(quantity): + price = quantity * UNIT_PRICE + if quantity > BULK_THRESHOLD: + price *= BULK_DISCOUNT + return price +``` + +### Refactoring Workflow + +1. **Ensure tests pass** - Start with green tests +2. **Make one change** - Small, focused refactor +3. **Run tests** - Verify behavior unchanged +4. **Commit** - Save working state +5. **Repeat** - Iterate on improvements + +### Do +- Refactor in small steps +- Run tests after each change +- Commit frequently +- Use IDE refactoring tools +- Keep behavior identical + +### Don't +- Refactor without tests +- Mix refactoring with feature work +- Make multiple changes at once +- Skip running tests +- Delay commits + +### AI Pitfalls +- Suggesting large refactors without incremental steps +- Omitting test runs between changes +- Changing behavior during refactoring + +--- + +## Version Control & Incremental Work + +**Definition:** Commit code in logical, testable chunks. Each commit should represent a complete, working unit of change. + +**Supported by:** *Refactoring*, *The Pragmatic Programmer*, Agile practices + +### Good Commit Practices + +**Atomic Commits** +``` +✓ "Add user email validation" +✓ "Extract payment processing to service" +✓ "Fix off-by-one error in pagination" + +✗ "Fixed stuff" +✗ "WIP" +✗ "Updated files" +``` + +**Commit Messages** +``` +# Good - Imperative mood, clear intent +Add password strength validation + +Implement validation rules: +- Minimum 8 characters +- At least one uppercase letter +- At least one number +- At least one special character + +Closes #123 + +# Bad +fixed login +``` + +### Commit Workflow + +```bash +# 1. Make a focused change +# 2. Run tests +pytest + +# 3. Review changes +git diff + +# 4. Stage related files +git add user_validator.py tests/test_validator.py + +# 5. Commit with clear message +git commit -m "Add email format validation" + +# 6. Repeat for next logical change +``` + +### Do +- Commit working, tested code +- Write descriptive commit messages +- Keep commits focused and atomic +- Use branches for features +- Commit frequently + +### Don't +- Commit broken code +- Mix unrelated changes in one commit +- Skip commit messages +- Commit sensitive data (API keys, passwords) +- Leave uncommitted changes overnight + +### AI Pitfalls +- Generating large changes without guiding commit boundaries +- Not suggesting logical commit points +- Creating code that can't be committed incrementally + +--- + +## Code Reviews + +**Definition:** Systematic examination of code changes by peers to catch issues, share knowledge, and maintain quality. + +### Review Checklist + +**Correctness** +- Does it solve the stated problem? +- Are edge cases handled? +- Is error handling appropriate? +- Are there off-by-one errors or race conditions? + +**Design** +- Is it in the right place? +- Does it follow existing patterns? +- Is complexity warranted? +- Could it be simpler? + +**Readability** +- Are names clear? +- Is logic easy to follow? +- Are comments helpful (not redundant)? +- Is formatting consistent? + +**Testing** +- Are tests included? +- Do tests cover edge cases? +- Are tests readable and maintainable? + +**Security** +- Is input validated? +- Are secrets hardcoded? +- Are SQL queries parameterized? +- Is authentication/authorization correct? + +### Review Etiquette + +**As Reviewer** +``` +✓ "Consider extracting this to a helper function for reusability" +✓ "Could we add a test for the empty list case?" +✓ "This is clever! Can we add a comment explaining the algorithm?" + +✗ "This is terrible" +✗ "Why didn't you just..." +✗ "Obviously this is wrong" +``` + +**As Author** +- Respond to all feedback +- Ask for clarification +- Explain non-obvious decisions +- Be open to suggestions +- Thank reviewers + +### Do +- Review promptly +- Focus on substance over style +- Suggest improvements, don't demand +- Automate style checks +- Learn from reviews you receive + +### Don't +- Approve without reading +- Nitpick trivial issues +- Review your own PRs +- Take criticism personally +- Skip review for "small" changes + +--- + +## Automation and Tooling + +**Definition:** Automate repetitive tasks and use tools to maintain consistency and quality. + +**Supported by:** *The Pragmatic Programmer*, *Clean Code* + +### Essential Tools + +**Linters** - Catch common mistakes +```bash +# Python +pylint myapp/ +flake8 myapp/ + +# JavaScript +eslint src/ + +# Go +golangci-lint run +``` + +**Formatters** - Maintain consistent style +```bash +# Python +black myapp/ + +# JavaScript +prettier --write src/ + +# Rust +rustfmt src/ +``` + +**Type Checkers** - Catch type errors +```bash +# Python +mypy myapp/ + +# TypeScript +tsc --noEmit + +# Flow +flow check +``` + +**Test Runners** - Verify behavior +```bash +# Python +pytest + +# JavaScript +jest + +# Go +go test ./... +``` + +### Continuous Integration + +```yaml +# .github/workflows/ci.yml +name: CI +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: pip install -r requirements.txt + - name: Lint + run: flake8 . + - name: Type check + run: mypy . + - name: Test + run: pytest --cov + - name: Security scan + run: bandit -r . +``` + +### Pre-commit Hooks + +```bash +# .pre-commit-config.yaml +repos: + - repo: https://github.com/psf/black + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + hooks: + - id: flake8 + - repo: https://github.com/pre-commit/pre-commit-hooks + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml +``` + +### Do +- Integrate tools into workflow +- Run checks locally before pushing +- Fail builds on violations +- Configure tools consistently +- Update tools regularly + +### Don't +- Rely on manual checks +- Ignore tool warnings +- Skip tools for "quick fixes" +- Disable checks without good reason + +### AI Pitfalls +- Producing code that doesn't pass linting +- Ignoring type annotations +- Generating code incompatible with project tools + +--- + +## Documentation + +**Definition:** Provide context and explanations where code alone isn't sufficient. + +### What to Document + +**APIs and Public Interfaces** +```python +def calculate_shipping_cost(weight_kg: float, destination: str) -> float: + """Calculate shipping cost based on weight and destination. + + Args: + weight_kg: Package weight in kilograms (must be positive) + destination: ISO 3166-1 alpha-2 country code + + Returns: + Shipping cost in USD + + Raises: + ValueError: If weight is negative or destination is invalid + + Example: + >>> calculate_shipping_cost(2.5, 'US') + 12.50 + """ +``` + +**Complex Algorithms** +```python +def dijkstra(graph, start): + """Find shortest paths using Dijkstra's algorithm. + + Time complexity: O((V + E) log V) where V is vertices, E is edges + Space complexity: O(V) + + See: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + """ +``` + +**Non-Obvious Decisions** +```python +# Using MD5 for cache keys only - NOT for security +# MD5 is fast and collision-resistant enough for this use case +cache_key = hashlib.md5(url.encode()).hexdigest() +``` + +**Setup and Configuration** +```markdown +# README.md + +## Installation + +pip install -r requirements.txt + +## Configuration + +Set environment variables: +- `DATABASE_URL`: PostgreSQL connection string +- `API_KEY`: Third-party service API key + +## Running + +python app.py +``` + +### Don't Document + +- Obvious code (let code be self-documenting) +- Implementation details that change frequently +- Duplicated information available elsewhere + +### Do +- Keep docs close to code +- Update docs with code changes +- Use examples liberally +- Link to external references + +### Don't +- Let docs become stale +- Over-document simple code +- Duplicate info across files + +--- + +## Summary + +Maintainable code: +- **Improves incrementally** - Boy Scout Rule +- **Refactors continuously** - Small, safe improvements +- **Commits logically** - Atomic, tested changes +- **Automates quality** - Linters, formatters, CI/CD +- **Documents appropriately** - Context where needed + +When maintaining code, ask: +- Can I improve this while I'm here? +- Is this change small and safe? +- Should I commit now? +- Are my tools catching issues? +- Does this need documentation? diff --git a/src/plugins/engineering-discipline/snippets/references/patterns/readability.txt b/src/plugins/engineering-discipline/snippets/references/patterns/readability.txt new file mode 100755 index 0000000..edf85e0 --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/patterns/readability.txt @@ -0,0 +1,195 @@ +# Readability & Clarity Principles + +## Descriptive Naming + +**Definition:** Use clear, meaningful names for variables, functions, classes, etc., so code reads like natural language. Avoid vague, abbreviated, or encoded names. Good names explain intent without requiring comments. + +**Supported by:** *Clean Code*, *Code Complete* + +### Examples + +```python +# Bad +def calc(a, b): + return a * b + 3 + +# Good +def calculate_rectangle_area(width, height): + margin = 3 + return width * height + margin +``` + +### Do +- Use nouns for data structures and variables +- Use verbs for functions and methods +- Use consistent domain terminology +- Make names pronounceable and searchable +- Use solution/problem domain names + +### Don't +- Use single-letter names (except loop counters in small scopes) +- Create misleading names +- Use encodings or prefixes (Hungarian notation) +- Use abbreviations unless universally known +- Mix naming conventions in the same scope + +### AI Pitfalls +- Repeating generic names like `data`, `temp`, `foo`, `result` +- Inconsistent naming across similar concepts +- Using placeholder names and forgetting to rename +- Over-shortening meaningful names for brevity + +--- + +## Consistent Style & Formatting + +**Definition:** Follow a uniform coding style and project conventions. Consistency aids readability and reduces cognitive load. + +**Supported by:** *Clean Code*, *Code Complete* + +### Examples + +```javascript +// Bad - Inconsistent spacing, braces, indentation +if(x>0){ +y= x+10; + console.log(y);} + +// Good - Consistent formatting +if (x > 0) { + let result = x + 10; + console.log(result); +} +``` + +### Do +- Stick to one brace style (K&R, Allman, etc.) +- Use consistent indentation (2 or 4 spaces, never mix tabs/spaces) +- Follow language conventions (PEP 8 for Python, Airbnb for JS) +- Maintain consistent line length (80-120 characters) +- Use automated formatters (Prettier, Black, rustfmt) + +### Don't +- Mix different formatting styles in one file +- Ignore project linting rules +- Use inconsistent whitespace +- Create overly long lines + +### AI Pitfalls +- Producing inconsistent formatting across code blocks +- Mixing indentation styles +- Ignoring existing project formatting conventions + +--- + +## Self-Documenting Code (Minimize Comments) + +**Definition:** Write code so its intent is clear from the code itself. Comments should explain *why*, not *what*. + +**Supported by:** *Clean Code*, *Code Complete* + +### Examples + +```python +# Bad - Redundant comment +# Increment i by 1 +i = i + 1 + +# Good - No comment needed +i = i + 1 + +# Acceptable - Explains business rule +# Block access for users under minimum age requirement +if user.age < 13: + block_access() + +# Good - Explains non-obvious why +# Using exponential backoff to avoid API rate limits +retry_delay = base_delay * (2 ** attempt_count) +``` + +### Do +- Use clear naming and logic structure +- Comment complex algorithms or business rules +- Explain performance optimizations +- Document API contracts and side effects +- Add TODO comments for future work (with ticket IDs) + +### Don't +- Write comments that restate the code +- Leave commented-out code +- Write misleading or outdated comments +- Use comments to fix bad naming + +### AI Pitfalls +- Over-commenting obvious operations +- Leaving stale or contradictory comments +- Using comments instead of refactoring unclear code + +--- + +## Small Functions & Single Responsibility + +**Definition:** Functions and methods should do one thing and do it well. Small, cohesive units are easier to understand, test, and maintain. + +**Supported by:** *Clean Code*, *Code Complete* + +### Examples + +```python +# Bad - Function does too many things +def update_user(data): + validate(data) + update_database(data) + send_email(data) + log_activity(data) + invalidate_cache(data) + +# Good - Separated concerns +def update_user(data): + validated_data = validate(data) + save_user(validated_data) + notify_user(validated_data) + +def save_user(data): + update_database(data) + invalidate_cache(data) + +def notify_user(data): + send_email(data) + log_activity(data) +``` + +### Do +- Keep functions under 20-30 lines when possible +- Extract helper functions for complex logic +- Use descriptive function names that indicate purpose +- Limit function parameters (ideally ≤ 3) +- Make one level of abstraction per function + +### Don't +- Combine unrelated operations +- Create deeply nested logic +- Use flag arguments to control behavior +- Write functions that both query and modify state + +### AI Pitfalls +- Creating monolithic functions with multiple responsibilities +- Over-fragmenting into excessive tiny functions +- Mixing abstraction levels within one function +- Generating functions that modify global state unexpectedly + +--- + +## Summary + +Readable code is: +- **Self-explanatory** through naming +- **Consistent** in style and structure +- **Minimal in comments** - code speaks for itself +- **Small and focused** - easy to understand at a glance + +When writing or reviewing code, ask: +- Can I understand this without the author present? +- Would I want to debug this at 2 AM? +- Does this follow the team's conventions? diff --git a/src/plugins/engineering-discipline/snippets/references/patterns/simplicity.txt b/src/plugins/engineering-discipline/snippets/references/patterns/simplicity.txt new file mode 100755 index 0000000..f0533d2 --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/patterns/simplicity.txt @@ -0,0 +1,279 @@ +# Simplicity & Efficiency Principles + +## KISS (Keep It Simple, Stupid) + +**Definition:** Use the simplest solution that solves the problem. Avoid unnecessary complexity, over-engineering, or premature optimization. + +**Supported by:** *Clean Code*, *The Pragmatic Programmer* + +### Examples + +```javascript +// Bad - Unnecessary abstraction +class SingleValueContainer { + constructor(value) { + this.values = [value]; + } + add(value) { + this.values.push(value); + } + getValue() { + return this.values[0]; + } +} + +// Good - Use built-in features +let numbers = [5]; +numbers.push(7); +let firstNumber = numbers[0]; +``` + +```python +# Bad - Over-complicated +def is_even(n): + return True if n % 2 == 0 else False + +# Good - Direct and clear +def is_even(n): + return n % 2 == 0 +``` + +### Do +- Use language built-ins and standard libraries +- Choose clear, direct solutions +- Optimize only when profiling shows need +- Prefer composition of simple parts +- Write code for the current requirement + +### Don't +- Create abstractions without clear benefit +- Add complexity for hypothetical future needs +- Use clever tricks that obscure intent +- Build custom solutions when standard ones exist + +### AI Pitfalls +- Using classes or design patterns unnecessarily +- Creating abstractions for single-use code +- Over-complicating simple conditional logic +- Generating enterprise patterns for simple scripts + +--- + +## DRY (Don't Repeat Yourself) + +**Definition:** Eliminate duplicated code and logic. Every piece of knowledge should have a single, authoritative representation. + +**Supported by:** *The Pragmatic Programmer*, *Clean Code* + +### Examples + +```python +# Bad - Duplicated logic +def circle_area(radius): + return 3.14159 * radius * radius + +def quarter_circle_area(radius): + return 3.14159 * radius * radius / 4 + +def sphere_volume(radius): + return (4/3) * 3.14159 * radius * radius * radius + +# Good - Extracted constant and reused logic +PI = 3.14159 + +def circle_area(radius): + return PI * radius ** 2 + +def quarter_circle_area(radius): + return circle_area(radius) / 4 + +def sphere_volume(radius): + return (4/3) * PI * radius ** 3 +``` + +```javascript +// Bad - Repeated validation +function createUser(name, email) { + if (!email.includes('@')) throw Error('Invalid email'); + // ... +} + +function updateEmail(userId, email) { + if (!email.includes('@')) throw Error('Invalid email'); + // ... +} + +// Good - Extracted validation +function validateEmail(email) { + if (!email.includes('@')) { + throw Error('Invalid email'); + } +} + +function createUser(name, email) { + validateEmail(email); + // ... +} + +function updateEmail(userId, email) { + validateEmail(email); + // ... +} +``` + +### Do +- Extract common logic into functions +- Use constants for repeated values +- Abstract similar patterns +- Share code across modules appropriately +- Keep abstractions at the right level + +### Don't +- Copy-paste code blocks +- Duplicate business rules +- Repeat validation logic +- Hard-code the same values multiple times +- Create premature abstractions (see Rule of Three) + +### Rule of Three +Wait until you see duplication **three times** before abstracting. Two instances might be coincidental; three suggests a pattern. + +### AI Pitfalls +- Producing repeated code structures from pattern prediction +- Duplicating similar functions instead of parameterizing +- Repeating validation or error handling logic +- Not recognizing when to extract shared utilities + +--- + +## YAGNI (You Aren't Gonna Need It) + +**Definition:** Don't implement features or infrastructure until you actually need them. Avoid speculative development. + +**Supported by:** *The Pragmatic Programmer*, Extreme Programming (XP) + +### Examples + +```python +# Bad - Building for hypothetical futures +def process_order(order): + prepare_invoice(order) + apply_future_discount_system(order) # Not used yet + schedule_loyalty_rewards(order) # Not needed now + prepare_for_blockchain_audit(order) # Speculative + +# Good - Only what's needed now +def process_order(order): + prepare_invoice(order) + charge_payment(order) + ship_order(order) +``` + +```javascript +// Bad - Over-engineered configuration +class DatabaseConfig { + constructor() { + this.primaryHost = 'localhost'; + this.replicaHosts = []; // Not using replication + this.shardingStrategy = null; // Not sharding + this.cacheLayer = null; // No cache yet + } +} + +// Good - Current requirements only +class DatabaseConfig { + constructor(host) { + this.host = host; + } +} +``` + +### Do +- Write code for current, known requirements +- Add features when they're actually requested +- Keep infrastructure minimal +- Refactor when new needs emerge +- Trust that future changes will be manageable + +### Don't +- Build "just in case" features +- Create extensibility points without use cases +- Add configuration for hypothetical scenarios +- Implement features before they're specified + +### AI Pitfalls +- Generating code for unspecified future features +- Adding unnecessary configuration options +- Creating extensibility hooks without current need +- Building infrastructure beyond MVP scope + +--- + +## Premature Optimization + +**Definition:** Don't optimize until you have evidence of a performance problem. Clarity and correctness come first. + +**Supported by:** *The Pragmatic Programmer*, Donald Knuth's famous quote + +> "Premature optimization is the root of all evil" — Donald Knuth + +### Examples + +```python +# Bad - Premature optimization +def find_user(user_id): + # Using complex caching before knowing if it's needed + cache_key = f"user:{user_id}:v2" + if cache_key in cache: + return deserialize(decompress(cache[cache_key])) + user = db.query(user_id) + cache[cache_key] = compress(serialize(user)) + return user + +# Good - Start simple, optimize if needed +def find_user(user_id): + return db.query(user_id) + +# Later, if profiling shows this is slow: +def find_user(user_id): + cached = cache.get(f"user:{user_id}") + if cached: + return cached + user = db.query(user_id) + cache.set(f"user:{user_id}", user) + return user +``` + +### Do +- Write clear, correct code first +- Profile before optimizing +- Optimize only proven bottlenecks +- Measure impact of optimizations +- Document why optimizations were made + +### Don't +- Sacrifice readability for unmeasured performance +- Optimize without profiling data +- Use complex algorithms for small datasets +- Cache everything "just in case" + +### AI Pitfalls +- Adding caching layers without justification +- Using complex data structures for simple cases +- Micro-optimizing at the expense of clarity + +--- + +## Summary + +Simple code is: +- **Direct** - solves the problem at hand +- **DRY** - has no unnecessary duplication +- **Minimal** - contains only what's needed now +- **Clear** - prioritizes readability over premature optimization + +When writing code, ask: +- Is this the simplest approach that works? +- Am I repeating myself? +- Do I actually need this now? +- Am I optimizing based on evidence? diff --git a/src/plugins/engineering-discipline/snippets/references/patterns/testing.txt b/src/plugins/engineering-discipline/snippets/references/patterns/testing.txt new file mode 100755 index 0000000..6ddc615 --- /dev/null +++ b/src/plugins/engineering-discipline/snippets/references/patterns/testing.txt @@ -0,0 +1,309 @@ +# Testing & Quality Principles + +## Write Automated Tests Early + +**Definition:** Use tests to guide design, prevent regressions, and validate behavior. Testing should be part of the development process, not an afterthought. + +**Supported by:** *Refactoring*, *The Pragmatic Programmer*, Test-Driven Development (TDD) + +### Examples + +```python +# Test-first approach +def test_calculate_discount(): + # Arrange + price = 100 + discount_percent = 10 + + # Act + result = calculate_discount(price, discount_percent) + + # Assert + assert result == 90 + +def calculate_discount(price, discount_percent): + return price * (1 - discount_percent / 100) +``` + +```python +# Test edge cases +def test_user_age_validation(): + assert is_adult(18) == True + assert is_adult(17) == False + assert is_adult(0) == False + assert is_adult(150) == True # No upper bound check yet + +def is_adult(age): + return age >= 18 +``` + +### Do +- Write tests before or alongside code +- Test edge cases and boundary conditions +- Test business logic thoroughly +- Use descriptive test names +- Keep tests fast and independent +- Use test fixtures and setup/teardown appropriately + +### Don't +- Skip tests for "simple" code +- Test implementation details instead of behavior +- Write brittle tests that break on refactoring +- Ignore failing tests +- Write tests that depend on external state + +### AI Pitfalls +- Missing tests entirely +- Writing overly broad test functions +- Not testing edge cases or error paths +- Creating tests with vague assertions + +--- + +## One Assert Per Test (Focus) + +**Definition:** Keep tests focused on a single behavior or scenario. This makes failures easy to diagnose. + +**Supported by:** *Clean Code*, TDD best practices + +### Examples + +```python +# Bad - Multiple unrelated assertions +def test_user(): + user = User("Alice", 25) + assert user.name == "Alice" + assert user.age == 25 + assert user.is_adult() == True + assert user.can_vote() == True + assert user.get_greeting() == "Hello, Alice" + +# Good - Focused tests +def test_user_name_is_set_correctly(): + user = User("Alice", 25) + assert user.name == "Alice" + +def test_user_age_is_set_correctly(): + user = User("Alice", 25) + assert user.age == 25 + +def test_user_is_adult_when_age_18_or_above(): + user = User("Alice", 25) + assert user.is_adult() == True + +def test_user_is_not_adult_when_age_below_18(): + user = User("Bob", 17) + assert user.is_adult() == False +``` + +### Guideline Exceptions + +Multiple assertions are acceptable when: +- Testing object state after a single operation +- Verifying related properties of one concept +- Testing list/collection contents + +```python +# Acceptable - Related assertions on same concept +def test_order_creation(): + order = Order(items=[item1, item2]) + assert len(order.items) == 2 + assert order.total == 50.00 + assert order.status == OrderStatus.PENDING +``` + +### Do +- Use test names to describe expected behavior +- Group related tests in test classes +- Use parametrized tests for similar scenarios +- Make test intent crystal clear + +### Don't +- Group many checks together +- Test multiple behaviors in one test +- Create generic test names like `test_user()` + +### AI Pitfalls +- Combining multiple assertions in one test function +- Creating catch-all test functions +- Not using descriptive test names + +--- + +## Test Coverage Guidelines + +**Definition:** Aim for meaningful coverage of critical paths, not just high percentages. Focus on business logic, edge cases, and failure modes. + +### What to Test + +**High Priority:** +- Business logic and algorithms +- Input validation and error handling +- State transitions +- Integration points +- Security-critical code + +**Medium Priority:** +- Data transformations +- Configuration handling +- User-facing features + +**Low Priority:** +- Trivial getters/setters +- Framework-generated code +- External library wrappers + +### Coverage Anti-Patterns + +```python +# Bad - Testing for coverage, not correctness +def test_add(): + add(2, 3) # No assertion! + +# Good - Test actual behavior +def test_add_returns_sum(): + result = add(2, 3) + assert result == 5 +``` + +### Do +- Focus on critical code paths +- Test public interfaces, not private methods +- Use code coverage as a guide, not a goal +- Write tests that catch real bugs + +### Don't +- Aim for 100% coverage blindly +- Test trivial code just for metrics +- Ignore untested critical paths + +--- + +## Test Pyramid + +**Definition:** Balance different types of tests - many unit tests, fewer integration tests, even fewer end-to-end tests. + +``` + /\ + / \ Few E2E tests (slow, brittle) + /____\ + / \ More integration tests (moderate speed) + /________\ + / \ Many unit tests (fast, isolated) +``` + +### Unit Tests +- Test individual functions/classes in isolation +- Fast execution (milliseconds) +- Mock external dependencies +- High count (hundreds to thousands) + +### Integration Tests +- Test interactions between components +- Moderate speed (seconds) +- Use real dependencies where practical +- Medium count (dozens to hundreds) + +### End-to-End Tests +- Test complete user workflows +- Slow execution (minutes) +- Test through actual UI/API +- Low count (handful to dozens) + +### Do +- Rely primarily on unit tests +- Use integration tests for critical paths +- Reserve E2E tests for key user journeys + +### Don't +- Over-rely on E2E tests +- Skip unit tests in favor of integration tests +- Test everything through the UI + +--- + +## Test Quality Checklist + +Good tests are: + +- **Fast** - Run in milliseconds +- **Isolated** - No shared state or order dependency +- **Repeatable** - Same result every time +- **Self-validating** - Pass/fail is clear +- **Timely** - Written close to code + +### Do +- Use test fixtures for setup +- Clean up resources in teardown +- Use meaningful test data +- Avoid test interdependence + +### Don't +- Rely on external services without mocks +- Use production data +- Write flaky tests +- Commit commented-out tests + +--- + +## Mocking & Test Doubles + +**Definition:** Use test doubles (mocks, stubs, fakes) to isolate the code under test. + +### Types of Test Doubles + +**Stub** - Returns canned responses +```python +class StubPaymentGateway: + def charge(self, amount): + return {"status": "success", "transaction_id": "123"} +``` + +**Mock** - Verifies interactions +```python +def test_order_charges_payment(): + mock_gateway = Mock() + processor = OrderProcessor(mock_gateway) + processor.process(order) + mock_gateway.charge.assert_called_once_with(100.00) +``` + +**Fake** - Simplified working implementation +```python +class FakeDatabase: + def __init__(self): + self.data = {} + + def save(self, key, value): + self.data[key] = value + + def get(self, key): + return self.data.get(key) +``` + +### Do +- Mock external dependencies (APIs, databases, file systems) +- Use dependency injection to enable mocking +- Verify behavior, not implementation +- Keep mocks simple + +### Don't +- Mock everything (test real code when possible) +- Create complex mock hierarchies +- Over-specify mock expectations + +--- + +## Summary + +Effective testing: +- **Guides design** - Tests drive better architecture +- **Prevents regressions** - Catches bugs early +- **Documents behavior** - Tests are living specifications +- **Enables refactoring** - Confidence to improve code + +When writing tests, ask: +- Does this test verify actual behavior? +- Will this test catch real bugs? +- Is this test easy to understand and maintain? +- Can this test run quickly and reliably? diff --git a/src/plugins/excalidraw/index.ts b/src/plugins/excalidraw/index.ts new file mode 100644 index 0000000..63dff4f --- /dev/null +++ b/src/plugins/excalidraw/index.ts @@ -0,0 +1,3 @@ +import { createStaticSkillPlugin } from "../../shared/static-skill.js"; + +export const excalidrawPlugin = createStaticSkillPlugin("excalidraw", "The excalidraw skill."); diff --git a/src/plugins/excalidraw/snippets/README.txt b/src/plugins/excalidraw/snippets/README.txt new file mode 100755 index 0000000..ecdd744 --- /dev/null +++ b/src/plugins/excalidraw/snippets/README.txt @@ -0,0 +1,76 @@ +# Excalidraw - Architecture Diagram Generator + +Generate architecture diagrams as `.excalidraw` files from codebase analysis, with optional PNG and SVG export. + +--- + +## Installation + +```bash +# Add the ccc marketplace (if not already added) +/plugin marketplace add ooiyeefei/ccc + +# Install the skills collection +/plugin install ccc-skills@ccc +``` + +## Quick Start + +After installing, just ask Claude Code: + +``` +"Generate an architecture diagram for this project" +"Create an excalidraw diagram of the system" +"Visualize this codebase as an excalidraw file" +``` + +Claude Code will analyze any codebase (Node.js, Python, Java, Go, etc.), identify components, map relationships, and generate a valid `.excalidraw` JSON file. + +## Features + +- **Any codebase**: Discovers services, databases, APIs, and infrastructure from source code +- **No prerequisites**: Works without existing diagrams, Terraform, or specific file types +- **Proper arrows**: 90-degree elbow arrows with correct edge binding (not curved) +- **Color-coded**: Components styled by type (database, API, storage, AI/ML, etc.) +- **Cloud palettes**: AWS, Azure, GCP, and Kubernetes color schemes +- **Multiple layouts**: Vertical flow, horizontal pipeline, hub-and-spoke patterns +- **PNG/SVG export**: Optionally export to PNG and/or SVG via Playwright + +## PNG/SVG Export + +After generating a diagram, Claude Code will ask if you want to export to PNG, SVG, or both. + +The export uses `@excalidraw/utils` loaded in a Playwright browser — fully programmatic, no manual upload to excalidraw.com needed. + +**Requirements:** Playwright MCP tools must be available. + +**Output:** Exported files are saved alongside the `.excalidraw` file: +``` +docs/architecture/ +├── system-architecture.excalidraw # Editable diagram +├── system-architecture.svg # Vector export +└── system-architecture.png # Raster export +``` + +## Output + +- **Location**: `docs/architecture/` or user-specified path +- **Format**: `.excalidraw` JSON (editable in [excalidraw.com](https://excalidraw.com) or VS Code extension) +- **Exports**: `.svg` and `.png` viewable directly or embeddable in documentation + +## Reference Files + +The skill includes detailed reference documentation: + +| File | Contents | +|------|----------| +| `references/json-format.md` | Element types, required properties, text bindings | +| `references/arrows.md` | Routing algorithm, patterns, bindings, staggering | +| `references/colors.md` | Default, AWS, Azure, GCP, K8s palettes | +| `references/examples.md` | Complete JSON examples, layout patterns | +| `references/validation.md` | Checklists, validation algorithm, bug fixes | +| `references/export.md` | PNG/SVG export procedure via Playwright | + +## License + +MIT diff --git a/src/plugins/excalidraw/snippets/SKILL.txt b/src/plugins/excalidraw/snippets/SKILL.txt new file mode 100755 index 0000000..26c8764 --- /dev/null +++ b/src/plugins/excalidraw/snippets/SKILL.txt @@ -0,0 +1,279 @@ +--- +name: excalidraw +description: Generate architecture diagrams as .excalidraw files from codebase analysis, with optional PNG/SVG export. Use when the user asks to create architecture diagrams, system diagrams, visualize codebase structure, generate excalidraw files, export excalidraw diagrams to PNG or SVG, or convert .excalidraw files to image formats. +--- + +# Excalidraw Diagram Generator + +Generate architecture diagrams as `.excalidraw` files directly from codebase analysis, with optional export to PNG and SVG. + +--- + +## Quick Start + +**User just asks:** +``` +"Generate an architecture diagram for this project" +"Create an excalidraw diagram of the system" +"Visualize this codebase as an excalidraw file" +``` + +**Claude Code will:** +1. Analyze the codebase (any language/framework) +2. Identify components, services, databases, APIs +3. Map relationships and data flows +4. Generate valid `.excalidraw` JSON with dynamic IDs and labels +5. Optionally export to PNG and/or SVG using Playwright + +**No prerequisites:** Works without existing diagrams, Terraform, or specific file types. + +--- + +## Critical Rules + +### 1. NEVER Use Diamond Shapes + +Diamond arrow connections are broken in raw Excalidraw JSON. Use styled rectangles instead: + +| Semantic Meaning | Rectangle Style | +|------------------|-----------------| +| Orchestrator/Hub | Coral (`#ffa8a8`/`#c92a2a`) + strokeWidth: 3 | +| Decision Point | Orange (`#ffd8a8`/`#e8590c`) + dashed stroke | + +### 2. Labels Require TWO Elements + +The `label` property does NOT work in raw JSON. Every labeled shape needs: + +```json +// 1. Shape with boundElements reference +{ + "id": "my-box", + "type": "rectangle", + "boundElements": [{ "type": "text", "id": "my-box-text" }] +} + +// 2. Separate text element with containerId +{ + "id": "my-box-text", + "type": "text", + "containerId": "my-box", + "text": "My Label" +} +``` + +### 3. Elbow Arrows Need Three Properties + +For 90-degree corners (not curved): + +```json +{ + "type": "arrow", + "roughness": 0, // Clean lines + "roundness": null, // Sharp corners + "elbowed": true // 90-degree mode +} +``` + +### 4. Arrow Edge Calculations + +Arrows must start/end at shape edges, not centers: + +| Edge | Formula | +|------|---------| +| Top | `(x + width/2, y)` | +| Bottom | `(x + width/2, y + height)` | +| Left | `(x, y + height/2)` | +| Right | `(x + width, y + height/2)` | + +**Detailed arrow routing:** See `references/arrows.md` + +--- + +## Element Types + +| Type | Use For | +|------|---------| +| `rectangle` | Services, databases, containers, orchestrators | +| `ellipse` | Users, external systems, start/end points | +| `text` | Labels inside shapes, titles, annotations | +| `arrow` | Data flow, connections, dependencies | +| `line` | Grouping boundaries, separators | + +**Full JSON format:** See `references/json-format.md` + +--- + +## Workflow + +### Step 1: Analyze Codebase + +Discover components by looking for: + +| Codebase Type | What to Look For | +|---------------|------------------| +| Monorepo | `packages/*/package.json`, workspace configs | +| Microservices | `docker-compose.yml`, k8s manifests | +| IaC | Terraform/Pulumi resource definitions | +| Backend API | Route definitions, controllers, DB models | +| Frontend | Component hierarchy, API calls | + +**Use tools:** +- `Glob` → `**/package.json`, `**/Dockerfile`, `**/*.tf` +- `Grep` → `app.get`, `@Controller`, `CREATE TABLE` +- `Read` → README, config files, entry points + +### Step 2: Plan Layout + +**Vertical flow (most common):** +``` +Row 1: Users/Entry points (y: 100) +Row 2: Frontend/Gateway (y: 230) +Row 3: Orchestration (y: 380) +Row 4: Services (y: 530) +Row 5: Data layer (y: 680) + +Columns: x = 100, 300, 500, 700, 900 +Element size: 160-200px x 80-90px +``` + +**Other patterns:** See `references/examples.md` + +### Step 3: Generate Elements + +For each component: +1. Create shape with unique `id` +2. Add `boundElements` referencing text +3. Create text with `containerId` +4. Choose color based on type + +**Color palettes:** See `references/colors.md` + +### Step 4: Add Connections + +For each relationship: +1. Calculate source edge point +2. Plan elbow route (avoid overlaps) +3. Create arrow with `points` array +4. Match stroke color to destination type + +**Arrow patterns:** See `references/arrows.md` + +### Step 5: Add Grouping (Optional) + +For logical groupings: +- Large transparent rectangle with `strokeStyle: "dashed"` +- Standalone text label at top-left + +### Step 6: Validate and Write + +Run validation before writing. Save to `docs/` or user-specified path. + +**Validation checklist:** See `references/validation.md` + +### Step 7: Export to PNG/SVG (Optional) + +After writing the `.excalidraw` file, ask the user if they want PNG, SVG, or both exports. + +Uses Playwright MCP tools and `@excalidraw/utils` to programmatically render the diagram — no manual upload to excalidraw.com needed. + +**Requires:** Playwright MCP tools (`browser_navigate`, `browser_run_code`, `browser_close`). + +**Full export procedure:** See `references/export.md` + +--- + +## Quick Arrow Reference + +**Straight down:** +```json +{ "points": [[0, 0], [0, 110]], "x": 590, "y": 290 } +``` + +**L-shape (left then down):** +```json +{ "points": [[0, 0], [-325, 0], [-325, 125]], "x": 525, "y": 420 } +``` + +**U-turn (callback):** +```json +{ "points": [[0, 0], [50, 0], [50, -125], [20, -125]], "x": 710, "y": 440 } +``` + +**Arrow width/height** = bounding box of points: +``` +points [[0,0], [-440,0], [-440,70]] → width=440, height=70 +``` + +**Multiple arrows from same edge** - stagger positions: +``` +5 arrows: 20%, 35%, 50%, 65%, 80% across edge width +``` + +--- + +## Default Color Palette + +| Component | Background | Stroke | +|-----------|------------|--------| +| Frontend | `#a5d8ff` | `#1971c2` | +| Backend/API | `#d0bfff` | `#7048e8` | +| Database | `#b2f2bb` | `#2f9e44` | +| Storage | `#ffec99` | `#f08c00` | +| AI/ML | `#e599f7` | `#9c36b5` | +| External APIs | `#ffc9c9` | `#e03131` | +| Orchestration | `#ffa8a8` | `#c92a2a` | +| Message Queue | `#fff3bf` | `#fab005` | +| Cache | `#ffe8cc` | `#fd7e14` | +| Users | `#e7f5ff` | `#1971c2` | + +**Cloud-specific palettes:** See `references/colors.md` + +--- + +## Quick Validation Checklist + +Before writing file: +- [ ] Every shape with label has boundElements + text element +- [ ] Text elements have containerId matching shape +- [ ] Multi-point arrows have `elbowed: true`, `roundness: null` +- [ ] Arrow x,y = source shape edge point +- [ ] Arrow final point offset reaches target edge +- [ ] No diamond shapes +- [ ] No duplicate IDs + +**Full validation algorithm:** See `references/validation.md` + +--- + +## Common Issues + +| Issue | Fix | +|-------|-----| +| Labels don't appear | Use TWO elements (shape + text), not `label` property | +| Arrows curved | Add `elbowed: true`, `roundness: null`, `roughness: 0` | +| Arrows floating | Calculate x,y from shape edge, not center | +| Arrows overlapping | Stagger start positions across edge | + +**Detailed bug fixes:** See `references/validation.md` + +--- + +## Reference Files + +| File | Contents | +|------|----------| +| `references/json-format.md` | Element types, required properties, text bindings | +| `references/arrows.md` | Routing algorithm, patterns, bindings, staggering | +| `references/colors.md` | Default, AWS, Azure, GCP, K8s palettes | +| `references/examples.md` | Complete JSON examples, layout patterns | +| `references/validation.md` | Checklists, validation algorithm, bug fixes | +| `references/export.md` | PNG/SVG export procedure via Playwright | + +--- + +## Output + +- **Location:** `docs/architecture/` or user-specified +- **Filename:** Descriptive, e.g., `system-architecture.excalidraw` +- **Exports (optional):** `system-architecture.svg` and/or `system-architecture.png` in same directory +- **Testing:** Open `.excalidraw` in https://excalidraw.com or VS Code extension; `.svg` and `.png` can be viewed directly or embedded in documentation diff --git a/src/plugins/excalidraw/snippets/references/arrows.txt b/src/plugins/excalidraw/snippets/references/arrows.txt new file mode 100755 index 0000000..9ce666d --- /dev/null +++ b/src/plugins/excalidraw/snippets/references/arrows.txt @@ -0,0 +1,288 @@ +# Arrow Routing Reference + +Complete guide for creating elbow arrows with proper connections. + +--- + +## Critical: Elbow Arrow Properties + +Three required properties for 90-degree corners: + +```json +{ + "type": "arrow", + "roughness": 0, // Clean lines + "roundness": null, // Sharp corners (not curved) + "elbowed": true // Enables elbow mode +} +``` + +**Without these, arrows will be curved, not 90-degree elbows.** + +--- + +## Edge Calculation Formulas + +| Shape Type | Edge | Formula | +|------------|------|---------| +| Rectangle | Top | `(x + width/2, y)` | +| Rectangle | Bottom | `(x + width/2, y + height)` | +| Rectangle | Left | `(x, y + height/2)` | +| Rectangle | Right | `(x + width, y + height/2)` | +| Ellipse | Top | `(x + width/2, y)` | +| Ellipse | Bottom | `(x + width/2, y + height)` | + +--- + +## Universal Arrow Routing Algorithm + +``` +FUNCTION createArrow(source, target, sourceEdge, targetEdge): + // Step 1: Get source edge point + sourcePoint = getEdgePoint(source, sourceEdge) + + // Step 2: Get target edge point + targetPoint = getEdgePoint(target, targetEdge) + + // Step 3: Calculate offsets + dx = targetPoint.x - sourcePoint.x + dy = targetPoint.y - sourcePoint.y + + // Step 4: Determine routing pattern + IF sourceEdge == "bottom" AND targetEdge == "top": + IF abs(dx) < 10: // Nearly aligned + points = [[0, 0], [0, dy]] + ELSE: // Need L-shape + points = [[0, 0], [dx, 0], [dx, dy]] + + ELSE IF sourceEdge == "right" AND targetEdge == "left": + IF abs(dy) < 10: + points = [[0, 0], [dx, 0]] + ELSE: + points = [[0, 0], [0, dy], [dx, dy]] + + ELSE IF sourceEdge == targetEdge: // U-turn + clearance = 50 + IF sourceEdge == "right": + points = [[0, 0], [clearance, 0], [clearance, dy], [dx, dy]] + ELSE IF sourceEdge == "bottom": + points = [[0, 0], [0, clearance], [dx, clearance], [dx, dy]] + + // Step 5: Calculate bounding box + width = max(abs(p[0]) for p in points) + height = max(abs(p[1]) for p in points) + + RETURN {x: sourcePoint.x, y: sourcePoint.y, points, width, height} + +FUNCTION getEdgePoint(shape, edge): + SWITCH edge: + "top": RETURN (shape.x + shape.width/2, shape.y) + "bottom": RETURN (shape.x + shape.width/2, shape.y + shape.height) + "left": RETURN (shape.x, shape.y + shape.height/2) + "right": RETURN (shape.x + shape.width, shape.y + shape.height/2) +``` + +--- + +## Arrow Patterns Reference + +| Pattern | Points | Use Case | +|---------|--------|----------| +| Down | `[[0,0], [0,h]]` | Vertical connection | +| Right | `[[0,0], [w,0]]` | Horizontal connection | +| L-left-down | `[[0,0], [-w,0], [-w,h]]` | Go left, then down | +| L-right-down | `[[0,0], [w,0], [w,h]]` | Go right, then down | +| L-down-left | `[[0,0], [0,h], [-w,h]]` | Go down, then left | +| L-down-right | `[[0,0], [0,h], [w,h]]` | Go down, then right | +| S-shape | `[[0,0], [0,h1], [w,h1], [w,h2]]` | Navigate around obstacles | +| U-turn | `[[0,0], [w,0], [w,-h], [0,-h]]` | Callback/return arrows | + +--- + +## Worked Examples + +### Vertical Connection (Bottom to Top) + +``` +Source: x=500, y=200, width=180, height=90 +Target: x=500, y=400, width=180, height=90 + +source_bottom = (500 + 180/2, 200 + 90) = (590, 290) +target_top = (500 + 180/2, 400) = (590, 400) + +Arrow x = 590, y = 290 +Distance = 400 - 290 = 110 +Points = [[0, 0], [0, 110]] +``` + +### Fan-out (One to Many) + +``` +Orchestrator: x=570, y=400, width=140, height=80 +Target: x=120, y=550, width=160, height=80 + +orchestrator_bottom = (570 + 140/2, 400 + 80) = (640, 480) +target_top = (120 + 160/2, 550) = (200, 550) + +Arrow x = 640, y = 480 +Horizontal offset = 200 - 640 = -440 +Vertical offset = 550 - 480 = 70 + +Points = [[0, 0], [-440, 0], [-440, 70]] // Left first, then down +``` + +### U-turn (Callback) + +``` +Source: x=570, y=400, width=140, height=80 +Target: x=550, y=270, width=180, height=90 +Connection: Right of source -> Right of target + +source_right = (570 + 140, 400 + 80/2) = (710, 440) +target_right = (550 + 180, 270 + 90/2) = (730, 315) + +Arrow x = 710, y = 440 +Vertical distance = 315 - 440 = -125 +Final x offset = 730 - 710 = 20 + +Points = [[0, 0], [50, 0], [50, -125], [20, -125]] +// Right 50px (clearance), up 125px, left 30px +``` + +--- + +## Staggering Multiple Arrows + +When N arrows leave from same edge, spread evenly: + +``` +FUNCTION getStaggeredPositions(shape, edge, numArrows): + positions = [] + FOR i FROM 0 TO numArrows-1: + percentage = 0.2 + (0.6 * i / (numArrows - 1)) + + IF edge == "bottom" OR edge == "top": + x = shape.x + shape.width * percentage + y = (edge == "bottom") ? shape.y + shape.height : shape.y + ELSE: + x = (edge == "right") ? shape.x + shape.width : shape.x + y = shape.y + shape.height * percentage + + positions.append({x, y}) + RETURN positions + +// Examples: +// 2 arrows: 20%, 80% +// 3 arrows: 20%, 50%, 80% +// 5 arrows: 20%, 35%, 50%, 65%, 80% +``` + +--- + +## Arrow Bindings + +For better visual attachment, use `startBinding` and `endBinding`: + +```json +{ + "id": "arrow-workflow-convert", + "type": "arrow", + "x": 525, + "y": 420, + "width": 325, + "height": 125, + "points": [[0, 0], [-325, 0], [-325, 125]], + "roughness": 0, + "roundness": null, + "elbowed": true, + "startBinding": { + "elementId": "cloud-workflows", + "focus": 0, + "gap": 1, + "fixedPoint": [0.5, 1] + }, + "endBinding": { + "elementId": "convert-pdf-service", + "focus": 0, + "gap": 1, + "fixedPoint": [0.5, 0] + }, + "startArrowhead": null, + "endArrowhead": "arrow" +} +``` + +### fixedPoint Values + +- Top center: `[0.5, 0]` +- Bottom center: `[0.5, 1]` +- Left center: `[0, 0.5]` +- Right center: `[1, 0.5]` + +### Update Shape boundElements + +```json +{ + "id": "cloud-workflows", + "boundElements": [ + { "type": "text", "id": "cloud-workflows-text" }, + { "type": "arrow", "id": "arrow-workflow-convert" } + ] +} +``` + +--- + +## Bidirectional Arrows + +For two-way data flows: + +```json +{ + "type": "arrow", + "startArrowhead": "arrow", + "endArrowhead": "arrow" +} +``` + +Arrowhead options: `null`, `"arrow"`, `"bar"`, `"dot"`, `"triangle"` + +--- + +## Arrow Labels + +Position standalone text near arrow midpoint: + +```json +{ + "id": "arrow-api-db-label", + "type": "text", + "x": 305, // Arrow x + offset + "y": 245, // Arrow midpoint + "text": "SQL", + "fontSize": 12, + "containerId": null, + "backgroundColor": "#ffffff" +} +``` + +**Positioning formula:** +- Vertical: `label.y = arrow.y + (total_height / 2)` +- Horizontal: `label.x = arrow.x + (total_width / 2)` +- L-shaped: Position at corner or longest segment midpoint + +--- + +## Width/Height Calculation + +Arrow `width` and `height` = bounding box of path: + +``` +points = [[0, 0], [-440, 0], [-440, 70]] +width = abs(-440) = 440 +height = abs(70) = 70 + +points = [[0, 0], [50, 0], [50, -125], [20, -125]] +width = max(abs(50), abs(20)) = 50 +height = abs(-125) = 125 +``` diff --git a/src/plugins/excalidraw/snippets/references/colors.txt b/src/plugins/excalidraw/snippets/references/colors.txt new file mode 100755 index 0000000..0f7eec0 --- /dev/null +++ b/src/plugins/excalidraw/snippets/references/colors.txt @@ -0,0 +1,91 @@ +# Color Palettes Reference + +Color schemes for different platforms and component types. + +--- + +## Default Palette (Platform-Agnostic) + +| Component Type | Background | Stroke | Example | +|----------------|------------|--------|---------| +| Frontend/UI | `#a5d8ff` | `#1971c2` | Next.js, React apps | +| Backend/API | `#d0bfff` | `#7048e8` | API servers, processors | +| Database | `#b2f2bb` | `#2f9e44` | PostgreSQL, MySQL, MongoDB | +| Storage | `#ffec99` | `#f08c00` | Object storage, file systems | +| AI/ML Services | `#e599f7` | `#9c36b5` | ML models, AI APIs | +| External APIs | `#ffc9c9` | `#e03131` | Third-party services | +| Orchestration | `#ffa8a8` | `#c92a2a` | Workflows, schedulers | +| Validation | `#ffd8a8` | `#e8590c` | Validators, checkers | +| Network/Security | `#dee2e6` | `#495057` | VPC, IAM, firewalls | +| Classification | `#99e9f2` | `#0c8599` | Routers, classifiers | +| Users/Actors | `#e7f5ff` | `#1971c2` | User ellipses | +| Message Queue | `#fff3bf` | `#fab005` | Kafka, RabbitMQ, SQS | +| Cache | `#ffe8cc` | `#fd7e14` | Redis, Memcached | +| Monitoring | `#d3f9d8` | `#40c057` | Prometheus, Grafana | + +--- + +## AWS Palette + +| Service Category | Background | Stroke | +|-----------------|------------|--------| +| Compute (EC2, Lambda, ECS) | `#ff9900` | `#cc7a00` | +| Storage (S3, EBS) | `#3f8624` | `#2d6119` | +| Database (RDS, DynamoDB) | `#3b48cc` | `#2d3899` | +| Networking (VPC, Route53) | `#8c4fff` | `#6b3dcc` | +| Security (IAM, KMS) | `#dd344c` | `#b12a3d` | +| Analytics (Kinesis, Athena) | `#8c4fff` | `#6b3dcc` | +| ML (SageMaker, Bedrock) | `#01a88d` | `#017d69` | + +--- + +## Azure Palette + +| Service Category | Background | Stroke | +|-----------------|------------|--------| +| Compute | `#0078d4` | `#005a9e` | +| Storage | `#50e6ff` | `#3cb5cc` | +| Database | `#0078d4` | `#005a9e` | +| Networking | `#773adc` | `#5a2ca8` | +| Security | `#ff8c00` | `#cc7000` | +| AI/ML | `#50e6ff` | `#3cb5cc` | + +--- + +## GCP Palette + +| Service Category | Background | Stroke | +|-----------------|------------|--------| +| Compute (GCE, Cloud Run) | `#4285f4` | `#3367d6` | +| Storage (GCS) | `#34a853` | `#2d8e47` | +| Database (Cloud SQL, Firestore) | `#ea4335` | `#c53929` | +| Networking | `#fbbc04` | `#d99e04` | +| AI/ML (Vertex AI) | `#9334e6` | `#7627b8` | + +--- + +## Kubernetes Palette + +| Component | Background | Stroke | +|-----------|------------|--------| +| Pod | `#326ce5` | `#2756b8` | +| Service | `#326ce5` | `#2756b8` | +| Deployment | `#326ce5` | `#2756b8` | +| ConfigMap/Secret | `#7f8c8d` | `#626d6e` | +| Ingress | `#00d4aa` | `#00a888` | +| Node | `#303030` | `#1a1a1a` | +| Namespace | `#f0f0f0` | `#c0c0c0` (dashed) | + +--- + +## Diagram Type Suggestions + +| Diagram Type | Recommended Layout | Key Elements | +|--------------|-------------------|--------------| +| Microservices | Vertical flow | Services, databases, queues, API gateway | +| Data Pipeline | Horizontal flow | Sources, transformers, sinks, storage | +| Event-Driven | Hub-and-spoke | Event bus center, producers/consumers | +| Kubernetes | Layered groups | Namespace boxes, pods inside deployments | +| CI/CD | Horizontal flow | Source -> Build -> Test -> Deploy -> Monitor | +| Network | Hierarchical | Internet -> LB -> VPC -> Subnets -> Instances | +| User Flow | Swimlanes | User actions, system responses, external calls | diff --git a/src/plugins/excalidraw/snippets/references/examples.txt b/src/plugins/excalidraw/snippets/references/examples.txt new file mode 100755 index 0000000..0574b60 --- /dev/null +++ b/src/plugins/excalidraw/snippets/references/examples.txt @@ -0,0 +1,381 @@ +# Complete Examples Reference + +Full JSON examples showing proper element structure. + +--- + +## 3-Tier Architecture Example + +This is a REFERENCE showing JSON structure. Replace IDs, labels, positions, and colors based on discovered components. + +```json +{ + "type": "excalidraw", + "version": 2, + "source": "claude-code-excalidraw-skill", + "elements": [ + { + "id": "user", + "type": "ellipse", + "x": 150, + "y": 50, + "width": 100, + "height": 60, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 2 }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [{ "type": "text", "id": "user-text" }], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "user-text", + "type": "text", + "x": 175, + "y": 67, + "width": 50, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2, + "version": 1, + "versionNonce": 2, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "text": "User", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 14, + "containerId": "user", + "originalText": "User", + "lineHeight": 1.25 + }, + { + "id": "frontend", + "type": "rectangle", + "x": 100, + "y": 180, + "width": 200, + "height": 80, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 3, + "version": 1, + "versionNonce": 3, + "isDeleted": false, + "boundElements": [{ "type": "text", "id": "frontend-text" }], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "frontend-text", + "type": "text", + "x": 105, + "y": 195, + "width": 190, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 4, + "version": 1, + "versionNonce": 4, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "text": "Frontend\nNext.js", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 14, + "containerId": "frontend", + "originalText": "Frontend\nNext.js", + "lineHeight": 1.25 + }, + { + "id": "database", + "type": "rectangle", + "x": 100, + "y": 330, + "width": 200, + "height": 80, + "angle": 0, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 5, + "version": 1, + "versionNonce": 5, + "isDeleted": false, + "boundElements": [{ "type": "text", "id": "database-text" }], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "database-text", + "type": "text", + "x": 105, + "y": 345, + "width": 190, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 6, + "version": 1, + "versionNonce": 6, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "text": "Database\nPostgreSQL", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 14, + "containerId": "database", + "originalText": "Database\nPostgreSQL", + "lineHeight": 1.25 + }, + { + "id": "arrow-user-frontend", + "type": "arrow", + "x": 200, + "y": 115, + "width": 0, + "height": 60, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 7, + "version": 1, + "versionNonce": 7, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [0, 60]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": true + }, + { + "id": "arrow-frontend-database", + "type": "arrow", + "x": 200, + "y": 265, + "width": 0, + "height": 60, + "angle": 0, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 8, + "version": 1, + "versionNonce": 8, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [0, 60]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": true + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} +``` + +--- + +## Layout Patterns + +### Vertical Flow (Most Common) + +``` +Grid positioning: +- Column width: 200-250px +- Row height: 130-150px +- Element size: 160-200px x 80-90px +- Spacing: 40-50px between elements + +Row positions (y): + Row 0: 20 (title) + Row 1: 100 (users/entry points) + Row 2: 230 (frontend/gateway) + Row 3: 380 (orchestration) + Row 4: 530 (services) + Row 5: 680 (data layer) + Row 6: 830 (external services) + +Column positions (x): + Col 0: 100 + Col 1: 300 + Col 2: 500 + Col 3: 700 + Col 4: 900 +``` + +### Horizontal Flow (Pipelines) + +``` +Stage positions (x): + Stage 0: 100 (input/source) + Stage 1: 350 (transform 1) + Stage 2: 600 (transform 2) + Stage 3: 850 (transform 3) + Stage 4: 1100 (output/sink) + +All stages at same y: 200 +Arrows: "right" -> "left" connections +``` + +### Hub-and-Spoke + +``` +Center hub: x=500, y=350 +8 positions at 45° increments: + N: (500, 150) + NE: (640, 210) + E: (700, 350) + SE: (640, 490) + S: (500, 550) + SW: (360, 490) + W: (300, 350) + NW: (360, 210) +``` + +--- + +## Complex Architecture Layout + +``` +Row 0: Title/Header (y: 20) +Row 1: Users/Clients (y: 80) +Row 2: Frontend/Gateway (y: 200) +Row 3: Orchestration (y: 350) +Row 4: Processing Services (y: 550) +Row 5: Data Layer (y: 680) +Row 6: External Services (y: 830) + +Columns (x): + Col 0: 120 + Col 1: 320 + Col 2: 520 + Col 3: 720 + Col 4: 920 +``` + +--- + +## Diagram Complexity Guidelines + +| Complexity | Max Elements | Max Arrows | Approach | +|------------|-------------|------------|----------| +| Simple | 5-10 | 5-10 | Single file, no groups | +| Medium | 10-25 | 15-30 | Use grouping rectangles | +| Complex | 25-50 | 30-60 | Split into multiple diagrams | +| Very Complex | 50+ | 60+ | Multiple focused diagrams | + +**When to split:** +- More than 50 elements +- Create: `architecture-overview.excalidraw`, `architecture-data-layer.excalidraw` + +**When to use groups:** +- 3+ related services +- Same deployment unit +- Logical boundaries (VPC, Security Zone) diff --git a/src/plugins/excalidraw/snippets/references/export.txt b/src/plugins/excalidraw/snippets/references/export.txt new file mode 100755 index 0000000..f059aa6 --- /dev/null +++ b/src/plugins/excalidraw/snippets/references/export.txt @@ -0,0 +1,124 @@ +# Export to PNG/SVG + +Export `.excalidraw` files to PNG and/or SVG using Playwright MCP tools and `@excalidraw/utils`. + +## Prerequisites + +- Playwright MCP tools available: `browser_navigate`, `browser_run_code`, `browser_close` +- Python 3 installed (for local HTTP server) + +## Procedure + +### 1. Start a Local HTTP Server + +A browser origin is required for dynamic ESM imports. Start a temporary server on any available port: + +```bash +python3 -m http.server 8765 & +SERVER_PID=$! +``` + +### 2. Navigate Playwright to the Server + +``` +browser_navigate → http://localhost:8765/ +``` + +The 404 page is fine — we only need the HTTP origin for the dynamic import to work. + +### 3. Read the .excalidraw File + +Use the Read tool to get the `.excalidraw` file contents as a string. This JSON string will be passed into the browser context in the next steps. + +### 4. Export SVG + +Use `browser_run_code` with the following pattern. Replace `EXCALIDRAW_JSON_HERE` with the actual JSON string from Step 3: + +```javascript +async (page) => { + const excalidrawJson = `EXCALIDRAW_JSON_HERE`; + + const svgString = await page.evaluate(async (json) => { + const utils = await import('https://esm.sh/@excalidraw/utils@0.1.2'); + const { exportToSvg } = utils.default; + const data = JSON.parse(json); + const svg = await exportToSvg({ + elements: data.elements, + appState: { ...data.appState, exportBackground: true }, + files: data.files || {} + }); + return svg.outerHTML; + }, excalidrawJson); + + return svgString; +} +``` + +Write the returned SVG string directly to `.svg` using the Write tool. + +### 5. Export PNG + +Use `browser_run_code` with the following pattern: + +```javascript +async (page) => { + const excalidrawJson = `EXCALIDRAW_JSON_HERE`; + + const pngBase64 = await page.evaluate(async (json) => { + const utils = await import('https://esm.sh/@excalidraw/utils@0.1.2'); + const { exportToBlob } = utils.default; + const data = JSON.parse(json); + const blob = await exportToBlob({ + elements: data.elements, + appState: { ...data.appState, exportBackground: true }, + files: data.files || {}, + mimeType: 'image/png' + }); + const reader = new FileReader(); + return new Promise((resolve) => { + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(blob); + }); + }, excalidrawJson); + + return pngBase64; +} +``` + +The result is a base64 data URL. Decode and write to `.png`: + +```bash +echo "" | base64 -d > .png +``` + +Strip the `data:image/png;base64,` prefix before decoding. + +### 6. Clean Up + +Close the browser and kill the HTTP server: + +``` +browser_close +``` + +```bash +kill $SERVER_PID +``` + +## Key Details + +- **Import path**: Export functions are on `utils.default`, not named exports — this is how `esm.sh` wraps the `@excalidraw/utils` package +- **Console errors**: ` attribute y: Expected length` warnings are cosmetic — exports are valid +- **Background**: `exportBackground: true` includes the white background in exports +- **Output location**: Save exported files alongside the `.excalidraw` file with matching filename (e.g., `system-architecture.excalidraw` → `system-architecture.svg`, `system-architecture.png`) +- **Visual fidelity**: Both exports produce the same visual output as opening in excalidraw.com + +## Troubleshooting + +| Issue | Fix | +|-------|-----| +| Port already in use | Try a different port: `python3 -m http.server 9876 &` | +| Dynamic import fails | Check network connectivity; `esm.sh` CDN must be reachable | +| Playwright tools not available | Ensure Playwright MCP server is configured and running | +| PNG is blank/corrupted | Verify the base64 prefix was stripped before decoding | +| SVG missing text | Cosmetic only — text renders correctly when opened in a browser | diff --git a/src/plugins/excalidraw/snippets/references/json-format.txt b/src/plugins/excalidraw/snippets/references/json-format.txt new file mode 100755 index 0000000..213ada4 --- /dev/null +++ b/src/plugins/excalidraw/snippets/references/json-format.txt @@ -0,0 +1,210 @@ +# Excalidraw JSON Format Reference + +Complete reference for Excalidraw JSON structure and element types. + +--- + +## File Structure + +```json +{ + "type": "excalidraw", + "version": 2, + "source": "claude-code-excalidraw-skill", + "elements": [], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} +``` + +--- + +## Element Types + +| Type | Use For | Arrow Reliability | +|------|---------|-------------------| +| `rectangle` | Services, components, databases, containers, orchestrators, decision points | Excellent | +| `ellipse` | Users, external systems, start/end points | Good | +| `text` | Labels inside shapes, titles, annotations | N/A | +| `arrow` | Data flow, connections, dependencies | N/A | +| `line` | Grouping boundaries, separators | N/A | + +### BANNED: Diamond Shapes + +**NEVER use `type: "diamond"` in generated diagrams.** + +Diamond arrow connections are fundamentally broken in raw Excalidraw JSON: +- Excalidraw applies `roundness` to diamond vertices during rendering +- Visual edges appear offset from mathematical edge points +- No offset formula reliably compensates +- Arrows appear disconnected/floating + +**Use styled rectangles instead** for visual distinction: + +| Semantic Meaning | Rectangle Style | +|------------------|-----------------| +| Orchestrator/Hub | Coral (`#ffa8a8`/`#c92a2a`) + strokeWidth: 3 | +| Decision Point | Orange (`#ffd8a8`/`#e8590c`) + dashed stroke | +| Central Router | Larger size + bold color | + +--- + +## Required Element Properties + +Every element MUST have these properties: + +```json +{ + "id": "unique-id-string", + "type": "rectangle", + "x": 100, + "y": 100, + "width": 200, + "height": 80, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false +} +``` + +--- + +## Text Inside Shapes (Labels) + +**Every labeled shape requires TWO elements:** + +### Shape with boundElements + +```json +{ + "id": "{component-id}", + "type": "rectangle", + "x": 500, + "y": 200, + "width": 200, + "height": 90, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "boundElements": [{ "type": "text", "id": "{component-id}-text" }], + // ... other required properties +} +``` + +### Text with containerId + +```json +{ + "id": "{component-id}-text", + "type": "text", + "x": 505, // shape.x + 5 + "y": 220, // shape.y + (shape.height - text.height) / 2 + "width": 190, // shape.width - 10 + "height": 50, + "text": "{Component Name}\n{Subtitle}", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "{component-id}", + "originalText": "{Component Name}\n{Subtitle}", + "lineHeight": 1.25, + // ... other required properties +} +``` + +### DO NOT Use the `label` Property + +The `label` property is for the JavaScript API, NOT raw JSON files: + +```json +// WRONG - will show empty boxes +{ "type": "rectangle", "label": { "text": "My Label" } } + +// CORRECT - requires TWO elements +// 1. Shape with boundElements reference +// 2. Separate text element with containerId +``` + +### Text Positioning + +- Text `x` = shape `x` + 5 +- Text `y` = shape `y` + (shape.height - text.height) / 2 +- Text `width` = shape `width` - 10 +- Use `\n` for multi-line labels +- Always use `textAlign: "center"` and `verticalAlign: "middle"` + +### ID Naming Convention + +Always use pattern: `{shape-id}-text` for text element IDs. + +--- + +## Dynamic ID Generation + +IDs and labels are generated from codebase analysis: + +| Discovered Component | Generated ID | Generated Label | +|---------------------|--------------|-----------------| +| Express API server | `express-api` | `"API Server\nExpress.js"` | +| PostgreSQL database | `postgres-db` | `"PostgreSQL\nDatabase"` | +| Redis cache | `redis-cache` | `"Redis\nCache Layer"` | +| S3 bucket for uploads | `s3-uploads` | `"S3 Bucket\nuploads/"` | +| Lambda function | `lambda-processor` | `"Lambda\nProcessor"` | +| React frontend | `react-frontend` | `"React App\nFrontend"` | + +--- + +## Grouping with Dashed Rectangles + +For logical groupings (namespaces, VPCs, pipelines): + +```json +{ + "id": "group-ai-pipeline", + "type": "rectangle", + "x": 100, + "y": 500, + "width": 1000, + "height": 280, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "strokeStyle": "dashed", + "roughness": 0, + "roundness": null, + "boundElements": null +} +``` + +Group labels are standalone text (no containerId) at top-left: + +```json +{ + "id": "group-ai-pipeline-label", + "type": "text", + "x": 120, + "y": 510, + "text": "AI Processing Pipeline (Cloud Run)", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null +} +``` diff --git a/src/plugins/excalidraw/snippets/references/validation.txt b/src/plugins/excalidraw/snippets/references/validation.txt new file mode 100755 index 0000000..774e2c9 --- /dev/null +++ b/src/plugins/excalidraw/snippets/references/validation.txt @@ -0,0 +1,182 @@ +# Validation Reference + +Checklists, validation algorithms, and common bug fixes. + +--- + +## Pre-Flight Validation Algorithm + +Run BEFORE writing the file: + +``` +FUNCTION validateDiagram(elements): + errors = [] + + // 1. Validate shape-text bindings + FOR each shape IN elements WHERE shape.boundElements != null: + FOR each binding IN shape.boundElements: + textElement = findById(elements, binding.id) + IF textElement == null: + errors.append("Shape {shape.id} references missing text {binding.id}") + ELSE IF textElement.containerId != shape.id: + errors.append("Text containerId doesn't match shape") + + // 2. Validate arrow connections + FOR each arrow IN elements WHERE arrow.type == "arrow": + sourceShape = findShapeNear(elements, arrow.x, arrow.y) + IF sourceShape == null: + errors.append("Arrow {arrow.id} doesn't start from shape edge") + + finalPoint = arrow.points[arrow.points.length - 1] + endX = arrow.x + finalPoint[0] + endY = arrow.y + finalPoint[1] + targetShape = findShapeNear(elements, endX, endY) + IF targetShape == null: + errors.append("Arrow {arrow.id} doesn't end at shape edge") + + IF arrow.points.length > 2: + IF arrow.elbowed != true: + errors.append("Arrow {arrow.id} missing elbowed:true") + IF arrow.roundness != null: + errors.append("Arrow {arrow.id} should have roundness:null") + + // 3. Validate unique IDs + ids = [el.id for el in elements] + duplicates = findDuplicates(ids) + IF duplicates.length > 0: + errors.append("Duplicate IDs: {duplicates}") + + // 4. Validate bounding boxes + FOR each arrow IN elements WHERE arrow.type == "arrow": + maxX = max(abs(p[0]) for p in arrow.points) + maxY = max(abs(p[1]) for p in arrow.points) + IF arrow.width < maxX OR arrow.height < maxY: + errors.append("Arrow {arrow.id} bounding box too small") + + RETURN errors + +FUNCTION findShapeNear(elements, x, y, tolerance=15): + FOR each shape IN elements WHERE shape.type IN ["rectangle", "ellipse"]: + edges = [ + (shape.x + shape.width/2, shape.y), // top + (shape.x + shape.width/2, shape.y + shape.height), // bottom + (shape.x, shape.y + shape.height/2), // left + (shape.x + shape.width, shape.y + shape.height/2) // right + ] + FOR each edge IN edges: + IF abs(edge.x - x) < tolerance AND abs(edge.y - y) < tolerance: + RETURN shape + RETURN null +``` + +--- + +## Checklists + +### Before Generating + +- [ ] Identified all components from codebase +- [ ] Mapped all connections/data flows +- [ ] Chose layout pattern (vertical, horizontal, hub-and-spoke) +- [ ] Selected color palette (default, AWS, Azure, K8s) +- [ ] Planned grid positions +- [ ] Created ID naming scheme + +### During Generation + +- [ ] Every labeled shape has BOTH shape AND text elements +- [ ] Shape has `boundElements: [{ "type": "text", "id": "{id}-text" }]` +- [ ] Text has `containerId: "{shape-id}"` +- [ ] Multi-point arrows have `elbowed: true`, `roundness: null`, `roughness: 0` +- [ ] Arrows have `startBinding` and `endBinding` +- [ ] No diamond shapes used +- [ ] Applied staggering formula for multiple arrows + +### Arrow Validation (Every Arrow) + +- [ ] Arrow `x,y` calculated from shape edge +- [ ] Final point offset = `targetEdge - sourceEdge` +- [ ] Arrow `width` = `max(abs(point[0]))` +- [ ] Arrow `height` = `max(abs(point[1]))` +- [ ] U-turn arrows have 40-60px clearance + +### After Generation + +- [ ] All `boundElements` IDs reference valid text elements +- [ ] All `containerId` values reference valid shapes +- [ ] All arrows start within 15px of shape edge +- [ ] All arrows end within 15px of shape edge +- [ ] No duplicate IDs +- [ ] Arrow bounding boxes match points +- [ ] File is valid JSON + +--- + +## Common Bugs and Fixes + +### Bug: Arrow appears disconnected/floating + +**Cause**: Arrow `x,y` not calculated from shape edge. + +**Fix**: +``` +Rectangle bottom: arrow_x = shape.x + shape.width/2 + arrow_y = shape.y + shape.height +``` + +### Bug: Arrow endpoint doesn't reach target + +**Cause**: Final point offset calculated incorrectly. + +**Fix**: +``` +target_edge = (target.x + target.width/2, target.y) +offset_x = target_edge.x - arrow.x +offset_y = target_edge.y - arrow.y +Final point = [offset_x, offset_y] +``` + +### Bug: Multiple arrows from same source overlap + +**Cause**: All arrows start from identical `x,y`. + +**Fix**: Stagger start positions: +``` +For 5 arrows from bottom edge: + arrow1.x = shape.x + shape.width * 0.2 + arrow2.x = shape.x + shape.width * 0.35 + arrow3.x = shape.x + shape.width * 0.5 + arrow4.x = shape.x + shape.width * 0.65 + arrow5.x = shape.x + shape.width * 0.8 +``` + +### Bug: Callback arrow doesn't loop correctly + +**Cause**: U-turn path lacks clearance. + +**Fix**: Use 4-point path: +``` +Points = [[0, 0], [clearance, 0], [clearance, -vert], [final_x, -vert]] +clearance = 40-60px +``` + +### Bug: Labels don't appear inside shapes + +**Cause**: Using `label` property instead of separate text element. + +**Fix**: Create TWO elements: +1. Shape with `boundElements` referencing text +2. Text with `containerId` referencing shape + +### Bug: Arrows are curved, not 90-degree + +**Cause**: Missing elbow properties. + +**Fix**: Add all three: +```json +{ + "roughness": 0, + "roundness": null, + "elbowed": true +} +``` diff --git a/src/plugins/frame-animator/index.ts b/src/plugins/frame-animator/index.ts new file mode 100644 index 0000000..b8b5ba2 --- /dev/null +++ b/src/plugins/frame-animator/index.ts @@ -0,0 +1,3 @@ +import { createStaticSkillPlugin } from "../../shared/static-skill.js"; + +export const frameAnimatorPlugin = createStaticSkillPlugin("frame-animator", "The frame-animator skill."); diff --git a/src/plugins/frame-animator/snippets/SKILL.txt b/src/plugins/frame-animator/snippets/SKILL.txt new file mode 100755 index 0000000..e9a5eff --- /dev/null +++ b/src/plugins/frame-animator/snippets/SKILL.txt @@ -0,0 +1,163 @@ +--- +name: frame-animator +description: Design and improve frame-based character animations for avatar/expression systems. Use when working on tick-based animation arrays, expression timing, blink cycles, idle loops, or mouth movement for a desktop character or mascot. Applies Disney's 12 animation principles concretely: asymmetric blinks, secondary actions, slow-in/slow-out, speaking rhythm, and idle personality. +metadata: + author: orkait + version: "1.0" +--- + +# Frame Animator + +Systematic process for designing natural-feeling frame animations for character avatar systems. Covers idle loops, speaking, listening, and all emotional stances. + +Assumes a frame-array model: each entry in the array = one tick. The array loops. No math — just explicit frames. + +## When to Use + +- Creating a new stance animation from scratch +- Improving an existing animation that "feels off" +- Auditing why an animation looks robotic or dead +- Designing a full set (idle + speaking + listening) for a new emotional state + +## Core Principles + +See [references/principles.md](references/principles.md) for the full reference. The rules that matter most in practice: + +**1. Asymmetric blinks** +Closing is faster than opening. Always use an intermediate (half-open) frame. +``` +open → half-open → half-open → closed → half-open → half-open → half-open → open + [ 2 frames close ] [hold] [ 3 frames open (slower) ] +``` +A blink that closes and opens at the same speed reads as mechanical. + +**2. Secondary actions** +Idle loops need something happening beyond the main expression. Ear twitches, subtle eye shifts — 1-2 frame actions that don't change the emotional read but suggest life. Place them at intervals that feel irregular (not every N frames exactly). + +**3. Uneven speaking rhythm** +Vowels hold 3 frames. Consonant transitions 1-2. The mouth should not open and close at a perfect even interval. Personality leaks through how much rest sits between phrases. + +**4. Blink frequency matches personality** +- Hyper-alert/focused: rare blinks (every 24-32 ticks) +- Calm/warm: moderate (every 16-24 ticks) +- Tired/sad: no blink needed (static expression reads correctly) +- Playful: happy-close acts as a natural blink, no separate sequence needed + +**5. Static vs live** +Not every stance needs animation in idle. Locked/frozen expressions (stern, guarded, sad, tired) work better as static 1-frame arrays. Motion on a frozen face reads as wrong. Reserve live idle loops for stances with emotional energy. + +## Process + +### Phase 1 — Classify the stance + +Before writing any frames, answer: + +| Question | Guides | +|----------|--------| +| Is this stance energetic or locked? | Energetic → live idle. Locked → static 1-frame idle. | +| What's the dominant eye asset? | That's frame 0. Everything else is variation from it. | +| Does the stance perk ears? | Sharp ears during listening. Rounded = stays soft. | +| How does this character speak — expansive or restrained? | Wide open vs barely opens mouth. | + +### Phase 2 — Design idle + +For **live** stances: +1. Establish base frame (dominant eye + mouth + ears) +2. Place blink — choose tick (not too early in the loop, not too late) +3. Build asymmetric blink sequence (2 close, 1 hold, 3 open) using half-open intermediate +4. Add 1 secondary action (ear twitch, or subtle eye shift) at a different position in the loop +5. Pad with base frames to reach 24-32 total ticks + +For **static** stances: +```rust +pub const IDLE: &[Frame] = &[ + Frame { face: "...", eyes: "...", mouth: "...", ears: "..." }, +]; +``` +`tick % 1 = 0` always. One frame, no animation. + +### Phase 3 — Design speaking + +12-frame loop is enough. Pattern: + +``` +rest rest rest | open open open | close | rest rest | open open | close rest + [pause] [vowel hold] [trans] [gap] [next phrase] +``` + +Vary by personality: +- **Warm/playful**: more open frames, expressive, fast transitions +- **Neutral/focused**: even timing, moderate open hold +- **Guarded/stern**: less open (barely opens), more rest, long pauses +- **Tired/sad**: heavy rest before opening, sluggish, late open + +For the open position — pick the mouth asset that fits the emotional register: +- `mouth_open_flat` → neutral/large open +- `mouth_tiny_triangle` → tight/guarded open +- `mouth_open_tongue` → playful/excited open +- `mouth_tiny_triangle` → curious/tense open + +### Phase 4 — Design listening + +12-frame loop. No mouth movement. Show attentiveness through eyes and ears. + +- Ears go sharp for most stances (perk up to listen), stay rounded for soft stances (warm, playful, sad, tired) +- Add 1-2 frames of a slightly different eye state mid-loop (attentive glance, slight processing dip) +- Otherwise hold the dominant eye asset + +### Phase 5 — Review checklist + +Before writing the files: + +- [ ] Blink uses half-open intermediate (not instant open→closed) +- [ ] Blink close is faster than open (2 frames down, 3 frames up) +- [ ] Speaking rhythm is uneven (not perfectly even open/close intervals) +- [ ] Idle loop has at least one secondary action +- [ ] Static stances stay static (no unnecessary blinking on locked expressions) +- [ ] Ear state is correct for listening (sharp or intentionally rounded) +- [ ] Loop length feels right — 24-32 for live idle, 12 for speaking/listening + +## Frame Structure + +```rust +pub struct Frame { + pub face: &'static str, // face_fill_blush | face_fill_rose + pub eyes: &'static str, // see references/principles.md for full list + pub mouth: &'static str, // see references/principles.md for full list + pub ears: &'static str, // ears_style_rounded | ears_style_sharp +} +``` + +Every field explicit on every frame. Duplicates are fine — Rust is fast enough. + +## File Layout + +``` +src/animations/ + mod.rs — Frame struct + pub mod declarations + neutral.rs — IDLE, SPEAKING, LISTENING + warm.rs + playful.rs + ... (one file per stance) +``` + +Each file exports three const arrays: `IDLE`, `SPEAKING`, `LISTENING`. + +Resolver in `avatar.rs`: +```rust +fn select_frames(state: &WhiteboxState) -> &'static [Frame] { + let frame = &frames[state.tick_count as usize % frames.len()]; + // ... +} +``` + +## Common Mistakes + +| Symptom | Cause | Fix | +|---------|-------|-----| +| Blink looks like flickering | Instant open→closed, no intermediate | Add half-open frames on both sides | +| Expression feels dead/static | No secondary actions in idle | Add ear twitch or eye variation 2/3 into the loop | +| Speaking looks like a metronome | Perfectly even open/close | Vary open hold length, add extra rest before some phrases | +| Sad/tired looks wrong when it blinks | Static expression shouldn't animate | Remove blink, use 1-frame static IDLE | +| Blink timing feels off | Placed too close to start or end of loop | Put blink around tick 8 in a 32-tick loop | +| Listening looks the same as idle | Ears not changed, no eye variation | Set ears to sharp, add 1-2 frame eye shift at mid-loop | diff --git a/src/plugins/frame-animator/snippets/references/principles.txt b/src/plugins/frame-animator/snippets/references/principles.txt new file mode 100755 index 0000000..e70e611 --- /dev/null +++ b/src/plugins/frame-animator/snippets/references/principles.txt @@ -0,0 +1,189 @@ +# Animation Principles Reference + +Research-backed timing and design rules for tick-based avatar animation at ~12fps (83ms per tick). + +## Available Assets (whitebox) + +### Eyes (11 variants) +| Asset | Reads as | +|-------|----------| +| `eyes_open_blush` | Neutral open, blush tint | +| `eyes_open_rose` | Open, warm/rose tint | +| `eyes_half_open_blush` | Half-open, blush — intermediate for blink, or concentrated base | +| `eyes_half_open_rose` | Half-open, rose — intermediate for blink on warm/curious | +| `eyes_soft_closed` | Gently closed — blink hold state | +| `eyes_happy_closed` | Closed with joy — playful blink / warm secondary | +| `eyes_excited_squint` | Squinted excitement — playful dominant | +| `eyes_worried_angled` | Angled worry — guarded dominant | +| `eyes_serious_angry` | Hard, locked — stern/angry dominant | +| `eyes_sleepy_flat` | Flat drooped — tired dominant | +| `eyes_teary` | Teary — sad dominant | + +**Blink pairs:** +- Blush stances: `eyes_open_blush` ↔ `eyes_half_open_blush` ↔ `eyes_soft_closed` +- Rose stances: `eyes_open_rose` ↔ `eyes_half_open_rose` ↔ `eyes_soft_closed` +- Playful: `eyes_excited_squint` ↔ `eyes_happy_closed` (squint is already mostly closed) +- Focused: `eyes_half_open_blush` ↔ `eyes_soft_closed` (already halfway, 1 frame down) +- Angry/stern: `eyes_serious_angry` → `eyes_soft_closed` (no intermediate, instant tense blink) + +### Mouths (10 variants) +| Asset | Reads as | Use for | +|-------|----------|---------| +| `mouth_flat_neutral` | Resting closed | Neutral/focused/tired rest position | +| `mouth_open_flat` | Open, neutral | General speech open, angry open | +| `mouth_soft_smile` | Closed smile | Warm rest position | +| `mouth_tiny_triangle` | Small tight open | Curious/guarded open, tense speech | +| `mouth_cat_smile` | Curved cat smile | Playful rest | +| `mouth_wavy_cat` | Wavy playful | Playful variation | +| `mouth_open_tongue` | Wide open, tongue | Playful excited open | +| `mouth_small_frown` | Downturned | Guarded/sad rest position | +| `mouth_chevron_serious` | Chevron tight | Stern rest position | +| `mouth_pout_loop` | Pout | Angry rest position | + +**Speaking open position by stance:** +- Neutral, Alert, Focused, Angry: `mouth_open_flat` +- Warm: `mouth_open_flat` (with soft_smile as rest) +- Playful: `mouth_open_tongue` or `mouth_open_flat` +- Curious: `mouth_open_flat` (with tiny_triangle as rest) +- Guarded: `mouth_tiny_triangle` (barely opens) +- Stern: `mouth_tiny_triangle` (tight, deliberate) +- Tired: `mouth_open_flat` (sluggish, arrives late) +- Sad: `mouth_open_flat` (heavy, reluctant) + +### Ears (2 variants) +| Asset | Use | +|-------|-----| +| `ears_style_rounded` | Default for soft/warm/sad/tired stances | +| `ears_style_sharp` | Alert, curious, focused, stern, guarded, angry. Also: all stances during listening except warm/playful/sad/tired | + +### Face (2 variants) +| Asset | Use | +|-------|-----| +| `face_fill_blush` | Most stances | +| `face_fill_rose` | Warm, playful, curious — warmer/softer emotional palette | + +--- + +## Disney's 12 Principles — Applied + +### 1. Slow In and Slow Out +Movement feels natural when it accelerates into and decelerates out of held positions. + +**For blinks**: closing is a fast acceleration (2 frames), holding is a brief stop (1 frame), opening is a slow deceleration (3 frames). Total: 6 frames. + +**For speaking**: mouth accelerates open (1-2 frames transition), holds at the vowel (2-3 frames), decelerates closed (1-2 frames). + +At 12fps: 2 frames = 166ms (fast), 3 frames = 249ms (noticeable slow). + +### 2. Secondary Action +The main action (idle expression, speaking mouth) should be accompanied by a supporting action that adds personality without stealing focus. + +**Examples:** +- Ear twitching 2/3 of the way through an idle loop +- Eyes staying slightly more open during speech (engaged) +- A brief happy-close during warm speaking (joy leaks through) + +Rule: secondary actions should be 1-3 frames, not draw attention on their own. + +### 3. Timing +Frame count = character weight and personality. + +| Personality trait | Speaking rhythm | Idle blink frequency | +|-------------------|-----------------|----------------------| +| Alert, urgent | Short vowel holds (2 frames), fast transitions | Very rare (1 blink per 32-tick loop) | +| Warm, expressive | Longer holds (3 frames), smooth | Moderate (blink at tick 8 of 24) | +| Focused, deliberate | Even, controlled | Slow (blink at tick 16 of 32) | +| Tired, sluggish | Long rest before opening, late open | No blink (static) | +| Stern, measured | Extra rest before each phrase | No blink (static) | +| Sad, heavy | Heavy pause before opening, frown returns fast | No blink (static) | +| Playful, energetic | Fast transitions, varied mouth shapes | Joy squish instead of blink | + +### 4. Anticipation +A small preparatory action before the main action makes it read more intentionally. + +For this avatar system, anticipation is implicit in the half-open blink frames — the eyes begin closing before they're fully closed, so the brain reads "a blink is happening" a frame before it completes. + +### 5. Exaggeration +At small avatar scale, subtle differences in expression vanish. Lean into distinct eye/mouth combinations per stance. Don't use the same open-mouth asset for all stances — let the tight `mouth_tiny_triangle` signal guarded/tense, and `mouth_open_tongue` signal playful. + +### 6. Appeal +Each stance should be immediately readable at a glance. Avoid expressions that look "in between" two stances — they read as broken, not subtle. + +Static stances (guarded, stern, tired, sad) achieve appeal through stillness. Motion on a naturally still face breaks the read. + +--- + +## Blink Timing by Stance + +| Stance | Blink position | Intermediate | Total blink frames | +|--------|---------------|--------------|-------------------| +| Neutral | Tick 7 of 32 | `eyes_half_open_blush` | 6 (2+1+3) | +| Warm | Tick 8 of 32 | `eyes_half_open_rose` | 6 (2+1+3) | +| Playful | Tick 9-10 of 24 | `eyes_happy_closed` acts as blink | 2 | +| Curious | Tick 8 of 32 | `eyes_half_open_rose` | 6 (2+1+3) | +| Alert | Tick 24 of 32 | `eyes_half_open_rose` | 4 (1+1+2) — very brief | +| Focused | Tick 16 of 32 | (already half-open, 1 frame close) | 4 (1+1+2) | +| Angry | Tick 20 of 24 | None — instant tense blink | 1 | +| Guarded | None | — | Static | +| Stern | None | — | Static | +| Tired | None | — | Static | +| Sad | None | — | Static | + +--- + +## Speaking Rhythm Patterns + +**Standard (neutral, alert):** +``` +rest rest | open open open | rest rest | open open | rest rest +``` +Uneven phrase lengths. 3-frame vowel holds. + +**Expressive (warm, playful):** +``` +rest | open open open | secondary | open open | rest +``` +Faster pace, secondary action mid-speech (happy close for warm, excited squint for playful). + +**Restrained (guarded, stern):** +``` +rest rest rest rest | tiny-open tiny-open | rest rest | tiny-open | rest rest +``` +More rest than open. Mouth barely parts. + +**Sluggish (tired):** +``` +rest rest rest rest rest rest rest rest rest | open open open +``` +8 frames rest before the mouth opens at all. Heavy reluctance. + +**Heavy (sad):** +``` +rest rest rest rest | open open open | rest rest rest rest +``` +Opens late, closes fast, then sits in long rest. + +--- + +## Idle Loop Structure + +``` +[base frames] → [blink 6 frames] → [base frames] → [secondary action 2 frames] → [base frames] +``` + +Positioning: +- Blink: ~1/4 into the loop (tick 7-8 of 32) +- Secondary action: ~2/3 into the loop (tick 19-22 of 32) +- Both positions should feel irregular — not exactly at the midpoint or end + +Total loop length: 24 for playful/energetic, 32 for most stances. + +--- + +## Sources + +- Disney's 12 Principles of Animation (Johnston & Thomas, 1981) +- Programming natural idle character animations — dev.to +- Sprite animation frame timing — sprite-ai.art +- Breathing life into idle animations — AnimSchool Blog +- Duolingo character animation with Rive — dev.to diff --git a/src/plugins/golang-design-pattern/index.ts b/src/plugins/golang-design-pattern/index.ts new file mode 100644 index 0000000..d01baf8 --- /dev/null +++ b/src/plugins/golang-design-pattern/index.ts @@ -0,0 +1,3 @@ +import { createStaticSkillPlugin } from "../../shared/static-skill.js"; + +export const golangDesignPatternPlugin = createStaticSkillPlugin("golang-design-pattern", "The golang-design-pattern skill."); diff --git a/src/plugins/golang-design-pattern/snippets/SKILL.txt b/src/plugins/golang-design-pattern/snippets/SKILL.txt new file mode 100755 index 0000000..3f6a085 --- /dev/null +++ b/src/plugins/golang-design-pattern/snippets/SKILL.txt @@ -0,0 +1,141 @@ +--- +name: golang-design-pattern +description: Design patterns adapted for Go's philosophy. Use when writing Go code, reviewing Go architecture, translating OOP patterns to idiomatic Go, or applying design patterns in a non-OOP language. +triggers: + - "Go design pattern" + - "Go architecture" + - "idiomatic Go" + - "OOP to Go" + - "Go interface" + - "Go concurrency pattern" + - "Go struct pattern" + - "Go anti-pattern" +activation: + mode: fuzzy + priority: normal + triggers: + - "Go design pattern" + - "Go architecture" + - "idiomatic Go" + - "OOP to Go" + - "Go interface" + - "Go concurrency pattern" + - "Go struct pattern" + - "Go anti-pattern" +compatibility: "Go 1.18+" +metadata: + version: "1.0.0" +references: + - references/patterns/anti-patterns.md + - references/patterns/full-patterns-guide.md + - references/examples/creational-patterns.md + - references/examples/structural-behavioral-patterns.md + - references/examples/concurrency-error-testing.md + - references/examples/anti-patterns.md +--- + +# Go Design Patterns & Principles + +Apply design patterns idiomatically in Go by respecting composition over inheritance, implicit interfaces, and simplicity over abstraction. + +## When to Use This Skill + +- Writing or reviewing Go code that needs structural patterns +- Translating OOP design patterns to Go +- Architecting Go services with clean patterns +- Avoiding common anti-patterns from OOP backgrounds + +--- + +## Core Philosophy + +Go rejects traditional OOP in favor of: + +1. **Composition over Inheritance** — No class hierarchies. Use embedding and interfaces. +2. **Implicit Interfaces** — Types satisfy interfaces automatically. No `implements` keyword. +3. **Small Interfaces** — Prefer single-method interfaces (`io.Reader`, `http.Handler`, `error`). +4. **Explicit Dependencies** — Dependency injection over globals. No magic. +5. **Concurrency via Communication** — Use channels, not shared memory locks. + +--- + +## Pattern Quick Reference + +### Creational +| Pattern | When to Use | Reference | +|---------|-------------|-----------| +| Constructor `New()` | Any struct needing validation | `references/examples/creational-patterns.md` | +| Functional Options | 5+ optional config params | `references/examples/creational-patterns.md` | +| Factory Function | Conditional object creation | `references/examples/creational-patterns.md` | +| Avoid Singleton | Use dependency injection | `references/examples/creational-patterns.md` | + +### Structural +| Pattern | When to Use | Reference | +|---------|-------------|-----------| +| Adapter | Wrap external/legacy types | `references/examples/structural-behavioral-patterns.md` | +| Decorator (Middleware) | Add behavior without changing type | `references/examples/structural-behavioral-patterns.md` | +| Composition/Embedding | Promote methods, NOT inheritance | `references/examples/structural-behavioral-patterns.md` | +| Consumer-Side Interface | Define interface where consumed | `references/examples/structural-behavioral-patterns.md` | + +### Behavioral +| Pattern | When to Use | Reference | +|---------|-------------|-----------| +| Strategy | Interchangeable algorithms | `references/examples/structural-behavioral-patterns.md` | +| Observer | Decouple event producers/consumers | `references/examples/structural-behavioral-patterns.md` | +| Command | Job queues, undo, task pipelines | `references/examples/structural-behavioral-patterns.md` | + +### Concurrency +| Pattern | When to Use | Reference | +|---------|-------------|-----------| +| Worker Pool | Bounded parallelism | `references/examples/concurrency-error-testing.md` | +| Pipeline | Stage-by-stage stream processing | `references/examples/concurrency-error-testing.md` | +| Fan-Out/Fan-In | Parallel work + result collection | `references/examples/concurrency-error-testing.md` | + +--- + +## Go Idioms vs OOP + +| Traditional OOP | Go Idiom | +|-----------------|----------| +| Inheritance | Composition via embedding | +| Abstract classes | Interfaces with multiple implementations | +| Factory classes | Constructor functions (`NewX()`) | +| Singletons | Dependency injection + `sync.Once` (avoid) | +| Template method | Function/interface injection | +| Observer | Channels or callback functions | +| Decorator | Middleware functions | +| Strategy | Interfaces or function types | + +--- + +## Best Practices + +**DO:** +- Use small, focused interfaces (1–3 methods max) +- Define interfaces at consumer side (where used) +- Accept interfaces, return concrete structs +- Use table-driven tests for all test cases +- Pass `context.Context` for cancellation +- Wrap errors with `fmt.Errorf("...: %w", err)` +- Close channels to signal completion +- Use `defer` for cleanup + +**DON'T:** +- Create class hierarchies with embedding +- Use `init()` for dependency setup +- Create global mutable state +- Ignore errors with `_` blank identifier +- Use `panic` for business logic errors +- Create interfaces before having 2+ implementations +- Start goroutines without cancellation mechanism + +--- + +## Critical Anti-Patterns + +See `references/examples/anti-patterns.md` for code examples of: +- OOP inheritance simulation +- Global state +- Ignored errors +- Goroutine leaks +- Premature abstraction diff --git a/src/plugins/golang-design-pattern/snippets/references/examples/anti-patterns.txt b/src/plugins/golang-design-pattern/snippets/references/examples/anti-patterns.txt new file mode 100755 index 0000000..5937524 --- /dev/null +++ b/src/plugins/golang-design-pattern/snippets/references/examples/anti-patterns.txt @@ -0,0 +1,77 @@ +# Go Anti-Patterns — What NOT to Do + +## Don't Simulate OOP Inheritance + +```go +// ❌ BAD: Embedding as inheritance is confusing +type Animal struct { Name string } +func (a *Animal) Speak() string { return "..." } + +type Dog struct { + Animal +} +func (d *Dog) Speak() string { return "Woof" } // Overrides parent — confusing! + +// ✅ GOOD: Explicit composition +type Dog struct { name string } +func (d *Dog) Speak() string { return "Woof" } +``` + +## Don't Create Global State + +```go +// ❌ BAD +var db *sql.DB +func init() { db, _ = sql.Open("postgres", dsn) } + +// ✅ GOOD: Dependency injection +type Service struct { db *sql.DB } +func NewService(db *sql.DB) *Service { return &Service{db: db} } +``` + +## Don't Ignore Errors + +```go +// ❌ BAD +file, _ := os.Open("data.txt") + +// ✅ GOOD +file, err := os.Open("data.txt") +if err != nil { + return fmt.Errorf("failed to open file: %w", err) +} +defer file.Close() +``` + +## Don't Create Goroutine Leaks + +```go +// ❌ BAD: No cancellation +func worker() { + for { /* runs forever */ } +} + +// ✅ GOOD: Context-aware +func worker(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + default: + // work + } + } +} +``` + +## Don't Use Premature Abstraction + +```go +// ❌ BAD: Interface with single implementation +type UserRepo interface { Get(id string) (*User, error) } +type PostgresUserRepo struct {} // Only implementation + +// ✅ GOOD: Start concrete, extract when 2+ implementations exist +type UserStore struct { db *sql.DB } +func (s *UserStore) Get(id string) (*User, error) { /* ... */ } +``` diff --git a/src/plugins/golang-design-pattern/snippets/references/examples/concurrency-error-testing.txt b/src/plugins/golang-design-pattern/snippets/references/examples/concurrency-error-testing.txt new file mode 100755 index 0000000..cf2061f --- /dev/null +++ b/src/plugins/golang-design-pattern/snippets/references/examples/concurrency-error-testing.txt @@ -0,0 +1,148 @@ +# Concurrency, Error Handling & Testing Patterns — Go Examples + +## Concurrency Patterns + +### Worker Pool — Bounded goroutine execution + +```go +type WorkerPool struct { + workers int + jobs chan Job + wg sync.WaitGroup +} + +func (p *WorkerPool) Start(ctx context.Context) { + for i := 0; i < p.workers; i++ { + p.wg.Add(1) + go p.worker(ctx) + } +} +``` + +### Pipeline — Chain processing stages with channels + +```go +func generator(nums ...int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for _, n := range nums { out <- n } + }() + return out +} + +func square(in <-chan int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for n := range in { out <- n * n } + }() + return out +} +``` + +### Fan-Out/Fan-In — Distribute work, collect results + +```go +func fanIn(channels ...<-chan int) <-chan int { + out := make(chan int) + var wg sync.WaitGroup + + for _, ch := range channels { + wg.Add(1) + go func(c <-chan int) { + defer wg.Done() + for n := range c { out <- n } + }(ch) + } + + go func() { wg.Wait(); close(out) }() + return out +} +``` + +## Error Handling Patterns + +### Sentinel Errors — Pre-defined error constants + +```go +var ( + ErrNotFound = errors.New("not found") + ErrUnauthorized = errors.New("unauthorized") +) + +if errors.Is(err, ErrNotFound) { /* handle */ } +``` + +### Error Wrapping — Add context with `%w` + +```go +func ProcessOrder(id string) error { + order, err := fetchOrder(id) + if err != nil { + return fmt.Errorf("failed to fetch order %s: %w", id, err) + } + return nil +} +``` + +### Custom Error Types — For structured error data + +```go +type ValidationError struct { + Field string + Issue string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("validation failed: %s - %s", e.Field, e.Issue) +} + +var validationErr *ValidationError +if errors.As(err, &validationErr) { /* use fields */ } +``` + +## Testing Patterns + +### Table-Driven Tests + +```go +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + want int + }{ + {"positive", 1, 2, 3}, + {"negative", -1, -1, -2}, + {"zero", 0, 0, 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Add(tt.a, tt.b) + if got != tt.want { + t.Errorf("got %d, want %d", got, tt.want) + } + }) + } +} +``` + +### Interface Mocking — Hand-written mocks with function fields + +```go +type MockUserRepo struct { + GetByIDFunc func(ctx context.Context, id string) (*User, error) +} + +func (m *MockUserRepo) GetByID(ctx context.Context, id string) (*User, error) { + if m.GetByIDFunc != nil { + return m.GetByIDFunc(ctx, id) + } + return nil, errors.New("not implemented") +} + +// Verify interface at compile time +var _ UserRepository = (*MockUserRepo)(nil) +``` diff --git a/src/plugins/golang-design-pattern/snippets/references/examples/creational-patterns.txt b/src/plugins/golang-design-pattern/snippets/references/examples/creational-patterns.txt new file mode 100755 index 0000000..5675407 --- /dev/null +++ b/src/plugins/golang-design-pattern/snippets/references/examples/creational-patterns.txt @@ -0,0 +1,60 @@ +# Creational Patterns — Go Examples + +## Constructor Pattern + +Use `New()` or `NewType()` functions with validation: + +```go +func NewClient(timeout time.Duration) (*Client, error) { + if timeout <= 0 { + return nil, fmt.Errorf("timeout must be positive") + } + return &Client{timeout: timeout}, nil +} +``` + +## Functional Options + +For complex configuration (5+ optional params): + +```go +type Option func(*Server) + +func WithTimeout(d time.Duration) Option { + return func(s *Server) { s.timeout = d } +} + +func NewServer(addr string, opts ...Option) *Server { + s := &Server{addr: addr, timeout: 30 * time.Second} + for _, opt := range opts { + opt(s) + } + return s +} +``` + +## Factory Functions + +Conditional object creation (not factory classes): + +```go +func NewLogger(typ string) (Logger, error) { + switch typ { + case "file": return &FileLogger{}, nil + case "console": return &ConsoleLogger{}, nil + default: return nil, fmt.Errorf("unknown type: %s", typ) + } +} +``` + +## Avoid Singleton — Use Dependency Injection + +```go +// ❌ BAD: Global singleton +var instance *Database +func GetDB() *Database { return instance } + +// ✅ GOOD: Explicit dependency +type Service struct { db *Database } +func NewService(db *Database) *Service { return &Service{db: db} } +``` diff --git a/src/plugins/golang-design-pattern/snippets/references/examples/structural-behavioral-patterns.txt b/src/plugins/golang-design-pattern/snippets/references/examples/structural-behavioral-patterns.txt new file mode 100755 index 0000000..39de3dd --- /dev/null +++ b/src/plugins/golang-design-pattern/snippets/references/examples/structural-behavioral-patterns.txt @@ -0,0 +1,105 @@ +# Structural & Behavioral Patterns — Go Examples + +## Structural Patterns + +### Adapter — Wrap external types to match your interface + +```go +type Storage interface { + Save(ctx context.Context, key string, data []byte) error +} + +type RedisAdapter struct { client *RedisClient } + +func (a *RedisAdapter) Save(ctx context.Context, key string, data []byte) error { + return a.client.Set(key, data, 5*time.Minute) +} +``` + +### Decorator — Middleware pattern for HTTP handlers + +```go +func WithLogging(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s %s", r.Method, r.URL.Path) + next.ServeHTTP(w, r) + }) +} +``` + +### Composition — Embed structs to promote fields/methods + +```go +type Logger struct { mu sync.Mutex; file *os.File } + +type Service struct { + Logger // Embedded — promotes Logger's methods + db *sql.DB +} +``` + +### Consumer-Side Interfaces — Define where used, not where implemented + +```go +// ✅ GOOD: Interface in consumer package +package service + +type UserGetter interface { + GetByID(ctx context.Context, id string) (*User, error) +} + +type UserService struct { + users UserGetter // Only needs GetByID +} +``` + +## Behavioral Patterns + +### Strategy — Interface or function type + +```go +type CompressionStrategy interface { + Compress([]byte) ([]byte, error) +} + +type FileStorage struct { compression CompressionStrategy } + +// Or simpler: function type for stateless strategies +type CompressFunc func([]byte) ([]byte, error) +``` + +### Observer — Use channels (more idiomatic than callbacks) + +```go +type EventBus struct { + subscribers []chan Event +} + +func (b *EventBus) Subscribe() <-chan Event { + ch := make(chan Event, 10) + b.subscribers = append(b.subscribers, ch) + return ch +} +``` + +### Command — Function closures for job queues + +```go +type Job func(context.Context) error + +type Worker struct { jobs chan Job } + +func (w *Worker) Submit(job Job) { w.jobs <- job } +``` + +### Template Method — Injected functions (no inheritance) + +```go +type ProcessingSteps struct { + Load func() ([]byte, error) + Transform func([]byte) ([]byte, error) + Save func([]byte) error +} + +type Processor struct { steps ProcessingSteps } +``` diff --git a/src/plugins/golang-design-pattern/snippets/references/misc/overview.txt b/src/plugins/golang-design-pattern/snippets/references/misc/overview.txt new file mode 100755 index 0000000..79c1152 --- /dev/null +++ b/src/plugins/golang-design-pattern/snippets/references/misc/overview.txt @@ -0,0 +1,122 @@ +# Go Design Patterns Skill + +A Kiro skill for applying design patterns idiomatically in Go, respecting composition over inheritance and Go's minimalist philosophy. + +## Structure + +``` +golang-design-patterns/ +├── SKILL.md # Main skill definition (load this) +├── references/ +│ ├── full-patterns-guide.md # Comprehensive pattern catalog +│ └── anti-patterns.md # What NOT to do +├── scripts/ +│ └── detect-antipatterns.sh # Scan code for common issues +└── README.md # This file +``` + +## Usage + +### As a Kiro Skill + +This skill activates when you: +- Mention "Go design patterns" +- Ask about translating OOP patterns to Go +- Request architectural guidance for Go services +- Need to review Go code for pattern usage + +### Manual Reference + +Read `SKILL.md` for quick guidance on: +- When to use each pattern category +- Go-idiomatic implementations +- Quick anti-pattern checklist + +Consult `references/` for: +- **full-patterns-guide.md**: Detailed explanations with examples +- **anti-patterns.md**: Common mistakes and corrections + +### Anti-Pattern Detection + +Run the provided script to scan your codebase: + +```bash +# Scan current directory +./scripts/detect-antipatterns.sh + +# Scan specific directory +./scripts/detect-antipatterns.sh /path/to/project +``` + +The script detects: +- Global mutable state +- init() function usage +- Ignored errors (`_ = err`) +- panic() in non-init code +- Goroutines without context +- Large interfaces (>5 methods) + +## Key Principles + +1. **No Inheritance**: Go doesn't have class hierarchies. Use composition. +2. **Implicit Interfaces**: Types automatically satisfy interfaces. +3. **Small Interfaces**: Prefer 1-3 methods per interface. +4. **Consumer-Side Interfaces**: Define interfaces where used, not implemented. +5. **Explicit Dependencies**: Inject dependencies, avoid globals. +6. **Context Everywhere**: Pass `context.Context` for cancellation. +7. **Return Errors**: Don't use `panic` for business logic. + +## Pattern Categories + +### Creational +- Constructor Pattern (`New()` functions) +- Functional Options (flexible configuration) +- Factory Functions (conditional creation) + +### Structural +- Adapter (interface wrapping) +- Decorator (middleware, io wrappers) +- Composite (recursive structures) + +### Behavioral +- Strategy (interchangeable algorithms) +- Observer (channels, callbacks) +- Command (job queues) + +### Concurrency +- Worker Pool (bounded goroutines) +- Pipeline (staged processing) +- Fan-Out/Fan-In (parallel processing) + +### Error Handling +- Sentinel Errors (predefined constants) +- Error Wrapping (`%w` format) +- Custom Error Types (structured data) + +### Testing +- Table-Driven Tests (data-driven) +- Interface Mocking (hand-written mocks) +- Testable Constructors (accept interfaces) + +## Integration + +This skill complements: +- **golang.md**: Core language standards +- **Clean Architecture**: Domain-driven design in Go +- **Standard Library**: Following stdlib patterns + +## Version + +1.0.0 - Initial release + +## License + +Public domain / CC0 + +## Contributing + +To extend this skill: +1. Add new patterns to `references/full-patterns-guide.md` +2. Update `SKILL.md` summary +3. Add detection rules to `scripts/detect-antipatterns.sh` +4. Keep examples simple and idiomatic diff --git a/src/plugins/golang-design-pattern/snippets/references/patterns/anti-patterns.txt b/src/plugins/golang-design-pattern/snippets/references/patterns/anti-patterns.txt new file mode 100755 index 0000000..091c527 --- /dev/null +++ b/src/plugins/golang-design-pattern/snippets/references/patterns/anti-patterns.txt @@ -0,0 +1,775 @@ +# Go Anti-Patterns: What NOT to Do + +Common mistakes when coming from OOP languages or misapplying design patterns in Go. + +--- + +## 1. Inheritance via Embedding + +**Problem:** Treating embedding as inheritance/polymorphism. + +```go +// ❌ BAD: Simulating inheritance +type Animal struct { + Name string +} + +func (a *Animal) Speak() string { + return "generic sound" +} + +type Dog struct { + Animal // Embedded to "inherit" +} + +func (d *Dog) Speak() string { + return "Woof" // Trying to "override" +} + +// Confusing behavior: +var animal Animal = Dog{Animal{Name: "Rex"}} // Compile error! +// Embedding doesn't create an "is-a" relationship + +// ✅ GOOD: Explicit composition +type Dog struct { + name string +} + +func (d *Dog) Speak() string { + return "Woof" +} + +type Cat struct { + name string +} + +func (c *Cat) Speak() string { + return "Meow" +} + +// Use interface for polymorphism +type Speaker interface { + Speak() string +} + +func MakeNoise(s Speaker) { + fmt.Println(s.Speak()) +} +``` + +**Why it's bad:** +- Embedding is for field/method promotion, not inheritance +- Creates confusion about method resolution +- Violates Go's composition philosophy + +--- + +## 2. Global Mutable State + +**Problem:** Using global variables for shared state. + +```go +// ❌ BAD: Global database connection +var db *sql.DB + +func init() { + var err error + db, err = sql.Open("postgres", os.Getenv("DSN")) + if err != nil { + log.Fatal(err) + } +} + +func GetUser(id string) (*User, error) { + // Hidden dependency on global db + return queryUser(db, id) +} + +// Testing is nightmare: +// - Can't run tests in parallel +// - Must mock global state +// - Hidden dependencies + +// ✅ GOOD: Explicit dependency injection +type UserService struct { + db *sql.DB +} + +func NewUserService(db *sql.DB) *UserService { + return &UserService{db: db} +} + +func (s *UserService) GetUser(id string) (*User, error) { + return queryUser(s.db, id) +} + +// Testing: +func TestUserService_GetUser(t *testing.T) { + mockDB := &MockDB{} + service := NewUserService(mockDB) + // Easy to test with mock +} + +// Main: +func main() { + db, _ := sql.Open("postgres", dsn) + defer db.Close() + + userService := NewUserService(db) + orderService := NewOrderService(db) + // Explicit dependencies visible +} +``` + +**Why it's bad:** +- Makes testing difficult (can't run parallel tests) +- Creates hidden dependencies +- Violates single responsibility (init does too much) +- Hard to trace data flow + +--- + +## 3. Init() Abuse + +**Problem:** Using `init()` for complex setup or dependency initialization. + +```go +// ❌ BAD: Complex init +var ( + config *Config + logger *Logger + cache *Cache +) + +func init() { + config = loadConfig() // File I/O + logger = setupLogger(config) + cache = newCache(config.CacheSize) + // Hard to control initialization order + // Can't handle errors properly + // Creates global state +} + +// ✅ GOOD: Explicit initialization +type App struct { + config *Config + logger *Logger + cache *Cache +} + +func NewApp() (*App, error) { + config, err := loadConfig() + if err != nil { + return nil, fmt.Errorf("load config: %w", err) + } + + logger, err := setupLogger(config) + if err != nil { + return nil, fmt.Errorf("setup logger: %w", err) + } + + cache := newCache(config.CacheSize) + + return &App{ + config: config, + logger: logger, + cache: cache, + }, nil +} + +func main() { + app, err := NewApp() + if err != nil { + log.Fatal(err) + } + defer app.Close() + + app.Run() +} +``` + +**Legitimate uses of init():** +- Registering drivers: `database/sql` +- Setting up package-level constants +- One-time computations with no side effects + +--- + +## 4. Ignoring Errors + +**Problem:** Using blank identifier `_` for errors or not checking them. + +```go +// ❌ BAD: Ignoring errors +file, _ := os.Open("config.json") +defer file.Close() +json.NewDecoder(file).Decode(&config) + +// Potential panic if file is nil! + +// ❌ BAD: Silent failures +func saveUser(user *User) { + _ = db.Save(user) // Error lost +} + +// ✅ GOOD: Always check errors +file, err := os.Open("config.json") +if err != nil { + return fmt.Errorf("failed to open config: %w", err) +} +defer file.Close() + +if err := json.NewDecoder(file).Decode(&config); err != nil { + return fmt.Errorf("failed to decode config: %w", err) +} + +// ✅ GOOD: At minimum, log errors +func saveUser(user *User) error { + if err := db.Save(user); err != nil { + log.Printf("WARNING: failed to save user %s: %v", user.ID, err) + return err + } + return nil +} +``` + +**Exception:** Explicitly documented intentional ignore +```go +// Ignore error because fallback is acceptable +_ = cache.Set(key, value) // Cache miss is acceptable + +// But better: +if err := cache.Set(key, value); err != nil { + log.Printf("cache set failed (non-critical): %v", err) +} +``` + +--- + +## 5. Goroutine Leaks + +**Problem:** Starting goroutines without termination mechanism. + +```go +// ❌ BAD: No way to stop +func streamData() <-chan Data { + ch := make(chan Data) + go func() { + for { + ch <- fetchData() // Runs forever + time.Sleep(time.Second) + } + }() + return ch +} + +// If consumer stops reading, goroutine blocks forever + +// ✅ GOOD: Context-aware cancellation +func streamData(ctx context.Context) <-chan Data { + ch := make(chan Data) + go func() { + defer close(ch) + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + select { + case ch <- fetchData(): + case <-ctx.Done(): + return + } + case <-ctx.Done(): + return + } + } + }() + return ch +} + +// Usage +ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) +defer cancel() + +for data := range streamData(ctx) { + process(data) +} +``` + +**Common leak patterns:** +```go +// ❌ Unbuffered channel with no receiver +ch := make(chan int) +go func() { + ch <- 1 // Blocks forever if no receiver +}() + +// ❌ Infinite loop with no exit +go func() { + for { + work() // No way to stop + } +}() + +// ❌ Blocked on channel send +go func() { + for item := range input { + output <- process(item) // Blocks if output is full + } +}() +``` + +--- + +## 6. Panic for Control Flow + +**Problem:** Using `panic` for business logic errors. + +```go +// ❌ BAD: Panic for expected errors +func GetUser(id string) *User { + user := db.Find(id) + if user == nil { + panic("user not found") // Don't use panic! + } + return user +} + +// ✅ GOOD: Return errors +func GetUser(id string) (*User, error) { + user := db.Find(id) + if user == nil { + return nil, ErrUserNotFound + } + return user, nil +} + +// Panic is only for: +// 1. Unrecoverable programmer errors +func validateConfig(cfg *Config) { + if cfg == nil { + panic("nil config") // Programmer error + } +} + +// 2. Init-time failures +func init() { + if err := loadCriticalData(); err != nil { + panic(fmt.Sprintf("failed to load critical data: %v", err)) + } +} +``` + +--- + +## 7. Premature Interface Abstraction + +**Problem:** Creating interfaces before they're needed. + +```go +// ❌ BAD: Interface with single implementation +package user + +type Repository interface { + Get(id string) (*User, error) + Create(user *User) error + Update(user *User) error + Delete(id string) error +} + +type PostgresRepository struct { + db *sql.DB +} +// Only implementation in entire codebase + +// ✅ GOOD: Start with concrete type +package user + +type Repository struct { + db *sql.DB +} + +func (r *Repository) Get(id string) (*User, error) { ... } +func (r *Repository) Create(user *User) error { ... } + +// Extract interface when you have 2+ implementations +// or when consumer package needs to define it +``` + +**When to create interfaces:** +- Consumer package needs to mock dependency (testing) +- 2+ implementations exist +- Defining contract between packages +- Standard library interfaces (`io.Reader`, `http.Handler`) + +--- + +## 8. Large Interfaces + +**Problem:** Creating interfaces with many methods. + +```go +// ❌ BAD: God interface +type UserManager interface { + Create(user *User) error + Update(user *User) error + Delete(id string) error + Get(id string) (*User, error) + List(filters Filters) ([]*User, error) + Authenticate(email, password string) (*User, error) + ResetPassword(id string) error + SendWelcomeEmail(id string) error + UpdatePreferences(id string, prefs Preferences) error +} + +// Hard to mock, violates interface segregation + +// ✅ GOOD: Small, focused interfaces +type UserReader interface { + Get(id string) (*User, error) + List(filters Filters) ([]*User, error) +} + +type UserWriter interface { + Create(user *User) error + Update(user *User) error + Delete(id string) error +} + +type Authenticator interface { + Authenticate(email, password string) (*User, error) +} + +// Services depend only on what they need +type ProfileService struct { + users UserReader // Only needs read access +} + +type AdminService struct { + users UserWriter // Only needs write access +} +``` + +**Go's guideline:** Interfaces should have 1-3 methods + +--- + +## 9. Context Misuse + +**Problem:** Not passing context or creating long-lived contexts. + +```go +// ❌ BAD: No context +func FetchData() ([]byte, error) { + resp, err := http.Get("http://api.example.com") + // Can't cancel, no timeout +} + +// ❌ BAD: Creating context in function +func FetchData() ([]byte, error) { + ctx := context.Background() // Should be passed in + req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) + // ... +} + +// ❌ BAD: Storing context in struct +type Fetcher struct { + ctx context.Context // Don't store context +} + +// ✅ GOOD: Pass context as first parameter +func FetchData(ctx context.Context) ([]byte, error) { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return io.ReadAll(resp.Body) +} + +// Usage with timeout +ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) +defer cancel() + +data, err := FetchData(ctx) +``` + +**Context rules:** +1. First parameter: `func DoSomething(ctx context.Context, ...)` +2. Never store in structs +3. Pass down the call chain +4. Create once at top level (main, HTTP handler) + +--- + +## 10. Mutex in Value Types + +**Problem:** Copying structs that contain mutexes. + +```go +// ❌ BAD: Mutex in value type +type Cache struct { + mu sync.Mutex // Copied when Cache is copied! + items map[string]interface{} +} + +func (c Cache) Get(key string) interface{} { // Value receiver + c.mu.Lock() // Locking a copy! + defer c.mu.Unlock() + return c.items[key] +} + +cache := Cache{items: make(map[string]interface{})} +cache2 := cache // Copies the mutex - breaks thread safety! + +// ✅ GOOD: Pointer receiver for types with mutexes +type Cache struct { + mu sync.Mutex + items map[string]interface{} +} + +func (c *Cache) Get(key string) interface{} { // Pointer receiver + c.mu.Lock() + defer c.mu.Unlock() + return c.items[key] +} + +// Or use sync.RWMutex for better read performance +type Cache struct { + mu sync.RWMutex + items map[string]interface{} +} + +func (c *Cache) Get(key string) interface{} { + c.mu.RLock() + defer c.mu.RUnlock() + return c.items[key] +} +``` + +**Rule:** Types containing sync primitives must use pointer receivers + +--- + +## 11. String Concatenation in Loops + +**Problem:** Using `+` for string building in loops. + +```go +// ❌ BAD: O(n²) performance +func buildQuery(columns []string) string { + query := "SELECT " + for i, col := range columns { + query += col // Creates new string each iteration + if i < len(columns)-1 { + query += ", " + } + } + return query + " FROM users" +} + +// ✅ GOOD: Use strings.Builder +func buildQuery(columns []string) string { + var b strings.Builder + b.WriteString("SELECT ") + + for i, col := range columns { + b.WriteString(col) + if i < len(columns)-1 { + b.WriteString(", ") + } + } + + b.WriteString(" FROM users") + return b.String() +} + +// Or strings.Join for simple cases +func buildQuery(columns []string) string { + return "SELECT " + strings.Join(columns, ", ") + " FROM users" +} +``` + +--- + +## 12. Not Closing Resources + +**Problem:** Forgetting to close files, connections, response bodies. + +```go +// ❌ BAD: Resource leak +func readConfig() (*Config, error) { + file, err := os.Open("config.json") + if err != nil { + return nil, err + } + // Missing: defer file.Close() + + var cfg Config + err = json.NewDecoder(file).Decode(&cfg) + return &cfg, err +} + +// ❌ BAD: Not checking Close error +defer file.Close() // Error ignored + +// ✅ GOOD: Always defer Close +func readConfig() (*Config, error) { + file, err := os.Open("config.json") + if err != nil { + return nil, err + } + defer file.Close() // Guaranteed cleanup + + var cfg Config + if err := json.NewDecoder(file).Decode(&cfg); err != nil { + return nil, fmt.Errorf("decode config: %w", err) + } + + return &cfg, nil +} + +// ✅ GOOD: Check Close error when it matters +func writeData(data []byte) (err error) { + file, err := os.Create("output.dat") + if err != nil { + return err + } + + defer func() { + if cerr := file.Close(); cerr != nil && err == nil { + err = cerr // Return close error if no other error + } + }() + + _, err = file.Write(data) + return err +} +``` + +**Common resources to close:** +- `*os.File` +- `*sql.Rows` +- `http.Response.Body` +- Network connections +- Custom types with `Close()` method + +--- + +## 13. Testing Anti-Patterns + +### Not Using Table-Driven Tests + +```go +// ❌ BAD: Separate test for each case +func TestAdd_PositiveNumbers(t *testing.T) { + result := Add(1, 2) + if result != 3 { + t.Errorf("expected 3, got %d", result) + } +} + +func TestAdd_NegativeNumbers(t *testing.T) { + result := Add(-1, -1) + if result != -2 { + t.Errorf("expected -2, got %d", result) + } +} + +// ✅ GOOD: Table-driven +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + want int + }{ + {"positive", 1, 2, 3}, + {"negative", -1, -1, -2}, + {"zero", 0, 0, 0}, + {"mixed", 5, -3, 2}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Add(tt.a, tt.b) + if got != tt.want { + t.Errorf("got %d, want %d", got, tt.want) + } + }) + } +} +``` + +### Testing Implementation Instead of Behavior + +```go +// ❌ BAD: Testing internal implementation +func TestCache_InternalMap(t *testing.T) { + cache := NewCache() + cache.items["key"] = "value" // Accessing internal field + + if len(cache.items) != 1 { + t.Error("expected 1 item in map") + } +} + +// ✅ GOOD: Test public behavior +func TestCache_SetAndGet(t *testing.T) { + cache := NewCache() + cache.Set("key", "value") + + got, ok := cache.Get("key") + if !ok { + t.Fatal("expected key to exist") + } + if got != "value" { + t.Errorf("got %v, want %v", got, "value") + } +} +``` + +--- + +## Quick Anti-Pattern Checklist + +❌ **AVOID:** +- Embedding for inheritance +- Global mutable state +- Complex `init()` functions +- Ignoring errors with `_` +- Starting goroutines without cancellation +- Using `panic` for business logic +- Interfaces before you need them +- Large interfaces (>3 methods) +- Not passing `context.Context` +- Storing mutexes in value types +- String concatenation in loops +- Not closing resources +- Separate tests instead of table-driven + +✅ **DO:** +- Use composition explicitly +- Inject dependencies +- Return errors from constructors +- Always check errors +- Use `context.Context` for cancellation +- Return errors, not panic +- Extract interfaces when needed +- Keep interfaces small (1-3 methods) +- Pass context as first parameter +- Use pointer receivers for mutex types +- Use `strings.Builder` for loops +- `defer` resource cleanup +- Write table-driven tests + +--- + +This anti-patterns guide helps you avoid common pitfalls when transitioning from OOP to Go or applying design patterns incorrectly. diff --git a/src/plugins/golang-design-pattern/snippets/references/patterns/full-patterns-guide.txt b/src/plugins/golang-design-pattern/snippets/references/patterns/full-patterns-guide.txt new file mode 100755 index 0000000..503888a --- /dev/null +++ b/src/plugins/golang-design-pattern/snippets/references/patterns/full-patterns-guide.txt @@ -0,0 +1,779 @@ +# Complete Go Design Patterns Reference + +This document provides comprehensive explanations, examples, and use cases for all design patterns adapted to Go. + +**Note:** This is reference material. For quick guidance, see the main `../SKILL.md`. + +--- + +## Table of Contents + +1. [Creational Patterns](#creational-patterns) +2. [Structural Patterns](#structural-patterns) +3. [Behavioral Patterns](#behavioral-patterns) +4. [Concurrency Patterns](#concurrency-patterns) +5. [Error Handling Patterns](#error-handling-patterns) +6. [Testing Patterns](#testing-patterns) + +--- + +## Creational Patterns + +### Constructor Pattern + +**Purpose:** Create instances with validation and initialization logic. + +**When to use:** +- Object requires validation during creation +- Default values need to be set +- Resource allocation needed (connections, files) + +**Implementation:** + +```go +// Basic constructor +func NewClient(timeout time.Duration) (*Client, error) { + if timeout <= 0 { + return nil, fmt.Errorf("timeout must be positive") + } + + return &Client{ + timeout: timeout, + client: &http.Client{Timeout: timeout}, + }, nil +} + +// Constructor with compile-time interface check +var _ http.Handler = (*MyHandler)(nil) + +func NewHandler(logger Logger) *MyHandler { + return &MyHandler{logger: logger} +} +``` + +**Common pitfalls:** +- Using `panic` instead of returning errors +- Not validating inputs +- Creating constructors for zero-value-useful types unnecessarily + +--- + +### Functional Options Pattern + +**Purpose:** Provide flexible configuration without telescoping constructors. + +**When to use:** +- 5+ optional configuration parameters +- Need backward-compatible API evolution +- Clear default values exist + +**Implementation:** + +```go +type Server struct { + addr string + timeout time.Duration + maxConn int + logger Logger +} + +type Option func(*Server) + +func WithTimeout(d time.Duration) Option { + return func(s *Server) { s.timeout = d } +} + +func WithMaxConnections(n int) Option { + return func(s *Server) { s.maxConn = n } +} + +func WithLogger(l Logger) Option { + return func(s *Server) { s.logger = l } +} + +func NewServer(addr string, opts ...Option) *Server { + s := &Server{ + addr: addr, + timeout: 30 * time.Second, // Sensible defaults + maxConn: 100, + logger: defaultLogger, + } + + for _, opt := range opts { + opt(s) + } + + return s +} + +// Usage +srv := NewServer(":8080", + WithTimeout(60 * time.Second), + WithMaxConnections(200), +) +``` + +**Advanced: Options with validation** + +```go +func WithTimeout(d time.Duration) Option { + return func(s *Server) error { + if d <= 0 { + return fmt.Errorf("timeout must be positive") + } + s.timeout = d + return nil + } +} + +// Signature changes to return error +type Option func(*Server) error + +func NewServer(addr string, opts ...Option) (*Server, error) { + s := &Server{/* defaults */} + + for _, opt := range opts { + if err := opt(s); err != nil { + return nil, fmt.Errorf("option failed: %w", err) + } + } + + return s, nil +} +``` + +**When NOT to use:** +- Simple constructors with <3 parameters +- When all parameters are required +- Struct literal initialization works fine + +--- + +### Factory Pattern + +**Purpose:** Create objects based on runtime conditions without exposing creation logic. + +**Traditional OOP vs Go:** + +```go +// ❌ Traditional OOP style (don't do this in Go) +type LoggerFactory struct{} + +func (f *LoggerFactory) CreateLogger(typ string) Logger { + // Factory class is unnecessary +} + +// ✅ Go style: simple function +func NewLogger(typ string) (Logger, error) { + switch typ { + case "file": + return &FileLogger{}, nil + case "console": + return &ConsoleLogger{}, nil + case "remote": + return &RemoteLogger{}, nil + default: + return nil, fmt.Errorf("unknown logger type: %s", typ) + } +} + +// ✅ Factory with configuration +func NewDatabase(cfg Config) (Database, error) { + switch cfg.Driver { + case "postgres": + return postgres.New(cfg.DSN) + case "mysql": + return mysql.New(cfg.DSN) + default: + return nil, fmt.Errorf("unsupported driver: %s", cfg.Driver) + } +} +``` + +**Registry pattern variation:** + +```go +type Factory func(config Config) (Plugin, error) + +var registry = make(map[string]Factory) + +func Register(name string, factory Factory) { + registry[name] = factory +} + +func Create(name string, cfg Config) (Plugin, error) { + factory, ok := registry[name] + if !ok { + return nil, fmt.Errorf("unknown plugin: %s", name) + } + return factory(cfg) +} + +// Plugins register themselves +func init() { + Register("aws", newAWSPlugin) + Register("gcp", newGCPPlugin) +} +``` + +--- + +### Builder Pattern + +**Purpose:** Construct complex objects step-by-step with validation. + +**When to use:** +- Object construction requires multiple steps +- Need to enforce construction order +- Want to validate intermediate states + +**Implementation:** + +```go +type QueryBuilder struct { + table string + columns []string + where []string + orderBy string + limit int + err error // Accumulate errors +} + +func NewQueryBuilder(table string) *QueryBuilder { + return &QueryBuilder{table: table} +} + +func (b *QueryBuilder) Select(columns ...string) *QueryBuilder { + if b.err != nil { + return b + } + if len(columns) == 0 { + b.err = fmt.Errorf("no columns specified") + return b + } + b.columns = columns + return b +} + +func (b *QueryBuilder) Where(condition string) *QueryBuilder { + if b.err != nil { + return b + } + if condition == "" { + b.err = fmt.Errorf("empty where condition") + return b + } + b.where = append(b.where, condition) + return b +} + +func (b *QueryBuilder) OrderBy(column string) *QueryBuilder { + if b.err != nil { + return b + } + b.orderBy = column + return b +} + +func (b *QueryBuilder) Limit(n int) *QueryBuilder { + if b.err != nil { + return b + } + if n <= 0 { + b.err = fmt.Errorf("limit must be positive") + return b + } + b.limit = n + return b +} + +func (b *QueryBuilder) Build() (string, error) { + if b.err != nil { + return "", b.err + } + + if b.table == "" { + return "", fmt.Errorf("table name required") + } + + // Build query string + cols := "*" + if len(b.columns) > 0 { + cols = strings.Join(b.columns, ", ") + } + + query := fmt.Sprintf("SELECT %s FROM %s", cols, b.table) + + if len(b.where) > 0 { + query += " WHERE " + strings.Join(b.where, " AND ") + } + + if b.orderBy != "" { + query += " ORDER BY " + b.orderBy + } + + if b.limit > 0 { + query += fmt.Sprintf(" LIMIT %d", b.limit) + } + + return query, nil +} + +// Usage +query, err := NewQueryBuilder("users"). + Select("id", "email", "name"). + Where("age > 18"). + Where("active = true"). + OrderBy("created_at DESC"). + Limit(10). + Build() + +if err != nil { + log.Fatal(err) +} +fmt.Println(query) +``` + +**When to use Builder vs Functional Options:** +- **Builder:** Complex construction with validation at each step +- **Functional Options:** Configuration with independent optional parameters + +--- + +### Singleton Pattern (Avoid!) + +**Purpose:** Ensure only one instance exists. + +**Why to avoid:** Global state makes testing difficult and creates hidden dependencies. + +**If you must use it:** + +```go +// Thread-safe lazy initialization +var ( + instance *Config + once sync.Once +) + +func GetConfig() *Config { + once.Do(func() { + instance = &Config{ + // Load from file/env + } + }) + return instance +} + +// ✅ BETTER: Dependency injection +type Service struct { + config *Config +} + +func NewService(cfg *Config) *Service { + return &Service{config: cfg} +} + +// In main() +func main() { + cfg := loadConfig() // Create once + + userService := NewUserService(cfg) + orderService := NewOrderService(cfg) + // Explicit dependencies +} +``` + +**Legitimate use cases:** +- Application configuration loaded once at startup +- Global logger (though context-based logging is better) + +--- + +## Structural Patterns + +### Adapter Pattern + +**Purpose:** Convert one interface to another to make incompatible interfaces work together. + +**When to use:** +- Integrating third-party libraries +- Wrapping legacy code +- Converting between different data representations + +**Implementation:** + +```go +// Your interface +type Storage interface { + Save(ctx context.Context, key string, value []byte) error + Load(ctx context.Context, key string) ([]byte, error) + Delete(ctx context.Context, key string) error +} + +// External library (Redis client) +type RedisClient struct { + client *redis.Client +} + +func (r *RedisClient) Set(key string, val []byte, ttl time.Duration) error { + return r.client.Set(context.Background(), key, val, ttl).Err() +} + +func (r *RedisClient) Get(key string) ([]byte, error) { + return r.client.Get(context.Background(), key).Bytes() +} + +func (r *RedisClient) Del(key string) error { + return r.client.Del(context.Background(), key).Err() +} + +// Adapter +type RedisStorageAdapter struct { + client *RedisClient + ttl time.Duration +} + +func NewRedisStorageAdapter(client *RedisClient, ttl time.Duration) *RedisStorageAdapter { + return &RedisStorageAdapter{ + client: client, + ttl: ttl, + } +} + +func (a *RedisStorageAdapter) Save(ctx context.Context, key string, value []byte) error { + return a.client.Set(key, value, a.ttl) +} + +func (a *RedisStorageAdapter) Load(ctx context.Context, key string) ([]byte, error) { + return a.client.Get(key) +} + +func (a *RedisStorageAdapter) Delete(ctx context.Context, key string) error { + return a.client.Del(key) +} + +// Compile-time interface verification +var _ Storage = (*RedisStorageAdapter)(nil) + +// Usage +storage := NewRedisStorageAdapter(redisClient, 5*time.Minute) +err := storage.Save(ctx, "user:123", userData) +``` + +**Best practices:** +- Keep adapters thin - only translation, no business logic +- Use compile-time interface checks: `var _ Interface = (*Adapter)(nil)` +- Document what's being adapted and why + +--- + +### Decorator Pattern + +**Purpose:** Add behavior to objects dynamically without modifying them. + +**Go's most common use:** HTTP middleware + +**Implementation:** + +```go +// Handler signature +type Handler func(http.ResponseWriter, *http.Request) + +// Decorator: Logging +func WithLogging(next Handler) Handler { + return func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + log.Printf("Started %s %s", r.Method, r.URL.Path) + + next(w, r) + + log.Printf("Completed %s in %v", r.URL.Path, time.Since(start)) + } +} + +// Decorator: Authentication +func WithAuth(next Handler) Handler { + return func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // Validate token + if !isValidToken(token) { + http.Error(w, "Invalid token", http.StatusForbidden) + return + } + + next(w, r) + } +} + +// Decorator: Rate limiting (with state) +func WithRateLimit(limit int) func(Handler) Handler { + limiter := rate.NewLimiter(rate.Limit(limit), limit) + + return func(next Handler) Handler { + return func(w http.ResponseWriter, r *http.Request) { + if !limiter.Allow() { + http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) + return + } + next(w, r) + } + } +} + +// Decorator: Error recovery +func WithRecovery(next Handler) Handler { + return func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + log.Printf("Panic: %v\n%s", err, debug.Stack()) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + }() + + next(w, r) + } +} + +// Stack decorators (applied bottom-up) +handler := WithRecovery( + WithLogging( + WithAuth( + WithRateLimit(100)( + actualHandler, + ), + ), + ), +) + +// Execution order: Recovery → Logging → Auth → RateLimit → Handler +``` + +**io.Reader/Writer decorators:** + +```go +// Counting decorator +type CountingReader struct { + reader io.Reader + count int64 +} + +func (r *CountingReader) Read(p []byte) (n int, err error) { + n, err = r.reader.Read(p) + atomic.AddInt64(&r.count, int64(n)) + return n, err +} + +func (r *CountingReader) BytesRead() int64 { + return atomic.LoadInt64(&r.count) +} + +// Compression decorator +type GzipReader struct { + reader io.Reader + gzReader *gzip.Reader +} + +func NewGzipReader(r io.Reader) (*GzipReader, error) { + gr, err := gzip.NewReader(r) + if err != nil { + return nil, err + } + return &GzipReader{reader: r, gzReader: gr}, nil +} + +func (g *GzipReader) Read(p []byte) (n int, err error) { + return g.gzReader.Read(p) +} + +func (g *GzipReader) Close() error { + return g.gzReader.Close() +} + +// Usage: Stack decorators +file, _ := os.Open("data.txt.gz") +gzipReader, _ := NewGzipReader(file) +counter := &CountingReader{reader: gzipReader} + +io.Copy(os.Stdout, counter) +fmt.Printf("Read %d bytes\n", counter.BytesRead()) +``` + +--- + +### Proxy Pattern + +**Purpose:** Control access to an object through a surrogate. + +**Use cases:** +- Lazy initialization +- Access control +- Caching +- Logging + +**Implementation: Caching Proxy** + +```go +type Database interface { + Query(ctx context.Context, sql string) ([]Row, error) + Execute(ctx context.Context, sql string) error +} + +type CachingDatabaseProxy struct { + db Database + cache map[string]cachedResult + mu sync.RWMutex + ttl time.Duration +} + +type cachedResult struct { + data []Row + timestamp time.Time +} + +func NewCachingProxy(db Database, ttl time.Duration) *CachingDatabaseProxy { + return &CachingDatabaseProxy{ + db: db, + cache: make(map[string]cachedResult), + ttl: ttl, + } +} + +func (p *CachingDatabaseProxy) Query(ctx context.Context, sql string) ([]Row, error) { + // Try cache first + p.mu.RLock() + if cached, ok := p.cache[sql]; ok { + if time.Since(cached.timestamp) < p.ttl { + p.mu.RUnlock() + return cached.data, nil // Cache hit + } + } + p.mu.RUnlock() + + // Cache miss - query database + rows, err := p.db.Query(ctx, sql) + if err != nil { + return nil, err + } + + // Update cache + p.mu.Lock() + p.cache[sql] = cachedResult{ + data: rows, + timestamp: time.Now(), + } + p.mu.Unlock() + + return rows, nil +} + +func (p *CachingDatabaseProxy) Execute(ctx context.Context, sql string) error { + // Invalidate cache on writes + p.mu.Lock() + p.cache = make(map[string]cachedResult) // Simple invalidation + p.mu.Unlock() + + return p.db.Execute(ctx, sql) +} + +// Verify interface +var _ Database = (*CachingDatabaseProxy)(nil) +``` + +**Implementation: Lazy Loading Proxy** + +```go +type ConnectionProxy struct { + once sync.Once + conn *sql.DB + err error + dsn string +} + +func NewConnectionProxy(dsn string) *ConnectionProxy { + return &ConnectionProxy{dsn: dsn} +} + +func (p *ConnectionProxy) getConnection() (*sql.DB, error) { + p.once.Do(func() { + p.conn, p.err = sql.Open("postgres", p.dsn) + }) + return p.conn, p.err +} + +func (p *ConnectionProxy) Query(ctx context.Context, query string) (*sql.Rows, error) { + conn, err := p.getConnection() + if err != nil { + return nil, fmt.Errorf("connection failed: %w", err) + } + return conn.QueryContext(ctx, query) +} +``` + +--- + +### Composite Pattern + +**Purpose:** Treat individual objects and compositions uniformly. + +**Go adaptation:** Interfaces with recursive structures + +```go +// Component interface +type FileSystemNode interface { + Name() string + Size() int64 + IsDir() bool +} + +// Leaf: File +type File struct { + name string + size int64 +} + +func (f *File) Name() string { return f.name } +func (f *File) Size() int64 { return f.size } +func (f *File) IsDir() bool { return false } + +// Composite: Directory +type Directory struct { + name string + children []FileSystemNode +} + +func (d *Directory) Name() string { return d.name } +func (d *Directory) IsDir() bool { return true } + +func (d *Directory) Size() int64 { + var total int64 + for _, child := range d.children { + total += child.Size() + } + return total +} + +func (d *Directory) Add(node FileSystemNode) { + d.children = append(d.children, node) +} + +// Usage +root := &Directory{name: "/"} +home := &Directory{name: "home"} +root.Add(home) + +home.Add(&File{name: "doc.txt", size: 1024}) +home.Add(&File{name: "image.png", size: 2048}) + +fmt.Printf("Total size: %d bytes\n", root.Size()) // 3072 +``` + +--- + +[Content continues with Behavioral Patterns, Concurrency Patterns, Error Handling, and Testing sections - truncated for length] + +This reference is meant to be comprehensive. For day-to-day work, use the quick reference in `../SKILL.md`. diff --git a/src/plugins/golang-design-pattern/snippets/scripts/detect-antipatterns.sh b/src/plugins/golang-design-pattern/snippets/scripts/detect-antipatterns.sh new file mode 100755 index 0000000..2620231 --- /dev/null +++ b/src/plugins/golang-design-pattern/snippets/scripts/detect-antipatterns.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# Pattern Detector: Scan Go files for common anti-patterns + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TARGET_DIR="${1:-.}" + +echo "🔍 Scanning Go files in: $TARGET_DIR" +echo "" + +# Initialize counters +issues_found=0 + +# Check for global variables +echo "Checking for global mutable state..." +if grep -rn --include="*.go" "^var.*=.*\(sql.DB\|http.Client\|redis.Client\)" "$TARGET_DIR" 2>/dev/null; then + echo -e "${RED}⚠ Found global database/client connections${NC}" + echo " Consider dependency injection instead" + ((issues_found++)) +fi + +# Check for init() usage +echo "" +echo "Checking for init() functions..." +init_count=$(grep -rn --include="*.go" "^func init()" "$TARGET_DIR" 2>/dev/null | wc -l) +if [ "$init_count" -gt 0 ]; then + echo -e "${YELLOW}⚠ Found $init_count init() functions${NC}" + grep -rn --include="*.go" "^func init()" "$TARGET_DIR" 2>/dev/null || true + echo " Consider explicit initialization in constructors" + ((issues_found++)) +fi + +# Check for ignored errors +echo "" +echo "Checking for ignored errors..." +if grep -rn --include="*.go" '^\s*_\s*=.*\(Error\|err\)' "$TARGET_DIR" 2>/dev/null; then + echo -e "${RED}⚠ Found ignored errors with blank identifier${NC}" + echo " Always check and handle errors" + ((issues_found++)) +fi + +# Check for panic in non-init code +echo "" +echo "Checking for panic usage..." +if grep -rn --include="*.go" 'panic(' "$TARGET_DIR" 2>/dev/null | grep -v "func init()" | head -5; then + echo -e "${YELLOW}⚠ Found panic() outside init()${NC}" + echo " Consider returning errors instead" + ((issues_found++)) +fi + +# Check for goroutines without context +echo "" +echo "Checking for goroutines without context..." +if grep -rn --include="*.go" 'go func()' "$TARGET_DIR" 2>/dev/null | grep -v "context.Context" | head -5; then + echo -e "${YELLOW}⚠ Found goroutines potentially without cancellation${NC}" + echo " Pass context.Context for proper cancellation" + ((issues_found++)) +fi + +# Check for large interfaces (>5 methods) +echo "" +echo "Checking for large interfaces..." +while IFS= read -r file; do + awk ' + /^type .* interface {/ { + interface_name = $2 + method_count = 0 + in_interface = 1 + } + in_interface && /^[[:space:]]+[A-Z].*\(/ { + method_count++ + } + in_interface && /^}/ { + if (method_count > 5) { + print FILENAME ":" interface_name " has " method_count " methods (consider splitting)" + } + in_interface = 0 + } + ' "$file" +done < <(find "$TARGET_DIR" -name "*.go" 2>/dev/null) + +# Summary +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +if [ $issues_found -eq 0 ]; then + echo -e "${GREEN}✓ No obvious anti-patterns detected${NC}" +else + echo -e "${RED}Found $issues_found potential issues${NC}" + echo "" + echo "Review the warnings above and consult:" + echo " - references/anti-patterns.md for detailed explanations" + echo " - SKILL.md for idiomatic alternatives" +fi +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" diff --git a/src/plugins/pinchtab/index.ts b/src/plugins/pinchtab/index.ts new file mode 100644 index 0000000..6b53ff4 --- /dev/null +++ b/src/plugins/pinchtab/index.ts @@ -0,0 +1,3 @@ +import { createStaticSkillPlugin } from "../../shared/static-skill.js"; + +export const pinchtabPlugin = createStaticSkillPlugin("pinchtab", "The pinchtab skill."); diff --git a/src/plugins/pinchtab/snippets/SKILL.txt b/src/plugins/pinchtab/snippets/SKILL.txt new file mode 100644 index 0000000..dc52671 --- /dev/null +++ b/src/plugins/pinchtab/snippets/SKILL.txt @@ -0,0 +1,494 @@ +--- +name: pinchtab +description: "Use this skill when a task needs browser automation through PinchTab: open a website, inspect interactive elements, click through flows, fill out forms, scrape page text, log into sites with a persistent profile, export screenshots or PDFs, manage multiple browser instances, or fall back to the HTTP API when the CLI is unavailable. Prefer this skill for token-efficient browser work driven by stable accessibility refs such as `e5` and `e12`." +metadata: + openclaw: + requires: + bins: + - pinchtab + anyBins: + - google-chrome + - google-chrome-stable + - chromium + - chromium-browser + env: + - PINCHTAB_TOKEN + - PINCHTAB_CONFIG + homepage: https://github.com/pinchtab/pinchtab + install: + - kind: brew + formula: pinchtab/tap/pinchtab + bins: [pinchtab] + - kind: go + package: github.com/pinchtab/pinchtab/cmd/pinchtab@latest + bins: [pinchtab] +--- + +# Browser Automation with PinchTab + +PinchTab gives agents a browser they can drive through stable accessibility refs, low-token text extraction, and persistent profiles or instances. Treat it as a CLI-first browser skill; use the HTTP API only when the CLI is unavailable or you need profile-management routes that do not exist in the CLI yet. + +Preferred tool surface: + +- Use `pinchtab` CLI commands first. +- Use `curl` for profile-management routes or non-shell/API fallback flows. +- Use `jq` only when you need structured parsing from JSON responses. + +## Safety Defaults + +- Default to `http://localhost` targets. Only use a remote PinchTab server when the user explicitly provides it and, if needed, a token. +- Prefer read-only operations first: `text`, `snap -i -c`, `snap -d`, `find`, `click`, `fill`, `type`, `press`, `select`, `hover`, `scroll`. +- Do not evaluate arbitrary JavaScript unless a simpler PinchTab command cannot answer the question. +- Do not upload local files unless the user explicitly names the file to upload and the destination flow requires it. +- Do not save screenshots, PDFs, or downloads to arbitrary paths. Use a user-specified path or a safe temporary/workspace path. +- Never use PinchTab to inspect unrelated local files, browser secrets, stored credentials, or system configuration outside the task. + +## Core Workflow + +Every PinchTab automation follows this pattern: + +1. Ensure the correct server, profile, or instance is available for the task. +2. Navigate with `pinchtab nav ` or `pinchtab instance navigate `. +3. Observe with `pinchtab snap -i -c`, `pinchtab snap --text`, or `pinchtab text`, then collect the current refs such as `e5`. +4. Interact with those fresh refs using `click`, `fill`, `type`, `press`, `select`, `hover`, or `scroll`. +5. Re-snapshot or re-read text after any navigation, submit, modal open, accordion expand, or other DOM-changing action. + +Rules: + +- Never act on stale refs after the page changes. +- Default to `pinchtab text` when you need content, not layout. +- Default to `pinchtab snap -i -c` when you need actionable elements. +- Use screenshots only for visual verification, UI diffs, or debugging. +- Start multi-site or parallel work by choosing the right instance or profile first. + +## Selectors + +PinchTab uses a unified selector system. Any command that targets an element accepts these formats: + +| Selector | Example | Resolves via | +|---|---|---| +| Ref | `e5` | Snapshot cache (fastest) | +| CSS | `#login`, `.btn`, `[data-testid="x"]` | `document.querySelector` | +| XPath | `xpath://button[@id="submit"]` | CDP search | +| Text | `text:Sign In` | Visible text match | +| Semantic | `find:login button` | Natural language query via `/find` | + +Auto-detection: bare `e5` → ref, `#id` / `.class` / `[attr]` → CSS, `//path` → XPath. Use explicit prefixes (`css:`, `xpath:`, `text:`, `find:`) when auto-detection is ambiguous. + +```bash +pinchtab click e5 # ref +pinchtab click "#submit" # CSS (auto-detected) +pinchtab click "text:Sign In" # text match +pinchtab click "xpath://button[@type]" # XPath +pinchtab fill "#email" "user@test.com" # CSS +pinchtab fill e3 "user@test.com" # ref +``` + +The same syntax works in the HTTP API via the `selector` field: + +```json +{"kind": "click", "selector": "text:Sign In"} +{"kind": "fill", "selector": "#email", "text": "user@test.com"} +{"kind": "click", "selector": "e5"} +``` + +Legacy `ref` field is still accepted for backward compatibility. + +## Command Chaining + +Use `&&` only when you do not need to inspect intermediate output before deciding the next step. + +Good: + +```bash +pinchtab nav https://example.com && pinchtab snap -i -c +pinchtab click --wait-nav e5 && pinchtab snap -i -c +pinchtab nav https://example.com --block-images && pinchtab text +``` + +Run commands separately when you must read the snapshot output first: + +```bash +pinchtab nav https://example.com +pinchtab snap -i -c +# Read refs, choose the correct e# +pinchtab click e7 +pinchtab snap -i -c +``` + +## Challenge Solving + +PinchTab includes a pluggable solver framework that auto-detects and resolves browser challenges (Cloudflare Turnstile, CAPTCHAs, interstitials). Use this **after navigation** when the page shows a challenge instead of the expected content. + +**Important:** Solvers work best with `stealthLevel: "full"` in the PinchTab config (or `instanceDefaults.stealthLevel: "full"`). Full stealth mode patches CDP detection vectors, rotates fingerprints, and masks automation signals — all of which challenge providers like Cloudflare check before and after the checkbox click. Without full stealth, the solver may click correctly but the challenge can still fail fingerprint verification. + +```bash +# Auto-detect and solve any challenge on the current page +curl -X POST http://localhost:9867/solve \ + -H 'Content-Type: application/json' \ + -d '{"maxAttempts": 3, "timeout": 30000}' + +# Use a specific solver +curl -X POST http://localhost:9867/solve/cloudflare \ + -H 'Content-Type: application/json' \ + -d '{"maxAttempts": 3}' + +# Tab-scoped solve +curl -X POST http://localhost:9867/tabs/TAB_ID/solve \ + -H 'Content-Type: application/json' \ + -d '{}' + +# List available solvers +curl http://localhost:9867/solvers +``` + +**When to use solve:** + +- Page title is "Just a moment..." or similar challenge indicator +- `pinchtab text` returns empty or challenge-page text after navigation +- A Cloudflare Turnstile widget blocks the target content + +**Workflow pattern:** + +```bash +pinchtab nav https://protected-site.com +pinchtab text # Check if page loaded or shows challenge +# If challenge detected: +curl -X POST http://localhost:9867/solve \ + -H 'Content-Type: application/json' -d '{}' +pinchtab text # Verify: should now show real page content +``` + +**Response fields:** `solver` (which solver handled it), `solved` (bool), `challengeType` (e.g. "managed"), `attempts`, `title` (final page title). + +The auto-detect mode (`POST /solve` without specifying a solver) tries each registered solver in order and returns immediately with `solved: true, attempts: 0` if no challenge is present. This makes it safe to call speculatively after any navigation. + +## Handling Authentication and State + +Pick one of these five patterns before you start interacting with the site. + +### 1. One-off public browsing + +Use a temporary instance for public pages, scraping, or tasks that do not need login persistence. + +```bash +pinchtab instance start +pinchtab instances +# Point CLI commands at the instance port you want to use. +pinchtab --server http://localhost:9868 nav https://example.com +pinchtab --server http://localhost:9868 text +``` + +### 2. Reuse an existing named profile + +Use this for recurring tasks against the same authenticated site. + +```bash +pinchtab profiles +pinchtab instance start --profile work --mode headed +pinchtab --server http://localhost:9868 nav https://mail.google.com +``` + +If the login is already stored in that profile, you can switch to headless later: + +```bash +pinchtab instance stop inst_ea2e747f +pinchtab instance start --profile work --mode headless +``` + +### 3. Create a dedicated auth profile over HTTP + +Use this when you need a durable profile and it does not exist yet. + +```bash +curl -X POST http://localhost:9867/profiles \ + -H "Content-Type: application/json" \ + -d '{"name":"billing","description":"Billing portal automation","useWhen":"Use for billing tasks"}' + +curl -X POST http://localhost:9867/profiles/billing/start \ + -H "Content-Type: application/json" \ + -d '{"headless":false}' +``` + +Then target the returned port with `--server`. + +### 4. Human-assisted headed login, then agent reuse + +Use this for CAPTCHA, MFA, or first-time setup. + +```bash +pinchtab instance start --profile work --mode headed +# Human completes login in the visible Chrome window. +pinchtab --server http://localhost:9868 nav https://app.example.com/dashboard +pinchtab --server http://localhost:9868 snap -i -c +``` + +Once the session is stored, reuse the same profile for later tasks. + +### 5. Remote or non-shell agent with tokenized HTTP API + +Use this when the agent cannot call the CLI directly. + +```bash +curl http://localhost:9867/health +curl -X POST http://localhost:9867/profiles \ + -H "Content-Type: application/json" \ + -d '{"name":"work"}' +curl -X POST http://localhost:9867/instances/start \ + -H "Content-Type: application/json" \ + -d '{"profileId":"work","mode":"headless"}' +curl -X POST http://localhost:9868/action \ + -H "Content-Type: application/json" \ + -d '{"kind":"click","selector":"e5"}' +``` + +If the server is exposed beyond localhost, require a token and use a dedicated automation profile. See [TRUST.md](./TRUST.md) and [config.md](../../docs/reference/config.md). + +## Essential Commands + +### Server and targeting + +```bash +pinchtab server # Start server foreground +pinchtab daemon install # Install as system service +pinchtab health # Check server status +pinchtab instances # List running instances +pinchtab profiles # List available profiles +pinchtab --server http://localhost:9868 snap -i -c # Target specific instance +``` + +### Navigation and tabs + +```bash +pinchtab nav +pinchtab nav --new-tab +pinchtab nav --tab +pinchtab nav --block-images +pinchtab nav --block-ads +pinchtab back # Navigate back in history +pinchtab forward # Navigate forward +pinchtab reload # Reload current page +pinchtab tab # List tabs or focus by ID +pinchtab tab new +pinchtab tab close +pinchtab instance navigate +``` + +### Observation + +```bash +pinchtab snap +pinchtab snap -i # Interactive elements only +pinchtab snap -i -c # Interactive + compact +pinchtab snap -d # Diff from previous snapshot +pinchtab snap --selector # Scope to CSS selector +pinchtab snap --max-tokens # Token budget limit +pinchtab snap --text # Text output format +pinchtab text # Page text content +pinchtab text --raw # Raw text extraction +pinchtab find # Semantic element search +pinchtab find --ref-only # Return refs only +``` + +Guidance: + +- `snap -i -c` is the default for finding actionable refs. +- `snap -d` is the default follow-up snapshot for multi-step flows. +- `text` is the default for reading articles, dashboards, reports, or confirmation messages. +- `find --ref-only` is useful when the page is large and you already know the semantic target. + +### Interaction + +All interaction commands accept unified selectors (refs, CSS, XPath, text, semantic). See the Selectors section above. + +```bash +pinchtab click # Click element +pinchtab click --wait-nav # Click and wait for navigation +pinchtab click --x 100 --y 200 # Click by coordinates +pinchtab dblclick # Double-click element +pinchtab type # Type with keystrokes +pinchtab fill # Set value directly +pinchtab press # Press key (Enter, Tab, Escape...) +pinchtab hover # Hover element +pinchtab select # Select dropdown option +pinchtab scroll # Scroll element or page +``` + +Rules: + +- Prefer `fill` for deterministic form entry. +- Prefer `type` only when the site depends on keystroke events. +- Prefer `click --wait-nav` when a click is expected to navigate. +- Re-snapshot immediately after `click`, `press Enter`, `select`, or `scroll` if the UI can change. + +### Export, debug, and verification + +```bash +pinchtab screenshot +pinchtab screenshot -o /tmp/pinchtab-page.png # Format driven by extension +pinchtab screenshot -q 60 # JPEG quality +pinchtab pdf +pinchtab pdf -o /tmp/pinchtab-report.pdf +pinchtab pdf --landscape +``` + +### Advanced operations: explicit opt-in only + +Use these only when the task explicitly requires them and safer commands are insufficient. + +```bash +pinchtab eval "document.title" +pinchtab download -o /tmp/pinchtab-download.bin +pinchtab upload /absolute/path/provided-by-user.ext -s +``` + +Rules: + +- `eval` is for narrow, read-only DOM inspection unless the user explicitly asks for a page mutation. +- `download` should prefer a safe temporary or workspace path over an arbitrary filesystem location. +- `upload` requires a file path the user explicitly provided or clearly approved for the task. + +### HTTP API fallback + +```bash +curl -X POST http://localhost:9868/navigate \ + -H "Content-Type: application/json" \ + -d '{"url":"https://example.com"}' + +curl "http://localhost:9868/snapshot?filter=interactive&format=compact" + +curl -X POST http://localhost:9868/action \ + -H "Content-Type: application/json" \ + -d '{"kind":"fill","selector":"e3","text":"ada@example.com"}' + +curl http://localhost:9868/text + +## Instance-scoped solve (instance port, not server port) +curl -X POST http://localhost:9868/solve \ + -H "Content-Type: application/json" \ + -d '{"maxAttempts": 3}' + +curl http://localhost:9868/solvers +``` + +Use the API when: + +- the agent cannot shell out, +- profile creation or mutation is required, +- or you need explicit instance- and tab-scoped routes. + +## Common Patterns + +### Open a page and inspect actions + +```bash +pinchtab nav https://pinchtab.com && pinchtab snap -i -c +``` + +### Fill and submit a form + +```bash +pinchtab nav https://example.com/login +pinchtab snap -i -c +pinchtab fill e3 "user@example.com" +pinchtab fill e4 "correct horse battery staple" +pinchtab click --wait-nav e5 +pinchtab text +``` + +### Search, then extract the result page cheaply + +```bash +pinchtab nav https://example.com +pinchtab snap -i -c +pinchtab fill e2 "quarterly report" +pinchtab press Enter +pinchtab text +``` + +### Use diff snapshots in a multi-step flow + +```bash +pinchtab nav https://example.com/checkout +pinchtab snap -i -c +pinchtab click e8 +pinchtab snap -d -i -c +``` + +### Target elements without a snapshot + +When you know the page structure, skip the snapshot and use CSS or text selectors directly: + +```bash +pinchtab click "text:Accept Cookies" +pinchtab fill "#search" "quarterly report" +pinchtab click "xpath://button[@type='submit']" +``` + +### Navigate through a Cloudflare-protected site + +```bash +pinchtab nav https://protected-site.com +# Page may show CF challenge ("Just a moment...") +curl -X POST http://localhost:9867/solve \ + -H 'Content-Type: application/json' -d '{"maxAttempts": 3}' +# Now the real page is loaded — proceed normally +pinchtab snap -i -c +pinchtab text +``` + +### Bootstrap an authenticated profile + +```bash +pinchtab profiles +pinchtab instance start --profile work --mode headed +# Human signs in once. +pinchtab --server http://localhost:9868 text +``` + +### Run separate instances for separate sites + +```bash +pinchtab instance start --profile work --mode headless +pinchtab instance start --profile staging --mode headless +pinchtab instances +``` + +Then point each command stream at its own port using `--server`. + +## Security and Token Economy + +- Use a dedicated automation profile, not a daily browsing profile. +- If PinchTab is reachable off-machine, require a token and bind conservatively. +- Prefer `text`, `snap -i -c`, and `snap -d` before screenshots, PDFs, eval, downloads, or uploads. +- Use `--block-images` for read-heavy tasks that do not need visual assets. +- Stop or isolate instances when switching between unrelated accounts or environments. + +## Diffing and Verification + +- Use `pinchtab snap -d` after each state-changing action in long workflows. +- Use `pinchtab text` to confirm success messages, table updates, or navigation outcomes. +- Use `pinchtab screenshot` only when visual regressions, CAPTCHA, or layout-specific confirmation matters. +- If a ref disappears after a change, treat that as expected and fetch fresh refs instead of retrying the stale one. + +## Privacy and Security + +PinchTab is a fully open-source, local-only browser automation tool: + +- **Runs on localhost only.** The server binds to `127.0.0.1` by default. No external network calls are made by PinchTab itself. +- **No telemetry or analytics.** The binary makes zero outbound connections. +- **Single Go binary (~16 MB).** Fully verifiable — anyone can build from source at [github.com/pinchtab/pinchtab](https://github.com/pinchtab/pinchtab). +- **Local Chrome profiles.** Persistent profiles store cookies and sessions on your machine only. This enables agents to reuse authenticated sessions without re-entering credentials, similar to how a human reuses their browser profile. +- **Token-efficient by design.** Uses the accessibility tree (structured text) instead of screenshots, keeping agent context windows small. Comparable to Playwright but purpose-built for AI agents. +- **Multi-instance isolation.** Each browser instance runs in its own profile directory with tab-level locking for safe multi-agent use. + +## References + +- Command surface: [commands.md](../../docs/commands.md) +- CLI overview: [cli.md](../../docs/reference/cli.md) +- Profiles: [profiles.md](../../docs/reference/profiles.md) +- Instances: [instances.md](../../docs/reference/instances.md) +- Full API: [api.md](./references/api.md) +- Minimal env vars: [env.md](./references/env.md) +- Config reference: [config.md](../../docs/reference/config.md) +- Security model: [TRUST.md](./TRUST.md) diff --git a/src/plugins/pinchtab/snippets/TRUST.txt b/src/plugins/pinchtab/snippets/TRUST.txt new file mode 100644 index 0000000..d378a42 --- /dev/null +++ b/src/plugins/pinchtab/snippets/TRUST.txt @@ -0,0 +1,83 @@ +# Pinchtab Security & Trust + +**TL;DR**: Pinchtab is a local, sandboxed browser control tool. It does not phone home, steal credentials, or exfiltrate data. Source code is public; binaries are signed and published via GitHub. + +## What Pinchtab Does + +- Launches a Chrome browser (local, under your control) +- Exposes navigation, clicking, typing, and page inspection via HTTP API +- Extracts the page's accessibility tree (for AI agents) +- Runs screenshots, PDFs, and JavaScript evaluation + +High-risk operations such as JavaScript evaluation, local-file upload, and direct file writes should be treated as explicit opt-in actions for the current task, not the default workflow. + +**All of this stays local.** No telemetry. No external API calls (except to sites you navigate to). + +## What Pinchtab Does NOT Do + +- ❌ Doesn't access your saved passwords/credentials (Chrome sandboxing) +- ❌ Doesn't exfiltrate data to remote servers +- ❌ Doesn't inject ads, malware, or miners +- ❌ Doesn't track browsing or send analytics +- ❌ Doesn't modify system files outside its state directory (`~/.pinchtab`) + +## Builds & Verification + +Every release includes **checksums** alongside binaries: + +```bash +# After downloading, verify: +sha256sum -c checksums.txt +``` + +Binaries are built automatically from tagged commits via GitHub Actions (publicly visible at https://github.com/pinchtab/pinchtab/actions). + +## Open Source + +- **Source**: https://github.com/pinchtab/pinchtab (MIT) +- **Releases**: https://github.com/pinchtab/pinchtab/releases +- **Latest**: v0.8.4 (Mar 2026) + +If you're concerned, audit the source—it's ~15MB, zero external dependencies, mostly Go stdlib. + +## VirusTotal Flag + +Pinchtab may trigger heuristic scanners on VirusTotal because: + +- ✓ It launches Chrome (subprocess execution — flagged by AV heuristics) +- ✓ It runs JavaScript evaluation (eval-like operations) +- ✓ It makes HTTP requests (network activity) + +These are **intentional design features**, not security flaws. Your browser does all three things by default. + +**False positives are common for development tools.** The VT flag is a known false positive for chromedp-based tools (subprocess + HTTP server). Always verify SHA256 checksums from GitHub releases before running. + +For maximum confidence, use the npm package (`npm install -g pinchtab`) or Docker image, which undergo additional validation. + +## Sandboxing + +Pinchtab runs a separate Chrome process with: + +- Isolated profile directory (default: `~/.pinchtab`) +- No access to your user's home files (unless you explicitly navigate to `file://` URLs) +- Standard Chrome security model (site isolation, CSP, etc.) + +Use `profiles.baseDir`, `profiles.defaultProfile`, or `PINCHTAB_CONFIG` if you need to control where PinchTab stores browser state. + +## Security History + +| CVE | Severity | Affected | Fixed In | Endpoint | +| --- | --- | --- | --- | --- | +| [CVE-2026-30834](https://github.com/advisories/GHSA-rw8p-c6hf-q3pg) | High (7.5) | < 0.7.7 | 0.7.7 | `/download` | + +**Type:** Server-Side Request Forgery (SSRF) — allowed exfiltration of internal files and network probing via crafted download URLs. + +**Fix PRs:** [#135](https://github.com/pinchtab/pinchtab/pull/135) (SafePath validation), [#288](https://github.com/pinchtab/pinchtab/pull/288) (expanded URL validation). + +**Minimum recommended version:** 0.8.3+ (includes full SSRF hardening). + +## Questions? + +- Source code: https://github.com/pinchtab/pinchtab +- Issues/security reports: https://github.com/pinchtab/pinchtab/issues +- Docs: https://pinchtab.com diff --git a/src/plugins/pinchtab/snippets/references/agent-optimization.txt b/src/plugins/pinchtab/snippets/references/agent-optimization.txt new file mode 100644 index 0000000..529b940 --- /dev/null +++ b/src/plugins/pinchtab/snippets/references/agent-optimization.txt @@ -0,0 +1,174 @@ +# Agent Optimization Playbook + +Practical guidance for running token-efficient, resilient PinchTab agent workflows. + +--- + +## Cheapest-Path Decision Tree + +Choose the lowest-cost tool that satisfies your goal: + +``` +Need to check page state? +├─ Know the element ref already? → skip snap, use click/type directly +├─ Need to find interactive elements? → snap -i -c (cheapest) +├─ Need to read text/data only? → pinchtab text (no tree overhead) +├─ Need to find a specific element? → pinchtab find "" +├─ Need full page structure? → snap -c (compact tree) +├─ Need to debug visually? → screenshot (use sparingly, large output) +└─ Need to run a JS check? → eval (precise, zero visual overhead) +``` + +**Token cost ranking (cheapest → most expensive):** +1. `eval` — single value, no DOM traversal output +2. `find` — targeted element list only +3. `text` — readable text only +4. `snap -i -c` — interactive elements, compact format +5. `snap -c` — full tree, compact +6. `snap -i` — interactive elements, verbose +7. `snap` — full tree, verbose +8. `screenshot` — image payload, highest token cost + +**Rule of thumb:** Reach for `snap -i -c` as your default snapshot. Only escalate to `screenshot` when visual layout matters (CAPTCHA, canvas, complex CSS). + +--- + +## Diff Snapshots for Follow-Up Reads + +After an interaction, use `snap -d` to see only what changed — not the full tree. + +```bash +pinchtab click e5 # trigger action +pinchtab snap -d # only the delta — much smaller +``` + +**When to use `-d`:** +- After clicks that update part of the UI (e.g. accordion opens, toast appears) +- After form submissions that show inline validation +- During multi-step wizards where only one section changes + +**When NOT to use `-d`:** +- After full page navigations (diff will be the entire new page) +- After `nav` — always take a fresh `snap` instead +- First snapshot of a session (no baseline exists) + +--- + +## Lite Engine + +Start PinchTab with `--engine lite` for minimal rendering overhead. + +```bash +pinchtab start --engine lite +``` + +**Lite engine capabilities:** +- Faster page loads (no CSS animations, reduced JS execution) +- Lower memory footprint — useful for multi-tab fleet workflows +- Accessibility tree (`snap`) works fully +- `text`, `find`, `eval` all work as normal + +**Lite engine limitations:** +- `screenshot` output may not reflect full visual styling +- Pages that depend on CSS transitions for state changes may behave differently +- Some canvas/WebGL content will not render +- Not suitable for visual regression testing + +**Best for:** Form automation, data extraction, API-heavy SPAs, scraping workflows where visual fidelity is not required. + +--- + +## Recovery Patterns + +### 403 Forbidden +**Cause:** `eval` called without `security.allowEvaluate: true`, or a page blocked the request. + +**Recovery:** +```bash +# Option 1: enable eval in config, restart server +# Option 2: switch to snap + find instead of eval +pinchtab find "target text" # avoids eval entirely +``` + +--- + +### 401 Unauthorized +**Cause:** Session expired, auth cookie gone, or protected resource. + +**Recovery:** +1. `pinchtab screenshot` — confirm login page is showing +2. Re-authenticate: `pinchtab nav `, then fill credentials +3. If using a profile: `pinchtab profile use ` may restore the session + +--- + +### Connection Refused +**Cause:** PinchTab server is not running or crashed. + +**Recovery:** +```bash +pinchtab health # confirm down +pinchtab start # restart +pinchtab health # confirm up before continuing +``` + +For fleet workflows: check `pinchtab instances` to confirm the right instance is running. + +--- + +### Stale Element Refs +**Cause:** A `snap` was taken, then the page re-rendered (navigation, dynamic update). Old refs (`e5`, `e12`) are no longer valid. + +**Symptoms:** Interaction returns "ref not found" or acts on the wrong element. + +**Recovery:** +```bash +pinchtab snap -i -c # fresh snapshot → new refs +# Now use the new refs from this response +``` + +**Prevention:** Never cache refs across navigations. Always re-snap after a page load or major DOM update. + +--- + +### Bot Detection / CAPTCHA / Cloudflare +**Cause:** Target site detected automated behavior or uses a challenge gateway. + +**Recovery options:** +1. Try `POST /solve` first — it auto-detects Cloudflare Turnstile and solves it: + ```bash + curl -X POST http://localhost:9867/solve \ + -H 'Content-Type: application/json' -d '{"maxAttempts": 3}' + ``` +2. If solve returns `solved: false`, try with more attempts or check `challengeType` +3. Slow down: add `pinchtab wait --ms 1500` between interactions +4. Switch to a profile with existing session cookies (CF cookies persist) +5. If unsupported CAPTCHA (not Cloudflare): report to user for manual intervention +6. Check `GET /solvers` to see which solver types are available +7. Verify `stealthLevel: "full"` is active — solvers depend on it. Check with `GET /stealth/status` + +--- + +### Timeout on Navigation +**Cause:** Page load exceeded default timeout (usually 30s). + +**Recovery:** +```bash +pinchtab nav --timeout 90 # extend timeout +``` + +If the page consistently times out, consider `--block-images` to speed up load: +```bash +pinchtab nav --block-images --timeout 60 +``` + +--- + +## General Efficiency Rules + +- **Batch reads before writes.** Snap once, extract all refs, then act. Avoid snap → act → snap → act loops when you can snap → act × N → snap once to verify. +- **Use `text` for extraction tasks.** If you only need to read content (not interact), `text` is cheaper than `snap` + parsing. +- **Scope snapshots.** Use `snap -s ` to target a specific section of the page when you know where the element is. +- **Prefer `fill` over `type` for framework forms.** Saves retries caused by React/Vue not detecting raw keystroke events. +- **Check health before long workflows.** Run `pinchtab health` at the start of a multi-step task to fail fast if the server is down. +- **Export network traces after sessions.** `pinchtab network-export -o session.har` captures every request. For live capture: `pinchtab network-export --stream -o live.har`. diff --git a/src/plugins/pinchtab/snippets/references/api.txt b/src/plugins/pinchtab/snippets/references/api.txt new file mode 100644 index 0000000..fa30c60 --- /dev/null +++ b/src/plugins/pinchtab/snippets/references/api.txt @@ -0,0 +1,426 @@ +# Pinchtab API Reference + +Base URL for all examples: `http://localhost:9867` + +> **CLI alternative:** All endpoints have CLI equivalents. Use `pinchtab help` for the full list. Examples are shown as `# CLI:` comments below. + +## Navigate + +```bash +# CLI: pinchtab nav https://pinchtab.com [--new-tab] [--block-images] +curl -X POST /navigate \ + -H 'Content-Type: application/json' \ + -d '{"url": "https://pinchtab.com"}' + +# With options: custom timeout, block images, open in new tab +curl -X POST /navigate \ + -H 'Content-Type: application/json' \ + -d '{"url": "https://pinchtab.com", "timeout": 60, "blockImages": true, "newTab": true}' +``` + +## Snapshot (accessibility tree) + +```bash +# CLI: pinchtab snap [-i] [-c] [-d] [-s main] [--max-tokens 2000] +# Full tree +curl /snapshot + +# Interactive elements only (buttons, links, inputs) — much smaller +curl "/snapshot?filter=interactive" + +# Limit depth +curl "/snapshot?depth=5" + +# Smart diff — only changes since last snapshot (massive token savings) +curl "/snapshot?diff=true" + +# Text format — indented tree, ~40-60% fewer tokens than JSON +curl "/snapshot?format=text" + +# Compact format — one-line-per-node, 56-64% fewer tokens than JSON (recommended) +curl "/snapshot?format=compact" + +# YAML format +curl "/snapshot?format=yaml" + +# Scope to CSS selector (e.g. main content only) +curl "/snapshot?selector=main" + +# Truncate to ~N tokens +curl "/snapshot?maxTokens=2000" + +# Combine for maximum efficiency +curl "/snapshot?format=compact&selector=main&maxTokens=2000&filter=interactive" + +# Disable animations before capture +curl "/snapshot?noAnimations=true" + +# Write to file +curl "/snapshot?output=file&path=/tmp/snapshot.json" +``` + +Returns flat JSON array of nodes with `ref`, `role`, `name`, `depth`, `value`, `nodeId`. + +**Token optimization**: Use `?format=compact` for best token efficiency. Add `?filter=interactive` for action-oriented tasks (~75% fewer nodes). Use `?selector=main` to scope to relevant content. Use `?maxTokens=2000` to cap output. Use `?diff=true` on multi-step workflows to see only changes. Combine all params freely. + +## Act on elements + +```bash +# CLI: pinchtab click e5 / pinchtab type e12 hello / pinchtab press Enter +# Click by ref +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "click", "ref": "e5"}' + +# Type into focused element (click first, then type) +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "click", "ref": "e12"}' +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "type", "ref": "e12", "text": "hello world"}' + +# Press a key +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "press", "key": "Enter"}' + +# Focus an element +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "focus", "ref": "e3"}' + +# Fill (set value directly, no keystrokes) +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "fill", "selector": "#email", "text": "user@pinchtab.com"}' + +# Hover (trigger dropdowns/tooltips) +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "hover", "ref": "e8"}' + +# Select dropdown option (by value or visible text) +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "select", "ref": "e10", "value": "option2"}' + +# Scroll to element +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "scroll", "ref": "e20"}' + +# Scroll by pixels (infinite scroll pages) +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "scroll", "scrollY": 800}' + +# Click and wait for navigation (link clicks) +curl -X POST /action -H 'Content-Type: application/json' \ + -d '{"kind": "click", "ref": "e5", "waitNav": true}' +``` + +## Batch actions + +```bash +# Execute multiple actions in sequence +curl -X POST /actions -H 'Content-Type: application/json' \ + -d '{"actions":[{"kind":"click","ref":"e3"},{"kind":"type","ref":"e3","text":"hello"},{"kind":"press","key":"Enter"}]}' + +# Stop on first error (default: false) +curl -X POST /actions -H 'Content-Type: application/json' \ + -d '{"tabId":"TARGET_ID","actions":[...],"stopOnError":true}' +``` + +## Extract text + +```bash +# CLI: pinchtab text [--raw] +# Readability mode (default) — strips nav/footer/ads +curl /text + +# Raw innerText +curl "/text?mode=raw" +``` + +Returns `{url, title, text}`. Cheapest option (~1K tokens for most pages). + +## PDF export + +Prefer returning base64 or raw bytes unless the user explicitly wants a file written to disk. +When writing to disk, use a safe temporary or workspace path. + +```bash +# CLI: pinchtab pdf --tab TAB_ID [-o file.pdf] [--landscape] [--scale 0.8] +# Returns base64 JSON +curl "/tabs/TAB_ID/pdf" + +# Raw PDF bytes +curl "/tabs/TAB_ID/pdf?raw=true" -o page.pdf + +# Save to disk in a safe temp location +curl "/tabs/TAB_ID/pdf?output=file&path=/tmp/pinchtab-page.pdf" + +# Landscape with custom scale +curl "/tabs/TAB_ID/pdf?landscape=true&scale=0.8&raw=true" -o page.pdf + +# Custom paper size (Letter: 8.5x11, A4: 8.27x11.69) +curl "/tabs/TAB_ID/pdf?paperWidth=8.5&paperHeight=11&marginTop=0.5&marginLeft=0.5&raw=true" -o custom.pdf + +# Export specific pages +curl "/tabs/TAB_ID/pdf?pageRanges=1-5&raw=true" -o pages.pdf + +# With header/footer +curl "/tabs/TAB_ID/pdf?displayHeaderFooter=true&headerTemplate=%3Cspan%20class=title%3E%3C/span%3E&raw=true" -o header.pdf + +# Accessible PDF with document outline +curl "/tabs/TAB_ID/pdf?generateTaggedPDF=true&generateDocumentOutline=true&raw=true" -o accessible.pdf + +# Honor CSS page size +curl "/tabs/TAB_ID/pdf?preferCSSPageSize=true&raw=true" -o css-sized.pdf +``` + +**Query Parameters:** + +| Param | Type | Default | Description | +|-------|------|---------|-------------| +| `paperWidth` | float | 8.5 | Paper width in inches | +| `paperHeight` | float | 11.0 | Paper height in inches | +| `landscape` | bool | false | Landscape orientation | +| `marginTop` | float | 0.4 | Top margin in inches | +| `marginBottom` | float | 0.4 | Bottom margin in inches | +| `marginLeft` | float | 0.4 | Left margin in inches | +| `marginRight` | float | 0.4 | Right margin in inches | +| `scale` | float | 1.0 | Print scale (0.1–2.0) | +| `pageRanges` | string | all | Pages to export (e.g., `1-3,5`) | +| `displayHeaderFooter` | bool | false | Show header and footer | +| `headerTemplate` | string | — | HTML template for header | +| `footerTemplate` | string | — | HTML template for footer | +| `preferCSSPageSize` | bool | false | Honor CSS `@page` size | +| `generateTaggedPDF` | bool | false | Generate accessible/tagged PDF | +| `generateDocumentOutline` | bool | false | Embed document outline | +| `output` | string | JSON | `file` to save to disk, default returns base64 | +| `path` | string | auto | Destination path (prefer temp or workspace paths with `output=file`) | +| `raw` | bool | false | Return raw PDF bytes instead of JSON | + +Wraps `Page.printToPDF`. Prints background graphics by default. + +## Download files + +Prefer raw bytes or base64 responses unless the user explicitly asks for a saved file. + +```bash +# Returns base64 JSON by default (uses browser session/cookies/stealth) +curl "/download?url=https://site.com/report.pdf" + +# Raw bytes (pipe to file) +curl "/download?url=https://site.com/image.jpg&raw=true" -o image.jpg + +# Save directly to disk in a safe temp location +curl "/download?url=https://site.com/export.csv&output=file&path=/tmp/pinchtab-export.csv" +``` + +## Upload files + +Only upload local files the user explicitly provided or approved for the task. + +```bash +# Upload a local file to a file input +curl -X POST "/upload?tabId=TAB_ID" -H "Content-Type: application/json" \ + -d '{"selector": "input[type=file]", "paths": ["/tmp/user-approved-photo.jpg"]}' + +# Upload base64-encoded data +curl -X POST /upload -H "Content-Type: application/json" \ + -d '{"selector": "#avatar-input", "files": ["data:image/png;base64,iVBOR..."]}' +``` + +Sets files on `` elements via CDP. Fires `change` events. Selector defaults to `input[type=file]` if omitted. + +## Screenshot + +```bash +# CLI: pinchtab ss [-o file.jpg] [-q 80] +# Returns raw JPEG (default) +curl "/screenshot?raw=true" -o screenshot.jpg +curl "/screenshot?raw=true&quality=50" -o screenshot.jpg + +# Returns raw PNG +curl "/screenshot?raw=true&format=png" -o screenshot.png +``` + +## Evaluate JavaScript + +Use this sparingly. Prefer `text`, `snapshot`, and normal actions first. +Default to read-only DOM inspection and avoid reading cookies, localStorage, or unrelated page secrets unless the user explicitly asks for that behavior. + +```bash +# CLI: pinchtab eval "document.title" +curl -X POST /evaluate -H 'Content-Type: application/json' \ + -d '{"expression": "document.title"}' +``` + +## Tab management + +```bash +# CLI: pinchtab tabs / pinchtab tabs new / pinchtab tabs close +# List tabs +curl /tabs + +# Open new tab +curl -X POST /tab -H 'Content-Type: application/json' \ + -d '{"action": "new", "url": "https://pinchtab.com"}' + +# Close tab +curl -X POST /tab -H 'Content-Type: application/json' \ + -d '{"action": "close", "tabId": "TARGET_ID"}' +``` + +Multi-tab: pass `?tabId=TARGET_ID` to snapshot/screenshot/text, or `"tabId"` in POST body. + +## Tab-specific endpoints + +All read/action endpoints have tab-scoped variants using `/tabs/{id}/...`: + +```bash +# Navigate a specific tab +curl -X POST /tabs/TARGET_ID/navigate \ + -H 'Content-Type: application/json' \ + -d '{"url": "https://pinchtab.com"}' + +# Snapshot a specific tab +curl "/tabs/TARGET_ID/snapshot" +curl "/tabs/TARGET_ID/snapshot?filter=interactive&format=compact" + +# Screenshot a specific tab +curl "/tabs/TARGET_ID/screenshot?raw=true" -o tab-screenshot.jpg + +# Extract text from a specific tab +curl "/tabs/TARGET_ID/text" + +# Action on a specific tab +curl -X POST /tabs/TARGET_ID/action \ + -H 'Content-Type: application/json' \ + -d '{"kind": "click", "ref": "e5"}' + +# Batch actions on a specific tab +curl -X POST /tabs/TARGET_ID/actions \ + -H 'Content-Type: application/json' \ + -d '{"actions": [{"kind": "click", "ref": "e3"}, {"kind": "type", "ref": "e3", "text": "hello"}]}' +``` + +These are equivalent to using `?tabId=TARGET_ID` on top-level endpoints but follow REST conventions. The tab ID comes from `/tabs` or from the `tabId` field in navigate/tab creation responses. + +## Tab locking (multi-agent) + +```bash +# Lock a tab (default 30s timeout, max 5min) +curl -X POST /tab/lock -H 'Content-Type: application/json' \ + -d '{"tabId": "TARGET_ID", "owner": "agent-1", "timeoutSec": 60}' + +# Unlock +curl -X POST /tab/unlock -H 'Content-Type: application/json' \ + -d '{"tabId": "TARGET_ID", "owner": "agent-1"}' +``` + +Locked tabs show `owner` and `lockedUntil` in `/tabs`. Returns 409 on conflict. + +## Cookies + +```bash +# Get cookies for current page +curl /cookies + +# Set cookies +curl -X POST /cookies -H 'Content-Type: application/json' \ + -d '{"url":"https://pinchtab.com","cookies":[{"name":"session","value":"abc123"}]}' +``` + +## Solve challenges + +PinchTab includes a pluggable solver framework for browser challenges (Cloudflare Turnstile, CAPTCHAs, interstitials). Solvers auto-detect the challenge type and resolve it using human-like interaction. + +```bash +# List available solvers +curl /solvers + +# Auto-detect and solve (tries each solver in order) +curl -X POST /solve -H 'Content-Type: application/json' \ + -d '{"maxAttempts": 3, "timeout": 30000}' + +# Use a specific solver by name +curl -X POST /solve/cloudflare -H 'Content-Type: application/json' \ + -d '{"maxAttempts": 3}' + +# Solve on a specific tab +curl -X POST /tabs/TAB_ID/solve -H 'Content-Type: application/json' \ + -d '{"solver": "cloudflare"}' + +# Solve on a specific tab with path-based solver +curl -X POST /tabs/TAB_ID/solve/cloudflare -H 'Content-Type: application/json' \ + -d '{}' +``` + +**Request fields:** + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `solver` | string | — | Solver name (omit for auto-detect) | +| `tabId` | string | — | Target tab (omit for default tab) | +| `maxAttempts` | int | 3 | Maximum solve attempts | +| `timeout` | float | 30000 | Overall timeout in ms | + +**Response:** + +```json +{ + "tabId": "DEADBEEF", + "solver": "cloudflare", + "solved": true, + "challengeType": "managed", + "attempts": 1, + "title": "Example Site" +} +``` + +Returns `solved: true, attempts: 0` when no challenge is detected — safe to call speculatively. + +**Built-in solvers:** `cloudflare` (Turnstile/interstitial — detects via page title, clicks checkbox with human-like input). + +**Stealth requirement:** Solvers work best with `stealthLevel: "full"`. Cloudflare checks browser fingerprints before and after the checkbox click. Verify stealth is active with `GET /stealth/status`. + +## Network Export + +```bash +# Export as HAR 1.2 (stream to response) +curl /network/export?format=har + +# Export as NDJSON (one JSON per line) +curl /network/export?format=ndjson + +# Save to server-side file +curl "/network/export?format=har&output=file&path=session.har" + +# Include response bodies (10 MB cap per entry) +curl "/network/export?format=har&body=true" + +# Include raw sensitive headers (Cookie, Authorization) +curl "/network/export?format=har&redact=false" + +# Live streaming export (entries written to file as they arrive) +curl -N "/network/export/stream?format=ndjson&path=live.ndjson" + +# Tab-scoped +curl /tabs/TAB_ID/network/export?format=har +``` + +All standard network filters apply: `filter`, `method`, `status`, `type`, `limit`. + +Formats are pluggable. `GET /network/export?format=unknown` returns `{"available": ["har", "ndjson"]}`. + +## Stealth + +```bash +# Check stealth status and score +curl /stealth/status + +# Rotate browser fingerprint +curl -X POST /fingerprint/rotate -H 'Content-Type: application/json' \ + -d '{"os":"windows"}' +# os: "windows", "mac", or omit for random +``` + +## Health check + +```bash +curl /health +``` diff --git a/src/plugins/pinchtab/snippets/references/commands.txt b/src/plugins/pinchtab/snippets/references/commands.txt new file mode 100644 index 0000000..de9c403 --- /dev/null +++ b/src/plugins/pinchtab/snippets/references/commands.txt @@ -0,0 +1,206 @@ +# CLI Commands Reference — Pinchtab 0.8.x + +> **Quick tip:** Use `pinchtab help` or `pinchtab --help` for full flag lists. + +--- + +## Control Plane + +### `pinchtab start` +Start the PinchTab server (default port 9867). + +```bash +pinchtab start +pinchtab start --port 9868 +pinchtab start --profile work --headless +``` + +### `pinchtab stop` +Stop the running server. + +### `pinchtab status` / `pinchtab health` +Check if the server is running and healthy. + +--- + +## Browser Commands + +### `pinchtab nav ` +Navigate the current tab to a URL. + +```bash +pinchtab nav https://example.com +pinchtab nav https://example.com --new-tab +pinchtab nav https://example.com --block-images +pinchtab nav https://example.com --timeout 60 +``` + +| Flag | Description | +|------|-------------| +| `--new-tab` | Open in a new tab instead of current | +| `--block-images` | Block image loading (faster, fewer tokens) | +| `--timeout ` | Navigation timeout in seconds | +| `--profile ` | Target a named profile | + +> ⚠️ **Quirk:** Use `--profile`, not `--profileId`. The long-form flag is required. + +### `pinchtab tab` (not `tabs`) +Manage browser tabs. + +```bash +pinchtab tab list # List all open tabs +pinchtab tab close # Close current tab +pinchtab tab close # Close specific tab +``` + +> ⚠️ **Quirk:** The command is `tab` (singular), not `tabs`. + +--- + +## Interaction Commands + +### `pinchtab click ` +Click an element by its accessibility ref (from `snap`). + +```bash +pinchtab click e5 +pinchtab click e5 --tab +``` + +### `pinchtab type ` +Type text into an input element. + +```bash +pinchtab type e12 "hello world" +``` + +### `pinchtab fill ` +Fill a form field using JS event dispatch. Prefer over `type` for React/Vue/Angular forms. + +```bash +pinchtab fill e12 "hello world" +``` + +### `pinchtab press ` +Press a named keyboard key. + +```bash +pinchtab press Enter +pinchtab press Tab +pinchtab press Escape +``` + +### `pinchtab hover ` +Hover over an element to trigger tooltips or hover styles. + +### `pinchtab scroll [ref]` +Scroll the page or a specific element. + +```bash +pinchtab scroll # scroll page down 300px +pinchtab scroll --pixels -300 # scroll up +pinchtab scroll e20 --pixels 500 +``` + +### `pinchtab select ` +Select an option from a ` +
+``` + +### Flag These (React-Specific) + +```jsx +// XSS - Explicit unsafe rendering +
// FLAG: Critical +// Only safe if userInput is sanitized with DOMPurify or similar + +// URL-based XSS +Link // FLAG: Check for javascript: protocol +