From 0b586b3216aca602a1a002455e944bd682f5267b Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sun, 30 Nov 2025 09:03:52 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 18 ++ README.md | 3 + agents/triage-agent.md | 258 +++++++++++++++++ commands/pp-add.md | 120 ++++++++ commands/pp-checkpoint.md | 181 ++++++++++++ commands/pp-clean.md | 194 +++++++++++++ commands/pp-edit.md | 129 +++++++++ commands/pp-fetch-docs.md | 270 ++++++++++++++++++ commands/pp-help.md | 248 ++++++++++++++++ .../pp-init-assets/templates/root/INDEX.md | 65 +++++ .../pp-init-assets/templates/root/LESSONS.md | 22 ++ .../templates/root/PRINCIPLES.md | 154 ++++++++++ .../pp-init-assets/templates/root/WORKFLOW.md | 141 +++++++++ .../templates/subproject/CHANGELOG.md | 44 +++ .../templates/subproject/CODEBASE.md | 33 +++ .../templates/subproject/INDEX.md | 45 +++ .../templates/subproject/LESSONS.md | 22 ++ .../templates/subproject/PRINCIPLES.md | 22 ++ .../templates/subproject/STATUS.md | 34 +++ .../templates/subproject/TODO.md | 26 ++ commands/pp-init.md | 164 +++++++++++ commands/pp-migrate-v2.md | 262 +++++++++++++++++ commands/pp-migrate.md | 175 ++++++++++++ commands/pp-remove.md | 122 ++++++++ commands/pp-resume.md | 123 ++++++++ commands/pp-status.md | 117 ++++++++ commands/pp-triage.md | 246 ++++++++++++++++ commands/pp-update.md | 116 ++++++++ commands/pp-version.md | 75 +++++ plugin.lock.json | 197 +++++++++++++ skills/gemini-imagegen/SKILL.md | 190 ++++++++++++ .../gemini-imagegen/scripts/compose_images.py | 157 ++++++++++ skills/gemini-imagegen/scripts/edit_image.py | 144 ++++++++++ .../gemini-imagegen/scripts/gemini_images.py | 263 +++++++++++++++++ .../gemini-imagegen/scripts/generate_image.py | 133 +++++++++ .../scripts/multi_turn_chat.py | 216 ++++++++++++++ skills/webapp-testing/LICENSE.txt | 202 +++++++++++++ skills/webapp-testing/SKILL.md | 96 +++++++ .../examples/console_logging.py | 35 +++ .../examples/element_discovery.py | 40 +++ .../examples/static_html_automation.py | 33 +++ skills/webapp-testing/scripts/with_server.py | 106 +++++++ 42 files changed, 5241 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 agents/triage-agent.md create mode 100644 commands/pp-add.md create mode 100644 commands/pp-checkpoint.md create mode 100644 commands/pp-clean.md create mode 100644 commands/pp-edit.md create mode 100644 commands/pp-fetch-docs.md create mode 100644 commands/pp-help.md create mode 100644 commands/pp-init-assets/templates/root/INDEX.md create mode 100644 commands/pp-init-assets/templates/root/LESSONS.md create mode 100644 commands/pp-init-assets/templates/root/PRINCIPLES.md create mode 100644 commands/pp-init-assets/templates/root/WORKFLOW.md create mode 100644 commands/pp-init-assets/templates/subproject/CHANGELOG.md create mode 100644 commands/pp-init-assets/templates/subproject/CODEBASE.md create mode 100644 commands/pp-init-assets/templates/subproject/INDEX.md create mode 100644 commands/pp-init-assets/templates/subproject/LESSONS.md create mode 100644 commands/pp-init-assets/templates/subproject/PRINCIPLES.md create mode 100644 commands/pp-init-assets/templates/subproject/STATUS.md create mode 100644 commands/pp-init-assets/templates/subproject/TODO.md create mode 100644 commands/pp-init.md create mode 100644 commands/pp-migrate-v2.md create mode 100644 commands/pp-migrate.md create mode 100644 commands/pp-remove.md create mode 100644 commands/pp-resume.md create mode 100644 commands/pp-status.md create mode 100644 commands/pp-triage.md create mode 100644 commands/pp-update.md create mode 100644 commands/pp-version.md create mode 100644 plugin.lock.json create mode 100644 skills/gemini-imagegen/SKILL.md create mode 100755 skills/gemini-imagegen/scripts/compose_images.py create mode 100755 skills/gemini-imagegen/scripts/edit_image.py create mode 100755 skills/gemini-imagegen/scripts/gemini_images.py create mode 100755 skills/gemini-imagegen/scripts/generate_image.py create mode 100755 skills/gemini-imagegen/scripts/multi_turn_chat.py create mode 100644 skills/webapp-testing/LICENSE.txt create mode 100644 skills/webapp-testing/SKILL.md create mode 100644 skills/webapp-testing/examples/console_logging.py create mode 100644 skills/webapp-testing/examples/element_discovery.py create mode 100644 skills/webapp-testing/examples/static_html_automation.py create mode 100755 skills/webapp-testing/scripts/with_server.py diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..a428400 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "unclecode-cc-toolkit", + "description": "Comprehensive Claude Code toolkit by unclecode. Includes project progress tracking commands, Gemini image generation, and web app testing capabilities. All-in-one productivity suite for development workflows.", + "version": "1.3.1", + "author": { + "name": "unclecode", + "url": "https://github.com/unclecode" + }, + "skills": [ + "./skills" + ], + "agents": [ + "./agents" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d6398e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# unclecode-cc-toolkit + +Comprehensive Claude Code toolkit by unclecode. Includes project progress tracking commands, Gemini image generation, and web app testing capabilities. All-in-one productivity suite for development workflows. diff --git a/agents/triage-agent.md b/agents/triage-agent.md new file mode 100644 index 0000000..9d46826 --- /dev/null +++ b/agents/triage-agent.md @@ -0,0 +1,258 @@ +# Triage Agent + +Root cause investigation specialist for bugs, issues, and feature requests. + +## Agent Role + +You are a systematic debugging and root cause analysis expert. Your job is to find the TRUE underlying cause of issues, not just treat symptoms. + +**Core Principle**: "Don't just treat the rash, find the root cause (diet, stress, allergy) and fix both." + +## Context You Receive + +When invoked, you'll receive: +1. **Issue description** - What the user is experiencing +2. **PP Context** - Pre-loaded context from project planning files: + - Active subproject + - Codebase structure (from CODEBASE.md) + - Current status (from STATUS.md) + - Recent changes (from CHANGELOG.md) + - Past lessons (from LESSONS.md) + - Project principles (from PRINCIPLES.md) + +## Your Investigation Methodology + +### Step 1: Start with PP Context (DON'T Re-Explore) + +You already have the context. USE IT: +- Check CODEBASE.md for file locations +- Check CHANGELOG.md for recent changes that might be related +- Check LESSONS.md for similar past issues +- Check STATUS.md for known blockers or issues +- Check PRINCIPLES.md for architectural patterns + +**DO NOT** re-explore the entire codebase from scratch. Start with PP files. + +### Step 2: Systematic Investigation + +Follow this path: + +1. **Understand the symptom** + - What is the observable behavior? + - When does it happen? + - What's the expected behavior? + +2. **Form hypotheses** + - Based on error messages, what could cause this? + - Based on recent changes (CHANGELOG), what might be related? + - Based on past lessons (LESSONS.md), what's similar? + +3. **Investigate code** + - Use CODEBASE.md to find relevant files + - Read those files to understand logic + - Trace execution path + - Look for the root cause + +4. **Distinguish cause from symptom** + - Symptom: "User sees 500 error" + - Cause: "Database connection pool exhausted because connection not released in error handler" + + Fix BOTH: Add connection release + increase pool size + +### Step 3: Determine Certainty + +**If root cause is CLEAR**: +- You can trace the exact logic flow +- You see the bug in the code +- You understand why it happens +- → Provide SOLUTION + +**If root cause is UNCLEAR**: +- Multiple possible causes +- Need runtime data to confirm +- Can't trace execution path +- → Provide DEBUGGING STRATEGY + +### Step 4: Format Your Report + +Use this EXACT structure (user expects this format): + +```markdown +## Triage Report: {Issue Title} + +**Date**: {YYYY-MM-DD HH:MM} +**Subproject**: {subproject name} +**Severity**: {Critical / High / Medium / Low} + +--- + +### Issue Summary + +{1-2 sentence summary of reported issue} + +--- + +### Root Cause Analysis + +**Symptom**: {What user sees/experiences} + +**Investigation Path**: +1. {What you checked first} +2. {What you found next} +3. {How you traced to root cause} + +**Root Cause**: {The ACTUAL underlying cause - be specific} + +**Why This Matters**: {Explain the causation chain: X causes Y which causes Z (symptom)} + +--- + +### Solution + +{EITHER "Recommended Fix" OR "Debugging Strategy" - pick ONE based on certainty} + +**Recommended Fix**: {If root cause is clear} +1. {Specific action 1} +2. {Specific action 2} +3. {Specific action 3} + +**Files to Modify**: +- `path/to/file.ext` - {exact change needed} +- `another/file.js` - {exact change needed} + +**Implementation Notes**: +- {Important consideration} +- {Edge case to handle} + +{OR} + +**Debugging Strategy**: {If root cause is unclear} +1. **Add logging**: + - In `file.ext` at line X: `console.log('variable:', variableName)` + - In `file2.js` before function Y: `log request state` + +2. **Test hypotheses**: + - Hypothesis 1: {theory} - Test by: {method} + - Hypothesis 2: {theory} - Test by: {method} + +3. **Narrow down**: + - {Specific approach to isolate the issue} + - {What to look for in logs} + +**Next Steps After Debugging**: +1. {Action 1} +2. {Action 2} +3. Re-run /pp-triage with findings + +--- + +### Related Context + +**Recent Changes That May Be Related**: +- {Entry from CHANGELOG.md if relevant, or "None found"} + +**Past Similar Issues**: +- {Entry from LESSONS.md if relevant, or "None found"} + +**Affected Components**: +- {Component/module 1} +- {Component/module 2} + +--- + +### Recommended Documentation Updates + +**Add to LESSONS.md**: +``` +## {Date} | {Issue Title} + +**Problem**: {Brief} +**Root Cause**: {Cause} +**Solution**: {Solution} +**Tag**: [BUG] / [FEATURE] / [CONFIG] / [PERFORMANCE] +``` + +**Update STATUS.md**: +- {Specific section and what to add} + +--- + +### Confidence Level + +**Root Cause Certainty**: High / Medium / Low +**Solution Confidence**: High / Medium / Low + +{If Low: Explain what additional information would increase confidence} +``` + +## Investigation Best Practices + +### DO: +- ✅ Start with PP context (CODEBASE, CHANGELOG, LESSONS, STATUS) +- ✅ Think about causation (A causes B causes C) +- ✅ Distinguish symptoms from root causes +- ✅ Provide specific file names and line numbers +- ✅ Suggest debugging when uncertain +- ✅ Be honest about confidence level +- ✅ Format report for easy scanning +- ✅ Think systematically (hypothesis → test → conclude) + +### DON'T: +- ❌ Re-explore the entire codebase from scratch +- ❌ Just describe the symptom without finding the cause +- ❌ Provide vague solutions ("check the code", "debug it") +- ❌ Claim high confidence when uncertain +- ❌ Ignore PP context that's provided +- ❌ Fix symptom without addressing root cause +- ❌ Provide solutions without explaining WHY they work + +## Severity Levels + +- **Critical**: System down, data loss, security breach +- **High**: Major feature broken, blocks users +- **Medium**: Feature works but has issues, workaround exists +- **Low**: Minor inconvenience, cosmetic issue + +## Output Tone + +- Professional but friendly +- Clear and scannable (user wants to grasp quickly) +- Honest about uncertainty +- Actionable (user should know exactly what to do next) +- Educational (explain the "why" not just the "what") + +## Example Investigation Patterns + +### Pattern 1: Clear Bug +``` +Symptom: 500 error on /api/users +Investigation: Checked CHANGELOG → recent DB migration +Root Cause: Migration added NOT NULL constraint but code doesn't validate +Solution: Add validation before DB insert OR make column nullable +``` + +### Pattern 2: Needs Debugging +``` +Symptom: Slow dashboard load +Investigation: CODEBASE shows N+1 query pattern possible, but need to confirm +Root Cause: UNCLEAR - could be queries, large payload, or client rendering +Debugging: Add query timing logs, measure payload size, profile client +``` + +### Pattern 3: Configuration Issue +``` +Symptom: Email not sending +Investigation: LESSONS shows similar issue before → env var +Root Cause: SMTP_HOST env var not set in production +Solution: Add SMTP_HOST to .env.production, redeploy +``` + +## Remember + +Your goal is to save the user time by: +1. Finding the REAL cause (not guessing) +2. Providing clear next steps +3. Updating their knowledge base (LESSONS.md suggestion) +4. Being systematic and honest + +**Philosophy**: A good diagnosis is more valuable than a quick guess. diff --git a/commands/pp-add.md b/commands/pp-add.md new file mode 100644 index 0000000..f7efb90 --- /dev/null +++ b/commands/pp-add.md @@ -0,0 +1,120 @@ +# Add New Subproject + +Create a new subproject in the project structure. + +## Arguments + +$ARGUMENTS + +## Purpose + +Add a new subproject with full file structure. Query should contain: +- Subproject name (folder name) +- Description/purpose +- Optionally: initial scope, dependencies + +## Execution Instructions + +### Step 1: Parse Query + +Extract from query: +- `name`: Folder name (lowercase, no spaces) +- `title`: Human-readable title +- `description`: What this subproject does +- `dependencies`: Relationships to other subprojects + +If query is empty or missing info, ask: +1. "Subproject folder name (e.g., 'api', 'frontend'):" +2. "Title (e.g., 'REST API Backend'):" +3. "Brief description:" +4. "Dependencies on other subprojects? (optional)" + +### Step 2: Create Directory Structure + +```bash +mkdir -p .context/project/{name}/prds .context/project/{name}/docs .context/project/{name}/archive +``` + +### Step 3: Copy and Process Templates + +Copy subproject templates from `~/.claude/commands/pp-init-assets/templates/subproject/`: + +```bash +# Copy all subproject templates +cp ~/.claude/commands/pp-init-assets/templates/subproject/*.md .context/project/{name}/ +``` + +Then process placeholders: +- `{{SUBPROJECT_NAME}}` → name +- `{{SUBPROJECT_TITLE}}` → title +- `{{SUBPROJECT_DESCRIPTION}}` → description +- `{{DATE}}` → current date +- `{{SUBPROJECT_STATUS}}` → "Not Started" +- Other placeholders → "TBD" or empty + +### Step 4: Update Root Files + +**INDEX.md**: Add to subproject table +```markdown +| {name}/ | Planned | {description} | +``` + +**STATUS.md**: Add to subproject summary table +```markdown +| {name}/ | - | Not Started | +``` + +**CODEBASE.md**: Add to subproject refs +```markdown +- `{name}/CODEBASE.md` - {title} files +``` + +**CHANGELOG.md**: Add entry +```markdown +## {DATE} | Added Subproject: {name} + +**Changes**: +- Created {name}/ subproject structure + +**Context**: +- {description} +``` + +### Step 5: Confirm Creation + +``` +## Created Subproject: {name}/ + +**Title**: {title} +**Description**: {description} + +**Files created**: +- {name}/INDEX.md +- {name}/STATUS.md +- {name}/TODO.md +- {name}/CHANGELOG.md +- {name}/PRINCIPLES.md +- {name}/CODEBASE.md +- {name}/LESSONS.md + +**Updated**: +- Root INDEX.md +- Root STATUS.md +- Root CODEBASE.md +- Root CHANGELOG.md + +To start working on this subproject, update STATUS.md to set it as active. +``` + +## Query Examples + +```bash +# Full description +/pp-add api - REST API backend using FastAPI, handles auth and data + +# Just name (will ask for details) +/pp-add mobile + +# With dependencies +/pp-add frontend - React dashboard, depends on api for data +``` diff --git a/commands/pp-checkpoint.md b/commands/pp-checkpoint.md new file mode 100644 index 0000000..8fd6348 --- /dev/null +++ b/commands/pp-checkpoint.md @@ -0,0 +1,181 @@ +# Save Session Checkpoint + +Save current chat session state and prepare instructions for next session. + +## Arguments + +$ARGUMENTS + +Optional query to specify what should happen next. + +## Purpose + +Called at the end of a chat session (or before hitting token limits) to: +1. Create ultra-compact summary of current session +2. Save "what to do next" instructions for new session +3. Link to full chat transcript (with warnings about size) + +## Execution Instructions + +### Step 1: Identify Current Session + +```bash +# Get current working directory +pwd + +# Convert to Claude projects path +# Example: /Users/unclecode/devs/project +# Becomes: ~/.claude/projects/-Users-unclecode-devs-project/ + +# Find most recent .jsonl file (exclude agent-* files) +ls -lt ~/.claude/projects/-Users-unclecode-devs-{converted-path}/*.jsonl | grep -v "agent-" | head -1 + +# Extract UUID from filename +# Example: 530170ff-52f5-4ad6-a333-22a09bd64773.jsonl +# UUID: 530170ff-52f5-4ad6-a333-22a09bd64773 +``` + +### Step 2: Determine Active Subproject + +Read `.context/project/INDEX.md` to identify active subproject. + +### Step 3: Generate "What To Do Next" + +**If user provided query:** +- Use their query as primary guidance +- Enhance with context from current state + +**If NO query provided:** +- Analyze current state: + - Read `TODO.md` - what's pending + - Read last 2-3 `CHANGELOG.md` entries - what was just done + - Read `STATUS.md` - current focus +- Generate clear next actions + +### Step 4: Create History Summary (Ultra-Compact) + +**Location**: `.context/project/{active}/history/{uuid}-{YYYY-MM-DD}.md` + +**Content** (token-efficient, ~200-400 tokens for main summary + warnings): +```markdown +# Chat Session {uuid} +**Date**: {YYYY-MM-DD HH:MM} +**Tokens Used**: ~{approximate} / 200k + +## Key Achievements +- [T###] Task completed +- Major milestone reached +- Decision made + +## Technical Decisions +- Technology choice with rationale +- Architecture decision +- Library/framework selected + +## Files Created/Modified +- path/to/file.ext +- another/file.js + +## What's Next +{Brief 1-2 line summary} + +--- + +## Full Chat Transcript + +**⚠️ IMPORTANT**: Full conversation available at: +`{full-path-to-jsonl-file}` + +**WARNING**: This file is LARGE (~2-3MB, ~140k+ tokens). NEVER load it entirely. + +**How to use it**: +- Use `grep` to search for specific topics +- Use `tail -n 100` for recent messages +- Use `head -n 100` for session start +- Use `sed -n 'START,ENDp'` for specific line ranges +- Parse JSON with `jq` for structured queries + +**Example commands**: +```bash +# Search for specific topic +grep -i "your topic" {path-to-file} + +# Get last 50 messages +tail -n 50 {path-to-file} + +# Count total lines +wc -l {path-to-file} +``` +``` + +**Important**: Keep main summary VERY compact. Transcript warnings go at end. + +### Step 5: Create/Update NEXT.md (Minimal) + +**Location**: `.context/project/{active}/NEXT.md` + +**Content** (keep minimal, link to history for details): +```markdown +# Next Chat Session + +## What To Do Next + +{AI-generated guidance OR user's query} + +Examples: +- User will fill knowledge base at webhook/maya_knowledge.md +- Begin T010: Node.js project setup +- Continue implementation of LLMService +- Review PRD and provide feedback + +## Previous Session Context + +**Session**: {uuid} ({YYYY-MM-DD}) + +See: [history/{uuid}-{YYYY-MM-DD}.md](history/{uuid}-{YYYY-MM-DD}.md) + +## Current State + +**Active Phase**: {from STATUS.md} +**Pending Tasks**: {high-priority tasks from TODO.md} +**Blockers**: {if any} +**Last Updated**: {files recently modified} +``` + +**Important**: Keep NEXT.md minimal. All detailed context (including full transcript warnings) goes in the history file. + +### Step 6: Output Confirmation + +Tell user: +``` +✅ Checkpoint saved! + +Files created/updated: +- .context/project/{active}/NEXT.md (instructions for next session) +- .context/project/{active}/history/{uuid}-{date}.md (session summary) + +When starting next session: +1. Run `/pp-resume` to load context + next steps +2. Continue from where we left off + +Session ID: {uuid} +``` + +## Notes + +- Always create history/ directory if it doesn't exist +- NEXT.md is overwritten each time (always shows latest) +- History files are never overwritten (one per session) +- Keep summaries ultra-compact to save tokens +- Full transcript path is for rare cases when specific detail needed + +## Error Handling + +If unable to find session UUID: +- Inform user +- Still create NEXT.md with guidance +- Skip history file creation (not critical) + +If active subproject unclear: +- Ask user which subproject +- Or use root .context/project/ as fallback diff --git a/commands/pp-clean.md b/commands/pp-clean.md new file mode 100644 index 0000000..132f303 --- /dev/null +++ b/commands/pp-clean.md @@ -0,0 +1,194 @@ +# Clean Project Files + +Clean up temporary files and archive useful markdown. + +## Arguments + +$ARGUMENTS + +## Purpose + +After tasks/updates, clean up the project: +1. **Delete** truly temporary files (test outputs, scratch, debug logs) +2. **Archive** potentially useful markdown (drafts, notes, old versions) +3. Keep project directory clean while preserving history + +## Execution Instructions + +### Step 1: Scan for Files to Clean + +Look in project root and common locations for: + +**Temporary files (to DELETE):** +- `*_test.md`, `test_*.md` +- `scratch*.md`, `temp*.md`, `tmp*.md` +- `debug*.md`, `*_debug.md` +- `*.log` (unless in logs/ folder) +- `output*.md`, `*_output.md` +- Files with `DELETE` or `TEMP` in name + +**Potentially useful markdown (to ARCHIVE):** +- `draft_*.md`, `*_draft.md` +- `notes_*.md`, `*_notes.md` +- `research_*.md` +- `old_*.md`, `*_old.md`, `*_backup.md` +- `v1_*.md`, `*_v1.md` (old versions) +- Any `.md` file in root that's not part of standard structure +- User-created markdown during session + +### Step 2: Parse Optional Query + +If query provided, focus on: +- Specific pattern: `/pp-clean test files` +- Specific folder: `/pp-clean webhook/` +- Specific action: `/pp-clean archive only` or `/pp-clean delete only` + +### Step 3: Show Preview + +``` +## Cleanup Preview + +### To DELETE (temporary files) +- scratch_booking.md +- test_output.md +- debug_log.md + +### To ARCHIVE (move to .context/project/archive/) +- draft_system_prompt.md +- research_notes.md +- old_booking_flow.md + +### No Action +- (files that look fine) + +--- +Proceed with cleanup? (yes/no/edit) +``` + +If user says "edit", allow them to: +- Move items between delete/archive +- Skip specific files +- Add files to clean + +### Step 4: Execute Cleanup + +**Delete temporary files:** +```bash +rm {temp_files} +``` + +**Archive useful markdown:** +```bash +# Create dated archive subfolder if multiple files +mkdir -p .context/project/archive/cleanup_{date} +mv {markdown_files} .context/project/archive/cleanup_{date}/ +``` + +Or if single file: +```bash +mv {file} .context/project/archive/{file} +``` + +### Step 5: Update CHANGELOG + +Add entry to appropriate CHANGELOG: + +```markdown +## {DATE} | Cleanup + +**Deleted**: +- scratch_booking.md +- test_output.md + +**Archived**: +- draft_system_prompt.md → archive/cleanup_{date}/ +- research_notes.md → archive/cleanup_{date}/ + +**Context**: +- Post-task cleanup +- {query context if provided} +``` + +### Step 6: Confirm Completion + +``` +## Cleanup Complete + +**Deleted**: 3 temporary files +**Archived**: 2 markdown files → archive/cleanup_{date}/ + +Project directory is clean. +``` + +## Auto-Trigger Integration + +This cleanup logic should also run: +- After `/pp-update` completes +- After major task completion +- Before commits (optional) + +When auto-triggered, still show preview and get confirmation. + +## Query Examples + +```bash +# General cleanup +/pp-clean + +# Focus on specific pattern +/pp-clean test files + +# Focus on specific folder +/pp-clean webhook/ + +# Archive only (no deletions) +/pp-clean archive only + +# Delete only (no archiving) +/pp-clean delete only + +# With context +/pp-clean after completing booking flow +``` + +## Safety Rules + +1. **Always show preview first** +2. **Always get confirmation** +3. **Never delete .md files that might be useful** - archive them +4. **Never touch:** + - `.context/project/` structure files + - Files in `prds/`, `docs/` folders + - `.env`, config files + - Source code files +5. **Log everything** in CHANGELOG + +## File Patterns Reference + +### Safe to DELETE +``` +*_test.md, test_*.md +scratch*.md, temp*.md, tmp*.md +debug*.md, *_debug.md +output*.md, *_output.md +*.log (loose logs) +*DELETE*, *TEMP* +``` + +### Should ARCHIVE +``` +draft_*.md, *_draft.md +notes_*.md, *_notes.md +research_*.md +old_*.md, *_old.md, *_backup.md +v[0-9]_*.md, *_v[0-9].md +Loose .md files not in structure +``` + +### Never Touch +``` +.context/project/**/*.md (structure files) +prds/*.md, docs/*.md +README.md, CLAUDE.md +.env, *.json, *.py, *.js, etc. +``` diff --git a/commands/pp-edit.md b/commands/pp-edit.md new file mode 100644 index 0000000..54c1e1b --- /dev/null +++ b/commands/pp-edit.md @@ -0,0 +1,129 @@ +# Edit Project Progress Files + +Quick, natural language edits to project planning files. + +## Arguments + +$ARGUMENTS + +Query describing what to edit. + +## Purpose + +Make direct edits to PP files using natural language without manually finding and editing files. + +## Execution Instructions + +### Step 1: Parse Edit Query + +Extract from query: +1. **Target file** (INDEX.md, WORKFLOW.md, PRINCIPLES.md, STATUS.md, TODO.md, etc.) +2. **Action** (add, update, remove, change) +3. **Content** (what to add/change/remove) +4. **Location** (root or subproject) + +**Query patterns:** +- "add X to Y" → append content +- "update X in Y to Z" → replace content +- "remove X from Y" → delete content +- "change X to Y" → replace content + +### Step 2: Identify File Location + +**Root files** (in `.context/project/`): +- INDEX.md +- WORKFLOW.md +- PRINCIPLES.md +- LESSONS.md + +**Subproject files** (in `.context/project/{subproject}/`): +- STATUS.md +- TODO.md +- CHANGELOG.md +- CODEBASE.md +- LESSONS.md +- PRINCIPLES.md (if exists) + +If query doesn't specify subproject and file is subproject-level: +- Read INDEX.md to get active subproject +- Apply edit to active subproject's file + +### Step 3: Validate Target + +Check if target file exists: +```bash +ls .context/project/{target_file} +# or +ls .context/project/{subproject}/{target_file} +``` + +If file doesn't exist, inform user and ask if they want to create it. + +### Step 4: Apply Edit + +**For "add" operations:** +- Append content to appropriate section +- Maintain markdown formatting +- Add to end of file or specific section if mentioned + +**For "update/change" operations:** +- Find existing content +- Replace with new content +- Preserve formatting + +**For "remove" operations:** +- Find and delete content +- Clean up extra blank lines + +### Step 5: Confirm Changes + +Show diff or summary: +``` +## Edit Applied + +**File**: .context/project/PRINCIPLES.md +**Action**: Added new principle + +**Content added**: +> Always use TypeScript strict mode + +**Location**: End of file + +File updated successfully. +``` + +## Query Examples + +```bash +# Add to root files +/pp-edit add "Daily standup at 9am" to WORKFLOW.md +/pp-edit update PRINCIPLES.md to include "Use functional programming patterns" + +# Update INDEX.md +/pp-edit change active subproject to backend +/pp-edit add high-level TODO "Launch MVP by March 15" + +# Edit subproject files (uses active subproject) +/pp-edit add task "T050: Implement password reset" to TODO.md +/pp-edit update STATUS.md working section with "API endpoints complete" +/pp-edit remove "T010: Initial setup" from TODO.md + +# Edit specific subproject +/pp-edit add "Use Redis for caching" to backend PRINCIPLES.md +/pp-edit update frontend STATUS.md blocked section with "Waiting for API completion" +``` + +## Smart Parsing + +If query is ambiguous, ask clarifying questions: +- "Which file? (INDEX.md, WORKFLOW.md, PRINCIPLES.md, STATUS.md, TODO.md)" +- "Which subproject? (main, backend, frontend)" +- "Where in the file? (beginning, end, specific section)" + +## Notes + +- Always preserve existing content unless explicitly asked to replace +- Maintain markdown formatting and structure +- If adding to structured files (TODO.md, CHANGELOG.md), follow existing format +- Show what changed so user can verify +- Suggest commit after edit: "Ready to commit? Run: /pp-update" diff --git a/commands/pp-fetch-docs.md b/commands/pp-fetch-docs.md new file mode 100644 index 0000000..8486111 --- /dev/null +++ b/commands/pp-fetch-docs.md @@ -0,0 +1,270 @@ +# Fetch API Documentation + +Fetch and cache API documentation in AI-readable markdown format. + +## Arguments + +$ARGUMENTS + +Library name, URL, or package identifier (e.g., "openai", "stripe", "https://docs.stripe.com") + +## Purpose + +Download and cache API documentation locally for quick reference across all projects. Docs are stored globally in the plugin directory and available in all chat sessions. + +## Storage Location + +**Global (available across all projects):** +``` +~/.claude/plugins/marketplaces/unclecode-tools/plugins/unclecode-cc-toolkit/api-docs/ +``` + +## Execution Instructions + +### Step 1: Parse Input + +Extract from query: +- **Library name** (e.g., "openai", "stripe", "nextjs") +- **URL** (if provided, e.g., "https://docs.stripe.com") +- **Version** (optional, e.g., "openai@4.0") + +Normalize library name: +- Remove special characters +- Convert to lowercase +- Remove version suffix for directory name + +### Step 2: Check if Docs Exist + +Check for existing documentation: +```bash +ls ~/.claude/plugins/marketplaces/unclecode-tools/plugins/unclecode-cc-toolkit/api-docs/{library}/ +``` + +**If directory exists:** +- List what's there: `ls -lah api-docs/{library}/` +- Show last modified date +- Ask user: +``` +## Documentation Exists + +**Library**: {library} +**Location**: api-docs/{library}/ +**Last Updated**: {date from stat} +**Files**: {file count} files, {total size} + +Documentation already cached. Would you like to: +1. Use existing docs (skip) +2. Update/re-fetch docs +3. View what's cached +``` + +Wait for user response. + +**If doesn't exist:** +- Proceed to Step 3 + +### Step 3: Fetch Documentation + +Try these methods in order: + +#### Method 1: Context7 API + +**If library name provided (no URL):** + +```bash +# Search for library +curl -X GET "https://context7.com/api/v1/search?query={library}" \ + -H "Authorization: Bearer CONTEXT7_API_KEY" + +# Get library ID from response +# Then fetch docs +curl -X GET "https://context7.com/api/v1/{library_id}/docs?type=txt&tokens=5000" \ + -H "Authorization: Bearer CONTEXT7_API_KEY" +``` + +**Note**: Free tier available (no API key needed but rate-limited) + +Save response to: `api-docs/{library}/context7-docs.md` + +If successful, skip to Step 4. + +#### Method 2: llms.txt + +**If URL provided OR Context7 doesn't have it:** + +1. Extract domain from URL +2. Try to fetch `{domain}/llms.txt`: + +```bash +curl -s https://{domain}/llms.txt +``` + +3. If llms.txt exists: + - Parse the file (contains list of markdown doc URLs) + - Fetch each markdown URL listed + - Save each to: `api-docs/{library}/{filename}.md` + +If successful, skip to Step 4. + +#### Method 3: Web Scraping + +**If neither Context7 nor llms.txt available:** + +1. Fetch the docs page HTML +2. Convert HTML to markdown using a clean method: + - Extract main content (remove nav, footer, ads) + - Convert to markdown + - Clean up formatting + +3. Save to: `api-docs/{library}/scraped-docs.md` + +Add note at top of file: +```markdown + + +``` + +### Step 4: Create Metadata File + +Create `api-docs/{library}/metadata.json`: +```json +{ + "library": "library-name", + "source": "context7|llms.txt|web-scraped", + "sourceUrl": "original-url", + "fetchedAt": "2025-01-23T10:30:00Z", + "fileCount": 3, + "totalSize": "250KB", + "version": "4.0.0" (if specified) +} +``` + +### Step 5: Create Index + +If multiple files were fetched, create `api-docs/{library}/INDEX.md`: +```markdown +# {Library} API Documentation + +**Fetched from**: {source} +**Date**: {date} +**Version**: {version if known} + +## Files + +- [context7-docs.md](./context7-docs.md) - Main documentation +- [api-reference.md](./api-reference.md) - API reference +- [guides.md](./guides.md) - Usage guides + +## Quick Links + +{Parse and list major sections/topics} +``` + +### Step 6: Confirm Success + +Show summary: +``` +## Documentation Fetched Successfully + +**Library**: {library} +**Source**: {Context7 | llms.txt | Web} +**Location**: ~/.claude/plugins/.../api-docs/{library}/ +**Files Saved**: {count} files ({total size}) + +### What's Available: + +{List files with brief description} + +### Usage: + +These docs are now available globally across all projects. +To reference in your code: +- Ask Claude to "check {library} docs" +- Use /pp-triage for debugging with library context +- Docs automatically available in all future sessions + +### Next Steps: + +- View docs: cat api-docs/{library}/INDEX.md +- Update later: /pp-fetch-docs {library} (will prompt to update) +``` + +## Query Examples + +```bash +# Fetch by library name (tries Context7 first) +/pp-fetch-docs openai +/pp-fetch-docs stripe +/pp-fetch-docs nextjs + +# Fetch by URL (tries llms.txt, then scrapes) +/pp-fetch-docs https://docs.stripe.com +/pp-fetch-docs https://platform.openai.com/docs + +# Fetch specific version +/pp-fetch-docs openai@4.0 +/pp-fetch-docs react@18 + +# Update existing docs +/pp-fetch-docs openai +> "Documentation exists. Update? (yes/no)" +> yes +> (re-fetches and overwrites) +``` + +## Error Handling + +**If all methods fail:** +``` +## Unable to Fetch Documentation + +**Library**: {library} +**Attempted**: +- ❌ Context7: Library not found +- ❌ llms.txt: Not available at {domain} +- ❌ Web scraping: {error reason} + +### Suggestions: + +1. Check library name spelling +2. Provide full URL to documentation +3. Try alternative library name (e.g., "@openai/sdk" vs "openai") +4. Manually download docs and save to: + ~/.claude/plugins/.../api-docs/{library}/docs.md + +Would you like me to: +- Search for alternative library names? +- Try a different URL? +- Help you manually organize docs? +``` + +## Notes + +- Docs are **global** - stored in plugin directory, not project-specific +- Available across ALL projects and chat sessions +- Context7 preferred (most up-to-date, version-specific) +- llms.txt is official standard (website-published) +- Web scraping is last resort (may need cleanup) +- Update anytime by re-running command +- Large docs may be split into multiple files for better organization + +## API Keys (Optional) + +For Context7 higher rate limits: +- Get free API key: https://context7.com/dashboard +- Set environment variable: `export CONTEXT7_API_KEY="your-key"` +- Plugin will auto-detect and use it + +## Best Practices + +**When to fetch:** +- Starting a new project with specific APIs +- Learning a new library +- Need up-to-date reference (re-fetch to update) +- Debugging issues (fresh docs help) + +**Storage management:** +- Each library typically: 100-500KB +- Plugin directory has no size limit +- Delete old docs: `rm -rf api-docs/{library}/` +- View all cached: `ls api-docs/` diff --git a/commands/pp-help.md b/commands/pp-help.md new file mode 100644 index 0000000..1cb4e03 --- /dev/null +++ b/commands/pp-help.md @@ -0,0 +1,248 @@ +# Project Progress System - Help Guide + +Complete guide for using the Project Progress (PP) system. + +## Arguments + +$ARGUMENTS + +Optional: Specify topic for focused help (e.g., `/pp-help init`, `/pp-help workflow`) + +## Overview + +The Project Progress system helps you track development with: +- **Subproject-based organization** (main, backend, frontend, etc.) +- **Structured documentation** (STATUS, TODO, CHANGELOG, CODEBASE) +- **Session continuity** (checkpoint and resume between chats) +- **AI-optimized format** (easy for Claude to understand context) + +--- + +## Quick Start + +### For New Projects + +```bash +# Interactive mode (recommended for first time) +/pp-init + +# Or quiet mode (instant setup with "main" subproject) +/pp-init --quiet +``` + +### For Existing Projects + +```bash +# Migrate existing project (uses AI agents to discover structure) +/pp-migrate +``` + +--- + +## File Structure + +``` +.context/project/ +├── INDEX.md # Project overview + active subproject + high-level TODOs +├── WORKFLOW.md # How to use this system +├── PRINCIPLES.md # Project principles +├── LESSONS.md # Lessons learned +│ +└── {subproject}/ # e.g., "main", "backend", "frontend" + ├── STATUS.md # Current status, what's working/blocked + ├── TODO.md # Detailed task list + ├── CHANGELOG.md # Change history + ├── CODEBASE.md # File inventory + ├── LESSONS.md # Subproject-specific lessons + ├── PRINCIPLES.md # (optional) Subproject-specific principles + ├── prds/ # Product requirement documents + ├── docs/ # Documentation + ├── archive/ # Archived items + └── history/ # Session checkpoints +``` + +--- + +## Core Commands + +### Start New Session +```bash +/pp-resume # Load all context, continue work +/pp-resume [direction] # Resume with specific focus +``` + +### Update Documentation +```bash +/pp-update # Interactive: ask what changed +/pp-update completed T010, T011 # Quick update with description +``` + +### Session Management +```bash +/pp-checkpoint # Save session, prepare for next chat +/pp-checkpoint [instructions] # Save with specific next-session instructions +``` + +### View Status +```bash +/pp-status # Full project status +/pp-status tasks # Focus on tasks only +/pp-status blockers # Focus on blockers +``` + +### Manage Structure +```bash +/pp-add [checkpoint] # Add a checkpoint +/pp-remove [checkpoint] # Remove a checkpoint +/pp-clean # Clean temporary files +``` + +### Utilities +```bash +/pp-version # Show plugin version +/pp-help # This help guide +``` + +--- + +## Typical Workflow + +### 1. First Session (New Project) + +```bash +# Initialize structure +/pp-init + +# Work on your project... +# (code, debug, build features) + +# Before ending session +/pp-update added initial setup files +/pp-checkpoint Continue with user authentication +``` + +### 2. Next Session + +```bash +# Load context +/pp-resume + +# Claude shows: "Ready to continue with user authentication" +# Work on tasks... + +# Update as you go +/pp-update completed T015, added auth service + +# End session +/pp-checkpoint Implement password reset flow +``` + +### 3. Check Status Anytime + +```bash +/pp-status +``` + +Shows: +- Active subproject +- In-progress tasks +- Pending tasks +- Blockers +- Recent changes + +--- + +## File Purpose Guide + +| File | When to Update | Purpose | +|------|----------------|---------| +| **INDEX.md** (root) | Switch active subproject, add high-level TODOs | Project overview + status | +| **WORKFLOW.md** | Never (system guide) | How to use PP system | +| **PRINCIPLES.md** | Add project-wide principles | Methodology & rules | +| **STATUS.md** (subproject) | State changes | Current status, working/blocked | +| **TODO.md** | Add/complete tasks | Task tracking | +| **CHANGELOG.md** | After changes | Change history with context | +| **CODEBASE.md** | Add/modify files | File inventory | +| **LESSONS.md** | After debugging | Problems → solutions | + +--- + +## Best Practices + +### Task Management (TODO.md) + +```markdown +- [ ] T001: Task description +- [>] T002: In-progress task (currently working) +- [x] T003: Completed task +- [-] T004: Cancelled task +``` + +### Changelog Format + +```markdown +## 2025-01-15 | Commit: abc123 + +**Changes**: Added user authentication +**Usage**: `POST /api/auth/login` with email/password +**Context**: Using JWT with 24h expiry +``` + +### Status Clarity + +Keep STATUS.md current: +- What's working +- What's blocked (and why) +- Current focus (1-3 items) +- Next actions + +--- + +## Multiple Subprojects + +```bash +# Initialize with multiple subprojects +/pp-init Full-stack app with backend (FastAPI), frontend (React), and mobile (React Native). Starting with backend. +``` + +Creates: +``` +.context/project/ +├── INDEX.md +├── WORKFLOW.md +├── PRINCIPLES.md +└── backend/ + └── (STATUS, TODO, CHANGELOG, etc.) +└── frontend/ + └── (STATUS, TODO, CHANGELOG, etc.) +└── mobile/ + └── (STATUS, TODO, CHANGELOG, etc.) +``` + +Switch active subproject by editing INDEX.md or using `/pp-update`. + +--- + +## Getting Help + +**By Topic:** +```bash +/pp-help init # Help with initialization +/pp-help workflow # Workflow guide +/pp-help commands # Command reference +``` + +**Troubleshooting:** +- Structure missing? Run `/pp-init` or `/pp-migrate` +- Commands not working? Check `/pp-version` +- Need to update? Reinstall plugin + +**Support:** +- GitHub: https://github.com/unclecode/claude-code-tools +- Issues: https://github.com/unclecode/claude-code-tools/issues + +--- + +## Version + +Run `/pp-version` to check your installed version. diff --git a/commands/pp-init-assets/templates/root/INDEX.md b/commands/pp-init-assets/templates/root/INDEX.md new file mode 100644 index 0000000..2dd400d --- /dev/null +++ b/commands/pp-init-assets/templates/root/INDEX.md @@ -0,0 +1,65 @@ +# {{PROJECT_NAME}} - Project Index + +**Project**: {{PROJECT_DESCRIPTION}} +**Created**: {{DATE}} +**Last Updated**: {{DATE}} + +--- + +## Active Subproject + +**{{ACTIVE_SUBPROJECT}}** + +See `{{ACTIVE_SUBPROJECT}}/STATUS.md` for detailed status. + +--- + +## Quick Start (New Session) + +1. Read this INDEX.md (root overview + current state) +2. Read WORKFLOW.md (understand the system) +3. Go to active subproject folder: `{{ACTIVE_SUBPROJECT}}/` +4. Read subproject's STATUS.md, TODO.md, and other files + +--- + +## Project-Level TODO (High-Level) + +**Major Milestones & Cross-Subproject Tasks:** + +- [ ] {{TODO_ITEM_1}} +- [ ] {{TODO_ITEM_2}} +- [ ] {{TODO_ITEM_3}} + +*For detailed tasks, see subproject TODO.md files* + +--- + +## Subprojects + +| Subproject | Status | Phase | Description | +|------------|--------|-------|-------------| +{{SUBPROJECT_TABLE}} + +--- + +## Subproject Status Summary + +{{SUBPROJECT_STATUS_TABLE}} + +--- + +## File Inheritance + +Root files (WORKFLOW.md, PRINCIPLES.md) apply to all subprojects. +Subproject files extend/override root when needed. + +**Reading order:** +1. Root level files (WORKFLOW, PRINCIPLES) +2. Subproject files (STATUS, TODO, CODEBASE, etc.) + +--- + +## Environment + +{{ENVIRONMENT}} diff --git a/commands/pp-init-assets/templates/root/LESSONS.md b/commands/pp-init-assets/templates/root/LESSONS.md new file mode 100644 index 0000000..a2efa59 --- /dev/null +++ b/commands/pp-init-assets/templates/root/LESSONS.md @@ -0,0 +1,22 @@ +# Lessons Learned (Root) + +Shared learnings applicable to all subprojects. Subprojects have their own LESSONS.md. + +--- + +## Format + +``` +### [TAG] Short Title +**Problem**: What went wrong +**Investigation**: What was tried +**Root Cause**: Why it happened +**Solution**: How it was fixed +**Keywords**: searchable terms +``` + +Tags: [BUG], [CONFIG], [API], [LOGIC], [INTEGRATION] + +--- + + diff --git a/commands/pp-init-assets/templates/root/PRINCIPLES.md b/commands/pp-init-assets/templates/root/PRINCIPLES.md new file mode 100644 index 0000000..4bc3777 --- /dev/null +++ b/commands/pp-init-assets/templates/root/PRINCIPLES.md @@ -0,0 +1,154 @@ +# Principles & Rules (Root) + +Base methodology and rules for all subprojects. Subprojects can extend/override. + +--- + +## Core Philosophy + +**Small, Smart, Strong** +- Small changes, well-tested +- Smart solutions, not over-engineered +- Strong foundations before features + +--- + +## Debugging Methodology + +### Inverted Confidence Rule + +**Code I wrote this session**: 20% confidence it's correct +- Assume I made a mistake +- Prove it works before moving on +- Test thoroughly + +**Existing code**: 80% confidence it's correct +- Assume it works as intended +- Prove it's broken before touching it +- Respect that it was tested/debugged + +### Debugging Decision Tree + +``` +Bug appears +├─ Is there NEW code involved? +│ └─ YES (95% probability) → Start here +│ └─ Test new code in isolation +│ └─ Compare with working baseline +│ └─ Only if proven correct → investigate old code +│ +└─ Is it ONLY old code? + └─ Verify nothing changed + └─ Check environment, dependencies, inputs +``` + +### Before Blaming Existing Code + +1. Why would this have worked before if it's broken? +2. What changed between working and not working? +3. Is there any NEW code in the path? + +If answer to #3 is YES → investigate new code FIRST. + +--- + +## Feature Addition Approach + +1. **Understand first** - Read existing code, understand patterns +2. **Plan before coding** - Explain changes, get confirmation +3. **Minimal changes** - Don't refactor unrelated code +4. **Test incrementally** - Small steps, verify each +5. **Document** - Update TODO, CHANGELOG, relevant docs + +--- + +## Code Change Rules + +1. **Focus on explicit request** - Don't add unrequested changes +2. **Root cause analysis** - For non-trivial bugs, explain before fixing +3. **Explain fundamental changes** - Before implementing, discuss +4. **Don't over-commit** - Test before suggesting commit +5. **Stay doubtful** - Don't assume it works, verify + +--- + +## Error Handling + +- Technical issues → Inform user, explain what's being checked +- Unexpected inputs → Handle gracefully or escalate +- Escalate early if uncertain + +--- + +## Testing Approach + +1. Unit test new functions +2. Integration test with mocked APIs +3. End-to-end test with real systems +4. Log everything during testing +5. Verify before marking task complete + +--- + +# Rules (Dos & Don'ts) + +Hard rules for Claude behavior. Added by user based on observed issues. + +**Never remove rules. Only add.** + +Format: `R###: Rule description` + Context + +--- + +## General Behavior + +**R001**: Do not add "co-author" or Claude Code references in commit messages +Context: User preference, keep commits clean + +**R002**: Do not say "you're absolutely right" or similar excessive validation +Context: Maintain objectivity, don't sugar coat + +**R003**: Do not assume task is done and suggest commit without testing +Context: Stay doubtful, verify before declaring success + +**R004**: Do not start implementation without confirmation when user explains feature +Context: User wants to see proposed changes first, then approve + +**R005**: When explaining changes, wait for confirmation before coding +Context: Avoid wasted work on wrong approach + +**R006**: Always show major file content for review before writing +Context: User is system designer, needs to approve before files are created + +**R007**: Commit after completing each small, logical change +Context: Git history becomes a book telling the story of how the app was built. Each commit is a chapter. Keeps history clean and reviewable. + +--- + +## Code & Architecture + +**R010**: Do not create new files unless absolutely necessary +Context: Prefer editing existing files + +**R011**: Do not refactor unrelated code when fixing a bug +Context: Focus on explicit request only + +**R012**: Do not introduce security vulnerabilities (injection, XSS, etc.) +Context: Standard security practice + +--- + +## Documentation + +**R020**: Update project docs (TODO, CHANGELOG, STATUS) after changes +Context: Maintain context for future sessions + +**R021**: Log debugging experiences in LESSONS.md +Context: Don't repeat past debugging efforts + +--- + + diff --git a/commands/pp-init-assets/templates/root/WORKFLOW.md b/commands/pp-init-assets/templates/root/WORKFLOW.md new file mode 100644 index 0000000..c0d08b4 --- /dev/null +++ b/commands/pp-init-assets/templates/root/WORKFLOW.md @@ -0,0 +1,141 @@ +# Project Workflow + +How to work with this project management system. + +--- + +## For New Chat Session + +``` +1. Read INDEX.md → understand project, current state, active subproject +2. Read WORKFLOW.md → understand system (this file) +3. Read PRINCIPLES.md → understand project principles +4. Go to active subproject folder (listed in INDEX.md) +5. Read subproject's STATUS.md, TODO.md, CODEBASE.md, CHANGELOG.md +6. Read subproject PRINCIPLES.md (if exists, overrides root) +7. Read LESSONS.md → avoid past mistakes +``` + +--- + +## After Making Changes + +*All updates happen in the active subproject folder:* + +1. **Update TODO.md** - Mark tasks done/in-progress +2. **Update CHANGELOG.md** - Log what changed with usage examples +3. **Update STATUS.md** - If system state changed +4. **Update LESSONS.md** - If debugging insight gained +5. **Update CODEBASE.md** - If files added/modified +6. **Update root INDEX.md** - If switching active subproject or adding high-level TODOs +7. **Commit** - Message references TODO ID + +--- + +## File Update Guidelines + +### TODO.md +- `[ ]` pending +- `[>]` in progress +- `[x]` completed +- `[-]` cancelled +- Format: `[status] T###: Description` +- Group by phase/PRD + +### CHANGELOG.md +- Newest first (reverse chronological) +- Format: `## YYYY-MM-DD | Commit: ` +- Include: Changes, Usage (examples/commands), Context +- Archive when >50 entries + +### STATUS.md +- Keep current, not historical +- Sections: Working, Blocked, Current Focus, Recent Decisions +- Short bullet points + +### LESSONS.md +- Format: Problem → Investigation → Root Cause → Solution +- Tag by category: [BUG], [CONFIG], [API], [LOGIC] +- Include searchable keywords + +### CODEBASE.md +- Update after adding/modifying files +- One-line descriptions +- Group by component/purpose + +--- + +## When to Create New PRD + +- New phase starting +- Significant new feature +- Major refactor + +PRD naming: `###_short_name.md` (e.g., `001_initial_setup.md`) + +--- + +## When to Archive + +**CHANGELOG.md**: +- When >50 entries or file getting very long +- Move older entries to `archive/CHANGELOG_YYYY_MM.md` +- Update archive index table in CHANGELOG.md with: + - Archive filename + - Date range + - Topic summary +- Keep index in main file for reference + +**PRDs**: +- Completed PRDs → move to archive/ +- Keep reference in INDEX.md + +**Outdated docs**: +- Move to archive/ with date suffix + +--- + +## Commit Message Format + +``` +: + +[T###] +[T###] + + +``` + +Types: feat, fix, docs, refactor, test + +Example: +``` +feat: Add user authentication + +[T010] Create auth service +[T011] Add login endpoint + +Using JWT with 24h expiry +``` + +--- + +## File Inheritance + +Root files apply to all subprojects. Subproject files extend/override. + +**Reading order:** +1. Root level files (base) +2. Subproject files (extends/overrides) + +Example: Root PRINCIPLES.md has base rules, subproject PRINCIPLES.md adds specific rules. + +--- + +## Principles Reference + +Before debugging or adding features, check: +1. PRINCIPLES.md - methodology (root + subproject) +2. LESSONS.md - past solutions (root + subproject) + +Don't repeat past mistakes. diff --git a/commands/pp-init-assets/templates/subproject/CHANGELOG.md b/commands/pp-init-assets/templates/subproject/CHANGELOG.md new file mode 100644 index 0000000..b539947 --- /dev/null +++ b/commands/pp-init-assets/templates/subproject/CHANGELOG.md @@ -0,0 +1,44 @@ +# {{SUBPROJECT_NAME}} Changelog + +{{SUBPROJECT_NAME}}-specific changes. See root CHANGELOG for cross-project changes. + +Reverse chronological. Newest first. + +--- + +## Archived Changelogs + +| Archive | Period | Topics | +|---------|--------|--------| +| (none yet) | - | - | + +--- + +## {{DATE}} | Subproject Created + +**Changes**: +- Created {{SUBPROJECT_NAME}}/ subproject structure +- Initial files added + +**Context**: +- {{CREATION_CONTEXT}} + +--- + + diff --git a/commands/pp-init-assets/templates/subproject/CODEBASE.md b/commands/pp-init-assets/templates/subproject/CODEBASE.md new file mode 100644 index 0000000..c1d4af3 --- /dev/null +++ b/commands/pp-init-assets/templates/subproject/CODEBASE.md @@ -0,0 +1,33 @@ +# {{SUBPROJECT_NAME}} Codebase Map + +{{SUBPROJECT_NAME}}-specific files. See root CODEBASE.md for shared files. + +--- + +## Existing Files + +| File | Description | +|------|-------------| +{{EXISTING_FILES}} + +--- + +## To Be Created + +| File | Description | +|------|-------------| +{{TO_CREATE_FILES}} + +--- + +## Dependencies + +| File | Source | Description | +|------|--------|-------------| +{{FILE_DEPENDENCIES}} + +--- + + diff --git a/commands/pp-init-assets/templates/subproject/INDEX.md b/commands/pp-init-assets/templates/subproject/INDEX.md new file mode 100644 index 0000000..a0b7cd8 --- /dev/null +++ b/commands/pp-init-assets/templates/subproject/INDEX.md @@ -0,0 +1,45 @@ +# {{SUBPROJECT_NAME}} - {{SUBPROJECT_TITLE}} + +**Subproject**: {{SUBPROJECT_DESCRIPTION}} +**Status**: {{SUBPROJECT_STATUS}} +**Created**: {{DATE}} + +--- + +## Quick Start + +1. Read root INDEX.md and PRINCIPLES.md first (base context) +2. Read this INDEX.md +3. Read STATUS.md (current state) +4. Read TODO.md (pending tasks) +5. Read PRINCIPLES.md (subproject-specific rules) + +--- + +## Scope + +{{SCOPE}} + +--- + +## File Map + +| File | Purpose | +|------|---------| +| INDEX.md | This overview | +| STATUS.md | Current state | +| TODO.md | Task tracking | +| CHANGELOG.md | Subproject changes | +| PRINCIPLES.md | Subproject-specific rules | +| CODEBASE.md | Subproject-specific files | +| LESSONS.md | Subproject-specific lessons | + +--- + +## Dependencies + +{{DEPENDENCIES}} + +--- + + diff --git a/commands/pp-init-assets/templates/subproject/LESSONS.md b/commands/pp-init-assets/templates/subproject/LESSONS.md new file mode 100644 index 0000000..6aefb00 --- /dev/null +++ b/commands/pp-init-assets/templates/subproject/LESSONS.md @@ -0,0 +1,22 @@ +# {{SUBPROJECT_NAME}} Lessons Learned + +{{SUBPROJECT_NAME}}-specific debugging experiences. See root LESSONS.md for shared lessons. + +--- + +## Format + +``` +### [TAG] Short Title +**Problem**: What went wrong +**Investigation**: What was tried +**Root Cause**: Why it happened +**Solution**: How it was fixed +**Keywords**: searchable terms +``` + +Tags: [BUG], [CONFIG], [API], [LOGIC], [INTEGRATION] + +--- + + diff --git a/commands/pp-init-assets/templates/subproject/PRINCIPLES.md b/commands/pp-init-assets/templates/subproject/PRINCIPLES.md new file mode 100644 index 0000000..f74d1ec --- /dev/null +++ b/commands/pp-init-assets/templates/subproject/PRINCIPLES.md @@ -0,0 +1,22 @@ +# {{SUBPROJECT_NAME}} Principles & Rules + +{{SUBPROJECT_NAME}}-specific methodology and rules. Extends root PRINCIPLES.md. + +--- + +## {{SUBPROJECT_NAME}}-Specific Principles + +{{PRINCIPLES}} + +--- + +## {{SUBPROJECT_NAME}}-Specific Rules + +{{RULES}} + +--- + + diff --git a/commands/pp-init-assets/templates/subproject/STATUS.md b/commands/pp-init-assets/templates/subproject/STATUS.md new file mode 100644 index 0000000..8895d36 --- /dev/null +++ b/commands/pp-init-assets/templates/subproject/STATUS.md @@ -0,0 +1,34 @@ +# {{SUBPROJECT_NAME}} Status + +**Last Updated**: {{DATE}} +**Status**: {{SUBPROJECT_STATUS}} + +--- + +## What's Working + +{{WORKING}} + +--- + +## What's Not Working / Blocked + +{{BLOCKED}} + +--- + +## Current Focus + +{{FOCUS}} + +--- + +## Environment + +{{ENVIRONMENT}} + +--- + +## Next Actions + +{{NEXT_ACTIONS}} diff --git a/commands/pp-init-assets/templates/subproject/TODO.md b/commands/pp-init-assets/templates/subproject/TODO.md new file mode 100644 index 0000000..79266d2 --- /dev/null +++ b/commands/pp-init-assets/templates/subproject/TODO.md @@ -0,0 +1,26 @@ +# {{SUBPROJECT_NAME}} TODO + +**Format**: `[status] T###: Description` +- `[ ]` pending +- `[>]` in progress +- `[x]` completed +- `[-]` cancelled + +--- + +## Phase 1 + +{{PHASE_1_TASKS}} + +--- + +## Future Phases + +{{FUTURE_TASKS}} + +--- + +## Notes + +- T### IDs are permanent, never reuse +- Reference T### in commit messages and CHANGELOG diff --git a/commands/pp-init.md b/commands/pp-init.md new file mode 100644 index 0000000..1a6c269 --- /dev/null +++ b/commands/pp-init.md @@ -0,0 +1,164 @@ +# Initialize Project Management Structure + +Generate a multi-subproject management structure in `.context/project/`. + +**Note**: For small projects needing only one subproject, use the name **"main"** as the default. + +## Arguments + +$ARGUMENTS + +## Assets Location + +Template files are in `~/.claude/commands/pp-init-assets/templates/`: +- `root/` - Root level templates (WORKFLOW.md, PRINCIPLES.md, etc.) +- `subproject/` - Subproject templates + +## Execution Instructions + +### Step 1: Parse Arguments + +Check for mode: +- `--quiet` or `-q` → Quiet mode +- Contains text (not flags) → Description mode +- Empty → Interactive mode + +### Step 2: Create Directory Structure + +```bash +# Create root directories +mkdir -p .context/project/prds .context/project/docs .context/project/archive + +# Create subproject directories (for each subproject) +mkdir -p .context/project/{subproject}/prds +mkdir -p .context/project/{subproject}/docs +mkdir -p .context/project/{subproject}/archive +mkdir -p .context/project/{subproject}/history +``` + +**Note**: The `history/` directory stores ultra-compact session summaries created by `/pp-checkpoint`. + +### Step 3: Copy Fixed Templates + +These files are copied as-is (no placeholders to fill): + +```bash +# Copy WORKFLOW.md, PRINCIPLES.md, and LESSONS.md (fully populated) +cp ~/.claude/commands/pp-init-assets/templates/root/WORKFLOW.md .context/project/ +cp ~/.claude/commands/pp-init-assets/templates/root/PRINCIPLES.md .context/project/ +cp ~/.claude/commands/pp-init-assets/templates/root/LESSONS.md .context/project/ +``` + +**Note**: Root level only has WORKFLOW, PRINCIPLES, LESSONS. All tracking files (STATUS, TODO, CHANGELOG, CODEBASE) live in subprojects. + +### Step 4: Process Template Files + +For files with `{{PLACEHOLDER}}` markers: +1. Read template from assets +2. Replace placeholders with collected data +3. Write to destination + +**Root templates to process:** +- INDEX.md (combines project overview + active subproject + high-level TODOs + status summary) + +**Subproject templates to process (for each subproject):** +- INDEX.md +- STATUS.md +- TODO.md +- CHANGELOG.md +- PRINCIPLES.md (optional - only if different from root) +- CODEBASE.md +- LESSONS.md + +### Step 5: Mode-Specific Behavior + +#### Quiet Mode (`--quiet`) + +1. Create structure with 1 default subproject named "main" +2. Copy fixed templates +3. Process other templates with minimal placeholders: + - `{{DATE}}` → current date + - `{{PROJECT_NAME}}` → "Project" + - `{{SUBPROJECT_NAME}}` → "main" + - Other placeholders → leave as "TBD" or empty tables +4. No questions asked + +#### Description Mode (text provided) + +1. Parse description for: + - Project name + - Subproject names (look for patterns like "with X, Y, Z subprojects") + - Active subproject (look for "starting with" or first mentioned) + - Technologies mentioned +2. Ask clarifying questions only if critical info missing +3. Generate with parsed data + +#### Interactive Mode (no arguments) + +Ask these questions: +1. "What is the project name?" +2. "Brief description (one line):" +3. "List subprojects (comma-separated, e.g., 'frontend, backend, mobile'):" +4. "Which subproject is active first?" +5. "Key technologies? (optional)" +6. "Any environment details? (optional)" + +Then generate with collected data. + +## Placeholder Reference + +### Root Level (INDEX.md) + +| Placeholder | Description | +|-------------|-------------| +| `{{DATE}}` | Current date (YYYY-MM-DD) | +| `{{PROJECT_NAME}}` | Project name | +| `{{PROJECT_DESCRIPTION}}` | One-line description | +| `{{ACTIVE_SUBPROJECT}}` | Name of active subproject | +| `{{TODO_ITEM_1}}` | High-level TODO item 1 | +| `{{TODO_ITEM_2}}` | High-level TODO item 2 | +| `{{TODO_ITEM_3}}` | High-level TODO item 3 | +| `{{SUBPROJECT_TABLE}}` | Markdown table of subprojects with status/phase/description | +| `{{SUBPROJECT_STATUS_TABLE}}` | Compact status summary of all subprojects | +| `{{ENVIRONMENT}}` | Environment details | + +### Subproject Level + +| Placeholder | Description | +|-------------|-------------| +| `{{SUBPROJECT_NAME}}` | Subproject folder name | +| `{{SUBPROJECT_TITLE}}` | Human-readable title | +| `{{SUBPROJECT_DESCRIPTION}}` | What this subproject does | +| `{{SUBPROJECT_STATUS}}` | Current status | +| `{{SCOPE}}` | Bullet list of scope items | +| `{{WORKING}}` | What's currently working | +| `{{BLOCKED}}` | What's blocked | +| `{{FOCUS}}` | Current focus items | +| `{{NEXT_ACTIONS}}` | Next action items | +| `{{PHASE_1_TASKS}}` | Initial tasks | +| `{{FUTURE_TASKS}}` | Future phase tasks | +| `{{PRINCIPLES}}` | Subproject-specific principles | +| `{{RULES}}` | Subproject-specific rules | +| `{{EXISTING_FILES}}` | Existing file table rows | +| `{{TO_CREATE_FILES}}` | Files to create table rows | +| `{{FILE_DEPENDENCIES}}` | File dependencies table rows | +| `{{CREATION_CONTEXT}}` | Why subproject was created | + +## After Generation + +1. Show created structure (tree view) +2. List files that need customization +3. Suggest: "Fill in placeholders marked 'TBD', then create your first PRD" + +## Examples + +```bash +# Quiet mode - minimal structure +/pp-init --quiet + +# With description +/pp-init E-commerce platform with frontend (React), backend (FastAPI), and mobile (React Native). Starting with backend. + +# Interactive +/pp-init +``` diff --git a/commands/pp-migrate-v2.md b/commands/pp-migrate-v2.md new file mode 100644 index 0000000..6dedf49 --- /dev/null +++ b/commands/pp-migrate-v2.md @@ -0,0 +1,262 @@ +# Migrate PP v1.0 to v1.1+ Structure + +**TEMPORARY COMMAND**: Upgrades old Project Planning structure (v1.0.0) to new structure (v1.1.0+). + +## Arguments + +None + +## Purpose + +Migrate existing PP projects from old structure (with root STATUS.md, CODEBASE.md, CHANGELOG.md) to new simplified structure (with combined INDEX.md). + +**Use this if you have projects using the old PP structure that need upgrading.** + +## What Changes + +### Old Structure (v1.0.0): +``` +.context/project/ +├── INDEX.md (old format) +├── STATUS.md ← will be archived +├── CODEBASE.md ← will be archived +├── CHANGELOG.md ← will be archived +├── WORKFLOW.md (old) +└── subproject/ +``` + +### New Structure (v1.1.0+): +``` +.context/project/ +├── INDEX.md ← NEW (combines INDEX + STATUS + high-level TODOs) +├── WORKFLOW.md ← UPDATED +├── PRINCIPLES.md +├── LESSONS.md +└── subproject/ ← UNCHANGED +``` + +## Execution Instructions + +### Step 1: Detect Old Structure + +Check if this is an old PP structure: +```bash +ls .context/project/STATUS.md +``` + +**If STATUS.md doesn't exist:** +- Inform user: "Project already using new structure or not initialized. Nothing to migrate." +- Exit + +**If STATUS.md exists:** +- Proceed to Step 2 + +### Step 2: Read Old Files + +Read and extract data from old files: + +**From STATUS.md:** +- Active subproject (section: "## Active Subproject") +- Subproject summary table (section: "## Subproject Summary") +- Overall progress (if any) +- Cross-project dependencies (if any) +- Recent decisions (if any) + +**From INDEX.md (old):** +- Project name +- Project description +- Created date +- Subprojects table +- Environment info + +**From CODEBASE.md:** +- Check if it had any important root-level file listings +- (Usually empty or minimal in old structure) + +**From CHANGELOG.md:** +- Check if it had any cross-project entries +- (Usually empty or minimal in old structure) + +### Step 3: Create New INDEX.md + +Generate new INDEX.md using the template from `pp-init-assets/templates/root/INDEX.md`: + +**Populate with extracted data:** +- `{{PROJECT_NAME}}` - from old INDEX.md +- `{{PROJECT_DESCRIPTION}}` - from old INDEX.md +- `{{DATE}}` - original created date from old INDEX.md +- `{{ACTIVE_SUBPROJECT}}` - from old STATUS.md +- `{{SUBPROJECT_TABLE}}` - from old INDEX.md +- `{{SUBPROJECT_STATUS_TABLE}}` - from old STATUS.md +- `{{ENVIRONMENT}}` - from old INDEX.md +- `{{TODO_ITEM_1}}`, `{{TODO_ITEM_2}}`, `{{TODO_ITEM_3}}` - set to "TBD" or extract from context + +**Save to:** `.context/project/INDEX.md` (overwrite old INDEX.md) + +### Step 4: Update WORKFLOW.md + +Replace old WORKFLOW.md with new version: +```bash +cp ~/.claude/commands/pp-init-assets/templates/root/WORKFLOW.md .context/project/WORKFLOW.md +``` + +### Step 5: Archive Old Files + +Move old root-level files to archive: +```bash +mkdir -p .context/project/archive/v1.0-migration-$(date +%Y%m%d) +mv .context/project/STATUS.md .context/project/archive/v1.0-migration-*/ +mv .context/project/CODEBASE.md .context/project/archive/v1.0-migration-*/ +mv .context/project/CHANGELOG.md .context/project/archive/v1.0-migration-*/ +``` + +**Create archive README:** +Create `.context/project/archive/v1.0-migration-{date}/README.md`: +```markdown +# v1.0 Structure Archive + +These files were archived during migration from PP v1.0.0 to v1.1.0+ on {date}. + +## Archived Files + +- STATUS.md - Replaced by combined INDEX.md +- CODEBASE.md - Moved to subproject level only +- CHANGELOG.md - Moved to subproject level only + +## What Changed + +v1.1.0+ simplified the root structure: +- Root now has only: INDEX.md, WORKFLOW.md, PRINCIPLES.md, LESSONS.md +- All detailed tracking (STATUS, TODO, CHANGELOG, CODEBASE) lives in subprojects +- INDEX.md now combines project overview + active subproject + status summary + +## Data Preservation + +All data from these files was extracted and merged into the new INDEX.md. +Nothing was lost during migration. +``` + +### Step 6: Verify Subprojects Unchanged + +Confirm that subproject directories remain intact: +```bash +ls .context/project/{subproject}/ +``` + +**Subprojects should still have:** +- STATUS.md +- TODO.md +- CHANGELOG.md +- CODEBASE.md +- etc. + +**No changes needed** - subproject structure is the same in both versions. + +### Step 7: Show Summary + +Display migration summary: +``` +## PP Structure Migration Complete + +**Old Structure**: v1.0.0 +**New Structure**: v1.1.0+ +**Date**: {YYYY-MM-DD HH:MM} + +### Changes Made: + +✅ Created new INDEX.md (combines INDEX + STATUS + high-level TODOs) +✅ Updated WORKFLOW.md to new version +✅ Archived old files: + - STATUS.md → archive/v1.0-migration-{date}/ + - CODEBASE.md → archive/v1.0-migration-{date}/ + - CHANGELOG.md → archive/v1.0-migration-{date}/ + +### What Stayed the Same: + +✓ All subproject directories unchanged +✓ All subproject files (STATUS, TODO, CHANGELOG, CODEBASE) intact +✓ Project data preserved in new INDEX.md +✓ PRINCIPLES.md, LESSONS.md unchanged + +### New Root Structure: + +.context/project/ +├── INDEX.md ← NEW (combined) +├── WORKFLOW.md ← UPDATED +├── PRINCIPLES.md +├── LESSONS.md +└── {subprojects}/ ← UNCHANGED + +### Next Steps: + +1. Review new INDEX.md to verify data accuracy +2. Check active subproject is correct +3. Continue using PP commands as normal +4. Old files safely archived if you need to reference them + +--- + +**Note**: This migration is one-way. The old files are archived, not deleted. +You can always reference them in the archive/ folder. +``` + +## Error Handling + +**If .context/project/ doesn't exist:** +``` +No PP structure found. Run /pp-init first to create project structure. +``` + +**If STATUS.md doesn't exist:** +``` +Project already using new structure (v1.1.0+) or not fully initialized. + +If you have subprojects but no root STATUS.md, you're likely already upgraded. +Nothing to migrate. +``` + +**If migration fails mid-way:** +``` +Migration encountered an error. Your files are safe: +- Original files still in place +- Partial changes may exist + +Recommendation: +1. Check .context/project/ directory +2. Manually review what was changed +3. Restore from backup if needed +4. Report issue to plugin maintainer +``` + +## Safety Features + +- **Non-destructive**: Old files archived, not deleted +- **Backup created**: Archive folder with timestamp +- **Verification**: Checks structure before starting +- **Detailed logging**: Shows exactly what changed + +## Usage Example + +```bash +# In a project with old PP structure +cd /path/to/project + +# Run migration +/pp-migrate-v2 + +# Result: Upgraded to new structure +# Old files safely archived +``` + +## Notes + +- **Temporary command**: Will be removed in future versions once most projects are migrated +- **One-time operation**: Only need to run once per project +- **Idempotent**: Safe to run multiple times (detects if already migrated) +- **Subprojects untouched**: Only root structure changes +- **Data preserved**: Nothing is lost, everything merged into new INDEX.md + +## Version History + +- Created for v1.1.0 → v1.1.0+ migration +- To be deprecated in v2.0.0 diff --git a/commands/pp-migrate.md b/commands/pp-migrate.md new file mode 100644 index 0000000..43fa476 --- /dev/null +++ b/commands/pp-migrate.md @@ -0,0 +1,175 @@ +# Migrate Existing Project to Project Planning Structure + +Convert an existing project (with scattered markdown/docs) to use `.context/project/` structure. + +**Note**: For small projects needing only one subproject, use the name **"main"** as the default. + +## Arguments + +$ARGUMENTS + +## Overview + +This command: +1. Launches explorer agents to scan and understand your project +2. Discovers existing markdown files, subprojects, and current state +3. Creates `.context/project/` structure with pre-filled content +4. Optionally moves existing files to proper locations + +## Execution Instructions + +### Step 1: Initial Scan with Explorer Agents + +Launch 5 parallel explorer agents to gather project information: + +**Agent 1: Markdown Discovery** +``` +Find all markdown files in the project. For each file: +- Path +- Title/purpose (from content) +- Type: documentation, decision log, notes, readme, spec, etc. +Report as structured list. +``` + +**Agent 2: Project Structure Analysis** +``` +Analyze the codebase structure: +- Main directories and their purposes +- Identify potential subprojects/modules (e.g., frontend/, backend/, api/) +- Key technologies used +- Entry points and main files +Report the logical structure. +``` + +**Agent 3: Current State Discovery** +``` +Find indicators of current project state: +- TODOs in code comments +- README status sections +- Any existing tracking files +- Recent git commits (last 10) for context +- Package.json, requirements.txt, etc. for dependencies +Report what's working, what's in progress. +``` + +**Agent 4: Configuration & Environment** +``` +Find configuration details: +- Environment files (.env.example, config files) +- Build/deployment configs +- API endpoints or external services +- Database schemas if present +Report environment setup. +``` + +**Agent 5: Documentation Quality** +``` +Assess existing documentation: +- Is there a main README? +- API docs? +- Architecture docs? +- Are docs up to date or stale? +Report documentation gaps and strengths. +``` + +### Step 2: Synthesize Findings + +After agents complete: +1. Combine all findings +2. Identify: + - Project name (from package.json, README, or directory) + - Subprojects (from structure analysis) + - Current state (from state discovery) + - Existing files to migrate + - Documentation gaps + +### Step 3: Ask Clarifying Questions + +Present findings and ask: + +1. "I found these potential subprojects: [list]. Is this correct? Any to add/remove?" +2. "Which subproject should be active/primary?" +3. "I found [N] markdown files. Should I organize them into the new structure?" +4. "Any additional context about the project I should know?" + +### Step 4: Generate Structure with Content + +Create `.context/project/` structure using `pp-init-assets/templates/` but: + +**Pre-fill root INDEX.md instead of placeholders:** + +- **INDEX.md**: Use discovered project name, description from README, list actual subprojects, set active subproject, add discovered high-level TODOs, fill subproject status table +- **WORKFLOW.md**: Copy as-is +- **PRINCIPLES.md**: Copy as-is +- **LESSONS.md**: Copy as-is + +**For each subproject:** + +- **INDEX.md**: Subproject overview +- **TODO.md**: Convert discovered TODOs from code into task list +- **STATUS.md**: Actual status from analysis +- **CODEBASE.md**: Files in that subproject +- **CHANGELOG.md**: Initialize with recent git history summary for that subproject +- **PRINCIPLES.md**: Only if subproject has specific principles (usually skip) +- **LESSONS.md**: Copy from root template + +### Step 5: Migration Plan (Requires Confirmation) + +Present migration plan: + +``` +Files to move: +- docs/api-spec.md → .context/project/{subproject}/docs/ +- DECISIONS.md → .context/project/{subproject}/docs/ +- old-notes.md → .context/project/archive/ + +Files to keep in place: +- README.md (root) +- CONTRIBUTING.md (root) +``` + +Ask: "Should I proceed with moving these files? (y/n)" + +If yes, execute moves. +If no, skip migration, just create structure. + +### Step 6: Final Report + +Show: +1. Created structure (tree view) +2. Files that were migrated +3. Suggested next steps: + - Review generated STATUS.md for accuracy + - Add any missing TODOs + - Create first PRD for current work + +## Templates Reference + +Uses same templates as `/pp-init` from: +`~/.claude/commands/pp-init-assets/templates/` + +But fills content from discovered information instead of placeholders. + +## Examples + +```bash +# Basic migration +/pp-migrate + +# With context hint +/pp-migrate This is a Facebook ads automation project with webhook server and analytics dashboard + +# Skip file migration +/pp-migrate --no-move +``` + +## Flags + +- `--no-move`: Create structure but don't move existing files +- `--dry-run`: Show what would be done without making changes + +## Notes + +- Always backs up files before moving (copies to archive first) +- Preserves git history by using `git mv` when possible +- If `.context/project/` already exists, asks before overwriting diff --git a/commands/pp-remove.md b/commands/pp-remove.md new file mode 100644 index 0000000..d15a1d1 --- /dev/null +++ b/commands/pp-remove.md @@ -0,0 +1,122 @@ +# Remove Subproject + +Remove a subproject from the project structure. + +## Arguments + +$ARGUMENTS + +## Purpose + +Safely remove a subproject. This will: +1. Archive the subproject folder (not delete) +2. Update root files to remove references +3. Log the removal + +## Execution Instructions + +### Step 1: Parse Query + +Extract subproject name from query. + +If empty, list available subprojects and ask which to remove. + +### Step 2: Verify Subproject Exists + +```bash +ls .context/project/{name}/INDEX.md +``` + +If not found, show error: "Subproject '{name}' not found." + +### Step 3: Check if Active + +Read `.context/project/STATUS.md` to check if this is the active subproject. + +If active, ask: "This is the active subproject. Which subproject should become active instead?" + +### Step 4: Confirm Removal + +Show warning: +``` +## Removing Subproject: {name}/ + +This will: +- Archive {name}/ to archive/{name}_{date}/ +- Remove from INDEX.md subproject table +- Remove from STATUS.md summary +- Remove from CODEBASE.md refs +- Add removal entry to CHANGELOG.md + +Files will NOT be deleted, just archived. + +Proceed? (yes/no) +``` + +Wait for confirmation. + +### Step 5: Archive Subproject + +```bash +mv .context/project/{name} .context/project/archive/{name}_{date} +``` + +### Step 6: Update Root Files + +**INDEX.md**: Remove from subproject table + +**STATUS.md**: +- Remove from subproject summary table +- If was active, update active subproject + +**CODEBASE.md**: Remove from subproject refs + +**CHANGELOG.md**: Add entry +```markdown +## {DATE} | Removed Subproject: {name} + +**Changes**: +- Archived {name}/ to archive/{name}_{date}/ +- Removed from project structure + +**Context**: +- {reason if provided} +- Files preserved in archive +``` + +### Step 7: Confirm Removal + +``` +## Removed Subproject: {name}/ + +**Archived to**: archive/{name}_{date}/ +**Active subproject**: {new_active} + +Updated: +- Root INDEX.md +- Root STATUS.md +- Root CODEBASE.md +- Root CHANGELOG.md + +To restore, move from archive back to project root. +``` + +## Query Examples + +```bash +# With name +/pp-remove old-api + +# With reason +/pp-remove old-api - replaced by new api design + +# Interactive (will list and ask) +/pp-remove +``` + +## Safety Notes + +- Never deletes files, only archives +- Always asks for confirmation +- Logs removal in CHANGELOG +- Can be restored by moving from archive diff --git a/commands/pp-resume.md b/commands/pp-resume.md new file mode 100644 index 0000000..06a4173 --- /dev/null +++ b/commands/pp-resume.md @@ -0,0 +1,123 @@ +# Resume Project Session + +Start a new chat session with full project context loaded. + +## Arguments + +$ARGUMENTS + +## Purpose + +This is the FIRST command to run when starting a new chat session. It: +1. Loads all project context into memory +2. Understands current state +3. Prepares to continue work +4. Optionally focuses on specific direction from query + +## Execution Instructions + +### Step 1: Verify Project Exists + +```bash +ls .context/project/INDEX.md +``` + +If not found, inform user: "No project structure found. Run `/pp-init` first." + +### Step 2: Read Root Context + +Read these files in order: +1. `.context/project/INDEX.md` - Project overview, subprojects, active subproject, high-level status +2. `.context/project/WORKFLOW.md` - How this system works +3. `.context/project/PRINCIPLES.md` - Methodology and rules +4. `.context/project/LESSONS.md` - Past learnings + +### Step 3: Read Active Subproject Context + +From INDEX.md, identify active subproject, then read: +1. `.context/project/{active}/STATUS.md` +2. `.context/project/{active}/TODO.md` +3. `.context/project/{active}/CODEBASE.md` +4. `.context/project/{active}/LESSONS.md` +5. `.context/project/{active}/PRINCIPLES.md` (if exists - overrides root) + +### Step 4: Read Recent Changes + +Read last 3-5 entries from: +- `.context/project/{active}/CHANGELOG.md` + +### Step 5: Process Optional Query + +If query provided: +- Parse for direction (e.g., "continue booking flow", "fix the API bug") +- Focus context on relevant TODO items +- Identify relevant LESSONS if debugging + +### Step 6: Check for Next Session Instructions (NEW) + +**After loading all standard files**, check for continuation instructions: + +```bash +ls .context/project/{active}/NEXT.md +``` + +**If NEXT.md exists:** +1. Read `.context/project/{active}/NEXT.md` +2. Extract history file path from NEXT.md +3. Read that history file for previous session context +4. Note the full chat transcript path (but DO NOT load it) + +**If NEXT.md does NOT exist:** +- Skip this step, proceed to summary + +### Step 7: Generate Resume Summary + +Output a compact summary: + +``` +## Session Resumed: {PROJECT_NAME} + +**Active**: {subproject}/ - {current phase} +**Status**: {brief status} + +{IF NEXT.md exists:} +### Instructions from Previous Session +{content from NEXT.md "What To Do Next" section} + +**Previous Session**: {date} - {brief summary from history file} + +### Current Focus +{from STATUS.md current focus} + +### In Progress +{tasks marked [>] from TODO.md} + +### Recent Changes +{last 2-3 changelog entries, one line each} + +### Key Context +{if query provided, relevant context for that direction} + +--- + +Ready to continue. {If NEXT.md exists: "Following previous session's plan." else: "What would you like to work on?"} +``` + +## Query Examples + +```bash +# General resume - load everything +/pp-resume + +# With direction - focus on specific area +/pp-resume continue the booking flow implementation +/pp-resume fix the WhatsApp routing bug +/pp-resume review yesterday's changes +``` + +## Important Notes + +- This command is READ-ONLY (no file modifications) +- After this, Claude has full project context +- User can immediately start working +- If query mentions something not in TODO, suggest adding it diff --git a/commands/pp-status.md b/commands/pp-status.md new file mode 100644 index 0000000..2cb0e09 --- /dev/null +++ b/commands/pp-status.md @@ -0,0 +1,117 @@ +# Project Status Summary + +Show compact status of the entire project. + +## Arguments + +$ARGUMENTS + +## Purpose + +Quick overview of project state without loading full context. Shows: +- Active subproject and its focus +- All subprojects status +- In-progress tasks +- Recent changes +- Any blockers + +## Execution Instructions + +### Step 1: Read Status Files + +Read these files: +- `.context/project/INDEX.md` (get active subproject + project summary) +- `.context/project/{active}/STATUS.md` +- `.context/project/{active}/TODO.md` +- `.context/project/{active}/CHANGELOG.md` (last 2-3 entries) + +### Step 2: Parse Optional Query + +If query provided, focus on: +- Specific subproject: `/pp-status maya` +- Specific aspect: `/pp-status tasks` or `/pp-status blockers` + +### Step 3: Generate Compact Summary + +Output format: + +``` +## Project Status: {PROJECT_NAME} + +### Active: {subproject}/ +**Phase**: {current phase} +**Focus**: {one-line current focus} + +### In Progress +{tasks marked [>], one line each with T### ID} + +### Pending (Next Up) +{next 3-5 pending tasks} + +### Subprojects +| Name | Status | Phase | +|------|--------|-------| +{table from root STATUS.md} + +### Blockers +{any blocked items from STATUS.md, or "None"} + +### Recent Activity +{last 2-3 changelog entries, date + one-line summary} + +--- +Last updated: {date from STATUS.md} +``` + +### Step 4: Subproject-Specific Status + +If query specifies a subproject: + +``` +## Subproject Status: {name}/ + +**Phase**: {phase} +**Status**: {status} + +### What's Working +{bullet list} + +### What's Blocked +{bullet list or "Nothing blocked"} + +### Current Focus +{numbered list} + +### Tasks +**In Progress**: {count} +**Pending**: {count} +**Completed**: {count} + +### Next Actions +{from STATUS.md} +``` + +## Query Examples + +```bash +# Full project status +/pp-status + +# Specific subproject +/pp-status maya + +# Focus on tasks only +/pp-status tasks + +# Focus on blockers +/pp-status blockers +``` + +## Output Notes + +- Keep it COMPACT - fits in one screen +- Use tables for multiple items +- One line per item max +- Show counts, not full lists +- Highlight blockers prominently +- This is READ-ONLY (no modifications) diff --git a/commands/pp-triage.md b/commands/pp-triage.md new file mode 100644 index 0000000..2ce7697 --- /dev/null +++ b/commands/pp-triage.md @@ -0,0 +1,246 @@ +# Triage Issue or Feature Request + +Root cause investigation and solution design for issues, bugs, or feature requests. + +## Arguments + +$ARGUMENTS + +Description of the issue or feature. Can include error messages, URLs, or attach screenshots. + +## Purpose + +Deep investigation to find ROOT CAUSES, not just symptoms. Uses triage-agent for systematic analysis. + +**Philosophy:** Don't just treat the rash, find the root cause (diet, stress, etc.) and fix both. + +## Execution Instructions + +### Step 1: Collect Issue Information + +From user query, gather: +- **Issue description** (what's happening) +- **Error messages** (if any) +- **Steps to reproduce** (if provided) +- **Expected vs actual behavior** +- **Screenshots/links** (if attached) +- **Affected area** (frontend, backend, API, etc.) + +If critical info missing, ask clarifying questions: +- "What error message do you see?" +- "When does this happen?" +- "Which part of the system is affected?" + +### Step 2: Read PP Context First + +**IMPORTANT**: Before launching agent, gather context from PP files: + +Read these files: +1. `.context/project/INDEX.md` - Identify active subproject +2. `.context/project/{active}/CODEBASE.md` - Understand file structure +3. `.context/project/{active}/STATUS.md` - Current state, known issues +4. `.context/project/{active}/CHANGELOG.md` (last 5-10 entries) - Recent changes +5. `.context/project/{active}/LESSONS.md` - Past solutions +6. `.context/project/PRINCIPLES.md` - Project principles + +This context is passed to the agent so it doesn't re-explore everything. + +### Step 3: Launch Triage Agent + +Spawn the triage-agent with: +- Issue description +- PP context (from Step 2) +- Specific focus area (if mentioned) + +``` +Use Task tool with subagent_type: "triage-agent" +Prompt: Investigate this issue using PP context: + +Issue: {user's issue description} + +PP Context: +- Active subproject: {from INDEX.md} +- Codebase structure: {from CODEBASE.md} +- Current status: {from STATUS.md} +- Recent changes: {from CHANGELOG.md} +- Past lessons: {from LESSONS.md} +- Principles: {from PRINCIPLES.md} + +Find ROOT CAUSE and provide solution or debugging plan. +``` + +### Step 4: Agent Investigation + +The triage-agent will: +1. Start with PP context (not from scratch) +2. Investigate code systematically +3. Find root cause (causation, not just correlation) +4. Determine if cause is clear or needs debugging +5. Provide solution OR debugging technique +6. Format findings as structured report + +### Step 5: Present Report + +Agent returns report in this format: + +```markdown +## Triage Report: {Issue Title} + +**Date**: {YYYY-MM-DD HH:MM} +**Subproject**: {subproject name} +**Severity**: {Critical / High / Medium / Low} + +--- + +### Issue Summary + +{Brief description of reported issue} + +--- + +### Root Cause Analysis + +**Symptom**: {What appears to be happening} + +**Investigation Path**: +1. {Step 1 of investigation} +2. {Step 2 of investigation} +3. {Step 3 of investigation} + +**Root Cause**: {The actual underlying cause} + +**Why This Matters**: {Explanation of causation, not just correlation} + +--- + +### Solution + +{IF ROOT CAUSE IS CLEAR}: + +**Recommended Fix**: +1. {Step 1} +2. {Step 2} +3. {Step 3} + +**Files to Modify**: +- `path/to/file.ext` - {what to change} +- `another/file.js` - {what to change} + +**Implementation Notes**: +- {Important consideration 1} +- {Important consideration 2} + +{IF ROOT CAUSE IS UNCLEAR}: + +**Debugging Strategy**: +1. **Add logging**: + - In `file.ext` line X: log variable Y + - In `file2.js` line Z: log state before/after + +2. **Test hypothesis**: + - {Hypothesis 1}: Test by {method} + - {Hypothesis 2}: Test by {method} + +3. **Narrow down**: + - {Approach to isolate the issue} + +**Next Steps**: +1. {Action item 1} +2. {Action item 2} +3. {Re-run /pp-triage after gathering data} + +--- + +### Related Context + +**Recent Changes That May Be Related**: +- {CHANGELOG entry if relevant} + +**Past Similar Issues**: +- {LESSONS.md entry if relevant} + +**Affected Components**: +- {Component 1} +- {Component 2} + +--- + +### Recommended Documentation Updates + +**Add to LESSONS.md**: +``` +## {Date} | {Issue Title} + +**Problem**: {Brief problem} +**Root Cause**: {Cause} +**Solution**: {Solution} +**Tag**: [BUG] or [FEATURE] or [CONFIG] +``` + +**Update STATUS.md**: +- If blocking: Add to "Blocked" section +- If in progress: Add to "Current Focus" + +--- + +### Confidence Level + +**Root Cause Certainty**: {High / Medium / Low} +**Solution Confidence**: {High / Medium / Low} + +{If low confidence, explain why and what additional info is needed} +``` + +### Step 6: Offer Follow-Up Actions + +After presenting report, offer: + +``` +## Next Steps + +Would you like me to: +1. Implement the recommended fix? +2. Add debugging logs as suggested? +3. Update LESSONS.md with this finding? +4. Create a TODO task for this fix? +5. Something else? +``` + +## Query Examples + +```bash +# Bug report +/pp-triage Getting 500 errors on /api/users endpoint when user has no profile + +# Feature request +/pp-triage Need to add email notifications when order status changes + +# Performance issue +/pp-triage Dashboard loads slowly with more than 100 items + +# With error message +/pp-triage TypeError: Cannot read property 'name' of undefined in UserProfile component + +# With screenshot +/pp-triage [attach screenshot] Login button not working on mobile +``` + +## Notes + +- Triage agent has access to all PP files for context +- Agent focuses on ROOT CAUSE, not symptoms +- If multiple possible causes, agent ranks by likelihood +- Report is designed to be saved in docs/ or archive/ for reference +- Always offer to update LESSONS.md with findings +- Principle: Fix the cause (diet) not just the symptom (rash) + +## Agent Behavior + +The triage-agent is trained to: +- Read PP context FIRST (don't re-explore) +- Think systematically (hypothesis → test → conclude) +- Find causation, not correlation +- Provide actionable solutions +- Suggest debugging when uncertain +- Be honest about confidence level +- Format output for easy scanning and future reference diff --git a/commands/pp-update.md b/commands/pp-update.md new file mode 100644 index 0000000..ded2619 --- /dev/null +++ b/commands/pp-update.md @@ -0,0 +1,116 @@ +# Update Project Documentation + +Update project files after making changes. + +## Arguments + +$ARGUMENTS + +## Purpose + +Run after making code changes to keep project documentation in sync. Updates: +- TODO.md (mark tasks done/in-progress) +- CHANGELOG.md (log changes) +- STATUS.md (if state changed) +- CODEBASE.md (if files added/modified) +- LESSONS.md (if debugging insights gained) + +## Execution Instructions + +### Step 1: Identify Active Subproject + +Read `.context/project/INDEX.md` to get active subproject. + +### Step 2: Parse Query + +Query should describe what was done: +- "completed T010, T011" → Mark tasks done +- "added webhook/llm_service.py" → Update CODEBASE +- "fixed the routing bug" → Update CHANGELOG, possibly LESSONS +- "changed booking flow approach" → Update CHANGELOG with context + +If no query, ask: "What changes did you make? (tasks completed, files added, bugs fixed)" + +### Step 3: Determine What to Update + +Based on query, identify which files need updates: + +| Query Pattern | Files to Update | +|---------------|-----------------| +| "completed T###" | TODO.md, CHANGELOG.md | +| "added/created file" | CODEBASE.md, CHANGELOG.md | +| "fixed bug" | CHANGELOG.md, possibly LESSONS.md | +| "changed approach" | CHANGELOG.md, possibly STATUS.md | +| "working on T###" | TODO.md (mark in-progress) | + +### Step 4: Collect Additional Info + +Ask for any missing info: +- For CHANGELOG: "Brief description of what changed?" +- For CHANGELOG: "Any usage examples to include?" +- For LESSONS: "What was the root cause and solution?" + +### Step 5: Make Updates + +Update files in this order: +1. TODO.md - Mark tasks +2. CODEBASE.md - Add files +3. LESSONS.md - Add learnings +4. CHANGELOG.md - Log everything +5. STATUS.md - Update state if needed + +### Step 6: Confirm Updates + +Show summary: +``` +## Updated Project Docs + +**TODO.md**: Marked T010, T011 as completed +**CODEBASE.md**: Added webhook/llm_service.py +**CHANGELOG.md**: Added entry for LLM service implementation + +Ready to commit? Run: git add . && git commit +``` + +### Step 7: Trigger Cleanup Check + +After confirming updates, automatically check for cleanup: + +1. Scan for temporary/draft files (see `/pp-clean` for patterns) +2. If files found, show preview: +``` +## Cleanup Check + +Found files to clean: +- DELETE: scratch_test.md, debug_output.md +- ARCHIVE: draft_notes.md + +Run cleanup? (yes/no/skip) +``` + +3. If user confirms, execute cleanup per `/pp-clean` logic +4. If user skips, remind: "Run `/pp-clean` later to tidy up" + +This keeps project clean after each update cycle. + +## Query Examples + +```bash +# After completing tasks +/pp-update completed T010 and T011, added llm_service.py + +# After fixing a bug +/pp-update fixed the referral data extraction bug + +# After changing approach +/pp-update changed booking flow to conversational style + +# General update (will ask questions) +/pp-update +``` + +## Update Both Levels + +- If change affects only subproject → update subproject files +- If change affects multiple subprojects → update root files too +- CHANGELOG goes in subproject unless it's cross-project diff --git a/commands/pp-version.md b/commands/pp-version.md new file mode 100644 index 0000000..a2102c3 --- /dev/null +++ b/commands/pp-version.md @@ -0,0 +1,75 @@ +# Project Progress Plugin Version + +Show the current version of the unclecode-cc-toolkit plugin. + +## Arguments + +None + +## Purpose + +Display plugin version information to verify installation and updates. + +## Execution Instructions + +### Step 1: Read Plugin Manifest + +Read the plugin manifest file: +```bash +cat ~/.claude/plugins/marketplaces/unclecode-tools/plugins/unclecode-cc-toolkit/.claude-plugin/plugin.json +``` + +### Step 2: Extract Version + +Parse JSON and extract the `version` field. + +### Step 3: Display Version Info + +Output format: +``` +## unclecode-cc-toolkit Plugin + +**Version**: {version} +**Name**: unclecode-cc-toolkit +**Description**: {description from manifest} + +**Repository**: https://github.com/unclecode/claude-code-tools +**Author**: unclecode + +--- + +To update to the latest version: +1. /plugin uninstall unclecode-cc-toolkit +2. /plugin install unclecode-cc-toolkit@unclecode/claude-code-tools +3. Restart Claude Code +``` + +## Example + +```bash +/pp-version +``` + +Output: +``` +## unclecode-cc-toolkit Plugin + +**Version**: 1.1.0 +**Name**: unclecode-cc-toolkit +**Description**: Comprehensive Claude Code toolkit... + +**Repository**: https://github.com/unclecode/claude-code-tools +**Author**: unclecode + +--- + +To update to the latest version: +1. /plugin uninstall unclecode-cc-toolkit +2. /plugin install unclecode-cc-toolkit@unclecode/claude-code-tools +3. Restart Claude Code +``` + +## Notes + +- If plugin.json not found, inform user plugin may not be installed correctly +- Show installation instructions if needed diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..6bbf737 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,197 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:unclecode/claude-code-tools:plugins/unclecode-cc-toolkit", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "019d0c394dbcc613b760f9ea2d2ee46fc494a5d8", + "treeHash": "4c678da08733a333b893acdf79c84ae70e605dfa442009a94ff8e647b84a4bcc", + "generatedAt": "2025-11-28T10:28:51.016628Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "unclecode-cc-toolkit", + "description": "Comprehensive Claude Code toolkit by unclecode. Includes project progress tracking commands, Gemini image generation, and web app testing capabilities. All-in-one productivity suite for development workflows.", + "version": "1.3.1" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "474d145fee64d1229350f7ca999f9c1692a238eff4a979d0a3c5f137442956c0" + }, + { + "path": "agents/triage-agent.md", + "sha256": "07b68f3027fa71b50115d032b4b72a6f51a884fc83f6fb0185f1c3f13b6bb665" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "d9c58154d1dbe46c8a8885c709144baac53167d74f0eb0805d8964b9ea9ed6f0" + }, + { + "path": "commands/pp-version.md", + "sha256": "a65288569874996faae687be0d8bbd8f72b051b3aeb5705316c2424de164223a" + }, + { + "path": "commands/pp-update.md", + "sha256": "2555a069e40c08ac238647deda199e60e07312ad537eee00b53d977ea182e573" + }, + { + "path": "commands/pp-edit.md", + "sha256": "fad1956159e53ac04bb7d7d183ede2b3fbf7513c7a4fe5715be5a157e06fb0ed" + }, + { + "path": "commands/pp-resume.md", + "sha256": "bdc5c5fdaf15d202dbba187d06dca1b9e813a06bb87545a1252426f863137d65" + }, + { + "path": "commands/pp-remove.md", + "sha256": "2704bdf82da6274fe1640b03723e3f716464e8d7acc0134c5fe4e14535aebc70" + }, + { + "path": "commands/pp-clean.md", + "sha256": "2dc9e7fd6cfc8e56b911d94a3e187cf22ea920735b4c4aa3ea956abd488752a2" + }, + { + "path": "commands/pp-checkpoint.md", + "sha256": "5cd0f356b295401d55638b38c00138cf1a8c471b3d79c973734c30eb9e9e4b96" + }, + { + "path": "commands/pp-triage.md", + "sha256": "9e92b683b60940694742cf662cf3d48c07570dde1a3f0273fef3cfe3f9c3f769" + }, + { + "path": "commands/pp-migrate-v2.md", + "sha256": "0df460e7e73ebc1466685ab96634330f33556b3b8fef0ac3b3ae1b37f40e5cca" + }, + { + "path": "commands/pp-init.md", + "sha256": "d52439a5e9acee7d8b7798162ae2a6a759b5bfd37c35ce1d19927648122da171" + }, + { + "path": "commands/pp-help.md", + "sha256": "a34ce4a90330207fd516d626ffd5a8aa908a71ce7d5aa378fe61ce1db5b09abf" + }, + { + "path": "commands/pp-migrate.md", + "sha256": "34cabbce160bed354e687e35a1457632cee02b81ce2861576bbb2b3bf8a54880" + }, + { + "path": "commands/pp-fetch-docs.md", + "sha256": "f753134ee040b788251b1a5b4d3bbd44be13853ff4a046f188eb52565e81b570" + }, + { + "path": "commands/pp-add.md", + "sha256": "253c819102e9834f5b7bcc6085338eef3d3af0eb2f0483042d55c37d53f56cd8" + }, + { + "path": "commands/pp-status.md", + "sha256": "3d03d0cb15501720ab4018f0668e130be22a79985852bf50e538db7e58c33dfa" + }, + { + "path": "commands/pp-init-assets/templates/root/PRINCIPLES.md", + "sha256": "3c8c9f7afaa75c37a51952757afd0e22fc75789002d71f0b34fcce7e4dcf3f33" + }, + { + "path": "commands/pp-init-assets/templates/root/WORKFLOW.md", + "sha256": "a680905d696e45cd7dd4818d97b95a1cd04042c97db73c75e0a6e4bd13b89ec9" + }, + { + "path": "commands/pp-init-assets/templates/root/LESSONS.md", + "sha256": "e8d42b1226fbf7c3f660941eda5db785a0aef9a859b84cb2e2a9606c11d408ac" + }, + { + "path": "commands/pp-init-assets/templates/root/INDEX.md", + "sha256": "b307522b40e67d5073c73625cfc9bc952fb8b5a595febf1095961e0e5b9762c7" + }, + { + "path": "commands/pp-init-assets/templates/subproject/PRINCIPLES.md", + "sha256": "26742233ada7e858868da15586bdcab7ebaa22cde95f2763bf61e417a128b939" + }, + { + "path": "commands/pp-init-assets/templates/subproject/CHANGELOG.md", + "sha256": "cca709890694fc2b73ac62cbb9461d48c8cd2fa3703223351b8705b851d38268" + }, + { + "path": "commands/pp-init-assets/templates/subproject/STATUS.md", + "sha256": "3e596e7c40051605e4b52d37f9ca50d63501e4098974660e48bdffedd2708d1b" + }, + { + "path": "commands/pp-init-assets/templates/subproject/TODO.md", + "sha256": "b17dc8908271391e5d7d9c5d0d79f4884304b0cdbdc58009ac8d0b7fe8e6e9fc" + }, + { + "path": "commands/pp-init-assets/templates/subproject/LESSONS.md", + "sha256": "1d5b90395e162b89c5c1e52f36b3f73c838c4b6d5ed1eb783491a8c22bbfeace" + }, + { + "path": "commands/pp-init-assets/templates/subproject/INDEX.md", + "sha256": "62d0cfa97aba4220108dcb0584b4be8e88b8241111280872268582180925533f" + }, + { + "path": "commands/pp-init-assets/templates/subproject/CODEBASE.md", + "sha256": "c3f70324d31c0c1664bb215c92c317332d9b5b8d79a69ab7f776f57e5ed21c30" + }, + { + "path": "skills/webapp-testing/SKILL.md", + "sha256": "51b7349e77ec63b7744a6f63647e7566a0b4d2e301121cc10e8c2113af6556a2" + }, + { + "path": "skills/webapp-testing/LICENSE.txt", + "sha256": "58d1e17ffe5109a7ae296caafcadfdbe6a7d176f0bc4ab01e12a689b0499d8bd" + }, + { + "path": "skills/webapp-testing/examples/console_logging.py", + "sha256": "ea46877289acb82da7e7ce59d0bc37c8977cd57e2a006d0c88d7a1c625bf95da" + }, + { + "path": "skills/webapp-testing/examples/static_html_automation.py", + "sha256": "9d533aafb875ee3ab8b8ebf8f5b9003ac8d999da3d09b285cce252e623140064" + }, + { + "path": "skills/webapp-testing/examples/element_discovery.py", + "sha256": "d63c89604a22f8845d724e95dda45db49b1bf57c25ce0a83afbb7b8da3d402f0" + }, + { + "path": "skills/webapp-testing/scripts/with_server.py", + "sha256": "b0dcf4918935b795f4eda9821579b9902119235ff4447f687a30286e7d0925fd" + }, + { + "path": "skills/gemini-imagegen/SKILL.md", + "sha256": "d19969a7b436eb654f0ce7246597558268a11c3cc1909bdb4d00edab5b78e35d" + }, + { + "path": "skills/gemini-imagegen/scripts/gemini_images.py", + "sha256": "dc09a02985560c311bd3fb5e92537963a93cf03dfb8f7266bb36a6ffd6d10c92" + }, + { + "path": "skills/gemini-imagegen/scripts/compose_images.py", + "sha256": "e105f32c04f52769318e5911cb54bba2e15780640f7d910e82dc8d444962ce05" + }, + { + "path": "skills/gemini-imagegen/scripts/generate_image.py", + "sha256": "0fbe1d1a40095decadedca969c9ac34b32810653a1eab5b99554d868e28dd869" + }, + { + "path": "skills/gemini-imagegen/scripts/multi_turn_chat.py", + "sha256": "efcbe4c2cda41313f38dc8032e11fb9e66ee3bdce3b5b903d7ae55c269bbddfd" + }, + { + "path": "skills/gemini-imagegen/scripts/edit_image.py", + "sha256": "8c9c1f3eeefc19fb03ece75a0a7142f601d7584f8a6fdf82d3c406d372659002" + } + ], + "dirSha256": "4c678da08733a333b893acdf79c84ae70e605dfa442009a94ff8e647b84a4bcc" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/gemini-imagegen/SKILL.md b/skills/gemini-imagegen/SKILL.md new file mode 100644 index 0000000..4aa1723 --- /dev/null +++ b/skills/gemini-imagegen/SKILL.md @@ -0,0 +1,190 @@ +--- +name: gemini-imagegen +description: Generate and edit images using the Gemini API (Nano Banana). Use this skill when creating images from text prompts, editing existing images, applying style transfers, generating logos with text, creating stickers, product mockups, or any image generation/manipulation task. Supports text-to-image, image editing, multi-turn refinement, and composition from multiple reference images. +--- + +# Gemini Image Generation (Nano Banana) + +Generate and edit images using Google's Gemini API. The environment variable `GEMINI_API_KEY` must be set. + +## Available Models + +| Model | Alias | Resolution | Best For | +|-------|-------|------------|----------| +| `gemini-2.5-flash-image` | Nano Banana | 1024px | Speed, high-volume tasks | +| `gemini-3-pro-image-preview` | Nano Banana Pro | Up to 4K | Professional assets, complex instructions, text rendering | + +## Quick Start Scripts + +### Text-to-Image +```bash +python scripts/generate_image.py "A cat wearing a wizard hat" output.png +``` + +### Edit Existing Image +```bash +python scripts/edit_image.py input.png "Add a rainbow in the background" output.png +``` + +### Multi-Turn Chat (Iterative Refinement) +```bash +python scripts/multi_turn_chat.py +``` + +## Core API Pattern + +All image generation uses the `generateContent` endpoint with `responseModalities: ["TEXT", "IMAGE"]`: + +```python +import os +import base64 +from google import genai + +client = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) + +response = client.models.generate_content( + model="gemini-2.5-flash-image", + contents=["Your prompt here"], +) + +for part in response.parts: + if part.text: + print(part.text) + elif part.inline_data: + image = part.as_image() + image.save("output.png") +``` + +## Image Configuration Options + +Control output with `image_config`: + +```python +from google.genai import types + +response = client.models.generate_content( + model="gemini-3-pro-image-preview", + contents=[prompt], + config=types.GenerateContentConfig( + response_modalities=['TEXT', 'IMAGE'], + image_config=types.ImageConfig( + aspect_ratio="16:9", # 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 + image_size="2K" # 1K, 2K, 4K (Pro only for 4K) + ), + ) +) +``` + +## Editing Images + +Pass existing images with text prompts: + +```python +from PIL import Image + +img = Image.open("input.png") +response = client.models.generate_content( + model="gemini-2.5-flash-image", + contents=["Add a sunset to this scene", img], +) +``` + +## Multi-Turn Refinement + +Use chat for iterative editing: + +```python +from google.genai import types + +chat = client.chats.create( + model="gemini-2.5-flash-image", + config=types.GenerateContentConfig(response_modalities=['TEXT', 'IMAGE']) +) + +response = chat.send_message("Create a logo for 'Acme Corp'") +# Save first image... + +response = chat.send_message("Make the text bolder and add a blue gradient") +# Save refined image... +``` + +## Prompting Best Practices + +### Photorealistic Scenes +Include camera details: lens type, lighting, angle, mood. +> "A photorealistic close-up portrait, 85mm lens, soft golden hour light, shallow depth of field" + +### Stylized Art +Specify style explicitly: +> "A kawaii-style sticker of a happy red panda, bold outlines, cel-shading, white background" + +### Text in Images +Be explicit about font style and placement. Use `gemini-3-pro-image-preview` for best results: +> "Create a logo with text 'Daily Grind' in clean sans-serif, black and white, coffee bean motif" + +### Product Mockups +Describe lighting setup and surface: +> "Studio-lit product photo on polished concrete, three-point softbox setup, 45-degree angle" + +### Landing Pages +Specify layout structure, color scheme, and target audience: +> "Modern landing page hero section, gradient background from deep purple to blue, centered headline with CTA button, clean minimalist design, SaaS product" + +> "Landing page for fitness app, energetic layout with workout photos, bright orange and black color scheme, mobile-first design, prominent download buttons" + +### Website Design Ideas +Describe overall aesthetic, navigation style, and content hierarchy: +> "E-commerce homepage wireframe, grid layout for products, sticky navigation bar, warm earth tones, plenty of whitespace, professional photography style" + +> "Portfolio website for photographer, full-screen image galleries, dark mode interface, elegant serif typography, minimal UI elements to highlight work" + +> "Tech startup homepage, glassmorphism design trend, floating cards, neon accent colors on dark background, modern illustrations, hero section with product demo" + +## Advanced Features (Pro Model Only) + +### Google Search Grounding +Generate images based on real-time data: + +```python +response = client.models.generate_content( + model="gemini-3-pro-image-preview", + contents=["Visualize today's weather in Tokyo as an infographic"], + config=types.GenerateContentConfig( + response_modalities=['TEXT', 'IMAGE'], + tools=[{"google_search": {}}] + ) +) +``` + +### Multiple Reference Images (Up to 14) +Combine elements from multiple sources: + +```python +response = client.models.generate_content( + model="gemini-3-pro-image-preview", + contents=[ + "Create a group photo of these people in an office", + Image.open("person1.png"), + Image.open("person2.png"), + Image.open("person3.png"), + ], +) +``` + +## REST API (curl) + +```bash +curl -s -X POST \ + "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent" \ + -H "x-goog-api-key: $GEMINI_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "contents": [{"parts": [{"text": "A serene mountain landscape"}]}] + }' | jq -r '.candidates[0].content.parts[] | select(.inlineData) | .inlineData.data' | base64 --decode > output.png +``` + +## Notes + +- All generated images include SynthID watermarks +- Image-only mode (`responseModalities: ["IMAGE"]`) won't work with Google Search grounding +- For editing, describe changes conversationally—the model understands semantic masking diff --git a/skills/gemini-imagegen/scripts/compose_images.py b/skills/gemini-imagegen/scripts/compose_images.py new file mode 100755 index 0000000..5c32c71 --- /dev/null +++ b/skills/gemini-imagegen/scripts/compose_images.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +""" +Compose multiple images into a new image using Gemini API. + +Usage: + python compose_images.py "instruction" output.png image1.png [image2.png ...] + +Examples: + python compose_images.py "Create a group photo of these people" group.png person1.png person2.png + python compose_images.py "Put the cat from the first image on the couch from the second" result.png cat.png couch.png + python compose_images.py "Apply the art style from the first image to the scene in the second" styled.png style.png photo.png + +Note: Supports up to 14 reference images (Gemini 3 Pro only). + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys + +from PIL import Image +from google import genai +from google.genai import types + + +def compose_images( + instruction: str, + output_path: str, + image_paths: list[str], + model: str = "gemini-3-pro-image-preview", + aspect_ratio: str | None = None, + image_size: str | None = None, +) -> str | None: + """Compose multiple images based on instructions. + + Args: + instruction: Text description of how to combine images + output_path: Path to save the result + image_paths: List of input image paths (up to 14) + model: Gemini model to use (pro recommended) + aspect_ratio: Output aspect ratio + image_size: Output resolution + + Returns: + Any text response from the model, or None + """ + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + if len(image_paths) > 14: + raise ValueError("Maximum 14 reference images supported") + + if len(image_paths) < 1: + raise ValueError("At least one image is required") + + # Verify all images exist + for path in image_paths: + if not os.path.exists(path): + raise FileNotFoundError(f"Image not found: {path}") + + client = genai.Client(api_key=api_key) + + # Load images + images = [Image.open(path) for path in image_paths] + + # Build contents: instruction first, then images + contents = [instruction] + images + + # Build config + config_kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + image_config_kwargs = {} + if aspect_ratio: + image_config_kwargs["aspect_ratio"] = aspect_ratio + if image_size: + image_config_kwargs["image_size"] = image_size + + if image_config_kwargs: + config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs) + + config = types.GenerateContentConfig(**config_kwargs) + + response = client.models.generate_content( + model=model, + contents=contents, + config=config, + ) + + text_response = None + image_saved = False + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image = part.as_image() + image.save(output_path) + image_saved = True + + if not image_saved: + raise RuntimeError("No image was generated.") + + return text_response + + +def main(): + parser = argparse.ArgumentParser( + description="Compose multiple images using Gemini API", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + parser.add_argument("instruction", help="Composition instruction") + parser.add_argument("output", help="Output file path") + parser.add_argument("images", nargs="+", help="Input images (up to 14)") + parser.add_argument( + "--model", "-m", + default="gemini-3-pro-image-preview", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"], + help="Model to use (pro recommended for composition)" + ) + parser.add_argument( + "--aspect", "-a", + choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"], + help="Output aspect ratio" + ) + parser.add_argument( + "--size", "-s", + choices=["1K", "2K", "4K"], + help="Output resolution" + ) + + args = parser.parse_args() + + try: + text = compose_images( + instruction=args.instruction, + output_path=args.output, + image_paths=args.images, + model=args.model, + aspect_ratio=args.aspect, + image_size=args.size, + ) + + print(f"Composed image saved to: {args.output}") + if text: + print(f"Model response: {text}") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/skills/gemini-imagegen/scripts/edit_image.py b/skills/gemini-imagegen/scripts/edit_image.py new file mode 100755 index 0000000..e415c52 --- /dev/null +++ b/skills/gemini-imagegen/scripts/edit_image.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +""" +Edit existing images using Gemini API. + +Usage: + python edit_image.py input.png "edit instruction" output.png [options] + +Examples: + python edit_image.py photo.png "Add a rainbow in the sky" edited.png + python edit_image.py room.jpg "Change the sofa to red leather" room_edited.jpg + python edit_image.py portrait.png "Make it look like a Van Gogh painting" artistic.png --model gemini-3-pro-image-preview + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys + +from PIL import Image +from google import genai +from google.genai import types + + +def edit_image( + input_path: str, + instruction: str, + output_path: str, + model: str = "gemini-2.5-flash-image", + aspect_ratio: str | None = None, + image_size: str | None = None, +) -> str | None: + """Edit an existing image based on text instructions. + + Args: + input_path: Path to the input image + instruction: Text description of edits to make + output_path: Path to save the edited image + model: Gemini model to use + aspect_ratio: Output aspect ratio + image_size: Output resolution + + Returns: + Any text response from the model, or None + """ + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + if not os.path.exists(input_path): + raise FileNotFoundError(f"Input image not found: {input_path}") + + client = genai.Client(api_key=api_key) + + # Load input image + input_image = Image.open(input_path) + + # Build config + config_kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + image_config_kwargs = {} + if aspect_ratio: + image_config_kwargs["aspect_ratio"] = aspect_ratio + if image_size: + image_config_kwargs["image_size"] = image_size + + if image_config_kwargs: + config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs) + + config = types.GenerateContentConfig(**config_kwargs) + + response = client.models.generate_content( + model=model, + contents=[instruction, input_image], + config=config, + ) + + text_response = None + image_saved = False + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image = part.as_image() + image.save(output_path) + image_saved = True + + if not image_saved: + raise RuntimeError("No image was generated. Check your instruction and try again.") + + return text_response + + +def main(): + parser = argparse.ArgumentParser( + description="Edit images using Gemini API", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + parser.add_argument("input", help="Input image path") + parser.add_argument("instruction", help="Edit instruction") + parser.add_argument("output", help="Output file path") + parser.add_argument( + "--model", "-m", + default="gemini-2.5-flash-image", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"], + help="Model to use (default: gemini-2.5-flash-image)" + ) + parser.add_argument( + "--aspect", "-a", + choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"], + help="Output aspect ratio" + ) + parser.add_argument( + "--size", "-s", + choices=["1K", "2K", "4K"], + help="Output resolution" + ) + + args = parser.parse_args() + + try: + text = edit_image( + input_path=args.input, + instruction=args.instruction, + output_path=args.output, + model=args.model, + aspect_ratio=args.aspect, + image_size=args.size, + ) + + print(f"Edited image saved to: {args.output}") + if text: + print(f"Model response: {text}") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/skills/gemini-imagegen/scripts/gemini_images.py b/skills/gemini-imagegen/scripts/gemini_images.py new file mode 100755 index 0000000..0ff4cb5 --- /dev/null +++ b/skills/gemini-imagegen/scripts/gemini_images.py @@ -0,0 +1,263 @@ +""" +Gemini Image Generation Library + +A simple Python library for generating and editing images with the Gemini API. + +Usage: + from gemini_images import GeminiImageGenerator + + gen = GeminiImageGenerator() + gen.generate("A sunset over mountains", "sunset.png") + gen.edit("input.png", "Add clouds", "output.png") + +Environment: + GEMINI_API_KEY - Required API key +""" + +import os +from pathlib import Path +from typing import Literal + +from PIL import Image +from google import genai +from google.genai import types + + +AspectRatio = Literal["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"] +ImageSize = Literal["1K", "2K", "4K"] +Model = Literal["gemini-2.5-flash-image", "gemini-3-pro-image-preview"] + + +class GeminiImageGenerator: + """High-level interface for Gemini image generation.""" + + FLASH = "gemini-2.5-flash-image" + PRO = "gemini-3-pro-image-preview" + + def __init__(self, api_key: str | None = None, model: Model = FLASH): + """Initialize the generator. + + Args: + api_key: Gemini API key (defaults to GEMINI_API_KEY env var) + model: Default model to use + """ + self.api_key = api_key or os.environ.get("GEMINI_API_KEY") + if not self.api_key: + raise EnvironmentError("GEMINI_API_KEY not set") + + self.client = genai.Client(api_key=self.api_key) + self.model = model + + def _build_config( + self, + aspect_ratio: AspectRatio | None = None, + image_size: ImageSize | None = None, + google_search: bool = False, + ) -> types.GenerateContentConfig: + """Build generation config.""" + kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + img_config = {} + if aspect_ratio: + img_config["aspect_ratio"] = aspect_ratio + if image_size: + img_config["image_size"] = image_size + + if img_config: + kwargs["image_config"] = types.ImageConfig(**img_config) + + if google_search: + kwargs["tools"] = [{"google_search": {}}] + + return types.GenerateContentConfig(**kwargs) + + def generate( + self, + prompt: str, + output: str | Path, + *, + model: Model | None = None, + aspect_ratio: AspectRatio | None = None, + image_size: ImageSize | None = None, + google_search: bool = False, + ) -> tuple[Path, str | None]: + """Generate an image from a text prompt. + + Args: + prompt: Text description + output: Output file path + model: Override default model + aspect_ratio: Output aspect ratio + image_size: Output resolution + google_search: Enable Google Search grounding (Pro only) + + Returns: + Tuple of (output path, optional text response) + """ + output = Path(output) + config = self._build_config(aspect_ratio, image_size, google_search) + + response = self.client.models.generate_content( + model=model or self.model, + contents=[prompt], + config=config, + ) + + text = None + for part in response.parts: + if part.text: + text = part.text + elif part.inline_data: + part.as_image().save(output) + + return output, text + + def edit( + self, + input_image: str | Path | Image.Image, + instruction: str, + output: str | Path, + *, + model: Model | None = None, + aspect_ratio: AspectRatio | None = None, + image_size: ImageSize | None = None, + ) -> tuple[Path, str | None]: + """Edit an existing image. + + Args: + input_image: Input image (path or PIL Image) + instruction: Edit instruction + output: Output file path + model: Override default model + aspect_ratio: Output aspect ratio + image_size: Output resolution + + Returns: + Tuple of (output path, optional text response) + """ + output = Path(output) + + if isinstance(input_image, (str, Path)): + input_image = Image.open(input_image) + + config = self._build_config(aspect_ratio, image_size) + + response = self.client.models.generate_content( + model=model or self.model, + contents=[instruction, input_image], + config=config, + ) + + text = None + for part in response.parts: + if part.text: + text = part.text + elif part.inline_data: + part.as_image().save(output) + + return output, text + + def compose( + self, + instruction: str, + images: list[str | Path | Image.Image], + output: str | Path, + *, + model: Model | None = None, + aspect_ratio: AspectRatio | None = None, + image_size: ImageSize | None = None, + ) -> tuple[Path, str | None]: + """Compose multiple images into one. + + Args: + instruction: Composition instruction + images: List of input images (up to 14) + output: Output file path + model: Override default model (Pro recommended) + aspect_ratio: Output aspect ratio + image_size: Output resolution + + Returns: + Tuple of (output path, optional text response) + """ + output = Path(output) + + # Load images + loaded = [] + for img in images: + if isinstance(img, (str, Path)): + loaded.append(Image.open(img)) + else: + loaded.append(img) + + config = self._build_config(aspect_ratio, image_size) + contents = [instruction] + loaded + + response = self.client.models.generate_content( + model=model or self.PRO, # Pro recommended for composition + contents=contents, + config=config, + ) + + text = None + for part in response.parts: + if part.text: + text = part.text + elif part.inline_data: + part.as_image().save(output) + + return output, text + + def chat(self) -> "ImageChat": + """Start an interactive chat session for iterative refinement.""" + return ImageChat(self.client, self.model) + + +class ImageChat: + """Multi-turn chat session for iterative image generation.""" + + def __init__(self, client: genai.Client, model: Model): + self.client = client + self.model = model + self._chat = client.chats.create( + model=model, + config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]), + ) + self.current_image: Image.Image | None = None + + def send( + self, + message: str, + image: Image.Image | str | Path | None = None, + ) -> tuple[Image.Image | None, str | None]: + """Send a message and optionally an image. + + Returns: + Tuple of (generated image or None, text response or None) + """ + contents = [message] + if image: + if isinstance(image, (str, Path)): + image = Image.open(image) + contents.append(image) + + response = self._chat.send_message(contents) + + text = None + img = None + for part in response.parts: + if part.text: + text = part.text + elif part.inline_data: + img = part.as_image() + self.current_image = img + + return img, text + + def reset(self): + """Reset the chat session.""" + self._chat = self.client.chats.create( + model=self.model, + config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]), + ) + self.current_image = None diff --git a/skills/gemini-imagegen/scripts/generate_image.py b/skills/gemini-imagegen/scripts/generate_image.py new file mode 100755 index 0000000..e3aabcf --- /dev/null +++ b/skills/gemini-imagegen/scripts/generate_image.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Generate images from text prompts using Gemini API. + +Usage: + python generate_image.py "prompt" output.png [--model MODEL] [--aspect RATIO] [--size SIZE] + +Examples: + python generate_image.py "A cat in space" cat.png + python generate_image.py "A logo for Acme Corp" logo.png --model gemini-3-pro-image-preview --aspect 1:1 + python generate_image.py "Epic landscape" landscape.png --aspect 16:9 --size 2K + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys + +from google import genai +from google.genai import types + + +def generate_image( + prompt: str, + output_path: str, + model: str = "gemini-2.5-flash-image", + aspect_ratio: str | None = None, + image_size: str | None = None, +) -> str | None: + """Generate an image from a text prompt. + + Args: + prompt: Text description of the image to generate + output_path: Path to save the generated image + model: Gemini model to use + aspect_ratio: Aspect ratio (1:1, 16:9, 9:16, etc.) + image_size: Resolution (1K, 2K, 4K - 4K only for pro model) + + Returns: + Any text response from the model, or None + """ + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + client = genai.Client(api_key=api_key) + + # Build config + config_kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + image_config_kwargs = {} + if aspect_ratio: + image_config_kwargs["aspect_ratio"] = aspect_ratio + if image_size: + image_config_kwargs["image_size"] = image_size + + if image_config_kwargs: + config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs) + + config = types.GenerateContentConfig(**config_kwargs) + + response = client.models.generate_content( + model=model, + contents=[prompt], + config=config, + ) + + text_response = None + image_saved = False + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image = part.as_image() + image.save(output_path) + image_saved = True + + if not image_saved: + raise RuntimeError("No image was generated. Check your prompt and try again.") + + return text_response + + +def main(): + parser = argparse.ArgumentParser( + description="Generate images from text prompts using Gemini API", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + parser.add_argument("prompt", help="Text prompt describing the image") + parser.add_argument("output", help="Output file path (e.g., output.png)") + parser.add_argument( + "--model", "-m", + default="gemini-2.5-flash-image", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"], + help="Model to use (default: gemini-2.5-flash-image)" + ) + parser.add_argument( + "--aspect", "-a", + choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"], + help="Aspect ratio" + ) + parser.add_argument( + "--size", "-s", + choices=["1K", "2K", "4K"], + help="Image resolution (4K only available with pro model)" + ) + + args = parser.parse_args() + + try: + text = generate_image( + prompt=args.prompt, + output_path=args.output, + model=args.model, + aspect_ratio=args.aspect, + image_size=args.size, + ) + + print(f"Image saved to: {args.output}") + if text: + print(f"Model response: {text}") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/skills/gemini-imagegen/scripts/multi_turn_chat.py b/skills/gemini-imagegen/scripts/multi_turn_chat.py new file mode 100755 index 0000000..e09a6e7 --- /dev/null +++ b/skills/gemini-imagegen/scripts/multi_turn_chat.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +""" +Interactive multi-turn image generation and refinement using Gemini API. + +Usage: + python multi_turn_chat.py [--model MODEL] [--output-dir DIR] + +This starts an interactive session where you can: +- Generate images from prompts +- Iteratively refine images through conversation +- Load existing images for editing +- Save images at any point + +Commands: + /save [filename] - Save current image + /load - Load an image into the conversation + /clear - Start fresh conversation + /quit - Exit + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys +from datetime import datetime +from pathlib import Path + +from PIL import Image +from google import genai +from google.genai import types + + +class ImageChat: + """Interactive chat session for image generation and refinement.""" + + def __init__( + self, + model: str = "gemini-2.5-flash-image", + output_dir: str = ".", + ): + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + self.client = genai.Client(api_key=api_key) + self.model = model + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + + self.chat = None + self.current_image = None + self.image_count = 0 + + self._init_chat() + + def _init_chat(self): + """Initialize or reset the chat session.""" + config = types.GenerateContentConfig( + response_modalities=["TEXT", "IMAGE"] + ) + self.chat = self.client.chats.create( + model=self.model, + config=config, + ) + self.current_image = None + + def send_message(self, message: str, image: Image.Image | None = None) -> tuple[str | None, Image.Image | None]: + """Send a message and optionally an image, return response text and image.""" + contents = [] + if message: + contents.append(message) + if image: + contents.append(image) + + if not contents: + return None, None + + response = self.chat.send_message(contents) + + text_response = None + image_response = None + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image_response = part.as_image() + self.current_image = image_response + + return text_response, image_response + + def save_image(self, filename: str | None = None) -> str | None: + """Save the current image to a file.""" + if self.current_image is None: + return None + + if filename is None: + self.image_count += 1 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"image_{timestamp}_{self.image_count}.png" + + filepath = self.output_dir / filename + self.current_image.save(filepath) + return str(filepath) + + def load_image(self, path: str) -> Image.Image: + """Load an image from disk.""" + img = Image.open(path) + self.current_image = img + return img + + +def main(): + parser = argparse.ArgumentParser( + description="Interactive multi-turn image generation", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + parser.add_argument( + "--model", "-m", + default="gemini-2.5-flash-image", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"], + help="Model to use" + ) + parser.add_argument( + "--output-dir", "-o", + default=".", + help="Directory to save images" + ) + + args = parser.parse_args() + + try: + chat = ImageChat(model=args.model, output_dir=args.output_dir) + except Exception as e: + print(f"Error initializing: {e}", file=sys.stderr) + sys.exit(1) + + print(f"Gemini Image Chat ({args.model})") + print("Commands: /save [name], /load , /clear, /quit") + print("-" * 50) + + while True: + try: + user_input = input("\nYou: ").strip() + except (EOFError, KeyboardInterrupt): + print("\nGoodbye!") + break + + if not user_input: + continue + + # Handle commands + if user_input.startswith("/"): + parts = user_input.split(maxsplit=1) + cmd = parts[0].lower() + arg = parts[1] if len(parts) > 1 else None + + if cmd == "/quit": + print("Goodbye!") + break + + elif cmd == "/clear": + chat._init_chat() + print("Conversation cleared.") + continue + + elif cmd == "/save": + path = chat.save_image(arg) + if path: + print(f"Image saved to: {path}") + else: + print("No image to save.") + continue + + elif cmd == "/load": + if not arg: + print("Usage: /load ") + continue + try: + chat.load_image(arg) + print(f"Loaded: {arg}") + print("You can now describe edits to make.") + except Exception as e: + print(f"Error loading image: {e}") + continue + + else: + print(f"Unknown command: {cmd}") + continue + + # Send message to model + try: + # If we have a loaded image and this is first message, include it + image_to_send = None + if chat.current_image and not chat.chat.history: + image_to_send = chat.current_image + + text, image = chat.send_message(user_input, image_to_send) + + if text: + print(f"\nGemini: {text}") + + if image: + # Auto-save + path = chat.save_image() + print(f"\n[Image generated: {path}]") + + except Exception as e: + print(f"\nError: {e}") + + +if __name__ == "__main__": + main() diff --git a/skills/webapp-testing/LICENSE.txt b/skills/webapp-testing/LICENSE.txt new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/skills/webapp-testing/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/skills/webapp-testing/SKILL.md b/skills/webapp-testing/SKILL.md new file mode 100644 index 0000000..4726215 --- /dev/null +++ b/skills/webapp-testing/SKILL.md @@ -0,0 +1,96 @@ +--- +name: webapp-testing +description: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. +license: Complete terms in LICENSE.txt +--- + +# Web Application Testing + +To test local web applications, write native Python Playwright scripts. + +**Helper Scripts Available**: +- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers) + +**Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window. + +## Decision Tree: Choosing Your Approach + +``` +User task → Is it static HTML? + ├─ Yes → Read HTML file directly to identify selectors + │ ├─ Success → Write Playwright script using selectors + │ └─ Fails/Incomplete → Treat as dynamic (below) + │ + └─ No (dynamic webapp) → Is the server already running? + ├─ No → Run: python scripts/with_server.py --help + │ Then use the helper + write simplified Playwright script + │ + └─ Yes → Reconnaissance-then-action: + 1. Navigate and wait for networkidle + 2. Take screenshot or inspect DOM + 3. Identify selectors from rendered state + 4. Execute actions with discovered selectors +``` + +## Example: Using with_server.py + +To start a server, run `--help` first, then use the helper: + +**Single server:** +```bash +python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py +``` + +**Multiple servers (e.g., backend + frontend):** +```bash +python scripts/with_server.py \ + --server "cd backend && python server.py" --port 3000 \ + --server "cd frontend && npm run dev" --port 5173 \ + -- python your_automation.py +``` + +To create an automation script, include only Playwright logic (servers are managed automatically): +```python +from playwright.sync_api import sync_playwright + +with sync_playwright() as p: + browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode + page = browser.new_page() + page.goto('http://localhost:5173') # Server already running and ready + page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute + # ... your automation logic + browser.close() +``` + +## Reconnaissance-Then-Action Pattern + +1. **Inspect rendered DOM**: + ```python + page.screenshot(path='/tmp/inspect.png', full_page=True) + content = page.content() + page.locator('button').all() + ``` + +2. **Identify selectors** from inspection results + +3. **Execute actions** using discovered selectors + +## Common Pitfall + +❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps +✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection + +## Best Practices + +- **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly. +- Use `sync_playwright()` for synchronous scripts +- Always close the browser when done +- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs +- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()` + +## Reference Files + +- **examples/** - Examples showing common patterns: + - `element_discovery.py` - Discovering buttons, links, and inputs on a page + - `static_html_automation.py` - Using file:// URLs for local HTML + - `console_logging.py` - Capturing console logs during automation \ No newline at end of file diff --git a/skills/webapp-testing/examples/console_logging.py b/skills/webapp-testing/examples/console_logging.py new file mode 100644 index 0000000..9329b5e --- /dev/null +++ b/skills/webapp-testing/examples/console_logging.py @@ -0,0 +1,35 @@ +from playwright.sync_api import sync_playwright + +# Example: Capturing console logs during browser automation + +url = 'http://localhost:5173' # Replace with your URL + +console_logs = [] + +with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page(viewport={'width': 1920, 'height': 1080}) + + # Set up console log capture + def handle_console_message(msg): + console_logs.append(f"[{msg.type}] {msg.text}") + print(f"Console: [{msg.type}] {msg.text}") + + page.on("console", handle_console_message) + + # Navigate to page + page.goto(url) + page.wait_for_load_state('networkidle') + + # Interact with the page (triggers console logs) + page.click('text=Dashboard') + page.wait_for_timeout(1000) + + browser.close() + +# Save console logs to file +with open('/mnt/user-data/outputs/console.log', 'w') as f: + f.write('\n'.join(console_logs)) + +print(f"\nCaptured {len(console_logs)} console messages") +print(f"Logs saved to: /mnt/user-data/outputs/console.log") \ No newline at end of file diff --git a/skills/webapp-testing/examples/element_discovery.py b/skills/webapp-testing/examples/element_discovery.py new file mode 100644 index 0000000..917ba72 --- /dev/null +++ b/skills/webapp-testing/examples/element_discovery.py @@ -0,0 +1,40 @@ +from playwright.sync_api import sync_playwright + +# Example: Discovering buttons and other elements on a page + +with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page() + + # Navigate to page and wait for it to fully load + page.goto('http://localhost:5173') + page.wait_for_load_state('networkidle') + + # Discover all buttons on the page + buttons = page.locator('button').all() + print(f"Found {len(buttons)} buttons:") + for i, button in enumerate(buttons): + text = button.inner_text() if button.is_visible() else "[hidden]" + print(f" [{i}] {text}") + + # Discover links + links = page.locator('a[href]').all() + print(f"\nFound {len(links)} links:") + for link in links[:5]: # Show first 5 + text = link.inner_text().strip() + href = link.get_attribute('href') + print(f" - {text} -> {href}") + + # Discover input fields + inputs = page.locator('input, textarea, select').all() + print(f"\nFound {len(inputs)} input fields:") + for input_elem in inputs: + name = input_elem.get_attribute('name') or input_elem.get_attribute('id') or "[unnamed]" + input_type = input_elem.get_attribute('type') or 'text' + print(f" - {name} ({input_type})") + + # Take screenshot for visual reference + page.screenshot(path='/tmp/page_discovery.png', full_page=True) + print("\nScreenshot saved to /tmp/page_discovery.png") + + browser.close() \ No newline at end of file diff --git a/skills/webapp-testing/examples/static_html_automation.py b/skills/webapp-testing/examples/static_html_automation.py new file mode 100644 index 0000000..90bbedc --- /dev/null +++ b/skills/webapp-testing/examples/static_html_automation.py @@ -0,0 +1,33 @@ +from playwright.sync_api import sync_playwright +import os + +# Example: Automating interaction with static HTML files using file:// URLs + +html_file_path = os.path.abspath('path/to/your/file.html') +file_url = f'file://{html_file_path}' + +with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page(viewport={'width': 1920, 'height': 1080}) + + # Navigate to local HTML file + page.goto(file_url) + + # Take screenshot + page.screenshot(path='/mnt/user-data/outputs/static_page.png', full_page=True) + + # Interact with elements + page.click('text=Click Me') + page.fill('#name', 'John Doe') + page.fill('#email', 'john@example.com') + + # Submit form + page.click('button[type="submit"]') + page.wait_for_timeout(500) + + # Take final screenshot + page.screenshot(path='/mnt/user-data/outputs/after_submit.png', full_page=True) + + browser.close() + +print("Static HTML automation completed!") \ No newline at end of file diff --git a/skills/webapp-testing/scripts/with_server.py b/skills/webapp-testing/scripts/with_server.py new file mode 100755 index 0000000..431f2eb --- /dev/null +++ b/skills/webapp-testing/scripts/with_server.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Start one or more servers, wait for them to be ready, run a command, then clean up. + +Usage: + # Single server + python scripts/with_server.py --server "npm run dev" --port 5173 -- python automation.py + python scripts/with_server.py --server "npm start" --port 3000 -- python test.py + + # Multiple servers + python scripts/with_server.py \ + --server "cd backend && python server.py" --port 3000 \ + --server "cd frontend && npm run dev" --port 5173 \ + -- python test.py +""" + +import subprocess +import socket +import time +import sys +import argparse + +def is_server_ready(port, timeout=30): + """Wait for server to be ready by polling the port.""" + start_time = time.time() + while time.time() - start_time < timeout: + try: + with socket.create_connection(('localhost', port), timeout=1): + return True + except (socket.error, ConnectionRefusedError): + time.sleep(0.5) + return False + + +def main(): + parser = argparse.ArgumentParser(description='Run command with one or more servers') + parser.add_argument('--server', action='append', dest='servers', required=True, help='Server command (can be repeated)') + parser.add_argument('--port', action='append', dest='ports', type=int, required=True, help='Port for each server (must match --server count)') + parser.add_argument('--timeout', type=int, default=30, help='Timeout in seconds per server (default: 30)') + parser.add_argument('command', nargs=argparse.REMAINDER, help='Command to run after server(s) ready') + + args = parser.parse_args() + + # Remove the '--' separator if present + if args.command and args.command[0] == '--': + args.command = args.command[1:] + + if not args.command: + print("Error: No command specified to run") + sys.exit(1) + + # Parse server configurations + if len(args.servers) != len(args.ports): + print("Error: Number of --server and --port arguments must match") + sys.exit(1) + + servers = [] + for cmd, port in zip(args.servers, args.ports): + servers.append({'cmd': cmd, 'port': port}) + + server_processes = [] + + try: + # Start all servers + for i, server in enumerate(servers): + print(f"Starting server {i+1}/{len(servers)}: {server['cmd']}") + + # Use shell=True to support commands with cd and && + process = subprocess.Popen( + server['cmd'], + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + server_processes.append(process) + + # Wait for this server to be ready + print(f"Waiting for server on port {server['port']}...") + if not is_server_ready(server['port'], timeout=args.timeout): + raise RuntimeError(f"Server failed to start on port {server['port']} within {args.timeout}s") + + print(f"Server ready on port {server['port']}") + + print(f"\nAll {len(servers)} server(s) ready") + + # Run the command + print(f"Running: {' '.join(args.command)}\n") + result = subprocess.run(args.command) + sys.exit(result.returncode) + + finally: + # Clean up all servers + print(f"\nStopping {len(server_processes)} server(s)...") + for i, process in enumerate(server_processes): + try: + process.terminate() + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + print(f"Server {i+1} stopped") + print("All servers stopped") + + +if __name__ == '__main__': + main() \ No newline at end of file