commit a3a73d67d73e131b61521460b08c5b877d0e2f72 Author: Zhongwei Li Date: Sun Nov 30 08:46:50 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..0f17671 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "smart-init", + "description": "Interactive ClaudeShack ecosystem initialization. Analyzes codebase, mines history, discusses findings with you, seeds Oracle with verified knowledge, sets up Context7 for current docs. Creates a foundation that improves over use.", + "version": "0.0.0-2025.11.28", + "author": { + "name": "Overlord-Z", + "email": "[email protected]" + }, + "skills": [ + "./skills" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..022a607 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# smart-init + +Interactive ClaudeShack ecosystem initialization. Analyzes codebase, mines history, discusses findings with you, seeds Oracle with verified knowledge, sets up Context7 for current docs. Creates a foundation that improves over use. diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..8a65764 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,297 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:Overlord-Z/ClaudeShack:", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "701b6d4ae60f3de1ad8ff6b6086a9b836c824715", + "treeHash": "c3af65490debb231d59a85a8eda1f04ce38943cae581bda9ea34e34e684ecf8b", + "generatedAt": "2025-11-28T10:12:24.218566Z", + "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": "smart-init", + "description": "Interactive ClaudeShack ecosystem initialization. Analyzes codebase, mines history, discusses findings with you, seeds Oracle with verified knowledge, sets up Context7 for current docs. Creates a foundation that improves over use.", + "version": null + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "dfc3f3dfaa35492d452055106c614cc7e934ef6c9feca6bf9dc259ecac8f5924" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "abfbd02da5ae83201a432938f5a54e7e329698b7e417e3fe80a0bd7397953ef3" + }, + { + "path": "skills/evaluator/SKILL.md", + "sha256": "bad017d01abd9fd91534c328aad41d5f43f1b33448238f86d95ed9cd52f24e17" + }, + { + "path": "skills/evaluator/scripts/track_event.py", + "sha256": "f20b72cccb991433d78ff2993e788e43875737bf524d4472fe11845689f5adab" + }, + { + "path": "skills/summoner/README.md", + "sha256": "2032917b3a373a5ce43b3b95ac1aa370d7d617e6ca7bceb29a5a730b17719d72" + }, + { + "path": "skills/summoner/SKILL.md", + "sha256": "676ee60a9585b7f4833c8f659497b33c5e92089a75fc4d452489c0dafc4b0d0e" + }, + { + "path": "skills/summoner/References/quality-gates.md", + "sha256": "98d2f3843f07d9a48202c366c4cd702fb45c060d2f33938e1809a708521a7fa6" + }, + { + "path": "skills/summoner/References/agent-spec-template.md", + "sha256": "c946ff3fbd1aa2034b503e2ee54956a2062dd585d5bae5ced4c8205932d3d9fe" + }, + { + "path": "skills/summoner/References/mission-control-template.md", + "sha256": "e281388fdbeb3cce3d3d7da02106f6612bc50486edbd86ed9a8e4005d7acfc84" + }, + { + "path": "skills/summoner/scripts/init_mission.py", + "sha256": "8f82583aafbd95a0fd91cfb331d40be12988481b281d98abc785ea0a05852703" + }, + { + "path": "skills/summoner/scripts/validate_quality.py", + "sha256": "354605e14a148c41e3da29202f6b74ad0299428c806f45f459a676f542e7aae3" + }, + { + "path": "skills/oracle/ENHANCEMENTS.md", + "sha256": "bd2341497cf866b5881adf82e161b3ce17201aa3853a53155af68b19f0567328" + }, + { + "path": "skills/oracle/README.md", + "sha256": "170cb853488cc884e28b96a77164b1cc04b13b91d3d83b38bf475fefff2527b7" + }, + { + "path": "skills/oracle/SKILL.md", + "sha256": "8978c7f88d1e50bf2696d722c02e6fd435ebcdd6c5611122776608527cdff0e7" + }, + { + "path": "skills/oracle/References/knowledge-schema.md", + "sha256": "9e5ad96e89dfdb291dc8dd52116c6a40d784429ff90067030b3addc859d794f8" + }, + { + "path": "skills/oracle/References/integration-guide.md", + "sha256": "ff62b6f6c315f0da23132f02cbe96fa66cf879f19d600c5dda63797e616a429a" + }, + { + "path": "skills/oracle/References/session-log-template.md", + "sha256": "697bdec67b6362a578eee1eea89352f6dab58949414d6c015ca9222555879fef" + }, + { + "path": "skills/oracle/References/pattern-library.md", + "sha256": "4059043d6d9d42713bcbbfff36785274cbdfd5fa583931b34ccb5122164cc3f4" + }, + { + "path": "skills/oracle/scripts/generate_context.py", + "sha256": "af67759fb26e8064e1ab1619701b0f52bb7eec75aaa71884896698db66984d06" + }, + { + "path": "skills/oracle/scripts/HOOK_SETUP.md", + "sha256": "5825c4f704cca188f266bb7f6a2a76d90e752ad3b9c24b727b2dad0867639efa" + }, + { + "path": "skills/oracle/scripts/query_knowledge.py", + "sha256": "3877933cf9213dedabb9293dbb12eb64fe98053a8acdecfe0e3fd1fb67b60ea3" + }, + { + "path": "skills/oracle/scripts/record_commit.py", + "sha256": "260311acf6636dbdead9003e8c255f9037e94d2f38e2cd88ae00a4520eddebb7" + }, + { + "path": "skills/oracle/scripts/analyze_patterns.py", + "sha256": "32fa219725b1a4e03a47e8bdd87121fae25f9aad24ac7c61bed7b889f9ad37a8" + }, + { + "path": "skills/oracle/scripts/smart_context.py", + "sha256": "be5ffd972b30445279437580974769ece81ba70b12900172f537d3d2a39791d5" + }, + { + "path": "skills/oracle/scripts/analyze_history.py", + "sha256": "2c58c37b938b4dc4098ec87284c4b88f43cf878a8067a7eb39b987e7647a89c3" + }, + { + "path": "skills/oracle/scripts/session_handoff.py", + "sha256": "9df35c52962ed51bbfb3ddf15aec27c289f87542a95e9eecf884ea2850874021" + }, + { + "path": "skills/oracle/scripts/record_session.py", + "sha256": "0689560786f790d72c270a9f40fd2e183e3db41d2d895f5ff724be30b6007a5e" + }, + { + "path": "skills/oracle/scripts/session_start_hook.py", + "sha256": "a9b1ed33ff18f55407210ca251942ce4589df0220c784c2fbd9b2d8c067bfd6f" + }, + { + "path": "skills/oracle/scripts/init_oracle.py", + "sha256": "a4ee844569cb9be508940dd558c56d32d58299c15ab406d30dde66633160fb6c" + }, + { + "path": "skills/oracle/scripts/load_context.py", + "sha256": "e1c5d48f9983b6a6121242d680165b8960a15de86dcddf168a51f51859522658" + }, + { + "path": "skills/oracle/Assets/example-knowledge-entries.json", + "sha256": "ad3a3e98640b9a385c62e49dcf7174ee797c43b053e1ff9974436aea2a960c2f" + }, + { + "path": "skills/wizard/SKILL.md", + "sha256": "db49347b5b65bf3317da104d740ac9830278ead6e497e464448742e9bf64eb11" + }, + { + "path": "skills/wizard/scripts/audit_docs.py", + "sha256": "e95af8fea71a9a3beb7f1f487a520e6c0d86f4afe1671edebc2615511aff030d" + }, + { + "path": "skills/style-master/README.md", + "sha256": "e05682e8596ef6b71f74a4a131dd65a9c3f8b27dc87aeb694c47610fbb438f01" + }, + { + "path": "skills/style-master/SKILL.md", + "sha256": "09f10ed8d719856b9c9b7989ba9de6c0822c7631a37edafaffd7cf5dcee50950" + }, + { + "path": "skills/style-master/References/style-guide-template.md", + "sha256": "e290f01549f734b444f9b6833b564a0a1bad28d66c7df83c92b6845f3eef5734" + }, + { + "path": "skills/style-master/scripts/validate_consistency.py", + "sha256": "d294b0defd089e9028ae088d5a33a46f994be5089643067e2bd850d22516fe0f" + }, + { + "path": "skills/style-master/scripts/analyze_styles.py", + "sha256": "247bf33d073a5a0746953bc26cd4cca40ff3d01c7d6cfd1a482c2c405d70f34f" + }, + { + "path": "skills/style-master/scripts/suggest_improvements.py", + "sha256": "ee8cbcbcd5a15a1eda99c106244b8a633c3aff4b509b9de2cae83f7c6f473946" + }, + { + "path": "skills/style-master/scripts/generate_styleguide.py", + "sha256": "cbbb44745ac7ad01051fd72ddfeeccba77b0d2369223b8561993ed6fcf20e870" + }, + { + "path": "skills/style-master/Assets/design-tokens/tokens.json", + "sha256": "a8580a0afccd5cc1263f7e37b63e91a6689539ff5b8fa45231cac7575922fd4b" + }, + { + "path": "skills/documentation-wizard/README.md", + "sha256": "5a1ca82712c53bd8c183cc6c50d3f3326b37033eaf8e4db8a25454f8c6882f84" + }, + { + "path": "skills/documentation-wizard/SKILL.md", + "sha256": "aaf8fb7985a0edf88396b050c10b79ac88605f263ff3e93d5ea8cb86a3c14bb3" + }, + { + "path": "skills/documentation-wizard/References/onboarding-template.md", + "sha256": "ff2441164aa1636b037a201fd06f3c56c2c2294fab4ba42a5b4e6a8609084508" + }, + { + "path": "skills/documentation-wizard/References/readme-template.md", + "sha256": "5a02ac3fcf9fb8d593944e654ffcfac53b6c00342a6919629f905372ace32579" + }, + { + "path": "skills/documentation-wizard/References/adr-template.md", + "sha256": "bd14f9c51d16432593b0c3de18c59422b94c26f9418e1316ae9d141131d95f48" + }, + { + "path": "skills/documentation-wizard/scripts/generate_docs.py", + "sha256": "2c05d12d17191baa61611701aa64938fd6a1a12f9ebd14cdb7e77aaff8a644b1" + }, + { + "path": "skills/documentation-wizard/scripts/generate_changelog.py", + "sha256": "2f0f25a22072490a7a3b873e0bf4987b551d9d105591713fb747d446863ce3ff" + }, + { + "path": "skills/documentation-wizard/scripts/validate_docs.py", + "sha256": "1315b1c9934b42bd946cfb27e51de7f5631c0bccb1f5e83f0ed9d1ff8658fa85" + }, + { + "path": "skills/documentation-wizard/scripts/sync_docs.py", + "sha256": "f5201dfb63cbe2f6041ca0daf952f2c9948fbae6840deba88c7b01f23f7107ba" + }, + { + "path": "skills/guardian/SKILL.md", + "sha256": "31a9b3d1f409265106c9f91700ef2659b644b5361632415a0bfa7adc5a62fd3a" + }, + { + "path": "skills/guardian/scripts/validator.py", + "sha256": "096fb1635d3d20039e2a9e9bf468bd5b5e85fd60e8bc0c0be06e2cb0101c3aab" + }, + { + "path": "skills/guardian/scripts/template_loader.py", + "sha256": "a3f98124a9d14ab1da6cb21c9d3f6f753ec820035449f66aeaafb428a5f1ea79" + }, + { + "path": "skills/guardian/scripts/context_filter.py", + "sha256": "4c03027816a97ac6bd59a78589e04489ad178568facfb8f57d706125f8ecbe8d" + }, + { + "path": "skills/guardian/scripts/README.md", + "sha256": "e1fdb6a92cab5844e8417826657983d7d87ce9c2f98295618491d6ff9936c258" + }, + { + "path": "skills/guardian/scripts/guardian.py", + "sha256": "ba09acf363e34d65927966bc1a4fe33c4e0a47f15d976e1dc344b6fdd7e163fd" + }, + { + "path": "skills/guardian/scripts/monitor_session.py", + "sha256": "9d81af1895858366e96b0a54be4821e32cfdec778d4d1b69f1707d14773f4334" + }, + { + "path": "skills/guardian/scripts/learning.py", + "sha256": "548cf0268c5caa4d7dd1a5cfb8ef5a4188dc62aaa41717ebebabab4a67e5aa37" + }, + { + "path": "skills/guardian/Templates/feature_planning.json", + "sha256": "cc83d0d0120018a9a1f5fcd6ee06e9a852b96fc9fca99b164de3fd50032acccf" + }, + { + "path": "skills/guardian/Templates/security_review.json", + "sha256": "09300ddbe6537957e4a143b13ed76e27ee84e0d7739152b8135520a7352b56dd" + }, + { + "path": "skills/guardian/Templates/README.md", + "sha256": "9e3137150ab94f8b57273bec54a8bb17339acc2a74d82d7709674cb7bf706522" + }, + { + "path": "skills/guardian/Templates/performance_review.json", + "sha256": "e5107856cc8cc68d9268f6fc5f1b5a442255d5a2f8ca73d1bd35bcfb6e46a060" + }, + { + "path": "skills/guardian/Templates/session_health.json", + "sha256": "5cc4533c1c3e04446a5dbf240c6d05652e3b585e91a0b9118c6bbf1b80fb942c" + }, + { + "path": "skills/smart-init/README.md", + "sha256": "2ab0025e64c5795dac1ba39032cf3fbeab203069f2af01645504a36936bce5b6" + }, + { + "path": "skills/smart-init/SKILL.md", + "sha256": "4781e7dbfbde922dc4560e6537893fc101d11622cc1f837c2308d9de6306bcad" + }, + { + "path": "skills/smart-init/scripts/discover.py", + "sha256": "56e6f9f3547d771597705212292b60bebd4c18436cbca0a6da4bcaf9da1ca218" + } + ], + "dirSha256": "c3af65490debb231d59a85a8eda1f04ce38943cae581bda9ea34e34e684ecf8b" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/documentation-wizard/README.md b/skills/documentation-wizard/README.md new file mode 100644 index 0000000..0a1fb77 --- /dev/null +++ b/skills/documentation-wizard/README.md @@ -0,0 +1,222 @@ +# Documentation Wizard + +**Living Documentation Expert - Always in Sync** + +Documentation Wizard keeps your documentation perfectly synchronized with code, decisions, and learnings. It integrates with Oracle, Summoner, and Style Master to create comprehensive, up-to-date documentation automatically. + +## What It Does + +### πŸ“ Auto-Generate Documentation +- README files from code + Oracle knowledge +- API documentation from code comments +- Architecture Decision Records (ADRs) from decisions +- Onboarding guides from Oracle sessions +- Changelogs from git history + Oracle + +### πŸ”„ Continuous Synchronization +- Detects code changes and updates docs +- Pulls patterns from Oracle knowledge +- Extracts decisions from Summoner MCDs +- Syncs Style Master style guides +- Validates examples still work + +### βœ… Documentation Validation +- Detects stale documentation +- Finds broken links +- Validates code examples +- Identifies missing documentation +- Checks for inconsistencies + +### πŸ”— Integration Powerhouse +- **Oracle**: Leverage learnings and decisions +- **Summoner**: Extract design docs from MCDs +- **Style Master**: Sync style guide documentation +- **Git**: Track evolution and changes + +## Quick Start + +```bash +# Generate initial documentation +python .claude/skills/documentation-wizard/scripts/generate_docs.py + +# Validate documentation +python .claude/skills/documentation-wizard/scripts/validate_docs.py + +# Sync from Oracle knowledge +python .claude/skills/documentation-wizard/scripts/sync_docs.py --source oracle + +# Generate changelog +python .claude/skills/documentation-wizard/scripts/generate_changelog.py +``` + +## Use Cases + +### 1. Initial Documentation Setup +``` +Use the documentation wizard to set up documentation for our project. + +[Analyzes codebase] +[Loads Oracle knowledge] +[Generates README, API docs, contributing guide] +[Creates documentation structure] +``` + +### 2. Keep Docs in Sync +``` +Update documentation based on recent changes. + +[Checks git diff] +[Identifies affected docs] +[Updates API documentation] +[Generates changelog entry] +``` + +### 3. Create ADRs from Decisions +``` +Create ADR for our decision to use PostgreSQL over MongoDB. + +[Loads Oracle decision entry] +[Reads Summoner MCD rationale] +[Generates ADR-015-database-choice.md] +[Links to Oracle and Summoner references] +``` + +## Documentation Types + +### Generated Documentation + +| Type | Source | Output | +|------|--------|--------| +| **README** | Code + Oracle + Summoner | README.md | +| **API Docs** | JSDoc/TypeDoc comments | docs/api/ | +| **ADRs** | Oracle decisions + Summoner MCDs | docs/adr/ | +| **Style Guide** | Style Master | docs/STYLEGUIDE.md | +| **Onboarding** | Oracle sessions | docs/ONBOARDING.md | +| **Changelog** | Git + Oracle | CHANGELOG.md | + +## Integration Examples + +### With Oracle 🧠 + +**Oracle knows**: "Use factory pattern for DB connections" + +**Docs generated**: +```markdown +## Database Connections + +All database connections use the factory pattern: +\`\`\`typescript +const db = DatabaseFactory.create('postgres'); +\`\`\` +This ensures proper connection pooling. See Oracle entry #42. +``` + +### With Summoner πŸ§™ + +**Summoner MCD**: Microservices migration decision + +**ADR generated**: +```markdown +# ADR-023: Migrate to Microservices Architecture + +Context: From Summoner Mission "Microservices Migration" +Decision: Extract auth service first, then user service +Rationale: [From Summoner MCD] +``` + +### With Style Master 🎨 + +**Style Master style guide**: Design tokens + +**Docs synced**: +```markdown +# Theme Customization + +Override design tokens in your CSS: +\`\`\`css +:root { + --color-primary: #your-color; +} +\`\`\` + +See [Style Guide](./STYLEGUIDE.md) for all tokens. +``` + +## Scripts Reference + +### generate_docs.py +Generates initial documentation from code and knowledge. + +**Options**: +- `--type readme|api|adr|onboarding|all` +- `--output docs/` +- `--include-oracle` (include Oracle knowledge) + +### validate_docs.py +Validates documentation for staleness and issues. + +**Checks**: +- Stale documentation +- Broken links +- Invalid examples +- Missing documentation + +### sync_docs.py +Synchronizes documentation from various sources. + +**Sources**: +- `--source oracle` (Oracle knowledge) +- `--source summoner` (Summoner MCDs) +- `--source style-master` (Style guides) +- `--source code` (Code comments) + +### generate_changelog.py +Generates changelog from git history and Oracle. + +**Format**: Semantic (Added/Changed/Fixed/Deprecated/Removed) + +## Best Practices + +### 1. Document WHY, Not Just WHAT +Use Oracle to capture the reasoning behind decisions. + +### 2. Keep Examples Executable +All code examples should be tested and copy-pasteable. + +### 3. Link Everything +Connect documentation to Oracle, Summoner, code, and external resources. + +### 4. Automate Updates +Set up hooks to update docs on code changes. + +### 5. Validate Regularly +Run validation as part of CI/CD. + +## Workflow + +```bash +# Daily: After development session +python .claude/skills/documentation-wizard/scripts/generate_docs.py --type api +python .claude/skills/documentation-wizard/scripts/validate_docs.py + +# Weekly: Comprehensive sync +python .claude/skills/documentation-wizard/scripts/sync_docs.py --source all +python .claude/skills/documentation-wizard/scripts/generate_changelog.py + +# Before release: Full documentation review +python .claude/skills/documentation-wizard/scripts/validate_docs.py --strict +``` + +## Templates Available + +- **README**: Project and package READMEs +- **ADR**: Architecture Decision Records +- **API**: API documentation +- **Onboarding**: New developer guides +- **Contributing**: Contribution guidelines + +See `References/` directory for all templates. + +--- + +**Documentation Wizard v1.0** - Documentation that stays alive diff --git a/skills/documentation-wizard/References/adr-template.md b/skills/documentation-wizard/References/adr-template.md new file mode 100644 index 0000000..a568dba --- /dev/null +++ b/skills/documentation-wizard/References/adr-template.md @@ -0,0 +1,74 @@ +# ADR-{number}: {Short title of solved problem and solution} + +Date: {YYYY-MM-DD} +Status: {Proposed | Accepted | Deprecated | Superseded} +Deciders: {List everyone involved in the decision} +Related: {Links to related ADRs, Oracle entries, Summoner MCDs} + +## Context and Problem Statement + +{Describe the context and problem statement, e.g., in free form using two to three sentences. You may want to articulate the problem in form of a question.} + +## Decision Drivers + +* {decision driver 1, e.g., a force, facing concern, ...} +* {decision driver 2, e.g., a force, facing concern, ...} +* {etc.} + +## Considered Options + +* {option 1} +* {option 2} +* {option 3} +* {etc.} + +## Decision Outcome + +Chosen option: "{option 1}", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}. + +### Positive Consequences + +* {e.g., improvement of quality attribute satisfaction, follow-up decisions required, ...} +* {etc.} + +### Negative Consequences + +* {e.g., compromising quality attribute, follow-up decisions required, ...} +* {etc.} + +## Pros and Cons of the Options + +### {option 1} + +{example | description | pointer to more information | ...} + +* Good, because {argument a} +* Good, because {argument b} +* Bad, because {argument c} +* {etc.} + +### {option 2} + +{example | description | pointer to more information | ...} + +* Good, because {argument a} +* Good, because {argument b} +* Bad, because {argument c} +* {etc.} + +### {option 3} + +{example | description | pointer to more information | ...} + +* Good, because {argument a} +* Good, because {argument b} +* Bad, because {argument c} +* {etc.} + +## Links + +* {Link to related Oracle knowledge entries} +* {Link to Summoner Mission Control Documents} +* {Link to code references} +* {Link to related ADRs} +* {External references} diff --git a/skills/documentation-wizard/References/onboarding-template.md b/skills/documentation-wizard/References/onboarding-template.md new file mode 100644 index 0000000..7020fb1 --- /dev/null +++ b/skills/documentation-wizard/References/onboarding-template.md @@ -0,0 +1,286 @@ +# Developer Onboarding Guide + +Welcome to {Project Name}! This guide will help you get up to speed quickly. + +## πŸ“‹ Checklist + +### Day 1 +- [ ] Set up development environment +- [ ] Clone repository and install dependencies +- [ ] Run the project locally +- [ ] Read architecture documentation +- [ ] Join team communication channels + +### Week 1 +- [ ] Complete first small task/bug fix +- [ ] Understand the codebase structure +- [ ] Review coding standards and patterns +- [ ] Set up IDE/tools properly +- [ ] Meet the team + +### Month 1 +- [ ] Contribute to a feature +- [ ] Understand the deployment process +- [ ] Review Oracle knowledge base +- [ ] Understand testing strategy + +## πŸš€ Getting Started + +### 1. Environment Setup + +**Required Tools:** +- Node.js 18+ / Python 3.9+ / etc. +- Git +- IDE (VS Code recommended) +- Docker (optional) + +**Installation:** +\`\`\`bash +# Clone repository +git clone https://github.com/{org}/{repo}.git +cd {repo} + +# Install dependencies +npm install + +# Set up environment +cp .env.example .env +# Edit .env with your configuration + +# Run development server +npm run dev +\`\`\` + +### 2. Project Structure + +\`\`\` +project/ +β”œβ”€β”€ src/ # Source code +β”‚ β”œβ”€β”€ components/ # UI components +β”‚ β”œβ”€β”€ services/ # Business logic +β”‚ └── utils/ # Utilities +β”œβ”€β”€ tests/ # Test files +β”œβ”€β”€ docs/ # Documentation +└── scripts/ # Build/deployment scripts +\`\`\` + +### 3. Key Concepts + +#### Architecture +{Brief architecture overview} + +See [ARCHITECTURE.md](./ARCHITECTURE.md) for details. + +#### Design Patterns + +*From Oracle knowledge base:* + +- **{Pattern 1}**: {Description} +- **{Pattern 2}**: {Description} +- **{Pattern 3}**: {Description} + +## πŸ“š Essential Reading + +1. **README.md** - Project overview +2. **ARCHITECTURE.md** - System architecture +3. **CONTRIBUTING.md** - Contribution guidelines +4. **docs/adr/** - Architecture decisions +5. **Oracle Knowledge** - Project-specific patterns and gotchas + +## 🎯 Common Tasks + +### Running Tests + +\`\`\`bash +# Run all tests +npm test + +# Run with coverage +npm run test:coverage + +# Run specific test file +npm test path/to/test.spec.ts +\`\`\` + +### Building + +\`\`\`bash +# Development build +npm run build:dev + +# Production build +npm run build:prod +\`\`\` + +### Debugging + +{Project-specific debugging tips} + +## ⚠️ Common Gotchas + +*From Oracle knowledge base:* + +### {Gotcha 1} +{Description and how to avoid} + +### {Gotcha 2} +{Description and how to avoid} + +### {Gotcha 3} +{Description and how to avoid} + +## πŸ”§ Development Workflow + +### 1. Pick a Task + +- Check the issue tracker +- Start with issues labeled \`good-first-issue\` +- Discuss approach with team if needed + +### 2. Create a Branch + +\`\`\`bash +git checkout -b feature/your-feature-name +# or +git checkout -b fix/bug-description +\`\`\` + +### 3. Make Changes + +- Follow coding standards +- Write tests +- Update documentation + +### 4. Commit + +\`\`\`bash +git add . +git commit -m "feat: add amazing feature" +\`\`\` + +We use [Conventional Commits](https://www.conventionalcommits.org/): +- \`feat:\` New feature +- \`fix:\` Bug fix +- \`docs:\` Documentation +- \`refactor:\` Code refactoring +- \`test:\` Tests +- \`chore:\` Maintenance + +### 5. Push and Create PR + +\`\`\`bash +git push origin feature/your-feature-name +\`\`\` + +Then create a Pull Request on GitHub. + +## 🎨 Coding Standards + +### Style Guide + +{Link to style guide or summary} + +### Best Practices + +*From Oracle knowledge base:* + +1. **{Practice 1}**: {Description} +2. **{Practice 2}**: {Description} +3. **{Practice 3}**: {Description} + +### Code Review + +- All code must be reviewed +- Address all comments +- Ensure tests pass +- Update documentation + +## πŸ§ͺ Testing Strategy + +### Unit Tests + +\`\`\`typescript +describe('MyComponent', () => { + it('should render correctly', () => { + // Test code + }); +}); +\`\`\` + +### Integration Tests + +{Integration testing approach} + +### End-to-End Tests + +{E2E testing approach} + +## πŸ“– Learning Resources + +### Internal +- **Oracle Knowledge Base**: Project-specific learnings +- **ADRs**: Why we made certain decisions +- **Team Wiki**: {Link} + +### External +- {Relevant external resource 1} +- {Relevant external resource 2} +- {Relevant external resource 3} + +## 🀝 Team + +### Communication Channels +- Slack: #{channel} +- Email: {team-email} +- Standups: {When/Where} + +### Key Contacts +- **Tech Lead**: {Name} +- **Product Owner**: {Name} +- **DevOps**: {Name} + +## ❓ FAQ + +### Q: How do I {common question}? +A: {Answer} + +### Q: Where can I find {common need}? +A: {Answer} + +### Q: What should I do if {common scenario}? +A: {Answer} + +## πŸŽ“ Your First Week + +### Suggested Learning Path + +**Day 1-2:** +- Complete environment setup +- Read all documentation +- Run the project locally +- Explore the codebase + +**Day 3-4:** +- Pick a "good first issue" +- Make your first contribution +- Go through code review +- Merge your first PR + +**Day 5:** +- Retrospective on first week +- Questions and clarifications +- Plan for next week + +## πŸŽ‰ Welcome Aboard! + +You're now part of the team! Don't hesitate to ask questions - everyone was new once. + +Remember: +- πŸ’¬ Ask questions in {channel} +- πŸ“– Check Oracle knowledge base first +- 🀝 Pair programming is encouraged +- 🎯 Focus on learning, not perfection + +--- + +*Generated by Documentation Wizard β€’ Last updated: {date}* diff --git a/skills/documentation-wizard/References/readme-template.md b/skills/documentation-wizard/References/readme-template.md new file mode 100644 index 0000000..3c1560a --- /dev/null +++ b/skills/documentation-wizard/References/readme-template.md @@ -0,0 +1,153 @@ +# {Project Name} + +{Brief description of the project - one or two sentences} + +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Version](https://img.shields.io/badge/version-1.0.0-green.svg)](package.json) + +## Overview + +{Detailed description of what this project does, why it exists, and what problems it solves} + +## Features + +- ✨ {Feature 1} +- πŸš€ {Feature 2} +- πŸ”’ {Feature 3} +- πŸ“Š {Feature 4} + +## Quick Start + +### Prerequisites + +- Node.js 18+ (or other runtime) +- {Other prerequisites} + +### Installation + +\`\`\`bash +npm install +# or +yarn install +\`\`\` + +### Basic Usage + +\`\`\`typescript +import { Something } from '{package-name}'; + +// Example code here +const example = new Something(); +example.doSomething(); +\`\`\` + +## Documentation + +- πŸ“– [API Documentation](./docs/api/) +- πŸ—οΈ [Architecture](./docs/ARCHITECTURE.md) +- πŸ“‹ [Architecture Decision Records](./docs/adr/) +- 🎨 [Style Guide](./docs/STYLEGUIDE.md) +- 🀝 [Contributing Guidelines](./CONTRIBUTING.md) + +## Configuration + +\`\`\`typescript +{ + // Configuration options + option1: "value1", + option2: "value2" +} +\`\`\` + +## Examples + +### Example 1: {Use case} + +\`\`\`typescript +// Code example +\`\`\` + +### Example 2: {Another use case} + +\`\`\`typescript +// Code example +\`\`\` + +## API Reference + +### Main API + +#### `functionName(param1, param2)` + +Description of what this function does. + +**Parameters:** +- `param1` (type): Description +- `param2` (type): Description + +**Returns:** Description of return value + +**Example:** +\`\`\`typescript +const result = functionName('value1', 'value2'); +\`\`\` + +## Architecture + +{Brief overview of the architecture} + +See [ARCHITECTURE.md](./docs/ARCHITECTURE.md) for detailed architecture documentation. + +## Development + +### Setup Development Environment + +\`\`\`bash +npm install +npm run dev +\`\`\` + +### Running Tests + +\`\`\`bash +npm test +npm run test:coverage +\`\`\` + +### Building + +\`\`\`bash +npm run build +\`\`\` + +## Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for details. + +### Development Process + +1. Fork the repository +2. Create a feature branch (\`git checkout -b feature/amazing-feature\`) +3. Commit your changes (\`git commit -m 'Add amazing feature'\`) +4. Push to the branch (\`git push origin feature/amazing-feature\`) +5. Open a Pull Request + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +* {Credit to contributors} +* {Credit to inspirations} +* {Credit to tools/libraries used} + +## Support + +- πŸ“§ Email: {email} +- πŸ’¬ Issues: [GitHub Issues](https://github.com/{user}/{repo}/issues) +- πŸ“– Documentation: [Full Docs](./docs/) + +--- + +*Generated by Documentation Wizard β€’ Last updated: {date}* diff --git a/skills/documentation-wizard/SKILL.md b/skills/documentation-wizard/SKILL.md new file mode 100644 index 0000000..9798596 --- /dev/null +++ b/skills/documentation-wizard/SKILL.md @@ -0,0 +1,525 @@ +--- +name: documentation-wizard +description: Keeps documentation in perfect sync with code and knowledge. Integrates with Oracle to leverage learnings, Summoner for design docs, and Style Master for style guides. Auto-generates and updates README, API docs, ADRs, and onboarding materials. Detects stale documentation and ensures it evolves with the codebase. +allowed-tools: Read, Write, Edit, Glob, Grep, Task, Bash +--- + +# Documentation Wizard: Living Documentation Expert + +You are now operating as the **Documentation Wizard**, a specialist in creating and maintaining living documentation that stays synchronized with code, decisions, and learnings. + +## Core Philosophy + +**"Documentation is code. It should be tested, reviewed, and maintained with the same rigor."** + +Documentation Wizard operates on these principles: + +1. **Living, Not Static**: Documentation evolves with the codebase +2. **Single Source of Truth**: One place for each piece of information +3. **Automated Synchronization**: Reduce manual documentation burden +4. **Context-Aware**: Leverage Oracle, Summoner, Style Master knowledge +5. **User-Focused**: Write for the audience (developers, users, stakeholders) +6. **Examples Over Explanations**: Show, don't just tell + +## Core Responsibilities + +### 1. Documentation Synchronization + +**Sync Sources**: +- **Oracle Knowledge**: Patterns, decisions, gotchas, solutions +- **Summoner MCDs**: Design decisions, architectural choices +- **Style Master Style Guides**: Design system documentation +- **Code Comments**: Extract structured documentation +- **Git History**: Track evolution and decisions +- **Test Files**: Generate examples from tests + +**Sync Targets**: +- README files (project, package, component) +- API documentation (OpenAPI, GraphQL schemas, JSDoc) +- Architecture Decision Records (ADRs) +- Onboarding guides +- Changelog +- Style guides +- Contributing guidelines + +### 2. Documentation Generation + +**Auto-Generate**: + +**README Files**: +```markdown +# Project Name + +## Overview +[From package.json description + Oracle context] + +## Installation +[Auto-detected from package.json scripts] + +## Usage +[Generated from examples and tests] + +## API Reference +[From code comments and type definitions] + +## Architecture +[From Summoner MCDs and Oracle patterns] + +## Contributing +[Standard template + project-specific guidelines] +``` + +**API Documentation**: +- Extract JSDoc/TypeDoc comments +- Generate OpenAPI specs from code +- Create GraphQL schema documentation +- Build SDK documentation + +**Architecture Decision Records (ADRs)**: +```markdown +# ADR-XXX: [Decision Title] + +Date: [From Oracle session or git log] +Status: [Accepted/Deprecated/Superseded] + +## Context +[From Oracle decision log or Summoner MCD] + +## Decision +[What was decided] + +## Consequences +[Positive and negative outcomes] + +## Alternatives Considered +[From Summoner MCD or Oracle] +``` + +### 3. Documentation Validation + +**Detect Issues**: +- ❌ Stale documentation (code changed, docs didn't) +- ❌ Missing documentation (public API without docs) +- ❌ Broken links (internal and external) +- ❌ Outdated examples (code examples don't run) +- ❌ Inconsistencies (contradictory information) +- ❌ Missing images/diagrams (referenced but not found) + +**Validation Checks**: +```bash +# Run validation +python .claude/skills/documentation-wizard/scripts/validate_docs.py + +# Output: +βœ… README.md is up-to-date +⚠️ API docs missing for 3 new functions +❌ ARCHITECTURE.md references removed module +βœ… All code examples validated +``` + +### 4. Oracle Integration + +**Leverage Oracle Knowledge**: + +```json +{ + "category": "pattern", + "title": "Use factory pattern for database connections", + "content": "All database connections through DatabaseFactory.create()", + "context": "When creating database connections" +} +``` + +**Generate Documentation Section**: +```markdown +## Database Connections + +All database connections should be created through the `DatabaseFactory`: + +\`\`\`typescript +const db = DatabaseFactory.create('postgres', config); +\`\`\` + +This ensures connection pooling and error handling. See [Architecture Decision Records](./docs/adr/001-database-factory.md). +``` + +**From Oracle Corrections** β†’ **Update Documentation**: +```json +{ + "category": "correction", + "content": "❌ Wrong: element.innerHTML = userInput\nβœ“ Right: element.textContent = userInput" +} +``` + +**Generates Warning in Docs**: +```markdown +## Security Considerations + +⚠️ **Never use `innerHTML` with user input** - this creates XSS vulnerabilities. Always use `textContent` or a sanitization library like DOMPurify. +``` + +### 5. Summoner Integration + +**From Mission Control Documents** β†’ **Design Documentation**: + +When Summoner completes a mission, Documentation Wizard: +1. Extracts key decisions from MCD +2. Creates/updates ADRs +3. Updates architecture documentation +4. Adds examples to relevant docs +5. Updates changelog + +### 6. Style Master Integration + +**Sync Style Guide**: +- Pull design tokens from Style Master +- Include component examples +- Show accessibility guidelines +- Document theme customization + +## Workflow + +### Initial Documentation Generation + +``` +1. Analyze codebase structure + ↓ +2. Load Oracle knowledge + ↓ +3. Detect documentation needs + ↓ +4. Generate initial docs from templates + ↓ +5. Populate with code-derived content + ↓ +6. Add Oracle context and patterns + ↓ +7. Validate and format +``` + +### Continuous Synchronization + +``` +1. Detect code changes (git diff) + ↓ +2. Identify affected documentation + ↓ +3. Load relevant Oracle/Summoner context + ↓ +4. Update documentation sections + ↓ +5. Validate examples still work + ↓ +6. Flag manual review items + ↓ +7. Commit documentation updates +``` + +### Documentation Review + +``` +1. Run validation checks + ↓ +2. Identify stale sections + ↓ +3. Check broken links + ↓ +4. Validate code examples + ↓ +5. Generate report + ↓ +6. Suggest updates +``` + +## Documentation Types + +### README Documentation + +**Project README**: +- Clear overview and value proposition +- Installation instructions (tested) +- Quick start guide with examples +- Link to detailed documentation +- Contributing guidelines +- License information + +**Package/Module README**: +- Purpose and use cases +- API overview +- Installation and setup +- Usage examples +- Configuration options + +### API Documentation + +**From Code**: +```typescript +/** + * Creates a new user account + * + * @param email - User's email address + * @param password - Must be at least 8 characters + * @returns The created user object + * @throws {ValidationError} If email is invalid + * @throws {ConflictError} If user already exists + * + * @example + * ```typescript + * const user = await createUser('user@example.com', 'password123'); + * console.log(user.id); + * ``` + */ +async function createUser(email: string, password: string): Promise +``` + +**Generates**: +Structured API documentation with type information, examples, and error cases. + +### Architecture Decision Records (ADRs) + +**Track Important Decisions**: +- Technology choices (React vs Vue, SQL vs NoSQL) +- Architecture patterns (microservices, event-driven) +- Library selections (auth provider, testing framework) +- Design system decisions (Tailwind vs styled-components) + +**ADR Template**: +```markdown +# ADR-{number}: {Short title} + +Date: {YYYY-MM-DD} +Status: {Proposed|Accepted|Deprecated|Superseded} +Deciders: {Who made this decision} + +## Context and Problem Statement + +{Describe the context and problem} + +## Decision Drivers + +* {Driver 1} +* {Driver 2} + +## Considered Options + +* {Option 1} +* {Option 2} +* {Option 3} + +## Decision Outcome + +Chosen option: "{Option X}", because {reasons}. + +### Positive Consequences + +* {Benefit 1} +* {Benefit 2} + +### Negative Consequences + +* {Drawback 1} +* {Drawback 2} + +## Links + +* {Related ADRs} +* {Related Oracle entries} +* {Related Summoner MCDs} +``` + +### Onboarding Documentation + +**Generated from Oracle Sessions**: +- Common questions and answers +- Setup procedures that worked +- Gotchas new developers encounter +- Recommended learning path +- Links to key resources + +## Scripts & Tools + +### Documentation Generator + +```bash +python .claude/skills/documentation-wizard/scripts/generate_docs.py +``` + +**Generates**: +- README from template + code analysis +- API docs from code comments +- Architecture docs from Oracle/Summoner +- Onboarding guide from Oracle sessions + +### Documentation Validator + +```bash +python .claude/skills/documentation-wizard/scripts/validate_docs.py +``` + +**Checks**: +- Stale documentation +- Missing documentation +- Broken links +- Invalid code examples +- Inconsistencies + +### Documentation Syncer + +```bash +python .claude/skills/documentation-wizard/scripts/sync_docs.py --source oracle +``` + +**Syncs**: +- Oracle knowledge β†’ README sections +- Summoner MCDs β†’ ADRs +- Style Master style guide β†’ design docs +- Code comments β†’ API docs + +### Changelog Generator + +```bash +python .claude/skills/documentation-wizard/scripts/generate_changelog.py +``` + +**From**: +- Git commit messages +- Oracle session logs +- Summoner MCD summaries + +**Generates**: +- Semantic changelog (Added/Changed/Fixed/Deprecated/Removed) +- Release notes +- Migration guides + +## Templates + +- **README Template**: See `References/readme-template.md` +- **ADR Template**: See `References/adr-template.md` +- **API Doc Template**: See `References/api-doc-template.md` +- **Onboarding Template**: See `References/onboarding-template.md` +- **Contributing Template**: See `References/contributing-template.md` + +## Integration Examples + +### Example 1: New Feature Documented + +**Flow**: +1. Summoner orchestrates feature implementation +2. Oracle records new patterns used +3. Code is written with JSDoc comments +4. Documentation Wizard triggers: + - Extracts JSDoc β†’ API docs + - Reads Oracle patterns β†’ README update + - Reads Summoner MCD β†’ Creates ADR + - Generates examples from tests + - Updates changelog + +### Example 2: Correction Documented + +**Oracle Records Correction**: +```json +{ + "category": "correction", + "title": "Don't use Date() for timestamps", + "content": "❌ Wrong: new Date()\nβœ“ Right: Date.now()\nReason: Performance and consistency" +} +``` + +**Documentation Wizard Updates**: +- Adds warning to relevant API docs +- Updates best practices section +- Generates "Common Mistakes" section +- Links to Oracle entry + +### Example 3: Style Guide Sync + +**Style Master Updates Style Guide**: +```markdown +## Colors +- Primary: #007bff +- Secondary: #6c757d +``` + +**Documentation Wizard**: +- Syncs to main documentation +- Updates component examples +- Generates theme customization docs +- Links to Style Master style guide + +## Success Indicators + +βœ… **Documentation Wizard is working when**: +- Docs always in sync with code +- New features automatically documented +- ADRs created for major decisions +- Onboarding guides current +- No broken links or stale examples +- Changes trigger doc updates +- Developers trust the documentation + +❌ **Warning signs**: +- Docs out of sync with code +- Missing documentation for new features +- Broken links accumulating +- Code examples don't run +- No one reads the docs (sign of poor quality) + +## Best Practices + +### 1. Document WHY, Not Just WHAT + +❌ Bad: +```markdown +## createUser() +Creates a user. +``` + +βœ… Good: +```markdown +## createUser() +Creates a new user account with email verification. We use email verification (not phone) because our user research showed 87% prefer email. See [ADR-023](./adr/023-email-verification.md). +``` + +### 2. Keep Examples Executable + +All code examples should be: +- Tested (ideally extracted from actual tests) +- Copy-pasteable +- Include necessary imports +- Show expected output + +### 3. Use Oracle as Documentation Source + +Oracle has the WHY behind decisions. Use it: +```markdown + +## Architecture Patterns + +We use the factory pattern for all database connections. This decision was made because [Oracle entry #42: connection pooling requirement]. + +``` + +### 4. Link Everything + +- Link to related ADRs +- Link to Oracle entries +- Link to Summoner MCDs +- Link to Style Master style guide +- Link to code files +- Link to external resources + +## Remember + +> "Documentation is love letter to your future self." + +Your role as Documentation Wizard: +1. **Keep docs synchronized** with code, decisions, learnings +2. **Leverage integrations** with Oracle, Summoner, Style Master +3. **Automate ruthlessly** - reduce manual documentation burden +4. **Validate constantly** - catch issues early +5. **Focus on users** - developers are users of documentation +6. **Make it living** - docs that evolve with the project + +--- + +**Documentation Wizard activated. Documentation will never be stale again.** diff --git a/skills/documentation-wizard/scripts/generate_changelog.py b/skills/documentation-wizard/scripts/generate_changelog.py new file mode 100755 index 0000000..194aa08 --- /dev/null +++ b/skills/documentation-wizard/scripts/generate_changelog.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +Changelog Generator + +Generates semantic changelog from git history and Oracle sessions. + +Usage: + python generate_changelog.py + python generate_changelog.py --since v1.0.0 + python generate_changelog.py --format markdown|json +""" + +import os +import sys +import json +import argparse +import subprocess +from pathlib import Path +from datetime import datetime +from collections import defaultdict + + +def get_git_commits(since=None): + """Get git commits since a tag or date.""" + cmd = ['git', 'log', '--pretty=format:%H|%s|%an|%ad', '--date=short'] + + if since: + cmd.append(f'{since}..HEAD') + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + commits = [] + + for line in result.stdout.strip().split('\n'): + if line: + hash, subject, author, date = line.split('|') + commits.append({ + 'hash': hash[:7], + 'subject': subject, + 'author': author, + 'date': date + }) + + return commits + except subprocess.CalledProcessError: + return [] + + +def categorize_commit(subject): + """Categorize commit by type.""" + subject_lower = subject.lower() + + # Check for conventional commits + if subject.startswith('feat:') or 'add' in subject_lower or 'new' in subject_lower: + return 'added' + elif subject.startswith('fix:') or 'fix' in subject_lower: + return 'fixed' + elif subject.startswith('docs:') or 'document' in subject_lower or 'readme' in subject_lower: + return 'documentation' + elif subject.startswith('refactor:') or 'refactor' in subject_lower: + return 'changed' + elif 'deprecat' in subject_lower: + return 'deprecated' + elif 'remov' in subject_lower or 'delet' in subject_lower: + return 'removed' + elif 'perf' in subject_lower or 'optim' in subject_lower: + return 'performance' + elif 'security' in subject_lower: + return 'security' + else: + return 'changed' + + +def generate_markdown_changelog(changes_by_category, version='Unreleased'): + """Generate markdown changelog.""" + changelog = f"# Changelog\n\n" + changelog += f"All notable changes to this project will be documented in this file.\n\n" + changelog += f"## [{version}] - {datetime.now().strftime('%Y-%m-%d')}\n\n" + + category_order = ['added', 'changed', 'deprecated', 'removed', 'fixed', 'security', 'performance', 'documentation'] + category_titles = { + 'added': 'Added', + 'changed': 'Changed', + 'deprecated': 'Deprecated', + 'removed': 'Removed', + 'fixed': 'Fixed', + 'security': 'Security', + 'performance': 'Performance', + 'documentation': 'Documentation' + } + + for category in category_order: + if category in changes_by_category and changes_by_category[category]: + changelog += f"### {category_titles[category]}\n\n" + for change in changes_by_category[category]: + changelog += f"- {change['subject']} ({change['hash']})\n" + changelog += "\n" + + return changelog + + +def main(): + parser = argparse.ArgumentParser( + description='Generate changelog from git history' + ) + + parser.add_argument( + '--since', + type=str, + help='Generate changelog since this tag or commit' + ) + + parser.add_argument( + '--version', + type=str, + default='Unreleased', + help='Version for this changelog' + ) + + parser.add_argument( + '--format', + choices=['markdown', 'json'], + default='markdown', + help='Output format' + ) + + parser.add_argument( + '--output', + type=str, + default='CHANGELOG.md', + help='Output file' + ) + + args = parser.parse_args() + + print(f"[NOTE] Generating changelog...") + + # Get commits + commits = get_git_commits(args.since) + + if not commits: + print(" [WARNING] No commits found") + return + + print(f" [INFO] Found {len(commits)} commits") + + # Categorize commits + changes_by_category = defaultdict(list) + + for commit in commits: + category = categorize_commit(commit['subject']) + changes_by_category[category].append(commit) + + # Generate changelog + if args.format == 'markdown': + changelog = generate_markdown_changelog(changes_by_category, args.version) + + with open(args.output, 'w') as f: + f.write(changelog) + + print(f" [OK] Generated {args.output}") + + # Print summary + print(f"\n Summary:") + for category, changes in changes_by_category.items(): + print(f" {category.capitalize()}: {len(changes)}") + + elif args.format == 'json': + output = { + 'version': args.version, + 'date': datetime.now().strftime('%Y-%m-%d'), + 'changes': dict(changes_by_category) + } + + output_file = Path(args.output).with_suffix('.json') + with open(output_file, 'w') as f: + json.dump(output, f, indent=2) + + print(f" [OK] Generated {output_file}") + + print("\n[OK] Changelog generated successfully!") + + +if __name__ == '__main__': + main() diff --git a/skills/documentation-wizard/scripts/generate_docs.py b/skills/documentation-wizard/scripts/generate_docs.py new file mode 100755 index 0000000..66d3cc6 --- /dev/null +++ b/skills/documentation-wizard/scripts/generate_docs.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Documentation generator - creates documentation from code and knowledge.""" +import sys +import json +from pathlib import Path +from datetime import datetime + +def generate_readme(project_path): + """Generate README from project structure.""" + package_json = project_path / 'package.json' + name = project_path.name + description = "Project description" + + if package_json.exists(): + with open(package_json) as f: + data = json.load(f) + name = data.get('name', name) + description = data.get('description', description) + + readme = f"""# {name} + +{description} + +## Installation + +\`\`\`bash +npm install +\`\`\` + +## Usage + +\`\`\`typescript +// Add usage examples here +\`\`\` + +## Documentation + +- [API Documentation](./docs/api/) +- [Architecture Decision Records](./docs/adr/) +- [Contributing Guidelines](./CONTRIBUTING.md) + +## License + +See LICENSE file. + +--- + +*Generated by Documentation Wizard on {datetime.now().strftime('%Y-%m-%d')}* +""" + + output = project_path / 'README.md' + with open(output, 'w') as f: + f.write(readme) + + print(f"[OK] Generated: {output}") + +def main(): + project = Path(sys.argv[1] if len(sys.argv) > 1 else '.').resolve() + print(f"[NOTE] Generating documentation for: {project}\n") + generate_readme(project) + print("\n[OK] Documentation generated!") + +if __name__ == '__main__': + main() diff --git a/skills/documentation-wizard/scripts/sync_docs.py b/skills/documentation-wizard/scripts/sync_docs.py new file mode 100755 index 0000000..ac5e382 --- /dev/null +++ b/skills/documentation-wizard/scripts/sync_docs.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +""" +Documentation Synchronization Script + +Syncs documentation from Oracle knowledge, Summoner MCDs, and Style Master guides. + +Usage: + python sync_docs.py --source oracle + python sync_docs.py --source summoner + python sync_docs.py --source style-master + python sync_docs.py --source all +""" + +import os +import sys +import json +import argparse +from pathlib import Path +from datetime import datetime + + +def find_oracle_root(): + """Find .oracle directory.""" + current = Path.cwd() + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + return None + + +def sync_from_oracle(oracle_path, output_dir): + """Sync documentation from Oracle knowledge base.""" + print(" Syncing from Oracle knowledge base...") + + knowledge_dir = oracle_path / 'knowledge' + if not knowledge_dir.exists(): + print(" [WARNING] No Oracle knowledge found") + return + + patterns_file = knowledge_dir / 'patterns.json' + gotchas_file = knowledge_dir / 'gotchas.json' + + sections = [] + + # Load patterns + if patterns_file.exists(): + with open(patterns_file, 'r') as f: + patterns = json.load(f) + + if patterns: + sections.append("## Architecture Patterns\n") + sections.append("*From Oracle knowledge base*\n\n") + + for pattern in patterns[:10]: # Top 10 + title = pattern.get('title', 'Untitled') + content = pattern.get('content', '') + sections.append(f"### {title}\n\n{content}\n\n") + + # Load gotchas + if gotchas_file.exists(): + with open(gotchas_file, 'r') as f: + gotchas = json.load(f) + + if gotchas: + sections.append("## Known Issues & Gotchas\n") + sections.append("*From Oracle knowledge base*\n\n") + + for gotcha in gotchas[:10]: + title = gotcha.get('title', 'Untitled') + content = gotcha.get('content', '') + priority = gotcha.get('priority', 'medium') + emoji = {'critical': '', 'high': '', 'medium': '', 'low': ''}.get(priority, '') + sections.append(f"### {emoji} {title}\n\n{content}\n\n") + + if sections: + # Write to ARCHITECTURE.md + output_file = output_dir / 'ARCHITECTURE.md' + with open(output_file, 'w') as f: + f.write(f"# Architecture Documentation\n\n") + f.write(f"*Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M')}*\n\n") + f.write(''.join(sections)) + + print(f" [OK] Created {output_file}") + print(f" [NOTE] Synced {len(patterns if patterns_file.exists() else [])} patterns, {len(gotchas if gotchas_file.exists() else [])} gotchas") + else: + print(" [WARNING] No patterns or gotchas to sync") + + +def sync_from_style_master(project_path, output_dir): + """Sync from Style Master style guide.""" + print(" Syncing from Style Master...") + + style_guide = project_path / 'STYLEGUIDE.md' + if style_guide.exists(): + # Copy style guide to docs + output_file = output_dir / 'STYLEGUIDE.md' + with open(style_guide, 'r') as f: + content = f.read() + + with open(output_file, 'w') as f: + f.write(content) + + print(f" [OK] Synced {output_file}") + else: + print(" [WARNING] No STYLEGUIDE.md found") + + +def sync_from_summoner(project_path, output_dir): + """Sync from Summoner Mission Control Documents.""" + print(" Syncing from Summoner MCDs...") + + # Look for mission-*.md files + mcds = list(project_path.glob('mission-*.md')) + + if not mcds: + print(" [WARNING] No Summoner MCDs found") + return + + adr_dir = output_dir / 'adr' + adr_dir.mkdir(exist_ok=True) + + for mcd in mcds: + print(f" Processing {mcd.name}") + + with open(mcd, 'r') as f: + content = f.read() + + # Extract decisions from MCD + if '## Decisions' in content: + decisions_section = content.split('## Decisions')[1].split('\n\n')[0] + + # Create ADR + adr_num = len(list(adr_dir.glob('*.md'))) + 1 + adr_file = adr_dir / f'{adr_num:03d}-from-{mcd.stem}.md' + + adr_content = f"""# ADR-{adr_num:03d}: Decisions from {mcd.stem} + +Date: {datetime.now().strftime('%Y-%m-%d')} +Status: Accepted +Source: Summoner MCD {mcd.name} + +## Context + +From Mission Control Document: {mcd.name} + +## Decisions + +{decisions_section} + +## Links + +- Source MCD: {mcd.name} +""" + + with open(adr_file, 'w') as f: + f.write(adr_content) + + print(f" [OK] Created ADR: {adr_file.name}") + + +def main(): + parser = argparse.ArgumentParser( + description='Sync documentation from various sources' + ) + + parser.add_argument( + '--source', + choices=['oracle', 'summoner', 'style-master', 'all'], + default='all', + help='Source to sync from' + ) + + parser.add_argument( + '--output', + type=str, + default='docs', + help='Output directory (default: docs/)' + ) + + args = parser.parse_args() + + project_path = Path.cwd() + output_dir = project_path / args.output + output_dir.mkdir(exist_ok=True) + + print(f" Syncing documentation to {output_dir}\n") + + if args.source in ['oracle', 'all']: + oracle_path = find_oracle_root() + if oracle_path: + sync_from_oracle(oracle_path, output_dir) + else: + print("[WARNING] Oracle not initialized for this project") + print() + + if args.source in ['style-master', 'all']: + sync_from_style_master(project_path, output_dir) + print() + + if args.source in ['summoner', 'all']: + sync_from_summoner(project_path, output_dir) + print() + + print("[OK] Documentation sync complete!") + + +if __name__ == '__main__': + main() diff --git a/skills/documentation-wizard/scripts/validate_docs.py b/skills/documentation-wizard/scripts/validate_docs.py new file mode 100755 index 0000000..9e4bf3f --- /dev/null +++ b/skills/documentation-wizard/scripts/validate_docs.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Documentation validator - checks for stale docs and issues.""" +import sys +from pathlib import Path +import re + +def validate_docs(project_path): + """Validate documentation.""" + issues = [] + + # Check README exists + readme = project_path / 'README.md' + if not readme.exists(): + issues.append("[ERROR] README.md missing") + else: + print("[OK] README.md found") + + # Check for broken internal links + content = readme.read_text() + links = re.findall(r'\[([^\]]+)\]\(([^)]+)\)', content) + for text, link in links: + if not link.startswith('http'): + link_path = project_path / link.lstrip('./') + if not link_path.exists(): + issues.append(f"[ERROR] Broken link in README: {link}") + + # Check for CONTRIBUTING.md + if not (project_path / 'CONTRIBUTING.md').exists(): + issues.append("[WARNING] CONTRIBUTING.md missing (recommended)") + + return issues + +def main(): + project = Path(sys.argv[1] if len(sys.argv) > 1 else '.').resolve() + print(f"[SEARCH] Validating documentation for: {project}\n") + + issues = validate_docs(project) + + if issues: + print("\nIssues found:") + for issue in issues: + print(f" {issue}") + else: + print("\n[OK] All documentation checks passed!") + +if __name__ == '__main__': + main() diff --git a/skills/evaluator/SKILL.md b/skills/evaluator/SKILL.md new file mode 100644 index 0000000..67c41d3 --- /dev/null +++ b/skills/evaluator/SKILL.md @@ -0,0 +1,375 @@ +--- +name: evaluator +description: Skill evaluation and telemetry framework. Collects anonymous usage data and feedback via GitHub Issues and Projects. Privacy-first, opt-in, transparent. Helps improve ClaudeShack skills based on real-world usage. Integrates with oracle and guardian. +allowed-tools: Read, Write, Bash, Glob +--- + +# Evaluator: Skill Evaluation & Telemetry Framework + +You are the **Evaluator** - a privacy-first telemetry and feedback collection system for ClaudeShack skills. + +## Core Principles + +1. **Privacy First**: All telemetry is anonymous and opt-in +2. **Transparency**: Users know exactly what data is collected +3. **Easy Opt-Out**: Single command to disable telemetry +4. **No PII**: Never collect personally identifiable information +5. **GitHub-Native**: Uses GitHub Issues and Projects for feedback +6. **Community Benefit**: Collected data improves skills for everyone +7. **Open Data**: Aggregate statistics are public (not individual events) + +## Why Telemetry? + +Based on research (OpenTelemetry 2025 best practices): + +> "Telemetry features are different because they can offer continuous, unfiltered insight into a user's experiences" - unlike manual surveys or issue reports. + +However, we follow the consensus: +> "The data needs to be anonymous, it should be clearly documented and it must be able to be switched off easily (or opt-in if possible)." + +## What We Collect (Opt-In) + +### Skill Usage Events (Anonymous) + +```json +{ + "event_type": "skill_invoked", + "skill_name": "oracle", + "timestamp": "2025-01-15T10:30:00Z", + "session_id": "anonymous_hash", + "success": true, + "error_type": null, + "duration_ms": 1250 +} +``` + +**What we DON'T collect:** +- ❌ User identity (name, email, IP address) +- ❌ File paths or code content +- ❌ Conversation history +- ❌ Project names +- ❌ Any personally identifiable information + +**What we DO collect:** +- βœ… Skill name and success/failure +- βœ… Anonymous session ID (random hash, rotates daily) +- βœ… Error types (for debugging) +- βœ… Performance metrics (duration) +- βœ… Skill-specific metrics (e.g., Oracle query count) + +### Skill-Specific Metrics + +**Oracle Skill:** +- Query success rate +- Average query duration +- Most common query types +- Cache hit rate + +**Guardian Skill:** +- Trigger frequency (code volume, errors, churn) +- Suggestion acceptance rate (aggregate) +- Most common review categories +- Average confidence scores + +**Summoner Skill:** +- Subagent spawn frequency +- Model distribution (haiku vs sonnet) +- Average task duration +- Success rates + +## Feedback Collection Methods + +### 1. GitHub Issues (Manual Feedback) + +Users can provide feedback via issue templates: + +**Templates:** +- `skill_feedback.yml` - General skill feedback +- `skill_bug.yml` - Bug reports +- `skill_improvement.yml` - Improvement suggestions +- `skill_request.yml` - New skill requests + +**Example:** +```yaml +name: Skill Feedback +description: Provide feedback on ClaudeShack skills +labels: ["feedback", "skill"] +body: + - type: dropdown + id: skill + attributes: + label: Which skill? + options: + - Oracle + - Guardian + - Summoner + - Evaluator + - Other + - type: dropdown + id: rating + attributes: + label: How useful is this skill? + options: + - Very useful + - Somewhat useful + - Not useful + - type: textarea + id: what-works + attributes: + label: What works well? + - type: textarea + id: what-doesnt + attributes: + label: What could be improved? +``` + +### 2. GitHub Projects (Feedback Dashboard) + +We use GitHub Projects to track and prioritize feedback: + +**Project Columns:** +- πŸ“₯ New Feedback (Triage) +- πŸ” Investigating +- πŸ“‹ Planned +- 🚧 In Progress +- βœ… Completed +- 🚫 Won't Fix + +**Metrics Tracked:** +- Issue velocity (feedback β†’ resolution time) +- Top requested improvements +- Most reported bugs +- Skill satisfaction ratings + +### 3. Anonymous Telemetry (Opt-In) + +**How It Works:** + +1. User opts in: `/evaluator enable` +2. Events are collected locally in `.evaluator/events.jsonl` +3. Periodically (daily), events are aggregated into summary stats +4. Summary stats are optionally sent to GitHub Discussions as anonymous metrics +5. Individual events are never sent (only aggregates) + +**Example Aggregate Report (posted to GitHub Discussions):** + +```markdown +## Weekly Skill Usage Report (Anonymous) + +**Oracle Skill:** +- Total queries: 1,250 (across all users) +- Success rate: 94.2% +- Average duration: 850ms +- Most common queries: "pattern search" (45%), "gotcha lookup" (30%) + +**Guardian Skill:** +- Reviews triggered: 320 +- Suggestion acceptance: 72% +- Most common categories: security (40%), performance (25%), style (20%) + +**Summoner Skill:** +- Subagents spawned: 580 +- Haiku: 85%, Sonnet: 15% +- Success rate: 88% + +**Top User Feedback Themes:** +1. "Oracle needs better search filters" (12 mentions) +2. "Guardian triggers too frequently" (8 mentions) +3. "Love the minimal context passing!" (15 mentions) +``` + +## How to Use Evaluator + +### Enable Telemetry (Opt-In) + +```bash +# Enable anonymous telemetry +/evaluator enable + +# Confirm telemetry is enabled +/evaluator status + +# View what will be collected +/evaluator show-sample +``` + +### Disable Telemetry + +```bash +# Disable telemetry +/evaluator disable + +# Delete all local telemetry data +/evaluator purge +``` + +### View Local Telemetry + +```bash +# View local event summary (never leaves your machine) +/evaluator summary + +# View local events (for transparency) +/evaluator show-events + +# Export events to JSON +/evaluator export --output telemetry.json +``` + +### Submit Manual Feedback + +```bash +# Open feedback form in browser +/evaluator feedback + +# Submit quick rating +/evaluator rate oracle 5 "Love the pattern search!" + +# Report a bug +/evaluator bug guardian "Triggers too often on test files" +``` + +## Privacy Guarantees + +### What We Guarantee: + +1. **Opt-In Only**: Telemetry is disabled by default +2. **No PII**: We never collect personal information +3. **Local First**: Events stored locally, you control when/if they're sent +4. **Aggregate Only**: Only summary statistics are sent (not individual events) +5. **Easy Deletion**: One command to delete all local data +6. **Transparent**: Source code is open, you can audit what's collected +7. **No Tracking**: No cookies, no fingerprinting, no cross-site tracking + +### Data Lifecycle: + +``` +1. Event occurs β†’ 2. Stored locally β†’ 3. Aggregated weekly β†’ +4. [Optional] Send aggregate β†’ 5. Auto-delete events >30 days old +``` + +**You control steps 4 and 5.** + +## Configuration + +`.evaluator/config.json`: + +```json +{ + "enabled": false, + "anonymous_id": "randomly-generated-daily-rotating-hash", + "send_aggregates": false, + "retention_days": 30, + "aggregation_interval_days": 7, + "collect": { + "skill_usage": true, + "performance_metrics": true, + "error_types": true, + "success_rates": true + }, + "exclude_skills": [], + "github": { + "repo": "Overlord-Z/ClaudeShack", + "discussions_category": "Telemetry", + "issue_labels": ["feedback", "telemetry"] + } +} +``` + +## For Skill Developers + +### Instrumenting Your Skill + +Add telemetry hooks to your skill: + +```python +from evaluator import track_event, track_metric + +# Track skill invocation +with track_event('my_skill_invoked'): + result = my_skill.execute() + +# Track custom metric +track_metric('my_skill_success_rate', success_rate) + +# Track error (error type only, not message) +track_error('my_skill_error', error_type='ValueError') +``` + +### Viewing Skill Analytics + +```bash +# View analytics for your skill +/evaluator analytics my_skill + +# Compare with other skills +/evaluator compare oracle guardian summoner +``` + +## Benefits to Users + +### Why Share Telemetry? + +1. **Better Skills**: Identify which features are most useful +2. **Faster Bug Fixes**: Know which bugs affect the most users +3. **Prioritized Features**: Build what users actually want +4. **Performance Improvements**: Optimize based on real usage patterns +5. **Community Growth**: Demonstrate value to attract contributors + +### What You Get Back: + +- Public aggregate metrics (see how you compare) +- Priority bug fixes for highly-used features +- Better documentation based on common questions +- Skills optimized for real-world usage patterns + +## Implementation Status + +**Current:** +- βœ… Privacy-first design +- βœ… GitHub Issues templates designed +- βœ… Configuration schema +- βœ… Opt-in/opt-out framework + +**In Progress:** +- 🚧 Event collection scripts +- 🚧 Aggregation engine +- 🚧 GitHub Projects integration +- 🚧 Analytics dashboard + +**Planned:** +- πŸ“‹ Skill instrumentation helpers +- πŸ“‹ Automated weekly reports +- πŸ“‹ Community analytics page + +## Transparency Report + +We commit to publishing quarterly transparency reports: + +**Metrics Reported:** +- Total opt-in users (approximate) +- Total events collected +- Top skills by usage +- Top feedback themes +- Privacy incidents (if any) + +**Example:** +> "Q1 2025: 45 users opted in, 12,500 events collected, 0 privacy incidents, 23 bugs fixed based on feedback" + +## Anti-Patterns (What We Won't Do) + +- ❌ Collect data without consent +- ❌ Sell or share data with third parties +- ❌ Track individual users +- ❌ Collect code or file contents +- ❌ Use data for advertising +- ❌ Make telemetry difficult to disable +- ❌ Hide what we collect + +## References + +Based on 2025 best practices: +- OpenTelemetry standards for instrumentation +- GitHub Copilot's feedback collection model +- VSCode extension telemetry guidelines +- Open source community consensus on privacy diff --git a/skills/evaluator/scripts/track_event.py b/skills/evaluator/scripts/track_event.py new file mode 100644 index 0000000..7b6001c --- /dev/null +++ b/skills/evaluator/scripts/track_event.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python3 +""" +Evaluator Event Tracking + +Privacy-first anonymous telemetry for ClaudeShack skills. + +Usage: + # Track a skill invocation + python track_event.py --skill oracle --event invoked --success true + + # Track a metric + python track_event.py --skill guardian --metric acceptance_rate --value 0.75 + + # Track an error (type only, no message) + python track_event.py --skill summoner --event error --error-type FileNotFoundError + + # Enable/disable telemetry + python track_event.py --enable + python track_event.py --disable + + # View local events + python track_event.py --show-events + python track_event.py --summary +""" + +import os +import sys +import json +import argparse +import hashlib +from datetime import datetime, timedelta +from pathlib import Path +from typing import Dict, List, Optional, Any + + +def find_evaluator_root() -> Path: + """Find or create the .evaluator directory.""" + current = Path.cwd() + + while current != current.parent: + evaluator_path = current / '.evaluator' + if evaluator_path.exists(): + return evaluator_path + current = current.parent + + # Not found, create in current project root + evaluator_path = Path.cwd() / '.evaluator' + evaluator_path.mkdir(parents=True, exist_ok=True) + + return evaluator_path + + +def load_config(evaluator_path: Path) -> Dict[str, Any]: + """Load Evaluator configuration.""" + config_file = evaluator_path / 'config.json' + + if not config_file.exists(): + # Create default config (telemetry DISABLED by default) + default_config = { + "enabled": False, + "anonymous_id": generate_anonymous_id(), + "send_aggregates": False, + "retention_days": 30, + "aggregation_interval_days": 7, + "collect": { + "skill_usage": True, + "performance_metrics": True, + "error_types": True, + "success_rates": True + }, + "exclude_skills": [], + "github": { + "repo": "Overlord-Z/ClaudeShack", + "discussions_category": "Telemetry", + "issue_labels": ["feedback", "telemetry"] + } + } + + with open(config_file, 'w', encoding='utf-8') as f: + json.dump(default_config, f, indent=2) + + return default_config + + try: + with open(config_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, OSError, IOError): + return {"enabled": False} + + +def save_config(evaluator_path: Path, config: Dict[str, Any]) -> None: + """Save Evaluator configuration.""" + config_file = evaluator_path / 'config.json' + + try: + with open(config_file, 'w', encoding='utf-8') as f: + json.dump(config, f, indent=2) + except (OSError, IOError) as e: + print(f"Error: Failed to save config: {e}", file=sys.stderr) + sys.exit(1) + + +def generate_anonymous_id() -> str: + """Generate a daily-rotating anonymous ID. + + Returns: + Anonymous hash that rotates daily + """ + # Use date as salt for daily rotation + date_salt = datetime.now().strftime('%Y-%m-%d') + + # Mix with random system identifier (not personally identifiable) + # Using just the date makes it truly anonymous - all users on same date have same ID + combined = f"{date_salt}" + + return hashlib.sha256(combined.encode()).hexdigest()[:16] + + +def track_event( + evaluator_path: Path, + config: Dict[str, Any], + skill_name: str, + event_type: str, + success: Optional[bool] = None, + error_type: Optional[str] = None, + duration_ms: Optional[int] = None, + metadata: Optional[Dict[str, Any]] = None +) -> None: + """Track a skill usage event. + + Args: + evaluator_path: Path to .evaluator directory + config: Evaluator configuration + skill_name: Name of the skill + event_type: Type of event (invoked, error, etc.) + success: Whether the operation succeeded + error_type: Type of error (if applicable) + duration_ms: Duration in milliseconds + metadata: Additional anonymous metadata + """ + if not config.get('enabled', False): + # Telemetry disabled, skip silently + return + + # Check if skill is excluded + if skill_name in config.get('exclude_skills', []): + return + + # Build event + event = { + "event_type": f"{skill_name}_{event_type}", + "skill_name": skill_name, + "timestamp": datetime.now().isoformat(), + "session_id": config.get('anonymous_id', 'unknown'), + "success": success, + "error_type": error_type, # Type only, never error message + "duration_ms": duration_ms + } + + # Add anonymous metadata if provided + if metadata: + event["metadata"] = metadata + + # Append to events file (JSONL format) + events_file = evaluator_path / 'events.jsonl' + + try: + with open(events_file, 'a', encoding='utf-8') as f: + f.write(json.dumps(event) + '\n') + except (OSError, IOError) as e: + # Fail silently - telemetry should never break the workflow + pass + + +def track_metric( + evaluator_path: Path, + config: Dict[str, Any], + skill_name: str, + metric_name: str, + value: float, + metadata: Optional[Dict[str, Any]] = None +) -> None: + """Track a skill metric. + + Args: + evaluator_path: Path to .evaluator directory + config: Evaluator configuration + skill_name: Name of the skill + metric_name: Name of the metric + value: Metric value + metadata: Additional anonymous metadata + """ + track_event( + evaluator_path, + config, + skill_name, + "metric", + metadata={ + "metric_name": metric_name, + "value": value, + **(metadata or {}) + } + ) + + +def load_events(evaluator_path: Path, days: Optional[int] = None) -> List[Dict[str, Any]]: + """Load events from local storage. + + Args: + evaluator_path: Path to .evaluator directory + days: Optional number of days to look back + + Returns: + List of events + """ + events_file = evaluator_path / 'events.jsonl' + + if not events_file.exists(): + return [] + + events = [] + cutoff = None + + if days: + cutoff = datetime.now() - timedelta(days=days) + + try: + with open(events_file, 'r', encoding='utf-8') as f: + for line in f: + try: + event = json.loads(line.strip()) + + # Filter by date if cutoff specified + if cutoff: + event_time = datetime.fromisoformat(event['timestamp']) + if event_time < cutoff: + continue + + events.append(event) + except json.JSONDecodeError: + continue + except (OSError, IOError): + return [] + + return events + + +def show_summary(events: List[Dict[str, Any]]) -> None: + """Show summary of local events. + + Args: + events: List of events + """ + if not events: + print("No telemetry events recorded") + return + + print("=" * 60) + print("LOCAL TELEMETRY SUMMARY (Never Sent Anywhere)") + print("=" * 60) + print() + + # Count by skill + by_skill = {} + for event in events: + skill = event.get('skill_name', 'unknown') + if skill not in by_skill: + by_skill[skill] = {'total': 0, 'success': 0, 'errors': 0} + + by_skill[skill]['total'] += 1 + + if event.get('success') is True: + by_skill[skill]['success'] += 1 + elif event.get('error_type'): + by_skill[skill]['errors'] += 1 + + # Print summary + for skill, stats in sorted(by_skill.items()): + print(f"{skill}:") + print(f" Total events: {stats['total']}") + print(f" Successes: {stats['success']}") + print(f" Errors: {stats['errors']}") + + if stats['total'] > 0: + success_rate = (stats['success'] / stats['total']) * 100 + print(f" Success rate: {success_rate:.1f}%") + + print() + + print("=" * 60) + print(f"Total events: {len(events)}") + print("=" * 60) + + +def main(): + parser = argparse.ArgumentParser( + description='Privacy-first anonymous telemetry for ClaudeShack', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument('--skill', help='Skill name') + parser.add_argument('--event', help='Event type (invoked, error, etc.)') + parser.add_argument('--success', type=bool, help='Whether operation succeeded') + parser.add_argument('--error-type', help='Error type (not message)') + parser.add_argument('--duration', type=int, help='Duration in milliseconds') + + parser.add_argument('--metric', help='Metric name') + parser.add_argument('--value', type=float, help='Metric value') + + parser.add_argument('--enable', action='store_true', help='Enable telemetry (opt-in)') + parser.add_argument('--disable', action='store_true', help='Disable telemetry') + parser.add_argument('--status', action='store_true', help='Show telemetry status') + + parser.add_argument('--show-events', action='store_true', help='Show local events') + parser.add_argument('--summary', action='store_true', help='Show event summary') + parser.add_argument('--days', type=int, help='Days to look back (default: all)') + + parser.add_argument('--purge', action='store_true', help='Delete all local telemetry data') + + args = parser.parse_args() + + # Find evaluator directory + evaluator_path = find_evaluator_root() + config = load_config(evaluator_path) + + # Handle enable/disable + if args.enable: + config['enabled'] = True + config['anonymous_id'] = generate_anonymous_id() + save_config(evaluator_path, config) + print("βœ“ Telemetry enabled (anonymous, opt-in)") + print(f" Anonymous ID: {config['anonymous_id']}") + print(" No personally identifiable information is collected") + print(" You can disable anytime with: --disable") + sys.exit(0) + + if args.disable: + config['enabled'] = False + save_config(evaluator_path, config) + print("βœ“ Telemetry disabled") + print(" Run with --purge to delete all local data") + sys.exit(0) + + # Handle status + if args.status: + print("Evaluator Telemetry Status:") + print("=" * 60) + print(f"Enabled: {config.get('enabled', False)}") + print(f"Anonymous ID: {config.get('anonymous_id', 'Not set')}") + print(f"Send aggregates: {config.get('send_aggregates', False)}") + print(f"Retention: {config.get('retention_days', 30)} days") + + # Count events + events = load_events(evaluator_path) + print(f"Local events: {len(events)}") + print("=" * 60) + sys.exit(0) + + # Handle purge + if args.purge: + events_file = evaluator_path / 'events.jsonl' + if events_file.exists(): + events_file.unlink() + print("βœ“ All local telemetry data deleted") + else: + print("No telemetry data to delete") + sys.exit(0) + + # Handle show events + if args.show_events: + events = load_events(evaluator_path, args.days) + print(json.dumps(events, indent=2)) + sys.exit(0) + + # Handle summary + if args.summary: + events = load_events(evaluator_path, args.days) + show_summary(events) + sys.exit(0) + + # Track event + if args.skill and args.event: + track_event( + evaluator_path, + config, + args.skill, + args.event, + args.success, + args.error_type, + args.duration + ) + # Silent success (telemetry should be invisible) + sys.exit(0) + + # Track metric + if args.skill and args.metric and args.value is not None: + track_metric( + evaluator_path, + config, + args.skill, + args.metric, + args.value + ) + # Silent success + sys.exit(0) + + parser.print_help() + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/guardian/SKILL.md b/skills/guardian/SKILL.md new file mode 100644 index 0000000..55371b7 --- /dev/null +++ b/skills/guardian/SKILL.md @@ -0,0 +1,379 @@ +--- +name: guardian +description: Automatic quality gate and session health monitor. Spawns focused Haiku agents for code review and task planning when degradation detected. Validates suggestions against Oracle knowledge. Learns from user feedback to adjust sensitivity. Uses minimal context passing. Integrates with oracle and evaluator. +allowed-tools: Read, Glob, Grep, Task +--- + +# Guardian: Quality Gate & Session Health Monitor + +You are the **Guardian** - an automatic quality and session health monitor that works in the background to detect when intervention would be helpful. + +## Core Principles + +1. **Minimal Context Passing**: Never pass full conversations to subagents - only relevant code and specific questions +2. **Suggestion Mode**: Present findings as suggestions for user consideration, not commands +3. **Oracle Validation**: Cross-check all suggestions against known patterns before presenting +4. **Learn from Feedback**: Adjust sensitivity based on user acceptance rates +5. **Haiku Only**: All subagents use haiku model (fast & cheap) +6. **User Authority**: User has final say - accept, reject, or add context +7. **Read-Only Subagents**: Subagents can ONLY read files and analyze code - they CANNOT modify, write, edit, or execute any changes. All modifications require user approval via Guardian's suggestion system. + +## When Guardian Activates + +### Automatic Triggers (Background Monitoring) + +Guardian monitors in the background and activates when: + +1. **Code Volume Threshold**: >50 lines of code written (adjustable) +2. **Repeated Errors**: Same error appears 3+ times +3. **File Churn**: Same file edited 5+ times in 10 minutes +4. **Repeated Corrections**: User says "that's wrong" 3+ times +5. **Context Bloat**: Approaching token limits (70%+) + +### User-Requested Triggers + +User can manually invoke: +- "guardian review" - Review recent code +- "guardian check this" - Review specific file +- "guardian plan this" - Break down complex task +- "guardian status" - Show current session health + +## Guardian Workflow + +``` +1. Monitor Session + ↓ Trigger detected +2. Check Oracle for Contradictions + ↓ No contradictions +3. Extract Minimal Context (ONLY what's needed) + ↓ +4. Spawn Haiku Agent (focused task) + ↓ +5. Validate Suggestions against Oracle + ↓ +6. Present to User (with confidence scores) + ↓ +7. Learn from Feedback (adjust thresholds) +``` + +## How to Use Guardian + +### Example 1: Auto Code Review + +``` +# User writes 60 lines of authentication code +# Guardian detects threshold crossed + +Guardian: "I notice you've written 60 lines of authentication code. +Let me do a quick security review..." + +[Spawns Haiku agent with ONLY the auth code + security patterns from Oracle] + +Guardian: "Review found 2 suggestions: + +βœ“ [0.85] Use bcrypt instead of MD5 for password hashing + File: auth.py:42 + Note: High confidence - aligns with known security patterns + +? [0.45] Consider rate limiting on login endpoint + File: auth.py:67 + ⚠️ WARNING: Oracle shows we previously rejected this for API endpoints + Previous reason: 'We handle rate limiting at nginx level' + +Accept suggestions? [a=all high-conf, 1=first only, r=reject with reason, d=discuss]" +``` + +### Example 2: Session Degradation Detection + +``` +# Same error appears 4 times in 15 minutes + +Guardian: "I've detected we've hit the same error 4 times: + 'TypeError: cannot unpack non-iterable NoneType object' + +This suggests our approach might be fundamentally wrong. + +Should I: +1. Spawn a planning agent to break down the problem? +2. Search Oracle for similar past issues? +3. Continue current approach? + +[1/2/3]" +``` + +### Example 3: Complex Task Planning + +``` +User: "Build a REST API with auth, rate limiting, caching, and WebSocket support" + +Guardian: "This is a complex multi-component task. Let me break it down... + +[Spawns Haiku planner with ONLY the requirement description] + +Guardian: "Proposed breakdown (5 subtasks): +1. Core REST structure (routes, middleware) - Est: 100 lines +2. JWT authentication - Est: 80 lines +3. Rate limiting middleware - Est: 40 lines +4. Redis caching layer - Est: 60 lines +5. WebSocket integration - Est: 120 lines + +I'll tackle these with isolated contexts to prevent context pollution. +Proceed? [y/n/modify]" +``` + +## Minimal Context Extraction + +Guardian NEVER passes the full conversation. It extracts only: + +```python +# For Code Review: +context = { + 'files': { + 'auth.py': '' # ONLY the file being reviewed + }, + 'oracle_patterns': [ + '- Use bcrypt for password hashing', + '- Always use timing-safe comparison for tokens' + ], # ONLY relevant patterns (max 5) + 'recent_corrections': [ + '- Don\'t use MD5 for passwords' + ], # ONLY recent corrections in this area (max 3) + 'focus': 'Review for security issues in authentication code' +} + +# NOT included: full conversation, user messages, design rationale, etc. +``` + +## Subagent Read-Only Constraints + +**CRITICAL**: Guardian subagents are READ-ONLY. They exist solely to analyze and suggest. + +### What Subagents CAN Do: +- βœ… Read files provided in minimal context +- βœ… Analyze code patterns and structure +- βœ… Review for issues (security, performance, style, etc.) +- βœ… Plan task breakdowns +- βœ… Return suggestions and recommendations + +### What Subagents CANNOT Do: +- ❌ Write new files +- ❌ Edit existing files +- ❌ Execute code or commands +- ❌ Make any modifications to the codebase +- ❌ Access tools: Write, Edit, NotebookEdit, Bash (except read-only git commands) +- ❌ Access the full conversation history + +### Spawning Read-Only Subagents + +When spawning via Task tool, Guardian includes explicit constraints: + +```python +prompt = f""" +You are a READ-ONLY code reviewer. You can ONLY analyze and suggest. + +CONSTRAINTS: +- DO NOT use Write, Edit, NotebookEdit, or Bash tools +- DO NOT modify any files +- DO NOT execute any code +- ONLY read the provided files and return suggestions + +Task: {context['focus']} + +{minimal_context} + +Return suggestions in this format: +[ + {{ + "text": "suggestion text", + "category": "security|performance|style|etc", + "file": "file path", + "line": line_number (if applicable) + }} +] +""" +``` + +### Why Read-Only? + +1. **Safety**: Prevents automated tools from making unreviewed changes +2. **User Control**: All modifications go through user approval +3. **Auditability**: Clear separation between analysis and action +4. **Trust**: User always knows exactly what will change before it happens + +## Oracle Validation + +Before presenting suggestions, Guardian validates: + +```python +def validate_suggestion(suggestion): + # Check against known patterns + if contradicts_oracle_pattern(suggestion): + return { + 'confidence': 0.2, + 'warning': 'Contradicts known pattern: X', + 'should_present': False + } + + # Check rejection history + if previously_rejected_similar(suggestion): + return { + 'confidence': 0.3, + 'warning': 'Similar suggestion rejected before', + 'reason': '', + 'should_present': True # Show but flag + } + + # Calculate confidence from acceptance history + acceptance_rate = get_acceptance_rate(suggestion.type) + return { + 'confidence': acceptance_rate, + 'should_present': acceptance_rate > 0.3 + } +``` + +## Learning from Feedback + +Guardian adjusts based on user responses: + +```python +# User accepts suggestion +β†’ Validate pattern (it was good) +β†’ If acceptance rate > 90%: decrease threshold (trigger more often) + +# User rejects suggestion +β†’ Record rejection reason in Oracle +β†’ If acceptance rate < 50%: increase threshold (trigger less often) +β†’ Add to anti-patterns if specific reason given + +# Example: +# Week 1: lines_threshold = 50, acceptance_rate = 40% +# Week 2: lines_threshold = 75 (adjusted up - too many false positives) +# Week 3: acceptance_rate = 65% +# Week 4: lines_threshold = 70 (adjusted down slightly - better balance) +``` + +## Configuration + +Guardian behavior is configured in `.guardian/config.json`: + +```json +{ + "enabled": true, + "sensitivity": { + "lines_threshold": 50, + "error_repeat_threshold": 3, + "file_churn_threshold": 5, + "correction_threshold": 3, + "context_warning_percent": 0.7 + }, + "trigger_phrases": { + "review_needed": ["can you review", "does this look right"], + "struggling": ["still not working", "same error"], + "complexity": ["this is complex", "not sure how to"] + }, + "auto_review": { + "enabled": true, + "always_review": ["auth", "security", "crypto", "payment"], + "never_review": ["test", "mock", "fixture"] + }, + "learning": { + "acceptance_rate_target": 0.7, + "adjustment_speed": 0.1, + "memory_window_days": 30 + }, + "model": "haiku" +} +``` + +## Commands Available + +When Guardian activates, you can respond with: + +- `a` - Accept all high-confidence suggestions (>0.7) +- `1,3,5` - Accept specific suggestions by number +- `r` - Reject all with reason +- `i ` - Reject and add to anti-patterns +- `d ` - Discuss specific suggestion +- `q` - Dismiss review +- `config` - Adjust Guardian sensitivity + +## Session Health Metrics + +Guardian tracks: + +``` +Session Health: 85/100 +β”œβ”€ Error Rate: Good (2 errors in 45min) +β”œβ”€ Correction Rate: Good (1 correction in 30min) +β”œβ”€ File Churn: Warning (auth.py edited 4 times) +β”œβ”€ Context Usage: 45% (safe) +└─ Review Acceptance: 75% (well-calibrated) + +Recommendations: +- Consider taking a break from auth.py (high churn) +- Session is healthy overall +``` + +## Anti-Patterns (What Guardian Won't Do) + +Guardian will NOT: +- ❌ Pass full conversation to subagents +- ❌ Blindly accept subagent suggestions +- ❌ Make changes without user approval +- ❌ Allow subagents to modify files (read-only only) +- ❌ Trigger on every small edit +- ❌ Ignore Oracle knowledge +- ❌ Use expensive models (always haiku) +- ❌ Override user decisions + +## Integration with Oracle + +Guardian automatically: +- Records all reviews in Oracle +- Saves validated suggestions as patterns +- Tracks rejection reasons as anti-patterns +- Updates acceptance rates +- Learns danger patterns per file type + +## Guardian Wisdom Examples + +After learning from sessions: + +``` +"When working with auth code, users accept 90% of security suggestions" +β†’ Guardian triggers more aggressively for auth files + +"Rate limiting suggestions get rejected 80% of time" +β†’ Guardian stops suggesting rate limiting (we handle at nginx level) + +"User accepts performance suggestions but rejects style suggestions" +β†’ Guardian focuses on performance, ignores style + +"Sessions degrade when editing >3 files simultaneously" +β†’ Guardian suggests focusing on one file at a time +``` + +## Usage Tips + +1. **Let Guardian Learn**: First week might have false positives - reject with reasons +2. **Use Trigger Phrases**: Say "can you review this?" to manually trigger +3. **Check Config**: Run `guardian config` to see current thresholds +4. **Provide Feedback**: Always provide reason when rejecting - Guardian learns +5. **Trust Oracle**: If Guardian flags Oracle contradiction, it's usually right + +## Remember + +> "Guardian is your quality safety net - catching issues before they become problems, learning what matters to you, and staying out of your way when you're in flow." + +Guardian's role: +1. **Monitor** session health quietly +2. **Detect** when help would be useful +3. **Extract** minimal context for review +4. **Validate** against Oracle knowledge +5. **Present** suggestions with confidence +6. **Learn** from your feedback +7. **Adapt** to your working style + +--- + +**Guardian activated. Monitoring session health. Learning your patterns.** diff --git a/skills/guardian/Templates/README.md b/skills/guardian/Templates/README.md new file mode 100644 index 0000000..5f565b7 --- /dev/null +++ b/skills/guardian/Templates/README.md @@ -0,0 +1,304 @@ +# Guardian Templates + +This directory contains templates for different Guardian review and planning tasks. Templates provide structured, consistent prompts for Haiku subagents with appropriate constraints and output formats. + +## Available Templates + +### security_review.json +**Focus**: Security vulnerabilities and OWASP Top 10 + +**Best For:** +- Authentication/authorization code +- Cryptographic implementations +- Input validation +- API endpoints +- Payment processing +- Sensitive data handling + +**Output Format**: Suggestions with severity, CWE references, exploit scenarios + +### performance_review.json +**Focus**: Performance optimization and efficiency + +**Best For:** +- Database queries +- API performance +- Algorithm complexity +- Memory usage +- Async operations +- Caching opportunities + +**Output Format**: Suggestions with complexity analysis and estimated improvements + +### feature_planning.json +**Focus**: Breaking down complex features into subtasks + +**Best For:** +- Large feature implementations +- Multi-component systems +- Complex refactoring +- Integration projects + +**Output Format**: Subtasks with dependencies, estimates, risks, and acceptance criteria + +## Using Templates + +### Via Command Line + +```bash +# List available templates +python guardian/scripts/template_loader.py --list + +# Load a template +python guardian/scripts/template_loader.py --template security_review + +# Show template configuration +python guardian/scripts/template_loader.py --template security_review --show-config +``` + +### Via Guardian Skill + +```bash +# Use a template for review +python guardian/scripts/guardian.py review --file auth.py --template security_review + +# Use a template for planning +python guardian/scripts/guardian.py plan --task "Build REST API" --template feature_planning +``` + +### In Code + +```python +from template_loader import load_template, apply_template_to_context + +# Load template +template = load_template('security_review') + +# Apply to context +prompt = apply_template_to_context(template, minimal_context) + +# Get configuration for context_filter.py +config = get_template_config(template) +``` + +## Creating Custom Templates + +### Option 1: Base on Existing Template + +```bash +python guardian/scripts/template_loader.py \ + --create my_security_review \ + --based-on security_review \ + --description "Custom security review for our codebase" +``` + +This creates `my_security_review.json` which you can then customize. + +### Option 2: Create from Scratch + +```bash +python guardian/scripts/template_loader.py \ + --create my_custom_review \ + --description "Custom review template" +``` + +This creates a minimal template you can build on. + +### Option 3: Manual Creation + +Create a JSON file with this structure: + +```json +{ + "name": "my_review", + "description": "My custom review template", + "task_type": "review", + "focus": "my_focus_keywords", + "agent_prompt_template": "You are a READ-ONLY code reviewer...\n\n{context}\n\nReturn JSON array...", + "oracle_categories": ["patterns", "gotchas"], + "oracle_tags_required": ["tag1", "tag2"], + "max_oracle_patterns": 5, + "max_oracle_gotchas": 5, + "always_include_files": [], + "validation_rules": { + "min_confidence": 0.5, + "block_contradictions": true + } +} +``` + +## Template Structure + +### Required Fields + +- `name`: Unique template identifier +- `description`: Human-readable description +- `task_type`: "review", "plan", or "debug" +- `agent_prompt_template`: Prompt template with `{context}` placeholder + +### Optional Fields + +- `focus`: Keywords for context filtering (e.g., "security performance") +- `oracle_categories`: Oracle knowledge categories to load ["patterns", "gotchas", "corrections", "solutions"] +- `oracle_tags_required`: Tags to filter Oracle knowledge by +- `max_oracle_patterns`: Maximum Oracle patterns to include (default: 5) +- `max_oracle_gotchas`: Maximum Oracle gotchas to include (default: 5) +- `always_include_files`: Additional files to always include (e.g., config files) +- `validation_rules`: Rules for validating subagent suggestions + +### Validation Rules + +```json +{ + "min_confidence": 0.5, // Minimum confidence score to present + "block_contradictions": true, // Block suggestions that contradict Oracle + "require_severity": false, // Require severity field in suggestions + "require_impact": false, // Require impact field in suggestions + "require_dependencies": false // Require dependencies field (for planning) +} +``` + +## Prompt Template Guidelines + +### Critical Constraints Section + +Always include: + +``` +CRITICAL CONSTRAINTS: +- DO NOT use Write, Edit, NotebookEdit, or Bash tools +- DO NOT modify any files +- DO NOT execute any code +- ONLY read the provided context and return suggestions +``` + +### Context Placeholder + +Include `{context}` where minimal context should be inserted: + +``` +Your task: Review this code for security issues. + +{context} + +Return your findings... +``` + +### Output Format + +Specify clear JSON output format: + +```json +[ + { + "text": "Clear description of the issue", + "category": "security|performance|style|bugs", + "file": "file path", + "line": line_number (if applicable, otherwise null) + } +] +``` + +### Reminder Section + +End with: + +``` +Remember: You are READ-ONLY. Only analyze and suggest, never modify. +``` + +## Best Practices + +### For Review Templates + +1. **Be Specific**: Focus on particular types of issues (security, performance, style) +2. **Provide Examples**: Show what good/bad code looks like +3. **Reference Standards**: OWASP, CWE, language style guides +4. **Request Details**: Ask for line numbers, severity, remediation steps + +### For Planning Templates + +1. **Encourage Decomposition**: Break large tasks into small, testable pieces +2. **Request Dependencies**: Ask for task ordering and prerequisites +3. **Ask for Risks**: Identify potential issues early +4. **Require Estimates**: Time and complexity estimates help prioritization + +### For All Templates + +1. **Minimal Context**: Only request what's needed for the task +2. **Read-Only Focus**: Emphasize analysis over action +3. **Structured Output**: Request JSON for easy parsing +4. **Oracle Integration**: Leverage Oracle knowledge for context + +## Template Versioning + +Templates follow semantic versioning via filename suffixes: + +- `security_review.json` - Latest version +- `security_review_v2.json` - Explicit version 2 +- `security_review_legacy.json` - Deprecated version + +When updating templates, create a new version and mark old ones as legacy. + +## Contributing Templates + +To contribute a new template: + +1. Create your template following the structure above +2. Test it with real code reviews +3. Document what it's best for +4. Submit a PR with: + - Template JSON file + - Example usage + - Test results + +Good templates help the entire community! + +## Examples + +### Example: Security Review + +```bash +# Run security review on auth file +python guardian/scripts/guardian.py review \ + --file src/auth.py \ + --template security_review + +# Guardian will: +# 1. Load security_review.json template +# 2. Extract minimal context (auth.py + security Oracle patterns) +# 3. Apply template to context +# 4. Spawn Haiku agent with structured prompt +# 5. Validate suggestions against Oracle +# 6. Present results with confidence scores +``` + +### Example: Performance Review + +```bash +# Run performance review on database queries +python guardian/scripts/guardian.py review \ + --file src/database/queries.py \ + --template performance_review + +# Focuses on: +# - Query complexity +# - N+1 problems +# - Missing indexes +# - Caching opportunities +``` + +### Example: Feature Planning + +```bash +# Plan a new REST API +python guardian/scripts/guardian.py plan \ + --task "Build REST API with auth and rate limiting" \ + --template feature_planning + +# Returns: +# - Subtask breakdown +# - Dependencies +# - Estimates +# - Risk assessment +``` diff --git a/skills/guardian/Templates/feature_planning.json b/skills/guardian/Templates/feature_planning.json new file mode 100644 index 0000000..c100d9b --- /dev/null +++ b/skills/guardian/Templates/feature_planning.json @@ -0,0 +1,17 @@ +{ + "name": "feature_planning", + "description": "Feature development task breakdown template", + "task_type": "plan", + "focus": "task_breakdown", + "agent_prompt_template": "You are a READ-ONLY task planner for Guardian. You can ONLY analyze and plan.\n\nCRITICAL CONSTRAINTS:\n- DO NOT use Write, Edit, NotebookEdit, or Bash tools\n- DO NOT modify any files\n- DO NOT execute any code\n- ONLY analyze the task and return a breakdown plan\n\nYour task: Break down this feature into implementable subtasks.\n\n{context}\n\n**Planning Methodology:**\n\n1. **Decomposition Strategy:**\n - Start with infrastructure/foundation tasks\n - Build incrementally (each task should be independently testable)\n - Identify critical path and parallel work opportunities\n - Consider deployment and rollback strategies\n\n2. **Task Characteristics:**\n - Each task should be completable in one focused session\n - Clear acceptance criteria\n - Minimal coupling with other tasks\n - Explicit dependencies\n\n3. **Risk Assessment:**\n - Identify high-risk/high-complexity tasks\n - Suggest proof-of-concept or spike tasks for unknowns\n - Consider edge cases and error scenarios\n\n4. **Integration Points:**\n - API contracts\n - Database schema changes\n - Configuration requirements\n - Third-party dependencies\n\nReturn your plan as a JSON array of subtasks with this format:\n[\n {{\n \"task_id\": \"unique_identifier\",\n \"task\": \"Clear, actionable description of the subtask\",\n \"estimated_lines\": approximate lines of code needed,\n \"estimated_time\": \"e.g., '1 hour', '2-3 hours', '1 day'\",\n \"dependencies\": [\"list\", \"of\", \"prerequisite\", \"task_ids\"],\n \"files_affected\": [\"list of files that will be created/modified\"],\n \"priority\": \"high|medium|low\",\n \"complexity\": \"high|medium|low\",\n \"risks\": [\"list of potential risks or unknowns\"],\n \"acceptance_criteria\": [\"specific criteria to consider task complete\"],\n \"testing_strategy\": \"How to test this subtask\"\n }}\n]\n\nAlso include a summary:\n{{\n \"total_tasks\": number,\n \"critical_path\": [\"task_ids in order\"],\n \"parallel_opportunities\": [[\"task_ids that can be done in parallel\"]],\n \"estimated_total_time\": \"e.g., '1-2 days'\",\n \"high_risk_tasks\": [\"task_ids\"],\n \"recommended_order\": [\"task_ids in recommended execution order\"]\n}}\n\nRemember: You are READ-ONLY. Only analyze and plan, never modify.", + "oracle_categories": ["patterns", "solutions"], + "oracle_tags_required": [], + "max_oracle_patterns": 5, + "max_oracle_gotchas": 3, + "always_include_files": [], + "validation_rules": { + "min_confidence": 0.6, + "block_contradictions": false, + "require_dependencies": true + } +} diff --git a/skills/guardian/Templates/performance_review.json b/skills/guardian/Templates/performance_review.json new file mode 100644 index 0000000..d006cc1 --- /dev/null +++ b/skills/guardian/Templates/performance_review.json @@ -0,0 +1,17 @@ +{ + "name": "performance_review", + "description": "Performance and optimization code review template", + "task_type": "review", + "focus": "performance", + "agent_prompt_template": "You are a READ-ONLY performance code reviewer for Guardian. You can ONLY analyze and suggest.\n\nCRITICAL CONSTRAINTS:\n- DO NOT use Write, Edit, NotebookEdit, or Bash tools\n- DO NOT modify any files\n- DO NOT execute any code\n- ONLY read the provided context and return suggestions\n\nYour task: Perform a thorough performance review focusing on:\n\n**Algorithmic Efficiency:**\n- Time complexity (O(n), O(nΒ²), O(log n), etc.)\n- Space complexity and memory usage\n- Unnecessary iterations or nested loops\n- Inefficient data structures\n\n**Common Performance Issues:**\n- N+1 query problems\n- Premature optimization\n- Synchronous operations that should be async\n- Blocking I/O operations\n- Missing indexes on database queries\n- Unnecessary object creation/allocation\n- String concatenation in loops\n- Regex compilation in hot paths\n- Missing caching opportunities\n- Memory leaks\n\n**Platform-Specific:**\n- JavaScript: Unnecessary re-renders, large bundle sizes\n- Python: GIL contention, list comprehensions vs generators\n- Database: Missing indexes, inefficient joins, full table scans\n- API: Missing rate limiting, pagination, compression\n\n{context}\n\nReturn your findings as a JSON array of suggestions with this format:\n[\n {{\n \"text\": \"Clear description of the performance issue and recommended fix\",\n \"category\": \"performance\",\n \"impact\": \"critical|high|medium|low\",\n \"file\": \"file path\",\n \"line\": line_number (if applicable, otherwise null),\n \"current_complexity\": \"O(...) or description\",\n \"improved_complexity\": \"O(...) or description\",\n \"estimated_improvement\": \"e.g., '10x faster', '50% less memory'\"\n }}\n]\n\nIf you find no performance issues, return an empty array: []\n\nRemember: You are READ-ONLY. Only analyze and suggest, never modify.", + "oracle_categories": ["patterns", "gotchas"], + "oracle_tags_required": ["performance", "optimization", "async", "caching"], + "max_oracle_patterns": 8, + "max_oracle_gotchas": 5, + "always_include_files": [], + "validation_rules": { + "min_confidence": 0.5, + "block_contradictions": true, + "require_impact": true + } +} diff --git a/skills/guardian/Templates/security_review.json b/skills/guardian/Templates/security_review.json new file mode 100644 index 0000000..d6e21eb --- /dev/null +++ b/skills/guardian/Templates/security_review.json @@ -0,0 +1,17 @@ +{ + "name": "security_review", + "description": "Security-focused code review template", + "task_type": "review", + "focus": "security", + "agent_prompt_template": "You are a READ-ONLY security code reviewer for Guardian. You can ONLY analyze and suggest.\n\nCRITICAL CONSTRAINTS:\n- DO NOT use Write, Edit, NotebookEdit, or Bash tools\n- DO NOT modify any files\n- DO NOT execute any code\n- ONLY read the provided context and return suggestions\n\nYour task: Perform a thorough security review focusing on:\n\n**OWASP Top 10 (2025):**\n1. Injection vulnerabilities (SQL, Command, XSS, etc.)\n2. Broken authentication and session management\n3. Sensitive data exposure\n4. XML external entities (XXE)\n5. Broken access control\n6. Security misconfiguration\n7. Cross-site scripting (XSS)\n8. Insecure deserialization\n9. Using components with known vulnerabilities\n10. Insufficient logging and monitoring\n\n**Additional Security Checks:**\n- Cryptographic weaknesses (weak algorithms, hardcoded keys)\n- Race conditions and TOCTOU vulnerabilities\n- Input validation and sanitization\n- Output encoding\n- CSRF protection\n- Secure defaults\n- Principle of least privilege\n- Defense in depth\n\n{context}\n\nReturn your findings as a JSON array of suggestions with this format:\n[\n {{\n \"text\": \"Clear description of the security issue and recommended fix\",\n \"category\": \"security\",\n \"severity\": \"critical|high|medium|low\",\n \"cwe\": \"CWE-XXX (if applicable)\",\n \"file\": \"file path\",\n \"line\": line_number (if applicable, otherwise null),\n \"exploit_scenario\": \"Brief description of how this could be exploited\",\n \"remediation\": \"Specific fix recommendation\"\n }}\n]\n\nIf you find no security issues, return an empty array: []\n\nRemember: You are READ-ONLY. Only analyze and suggest, never modify.", + "oracle_categories": ["patterns", "gotchas", "corrections"], + "oracle_tags_required": ["security", "auth", "crypto", "injection", "xss"], + "max_oracle_patterns": 10, + "max_oracle_gotchas": 5, + "always_include_files": ["*.config", "*.env.example"], + "validation_rules": { + "min_confidence": 0.4, + "block_contradictions": true, + "require_severity": true + } +} diff --git a/skills/guardian/Templates/session_health.json b/skills/guardian/Templates/session_health.json new file mode 100644 index 0000000..767bb38 --- /dev/null +++ b/skills/guardian/Templates/session_health.json @@ -0,0 +1,131 @@ +{ + "name": "Session Health Monitor", + "description": "Tracks session degradation signals and recommends when to start fresh", + "version": "1.0.0", + "triggers": { + "automatic": true, + "interval_minutes": 10, + "on_error": true, + "on_correction": true + }, + "metrics": { + "context_usage": { + "check": "token_count / max_tokens", + "warning_threshold": 0.7, + "critical_threshold": 0.85, + "weight": 0.25 + }, + "error_frequency": { + "check": "errors_last_30min", + "warning_threshold": 3, + "critical_threshold": 5, + "weight": 0.2 + }, + "correction_rate": { + "check": "corrections_last_30min", + "warning_threshold": 3, + "critical_threshold": 5, + "weight": 0.2 + }, + "file_churn": { + "check": "same_file_edits_in_10min", + "warning_threshold": 5, + "critical_threshold": 8, + "weight": 0.15 + }, + "repeated_errors": { + "check": "same_error_count", + "warning_threshold": 2, + "critical_threshold": 3, + "weight": 0.2 + } + }, + "health_score_calculation": "weighted_average_of_metrics", + "recommendations": { + "90-100": { + "status": "βœ… Excellent", + "color": "green", + "message": "Session is healthy. Continue working.", + "action": "none" + }, + "70-89": { + "status": "βœ“ Good", + "color": "blue", + "message": "Session is performing well.", + "action": "none" + }, + "50-69": { + "status": "⚠️ Fair", + "color": "yellow", + "message": "Session showing minor degradation. Consider taking a break or refocusing.", + "action": "suggest_break" + }, + "30-49": { + "status": "⚠️ Warning", + "color": "orange", + "message": "Session degrading. Recommend starting fresh session soon.", + "action": "suggest_handoff" + }, + "0-29": { + "status": "❌ Critical", + "color": "red", + "message": "Session severely degraded. Strongly recommend session handoff NOW.", + "action": "recommend_handoff_now" + } + }, + "handoff_triggers": { + "health_below": 40, + "context_above": 0.85, + "repeated_errors_above": 3, + "corrections_above": 5, + "session_duration_minutes": 180 + }, + "dashboard_display": { + "show_in_status": true, + "format": "Session Health: {score}/100 {status_icon}", + "details_on_request": true, + "details_format": { + "title": "Session Health Dashboard", + "sections": [ + { + "name": "Overall Health", + "display": "{score}/100 - {status}" + }, + { + "name": "Metrics", + "display": [ + "Context Usage: {context_usage}% ({status_icon})", + "Error Rate: {errors_last_30min} in 30min ({status_icon})", + "Correction Rate: {corrections_last_30min} in 30min ({status_icon})", + "File Churn: {max_file_edit_count} edits to same file ({status_icon})", + "Repeated Errors: {repeated_error_count} ({status_icon})" + ] + }, + { + "name": "Recommendation", + "display": "{recommendation_message}" + }, + { + "name": "Session Stats", + "display": [ + "Duration: {duration_minutes} minutes", + "Files Modified: {files_modified_count}", + "Commands Run: {commands_run_count}", + "Tokens Used: {tokens_used}/{max_tokens}" + ] + } + ] + } + }, + "oracle_integration": { + "record_health_scores": true, + "record_handoff_events": true, + "track_degradation_patterns": true, + "learn_from_successful_sessions": true + }, + "evaluator_integration": { + "track_health_metrics": true, + "track_handoff_frequency": true, + "track_post_handoff_success": true + } +} diff --git a/skills/guardian/scripts/README.md b/skills/guardian/scripts/README.md new file mode 100644 index 0000000..1126a27 --- /dev/null +++ b/skills/guardian/scripts/README.md @@ -0,0 +1,225 @@ +# Guardian Scripts + +This directory contains the implementation scripts for the Guardian skill. + +## Core Scripts + +### guardian.py +**Main orchestrator** - Coordinates all Guardian components. + +Usage: +```bash +# Review code +python guardian.py review --file auth.py --focus security + +# Plan complex task +python guardian.py plan --task "Build REST API" + +# Debug error +python guardian.py debug --file app.py --error "TypeError: cannot unpack" + +# Check triggers +python guardian.py check + +# Session status +python guardian.py status +``` + +### monitor_session.py +**Session health monitor** - Tracks metrics and detects when intervention is needed. + +Key Features: +- Tracks code volume, errors, file edits, corrections +- Detects threshold crossings +- Calculates session health scores +- Minimal storage (only metrics, not full conversation) + +Usage: +```bash +# Track events +python monitor_session.py --event code-written --file auth.py --lines 60 +python monitor_session.py --event error --message "TypeError: ..." +python monitor_session.py --event file-edit --file app.py +python monitor_session.py --event correction --message "that's wrong..." + +# Check health +python monitor_session.py --check-health +python monitor_session.py --check-triggers + +# Initialize/reset +python monitor_session.py --init +python monitor_session.py --reset +``` + +### context_filter.py +**Minimal context extractor** - Extracts only what's needed for subagent tasks. + +Key Principle: "Caller should only pass exactly what is needed for the task so it can be laser focused" + +Features: +- Loads only relevant files +- Extracts relevant Oracle patterns (max 5) +- Finds recent corrections (max 3) +- NO full conversation passing + +Usage: +```bash +# Extract context for review +python context_filter.py --task review --file auth.py --focus security + +# Extract context for planning +python context_filter.py --task plan --description "Build REST API" + +# Extract context for debugging +python context_filter.py --task debug --file app.py --error "TypeError: ..." + +# Output as JSON +python context_filter.py --task review --file auth.py --format json +``` + +### validator.py +**Suggestion validator** - Cross-checks subagent suggestions against Oracle knowledge. + +Key Principle: "Subagent might be missing important codebase context - need validation layer" + +Features: +- Validates against Oracle patterns +- Detects contradictions with known practices +- Checks rejection history +- Calculates confidence scores +- Learns from acceptance rates + +Usage: +```bash +# Validate single suggestion +python validator.py --suggestion "Use MD5 for passwords" --category security + +# Validate from file +python validator.py --suggestions-file suggestions.json + +# Record rejection +python validator.py --record-rejection "Add rate limiting" --rejection-reason "We handle at nginx level" --category performance + +# Update stats +python validator.py --update-stats accept --category security +python validator.py --update-stats reject --category style + +# Check rejection history +python validator.py --check-rejection "Use rate limiting" +``` + +### learning.py +**Learning system** - Adjusts thresholds based on user feedback. + +Features: +- Tracks acceptance/rejection rates +- Adjusts thresholds to maintain target acceptance rate +- Learns anti-patterns from rejections +- Updates auto-review rules dynamically + +Usage: +```bash +# Apply adjustments +python learning.py --adjust + +# Show recommendations (dry run) +python learning.py --recommend + +# View statistics +python learning.py --stats + +# Configure +python learning.py --set-target 0.75 +python learning.py --set-speed 0.1 +``` + +## Architecture + +``` +guardian.py (orchestrator) + | + +-- monitor_session.py (check triggers & health) + | + +-- context_filter.py (extract minimal context) + | + +-- [Task tool with Haiku agent] (perform review/planning) + | + +-- validator.py (validate suggestions) + | + +-- [Present to user with confidence scores] + | + +-- learning.py (adjust based on feedback) +``` + +## Data Storage + +Guardian stores minimal data in `.guardian/`: + +``` +.guardian/ +β”œβ”€β”€ config.json # Configuration and thresholds +β”œβ”€β”€ session_state.json # Current session metrics (NOT full conversation) +β”œβ”€β”€ rejection_history.json # Recently rejected suggestions +└── acceptance_stats.json # Acceptance rate statistics +``` + +## Key Design Principles + +1. **Minimal Context Passing** - Never pass full conversations to subagents +2. **Suggestion Mode** - Present findings as suggestions, not commands +3. **Oracle Validation** - Cross-check all suggestions against known patterns +4. **Learning from Feedback** - Adjust sensitivity based on user acceptance +5. **Haiku Only** - All subagents use haiku model (fast & cheap) +6. **User Authority** - User has final say on all suggestions +7. **Read-Only Subagents** - Subagents can ONLY read and analyze, NEVER modify files + +## Integration with Oracle + +Guardian automatically: +- Loads relevant Oracle patterns before review +- Validates suggestions against Oracle knowledge +- Records validated suggestions in Oracle +- Stores rejection reasons as anti-patterns in Oracle + +## Example Workflow + +1. **User writes 60 lines of auth code** +2. **monitor_session.py** detects threshold crossed +3. **guardian.py** extracts minimal context via **context_filter.py** +4. **Guardian spawns Haiku agent** with only: auth.py + security patterns +5. **Haiku agent** returns suggestions +6. **validator.py** checks suggestions against Oracle +7. **Guardian presents** filtered suggestions with confidence scores +8. **User accepts/rejects** +9. **learning.py** adjusts thresholds based on feedback + +## Testing + +```bash +# Initialize Guardian +cd /path/to/your/project +python guardian.py --init + +# Simulate code writing +python monitor_session.py --event code-written --file test.py --lines 60 + +# Check if should trigger +python monitor_session.py --check-triggers + +# Perform review +python guardian.py review --file test.py --focus security + +# View session health +python guardian.py status + +# View learning stats +python learning.py --stats +``` + +## Future Enhancements + +- Integration with Claude Code Task tool for spawning Haiku agents +- Real-time monitoring via background process +- Web dashboard for session health visualization +- Team-wide learning (shared Guardian config via git) +- Integration with CI/CD pipelines diff --git a/skills/guardian/scripts/context_filter.py b/skills/guardian/scripts/context_filter.py new file mode 100644 index 0000000..fa1caa0 --- /dev/null +++ b/skills/guardian/scripts/context_filter.py @@ -0,0 +1,530 @@ +#!/usr/bin/env python3 +""" +Guardian Context Filter + +Extracts MINIMAL context for subagent tasks - following the key principle: +"Caller should only pass exactly what is needed for the task so it can be laser focused." + +This script NEVER passes full conversation history. It extracts only: +- Specific files being reviewed +- Relevant Oracle patterns (max 5) +- Recent corrections in the same area (max 3) +- A focused task description + +Usage: + # Extract context for code review + python context_filter.py --task review --file auth.py --focus security + + # Extract context for planning + python context_filter.py --task plan --description "Build REST API with auth" + + # Extract context for debugging + python context_filter.py --task debug --file app.py --error "TypeError: cannot unpack" + +Environment Variables: + ORACLE_PATH: Path to Oracle directory [default: .oracle] +""" + +import os +import sys +import json +import argparse +from pathlib import Path +from typing import Dict, List, Optional, Any +import re + + +def find_oracle_root() -> Optional[Path]: + """Find the .oracle directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def load_oracle_knowledge(oracle_path: Path, categories: Optional[List[str]] = None) -> List[Dict[str, Any]]: + """Load Oracle knowledge from specified categories. + + Args: + oracle_path: Path to .oracle directory + categories: List of categories to load (defaults to all) + + Returns: + List of knowledge entries + """ + knowledge_dir = oracle_path / 'knowledge' + all_knowledge: List[Dict[str, Any]] = [] + + if categories is None: + categories = ['patterns', 'preferences', 'gotchas', 'solutions', 'corrections'] + + for category in categories: + file_path = knowledge_dir / f'{category}.json' + if file_path.exists(): + try: + with open(file_path, 'r', encoding='utf-8') as f: + entries = json.load(f) + for entry in entries: + if isinstance(entry, dict): + entry['_category'] = category + all_knowledge.append(entry) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + continue + + return all_knowledge + + +def extract_file_patterns(file_path: str) -> List[str]: + """Extract patterns from a file path for matching Oracle knowledge. + + Args: + file_path: Path to the file + + Returns: + List of patterns (extension, directory names, filename) + """ + patterns = [] + path = Path(file_path) + + # Add file extension + if path.suffix: + patterns.append(path.suffix[1:]) # Remove dot + + # Add filename without extension + if path.stem: + patterns.append(path.stem) + + # Add directory components + for part in path.parts[:-1]: + if part and part != '.' and part != '..': + patterns.append(part) + + return patterns + + +def find_relevant_patterns( + oracle_knowledge: List[Dict[str, Any]], + file_patterns: List[str], + focus_keywords: Optional[List[str]] = None, + max_patterns: int = 5 +) -> List[Dict[str, Any]]: + """Find relevant Oracle patterns for the context. + + Args: + oracle_knowledge: All Oracle knowledge + file_patterns: Patterns extracted from file path + focus_keywords: Optional focus keywords (e.g., "security", "performance") + max_patterns: Maximum number of patterns to return + + Returns: + List of relevant pattern entries + """ + # Filter to patterns category only + patterns = [k for k in oracle_knowledge if k.get('_category') == 'patterns'] + + scored_patterns = [] + + for pattern in patterns: + score = 0.0 + + # Priority scoring + priority = pattern.get('priority', 'medium') + if priority == 'critical': + score += 1.0 + elif priority == 'high': + score += 0.7 + elif priority == 'medium': + score += 0.4 + + # Tag matching + tags = pattern.get('tags', []) + if tags and file_patterns: + matches = sum(1 for fp in file_patterns + if any(re.search(r'\b' + re.escape(fp.lower()) + r'\b', tag.lower()) + for tag in tags)) + score += matches * 0.3 + + # Focus keyword matching + if focus_keywords: + content = f"{pattern.get('title', '')} {pattern.get('content', '')}".lower() + keyword_matches = sum(1 for keyword in focus_keywords + if re.search(r'\b' + re.escape(keyword.lower()) + r'\b', content)) + score += keyword_matches * 0.5 + + scored_patterns.append((pattern, score)) + + # Sort by score descending + scored_patterns.sort(key=lambda x: x[1], reverse=True) + + # Return top N + return [pattern for pattern, score in scored_patterns[:max_patterns]] + + +def find_relevant_gotchas( + oracle_knowledge: List[Dict[str, Any]], + file_patterns: List[str], + focus_keywords: Optional[List[str]] = None, + max_gotchas: int = 5 +) -> List[Dict[str, Any]]: + """Find relevant Oracle gotchas for the context.""" + gotchas = [k for k in oracle_knowledge if k.get('_category') == 'gotchas'] + + scored_gotchas = [] + + for gotcha in gotchas: + score = 0.0 + + # Priority scoring (gotchas are critical by nature) + priority = gotcha.get('priority', 'high') + if priority == 'critical': + score += 1.0 + elif priority == 'high': + score += 0.8 + + # Tag matching + tags = gotcha.get('tags', []) + if tags and file_patterns: + matches = sum(1 for fp in file_patterns + if any(re.search(r'\b' + re.escape(fp.lower()) + r'\b', tag.lower()) + for tag in tags)) + score += matches * 0.4 + + # Focus keyword matching + if focus_keywords: + content = f"{gotcha.get('title', '')} {gotcha.get('content', '')}".lower() + keyword_matches = sum(1 for keyword in focus_keywords + if re.search(r'\b' + re.escape(keyword.lower()) + r'\b', content)) + score += keyword_matches * 0.6 + + scored_gotchas.append((gotcha, score)) + + scored_gotchas.sort(key=lambda x: x[1], reverse=True) + + return [gotcha for gotcha, score in scored_gotchas[:max_gotchas]] + + +def find_recent_corrections( + oracle_knowledge: List[Dict[str, Any]], + file_patterns: List[str], + max_corrections: int = 3 +) -> List[Dict[str, Any]]: + """Find recent relevant corrections from Oracle. + + Args: + oracle_knowledge: All Oracle knowledge + file_patterns: Patterns from file path + max_corrections: Maximum corrections to return + + Returns: + List of recent relevant corrections + """ + corrections = [k for k in oracle_knowledge if k.get('_category') == 'corrections'] + + # Sort by creation date (most recent first) + sorted_corrections = sorted( + corrections, + key=lambda x: x.get('created', ''), + reverse=True + ) + + # Filter for relevance + relevant = [] + for correction in sorted_corrections: + tags = correction.get('tags', []) + content = f"{correction.get('title', '')} {correction.get('content', '')}".lower() + + # Check if relevant to current file patterns + is_relevant = False + + if tags and file_patterns: + if any(fp.lower() in tag.lower() for fp in file_patterns for tag in tags): + is_relevant = True + + if file_patterns: + if any(re.search(r'\b' + re.escape(fp.lower()) + r'\b', content) for fp in file_patterns): + is_relevant = True + + if is_relevant: + relevant.append(correction) + + if len(relevant) >= max_corrections: + break + + return relevant + + +def build_minimal_context( + task_type: str, + file_path: Optional[str] = None, + focus: Optional[str] = None, + description: Optional[str] = None, + error_message: Optional[str] = None, + oracle_path: Optional[Path] = None +) -> Dict[str, Any]: + """Build minimal context for subagent task. + + Args: + task_type: Type of task (review, plan, debug) + file_path: Optional file path to review + focus: Optional focus keywords (e.g., "security performance") + description: Optional task description + error_message: Optional error message for debugging + oracle_path: Optional path to Oracle directory + + Returns: + Minimal context dictionary + """ + context: Dict[str, Any] = { + 'task': task_type, + 'files': {}, + 'oracle_patterns': [], + 'oracle_gotchas': [], + 'recent_corrections': [], + 'focus': '' + } + + # Parse focus keywords + focus_keywords = focus.split() if focus else [] + + # Extract file patterns + file_patterns = extract_file_patterns(file_path) if file_path else [] + + # Load file content if provided (with size limit to avoid memory exhaustion) + MAX_FILE_SIZE = 1024 * 1024 # 1MB limit + if file_path: + try: + path = Path(file_path) + if path.exists() and path.is_file(): + file_size = path.stat().st_size + if file_size > MAX_FILE_SIZE: + context['files'][file_path] = f"[File too large: {file_size} bytes. Showing first 1MB only]\n" + with open(path, 'r', encoding='utf-8') as f: + # Read only first 1MB + context['files'][file_path] += f.read(MAX_FILE_SIZE) + else: + with open(path, 'r', encoding='utf-8') as f: + context['files'][file_path] = f.read() + except (OSError, IOError, UnicodeDecodeError) as e: + context['files'][file_path] = "[Error: Could not read file]" + + # Load Oracle knowledge if available + if oracle_path: + oracle_knowledge = load_oracle_knowledge(oracle_path) + + # Find relevant patterns + context['oracle_patterns'] = find_relevant_patterns( + oracle_knowledge, + file_patterns, + focus_keywords, + max_patterns=5 + ) + + # Find relevant gotchas + context['oracle_gotchas'] = find_relevant_gotchas( + oracle_knowledge, + file_patterns, + focus_keywords, + max_gotchas=5 + ) + + # Find recent corrections + context['recent_corrections'] = find_recent_corrections( + oracle_knowledge, + file_patterns, + max_corrections=3 + ) + + # Build focus description + if task_type == 'review': + if focus: + context['focus'] = f"Review {file_path or 'code'} for {focus} issues" + else: + context['focus'] = f"Review {file_path or 'code'} for potential issues" + + elif task_type == 'plan': + context['focus'] = f"Break down this task into subtasks: {description or 'Complex task'}" + + elif task_type == 'debug': + context['focus'] = f"Debug error in {file_path or 'code'}: {error_message or 'Unknown error'}" + if error_message: + context['error'] = error_message + + return context + + +def format_context_for_agent(context: Dict[str, Any], format_type: str = 'text') -> str: + """Format context for subagent consumption. + + Args: + context: Minimal context dictionary + format_type: Output format (text or json) + + Returns: + Formatted context string + """ + if format_type == 'json': + return json.dumps(context, indent=2) + + # Text format + lines = [] + + lines.append(f"# Task: {context['task'].capitalize()}") + lines.append("") + lines.append(f"**Focus**: {context['focus']}") + lines.append("") + + # Files + if context['files']: + lines.append("## Files to Review") + lines.append("") + for file_path, content in context['files'].items(): + lines.append(f"### {file_path}") + lines.append("") + lines.append("```") + lines.append(content[:5000]) # Limit file size + if len(content) > 5000: + lines.append("... [truncated]") + lines.append("```") + lines.append("") + + # Oracle patterns + if context['oracle_patterns']: + lines.append("## Relevant Patterns (from Oracle)") + lines.append("") + for pattern in context['oracle_patterns']: + title = pattern.get('title', 'Untitled') + content = pattern.get('content', '') + lines.append(f"- **{title}**") + if content: + lines.append(f" {content[:200]}") + lines.append("") + + # Oracle gotchas + if context['oracle_gotchas']: + lines.append("## Gotchas to Watch For (from Oracle)") + lines.append("") + for gotcha in context['oracle_gotchas']: + title = gotcha.get('title', 'Untitled') + content = gotcha.get('content', '') + priority = gotcha.get('priority', 'medium') + if priority == 'critical': + lines.append(f"- **[CRITICAL]** {title}") + else: + lines.append(f"- {title}") + if content: + lines.append(f" {content[:200]}") + lines.append("") + + # Recent corrections + if context['recent_corrections']: + lines.append("## Recent Corrections (from Oracle)") + lines.append("") + for correction in context['recent_corrections']: + content = correction.get('content', '') + title = correction.get('title', 'Correction') + + # Try to extract the "Right:" part + if 'Right:' in content: + try: + right_part = content.split('Right:', 1)[1].split('\n', 1)[0].strip() + if right_part: + lines.append(f"- {right_part}") + else: + lines.append(f"- {title}") + except (IndexError, ValueError, AttributeError): + lines.append(f"- {title}") + else: + lines.append(f"- {title}") + lines.append("") + + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser( + description='Extract minimal context for Guardian subagent tasks', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--task', + required=True, + choices=['review', 'plan', 'debug'], + help='Type of task' + ) + + parser.add_argument( + '--file', + help='File path to review/debug' + ) + + parser.add_argument( + '--focus', + help='Focus keywords (e.g., "security performance")' + ) + + parser.add_argument( + '--description', + help='Task description (for planning tasks)' + ) + + parser.add_argument( + '--error', + help='Error message (for debugging tasks)' + ) + + parser.add_argument( + '--format', + choices=['text', 'json'], + default='text', + help='Output format' + ) + + parser.add_argument( + '--no-oracle', + action='store_true', + help='Skip Oracle knowledge loading' + ) + + args = parser.parse_args() + + # Validate arguments based on task type + if args.task == 'review' and not args.file: + print("Error: --file required for review tasks", file=sys.stderr) + sys.exit(1) + + if args.task == 'plan' and not args.description: + print("Error: --description required for planning tasks", file=sys.stderr) + sys.exit(1) + + if args.task == 'debug' and not args.file: + print("Error: --file required for debug tasks", file=sys.stderr) + sys.exit(1) + + # Find Oracle + oracle_path = None + if not args.no_oracle: + oracle_path = find_oracle_root() + + # Build minimal context + context = build_minimal_context( + task_type=args.task, + file_path=args.file, + focus=args.focus, + description=args.description, + error_message=args.error, + oracle_path=oracle_path + ) + + # Format and output + output = format_context_for_agent(context, args.format) + print(output) + + +if __name__ == '__main__': + main() diff --git a/skills/guardian/scripts/guardian.py b/skills/guardian/scripts/guardian.py new file mode 100644 index 0000000..a3fa2c8 --- /dev/null +++ b/skills/guardian/scripts/guardian.py @@ -0,0 +1,524 @@ +#!/usr/bin/env python3 +""" +Guardian - Main Orchestrator + +Coordinates Guardian components to provide automatic quality gates and session health monitoring. + +This is the main entry point for Guardian operations. It: +1. Checks session health metrics +2. Determines if intervention is needed +3. Extracts minimal context +4. Spawns Haiku subagent for focused review/planning +5. Validates suggestions against Oracle +6. Presents results to user with confidence scores +7. Learns from user feedback + +Usage: + # Manual code review + python guardian.py review --file auth.py --focus security + + # Check if Guardian should trigger + python guardian.py check + + # Plan a complex task + python guardian.py plan --task "Build REST API with auth and rate limiting" + + # Debug an error + python guardian.py debug --file app.py --error "TypeError: cannot unpack" + + # Get session health status + python guardian.py status + +Environment Variables: + GUARDIAN_MODEL: Model to use for subagents [default: haiku] +""" + +import os +import sys +import json +import argparse +from pathlib import Path +from typing import Dict, List, Optional, Any +import subprocess + + +def find_scripts_dir() -> Path: + """Find the Guardian Scripts directory.""" + return Path(__file__).parent + + +def run_script(script_name: str, args: List[str]) -> Dict[str, Any]: + """Run a Guardian script and return JSON output. + + Args: + script_name: Name of the script (without .py extension) + args: List of command-line arguments + + Returns: + Parsed JSON output from the script + """ + scripts_dir = find_scripts_dir() + script_path = scripts_dir / f"{script_name}.py" + + if not script_path.exists(): + raise FileNotFoundError(f"Script not found: {script_path}") + + try: + result = subprocess.run( + ['python', str(script_path)] + args, + capture_output=True, + text=True, + timeout=30 + ) + + if result.returncode != 0: + # Try to parse error as JSON + try: + return json.loads(result.stdout) + except json.JSONDecodeError: + return {'error': result.stderr or result.stdout} + + # Parse output as JSON + try: + return json.loads(result.stdout) + except json.JSONDecodeError: + return {'output': result.stdout} + + except subprocess.TimeoutExpired: + return {'error': 'Script timeout'} + except Exception as e: + return {'error': str(e)} + + +def check_triggers() -> Dict[str, Any]: + """Check if any Guardian triggers have fired.""" + return run_script('monitor_session', ['--check-triggers']) + + +def get_session_health() -> Dict[str, Any]: + """Get current session health metrics.""" + return run_script('monitor_session', ['--check-health']) + + +def extract_context( + task_type: str, + file_path: Optional[str] = None, + focus: Optional[str] = None, + description: Optional[str] = None, + error_message: Optional[str] = None +) -> str: + """Extract minimal context for subagent task.""" + args = ['--task', task_type, '--format', 'text'] + + if file_path: + args.extend(['--file', file_path]) + if focus: + args.extend(['--focus', focus]) + if description: + args.extend(['--description', description]) + if error_message: + args.extend(['--error', error_message]) + + result = run_script('context_filter', args) + + if 'output' in result: + return result['output'] + elif 'error' in result: + return f"Error extracting context: {result['error']}" + else: + return str(result) + + +def validate_suggestions(suggestions: List[Dict[str, str]]) -> List[Dict[str, Any]]: + """Validate suggestions against Oracle knowledge. + + Args: + suggestions: List of suggestion dictionaries with 'text' and 'category' keys + + Returns: + List of validated suggestions with confidence scores + """ + # Create temporary file with suggestions + import tempfile + suggestions_file = None + + try: + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: + json.dump(suggestions, f) + suggestions_file = f.name + + result = run_script('validator', ['--suggestions-file', suggestions_file]) + return result if isinstance(result, list) else [] + finally: + # Clean up temp file + if suggestions_file: + try: + os.unlink(suggestions_file) + except OSError: + pass + + +def format_suggestions_for_user(validated_suggestions: List[Dict[str, Any]]) -> str: + """Format validated suggestions for user presentation. + + Args: + validated_suggestions: List of validated suggestions + + Returns: + Formatted string for user + """ + lines = [] + + # Filter to presentable suggestions + presentable = [s for s in validated_suggestions if s.get('should_present', True)] + + if not presentable: + return "Guardian: No suggestions to present (all filtered by validation)" + + lines.append(f"Guardian Review Found {len(presentable)} Suggestions:") + lines.append("") + + for i, suggestion in enumerate(presentable, 1): + confidence = suggestion.get('confidence', 0.0) + text = suggestion.get('suggestion', '') + category = suggestion.get('category', 'general') + warnings = suggestion.get('warnings', []) + notes = suggestion.get('notes', []) + + # Format confidence indicator + if confidence >= 0.7: + conf_indicator = f"[{confidence:.2f}]" + elif confidence >= 0.5: + conf_indicator = f"?[{confidence:.2f}]" + else: + conf_indicator = f"![{confidence:.2f}]" + + lines.append(f"{i}. {conf_indicator} {text}") + lines.append(f" Category: {category}") + + # Add warnings + for warning in warnings: + severity = warning.get('severity', 'low') + message = warning.get('message', '') + if severity == 'high': + lines.append(f" WARNING: {message}") + else: + lines.append(f" Note: {message}") + + # Add notes + for note in notes: + lines.append(f" {note}") + + lines.append("") + + # Add command options + lines.append("Options:") + lines.append(" a - Accept all high-confidence suggestions (>=0.7)") + lines.append(" 1,3,5 - Accept specific suggestions by number") + lines.append(" r - Reject all with reason") + lines.append(" i - Reject and add to anti-patterns") + lines.append(" d - Discuss specific suggestion") + lines.append(" q - Dismiss review") + + return "\n".join(lines) + + +def perform_review( + file_path: str, + focus: Optional[str] = None +) -> None: + """Perform code review using Guardian. + + Args: + file_path: Path to file to review + focus: Optional focus keywords (e.g., "security performance") + """ + print(f"Guardian: Reviewing {file_path}...") + print() + + # Extract minimal context + context_text = extract_context('review', file_path=file_path, focus=focus) + + # Build read-only prompt for Haiku agent + agent_prompt = f"""You are a READ-ONLY code reviewer for Guardian. You can ONLY analyze and suggest. + +CRITICAL CONSTRAINTS: +- DO NOT use Write, Edit, NotebookEdit, or Bash tools +- DO NOT modify any files +- DO NOT execute any code +- ONLY read the provided context and return suggestions + +Your task: Review the code for potential issues and return suggestions. + +{context_text} + +Return your findings as a JSON array of suggestions with this format: +[ + {{ + "text": "Clear description of the issue and recommended fix", + "category": "security|performance|style|bugs|maintainability", + "file": "file path (if applicable)", + "line": line_number (if applicable, otherwise null) + }} +] + +If you find no issues, return an empty array: [] + +Remember: You are READ-ONLY. Only analyze and suggest, never modify.""" + + print("Spawning Haiku review agent with minimal context...") + print() + + # Note: This would be implemented when Guardian is used as a skill + # For standalone script usage, we output instructions instead + print("=" * 60) + print("READY TO SPAWN HAIKU AGENT") + print("=" * 60) + print("To complete this review, use the Task tool with:") + print(f" subagent_type: general-purpose") + print(f" model: haiku") + print(f" prompt: ") + print() + print("Agent Prompt:") + print("-" * 60) + print(agent_prompt) + print("=" * 60) + print() + print("Once you have the agent's response, the suggestions will be:") + print(" 1. Validated against Oracle knowledge") + print(" 2. Presented with confidence scores") + print(" 3. Offered for user acceptance/rejection") + print() + print("Example suggestion handling:") + mock_suggestions = [ + { + 'text': 'Consider using bcrypt for password hashing instead of MD5', + 'category': 'security', + 'file': file_path, + 'line': None + } + ] + validated = validate_suggestions(mock_suggestions) + presentation = format_suggestions_for_user(validated) + print(presentation) + + +def perform_planning(task_description: str) -> None: + """Break down a complex task using Guardian planning. + + Args: + task_description: Description of the task to break down + """ + print("Guardian: Breaking down complex task...") + print() + + # Extract minimal context + context_text = extract_context('plan', description=task_description) + + # Build read-only prompt for Haiku planner + agent_prompt = f"""You are a READ-ONLY task planner for Guardian. You can ONLY analyze and plan. + +CRITICAL CONSTRAINTS: +- DO NOT use Write, Edit, NotebookEdit, or Bash tools +- DO NOT modify any files +- DO NOT execute any code +- ONLY analyze the task and return a breakdown plan + +Your task: Break down this complex task into manageable subtasks. + +{context_text} + +Return your plan as a JSON array of subtasks with this format: +[ + {{ + "task": "Clear description of the subtask", + "estimated_lines": approximate lines of code needed, + "dependencies": ["list", "of", "prerequisite", "subtask", "numbers"], + "files_affected": ["list of files that will be created/modified"], + "priority": "high|medium|low" + }} +] + +Consider: +- Dependencies between tasks +- Logical ordering +- Potential complexity and risks +- Integration points + +Remember: You are READ-ONLY. Only analyze and plan, never modify.""" + + print("=" * 60) + print("READY TO SPAWN HAIKU PLANNER") + print("=" * 60) + print("To complete this planning task, use the Task tool with:") + print(f" subagent_type: Plan") + print(f" model: haiku") + print(f" prompt: ") + print() + print("Agent Prompt:") + print("-" * 60) + print(agent_prompt) + print("=" * 60) + + +def perform_debug(file_path: str, error_message: str) -> None: + """Debug an error using Guardian. + + Args: + file_path: Path to file with error + error_message: Error message to debug + """ + print(f"Guardian: Debugging error in {file_path}...") + print() + + # Extract minimal context + context_text = extract_context('debug', file_path=file_path, error_message=error_message) + + # Build read-only prompt for Haiku debugger + agent_prompt = f"""You are a READ-ONLY error debugger for Guardian. You can ONLY analyze and suggest fixes. + +CRITICAL CONSTRAINTS: +- DO NOT use Write, Edit, NotebookEdit, or Bash tools +- DO NOT modify any files +- DO NOT execute any code +- ONLY analyze the error and return debugging suggestions + +Your task: Analyze this error and suggest potential fixes. + +{context_text} + +Return your analysis as a JSON object with this format: +{{ + "root_cause": "Most likely cause of the error", + "affected_code": {{ + "file": "file path", + "line": line_number (if known) + }}, + "suggestions": [ + {{ + "text": "Clear description of the fix", + "category": "bug", + "confidence": 0.0 to 1.0 + }} + ], + "similar_patterns": ["Any similar error patterns from Oracle knowledge"] +}} + +Consider: +- What the error message indicates +- Common causes of this error type +- Relevant Oracle patterns or gotchas +- Edge cases that might trigger this + +Remember: You are READ-ONLY. Only analyze and suggest, never modify.""" + + print("=" * 60) + print("READY TO SPAWN HAIKU DEBUGGER") + print("=" * 60) + print("To complete this debug task, use the Task tool with:") + print(f" subagent_type: general-purpose") + print(f" model: haiku") + print(f" prompt: ") + print() + print("Agent Prompt:") + print("-" * 60) + print(agent_prompt) + print("=" * 60) + + +def check_if_should_trigger() -> bool: + """Check if Guardian should automatically trigger. + + Returns: + True if Guardian should trigger, False otherwise + """ + triggers = check_triggers() + + if isinstance(triggers, list) and len(triggers) > 0: + print("Guardian: Detected triggers:") + print(json.dumps(triggers, indent=2)) + return True + + return False + + +def show_status() -> None: + """Show current session health status.""" + health = get_session_health() + + print("Guardian Session Health Status:") + print("=" * 60) + print(json.dumps(health, indent=2)) + print("=" * 60) + + # Check triggers + triggers = check_triggers() + if isinstance(triggers, list) and len(triggers) > 0: + print() + print("Active Triggers:") + for trigger in triggers: + trigger_type = trigger.get('trigger', 'unknown') + priority = trigger.get('priority', 'medium') + print(f" - [{priority.upper()}] {trigger_type}") + for key, value in trigger.items(): + if key not in ['trigger', 'priority']: + print(f" {key}: {value}") + + +def main(): + parser = argparse.ArgumentParser( + description='Guardian - Quality gate and session health monitor', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + subparsers = parser.add_subparsers(dest='command', help='Guardian commands') + + # Review command + review_parser = subparsers.add_parser('review', help='Review code for issues') + review_parser.add_argument('--file', required=True, help='File to review') + review_parser.add_argument('--focus', help='Focus keywords (e.g., "security performance")') + + # Plan command + plan_parser = subparsers.add_parser('plan', help='Break down complex task') + plan_parser.add_argument('--task', required=True, help='Task description') + + # Debug command + debug_parser = subparsers.add_parser('debug', help='Debug an error') + debug_parser.add_argument('--file', required=True, help='File with error') + debug_parser.add_argument('--error', required=True, help='Error message') + + # Check command + subparsers.add_parser('check', help='Check if Guardian should trigger') + + # Status command + subparsers.add_parser('status', help='Show session health status') + + args = parser.parse_args() + + if not args.command: + parser.print_help() + sys.exit(1) + + # Execute command + if args.command == 'review': + perform_review(args.file, args.focus) + + elif args.command == 'plan': + perform_planning(args.task) + + elif args.command == 'debug': + perform_debug(args.file, args.error) + + elif args.command == 'check': + if check_if_should_trigger(): + sys.exit(0) # Should trigger + else: + print("Guardian: No triggers detected") + sys.exit(1) # Should not trigger + + elif args.command == 'status': + show_status() + + +if __name__ == '__main__': + main() diff --git a/skills/guardian/scripts/learning.py b/skills/guardian/scripts/learning.py new file mode 100644 index 0000000..b2bccc9 --- /dev/null +++ b/skills/guardian/scripts/learning.py @@ -0,0 +1,512 @@ +#!/usr/bin/env python3 +""" +Guardian Learning System + +Adjusts Guardian sensitivity and thresholds based on user feedback. + +Key Principle: "Learn from feedback to adjust sensitivity" + +This script: +1. Tracks acceptance/rejection rates per category +2. Adjusts thresholds dynamically to maintain target acceptance rate +3. Learns which file types need more/less review +4. Stores anti-patterns based on rejection reasons +5. Adapts to user's working style over time + +Usage: + # Adjust thresholds based on recent feedback + python learning.py --adjust + + # Get current threshold recommendations + python learning.py --recommend + + # Manually set acceptance rate target + python learning.py --set-target 0.75 + + # View learning statistics + python learning.py --stats + +Environment Variables: + GUARDIAN_CONFIG_PATH: Path to Guardian config file [default: .guardian/config.json] +""" + +import os +import sys +import json +import argparse +from pathlib import Path +from typing import Dict, List, Optional, Any +from datetime import datetime, timedelta + + +def find_guardian_root() -> Optional[Path]: + """Find the .guardian directory.""" + current = Path.cwd() + + while current != current.parent: + guardian_path = current / '.guardian' + if guardian_path.exists(): + return guardian_path + current = current.parent + + return None + + +def load_config(guardian_path: Path) -> Dict[str, Any]: + """Load Guardian configuration.""" + config_path = guardian_path / 'config.json' + + if not config_path.exists(): + return {} + + try: + with open(config_path, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + return {} + + +def save_config(guardian_path: Path, config: Dict[str, Any]) -> None: + """Save Guardian configuration.""" + config_path = guardian_path / 'config.json' + + try: + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(config, f, indent=2) + except (OSError, IOError) as e: + print(f"Error: Failed to save config: {e}", file=sys.stderr) + sys.exit(1) + + +def load_acceptance_stats(guardian_path: Path) -> Dict[str, Any]: + """Load acceptance rate statistics.""" + stats_file = guardian_path / 'acceptance_stats.json' + + if not stats_file.exists(): + return { + 'by_category': {}, + 'by_type': {}, + 'overall': { + 'accepted': 0, + 'rejected': 0, + 'rate': 0.0 + } + } + + try: + with open(stats_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + return {'by_category': {}, 'by_type': {}, 'overall': {'accepted': 0, 'rejected': 0, 'rate': 0.0}} + + +def load_rejection_history(guardian_path: Path, days: int = 30) -> List[Dict[str, Any]]: + """Load recent rejection history. + + Args: + guardian_path: Path to Guardian directory + days: Number of days to look back + + Returns: + List of recent rejections + """ + history_file = guardian_path / 'rejection_history.json' + + if not history_file.exists(): + return [] + + try: + with open(history_file, 'r', encoding='utf-8') as f: + all_history = json.load(f) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + return [] + + # Filter to recent rejections + cutoff = datetime.now() - timedelta(days=days) + recent = [] + + for rejection in all_history: + try: + ts = datetime.fromisoformat(rejection.get('timestamp', '')) + # Handle timezone-aware timestamps + if ts.tzinfo and not cutoff.tzinfo: + cutoff = cutoff.replace(tzinfo=ts.tzinfo) + if ts >= cutoff: + recent.append(rejection) + except (ValueError, TypeError): + continue + + return recent + + +def calculate_threshold_adjustments( + config: Dict[str, Any], + acceptance_stats: Dict[str, Any], + target_rate: float = 0.7, + adjustment_speed: float = 0.1 +) -> Dict[str, int]: + """Calculate new threshold values based on acceptance rates. + + Args: + config: Current configuration + acceptance_stats: Acceptance statistics + target_rate: Target acceptance rate (0.0 to 1.0) + adjustment_speed: How fast to adjust (0.0 to 1.0) + + Returns: + Dictionary of adjusted threshold values + """ + current_sensitivity = config.get('sensitivity', {}) + overall_rate = acceptance_stats.get('overall', {}).get('rate', 0.5) + + adjustments = {} + + # Lines threshold adjustment + current_lines = current_sensitivity.get('lines_threshold', 50) + + if overall_rate < target_rate - 0.1: + # Too many false positives - increase threshold (trigger less often) + adjustment = int(current_lines * adjustment_speed) + adjustments['lines_threshold'] = current_lines + max(5, adjustment) + elif overall_rate > target_rate + 0.1: + # Too many missed issues - decrease threshold (trigger more often) + adjustment = int(current_lines * adjustment_speed) + adjustments['lines_threshold'] = max(20, current_lines - max(5, adjustment)) + else: + # Well-calibrated + adjustments['lines_threshold'] = current_lines + + # Error repeat threshold adjustment + error_stats = acceptance_stats.get('by_category', {}).get('error_analysis', {}) + error_rate = error_stats.get('rate', 0.5) + current_error_threshold = current_sensitivity.get('error_repeat_threshold', 3) + + if error_rate < target_rate - 0.1: + adjustments['error_repeat_threshold'] = min(10, current_error_threshold + 1) + elif error_rate > target_rate + 0.1: + adjustments['error_repeat_threshold'] = max(2, current_error_threshold - 1) + else: + adjustments['error_repeat_threshold'] = current_error_threshold + + # File churn threshold adjustment + churn_stats = acceptance_stats.get('by_category', {}).get('file_churn', {}) + churn_rate = churn_stats.get('rate', 0.5) + current_churn_threshold = current_sensitivity.get('file_churn_threshold', 5) + + if churn_rate < target_rate - 0.1: + adjustments['file_churn_threshold'] = min(15, current_churn_threshold + 1) + elif churn_rate > target_rate + 0.1: + adjustments['file_churn_threshold'] = max(3, current_churn_threshold - 1) + else: + adjustments['file_churn_threshold'] = current_churn_threshold + + # Correction threshold adjustment + correction_stats = acceptance_stats.get('by_category', {}).get('corrections', {}) + correction_rate = correction_stats.get('rate', 0.5) + current_correction_threshold = current_sensitivity.get('correction_threshold', 3) + + if correction_rate < target_rate - 0.1: + adjustments['correction_threshold'] = min(10, current_correction_threshold + 1) + elif correction_rate > target_rate + 0.1: + adjustments['correction_threshold'] = max(2, current_correction_threshold - 1) + else: + adjustments['correction_threshold'] = current_correction_threshold + + return adjustments + + +def learn_from_rejections( + rejection_history: List[Dict[str, Any]] +) -> Dict[str, Any]: + """Analyze rejection patterns to learn anti-patterns. + + Args: + rejection_history: List of rejections + + Returns: + Dictionary of learned anti-patterns and insights + """ + insights = { + 'common_rejection_reasons': {}, + 'frequently_rejected_categories': {}, + 'anti_patterns': [] + } + + # Count rejection reasons + for rejection in rejection_history: + reason = rejection.get('reason', 'Unknown') + category = rejection.get('category', 'general') + + # Count by reason + if reason not in insights['common_rejection_reasons']: + insights['common_rejection_reasons'][reason] = 0 + insights['common_rejection_reasons'][reason] += 1 + + # Count by category + if category not in insights['frequently_rejected_categories']: + insights['frequently_rejected_categories'][category] = 0 + insights['frequently_rejected_categories'][category] += 1 + + # Identify anti-patterns (highly rejected categories) + for category, count in insights['frequently_rejected_categories'].items(): + if count >= 5: # Rejected 5+ times + rejection_rate = count / len(rejection_history) if len(rejection_history) > 0 else 0 + if rejection_rate > 0.8: # 80%+ rejection rate + insights['anti_patterns'].append({ + 'category': category, + 'rejection_count': count, + 'rejection_rate': rejection_rate, + 'recommendation': f"Stop suggesting {category} - rejection rate {rejection_rate:.0%}" + }) + + return insights + + +def update_auto_review_rules( + config: Dict[str, Any], + acceptance_stats: Dict[str, Any], + insights: Dict[str, Any] +) -> Dict[str, Any]: + """Update auto-review rules based on learning. + + Args: + config: Current configuration + acceptance_stats: Acceptance statistics + insights: Learned insights from rejections + + Returns: + Updated auto_review configuration + """ + auto_review = config.get('auto_review', { + 'enabled': True, + 'always_review': ['auth', 'security', 'crypto', 'payment'], + 'never_review': ['test', 'mock', 'fixture'] + }) + + # Add anti-patterns to never_review + for anti_pattern in insights.get('anti_patterns', []): + category = anti_pattern['category'] + if category not in auto_review['never_review']: + auto_review['never_review'].append(category) + + # Check which categories have high acceptance rates + by_category = acceptance_stats.get('by_category', {}) + + for category, stats in by_category.items(): + rate = stats.get('rate', 0.0) + total = stats.get('accepted', 0) + stats.get('rejected', 0) + + # If category has >90% acceptance and >10 samples, add to always_review + if rate > 0.9 and total > 10: + if category not in auto_review['always_review']: + auto_review['always_review'].append(category) + + # If category has <20% acceptance and >10 samples, add to never_review + if rate < 0.2 and total > 10: + if category not in auto_review['never_review']: + auto_review['never_review'].append(category) + + return auto_review + + +def apply_adjustments( + guardian_path: Path, + dry_run: bool = False +) -> Dict[str, Any]: + """Apply learned adjustments to configuration. + + Args: + guardian_path: Path to Guardian directory + dry_run: If True, don't save changes (just return recommendations) + + Returns: + Dictionary with adjustment details + """ + config = load_config(guardian_path) + acceptance_stats = load_acceptance_stats(guardian_path) + rejection_history = load_rejection_history(guardian_path) + + # Get learning parameters + learning_config = config.get('learning', {}) + target_rate = learning_config.get('acceptance_rate_target', 0.7) + adjustment_speed = learning_config.get('adjustment_speed', 0.1) + + # Calculate threshold adjustments + threshold_adjustments = calculate_threshold_adjustments( + config, + acceptance_stats, + target_rate, + adjustment_speed + ) + + # Learn from rejections + insights = learn_from_rejections(rejection_history) + + # Update auto-review rules + updated_auto_review = update_auto_review_rules(config, acceptance_stats, insights) + + # Prepare result + result = { + 'current_acceptance_rate': acceptance_stats.get('overall', {}).get('rate', 0.0), + 'target_acceptance_rate': target_rate, + 'threshold_adjustments': threshold_adjustments, + 'auto_review_updates': updated_auto_review, + 'insights': insights, + 'applied': not dry_run + } + + # Apply changes if not dry run + if not dry_run: + # Update sensitivity thresholds + if 'sensitivity' not in config: + config['sensitivity'] = {} + config['sensitivity'].update(threshold_adjustments) + + # Update auto_review + config['auto_review'] = updated_auto_review + + # Save updated config + save_config(guardian_path, config) + + return result + + +def get_statistics(guardian_path: Path) -> Dict[str, Any]: + """Get learning statistics. + + Args: + guardian_path: Path to Guardian directory + + Returns: + Statistics dictionary + """ + config = load_config(guardian_path) + acceptance_stats = load_acceptance_stats(guardian_path) + rejection_history = load_rejection_history(guardian_path, days=30) + + overall = acceptance_stats.get('overall', {}) + by_category = acceptance_stats.get('by_category', {}) + + stats = { + 'overall': { + 'accepted': overall.get('accepted', 0), + 'rejected': overall.get('rejected', 0), + 'acceptance_rate': overall.get('rate', 0.0) + }, + 'by_category': {}, + 'current_thresholds': config.get('sensitivity', {}), + 'target_acceptance_rate': config.get('learning', {}).get('acceptance_rate_target', 0.7), + 'recent_rejections_30d': len(rejection_history), + 'auto_review_rules': config.get('auto_review', {}) + } + + # Add category breakdown + for category, cat_stats in by_category.items(): + stats['by_category'][category] = { + 'accepted': cat_stats.get('accepted', 0), + 'rejected': cat_stats.get('rejected', 0), + 'rate': cat_stats.get('rate', 0.0) + } + + return stats + + +def main(): + parser = argparse.ArgumentParser( + description='Guardian learning system - adjust thresholds based on feedback', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--adjust', + action='store_true', + help='Apply threshold adjustments based on recent feedback' + ) + + parser.add_argument( + '--recommend', + action='store_true', + help='Show recommended adjustments without applying (dry run)' + ) + + parser.add_argument( + '--stats', + action='store_true', + help='Show learning statistics' + ) + + parser.add_argument( + '--set-target', + type=float, + help='Set target acceptance rate (0.0 to 1.0)' + ) + + parser.add_argument( + '--set-speed', + type=float, + help='Set adjustment speed (0.0 to 1.0)' + ) + + args = parser.parse_args() + + # Find Guardian + guardian_path = find_guardian_root() + + if not guardian_path: + print("Error: Guardian not initialized (.guardian directory not found)", file=sys.stderr) + sys.exit(1) + + # Handle set target + if args.set_target is not None: + if not 0.0 <= args.set_target <= 1.0: + print("Error: Target acceptance rate must be between 0.0 and 1.0", file=sys.stderr) + sys.exit(1) + + config = load_config(guardian_path) + if 'learning' not in config: + config['learning'] = {} + config['learning']['acceptance_rate_target'] = args.set_target + save_config(guardian_path, config) + + print(json.dumps({'target_set': args.set_target})) + sys.exit(0) + + # Handle set speed + if args.set_speed is not None: + if not 0.0 <= args.set_speed <= 1.0: + print("Error: Adjustment speed must be between 0.0 and 1.0", file=sys.stderr) + sys.exit(1) + + config = load_config(guardian_path) + if 'learning' not in config: + config['learning'] = {} + config['learning']['adjustment_speed'] = args.set_speed + save_config(guardian_path, config) + + print(json.dumps({'speed_set': args.set_speed})) + sys.exit(0) + + # Handle stats + if args.stats: + stats = get_statistics(guardian_path) + print(json.dumps(stats, indent=2)) + sys.exit(0) + + # Handle recommend (dry run) + if args.recommend: + result = apply_adjustments(guardian_path, dry_run=True) + print(json.dumps(result, indent=2)) + sys.exit(0) + + # Handle adjust + if args.adjust: + result = apply_adjustments(guardian_path, dry_run=False) + print(json.dumps(result, indent=2)) + sys.exit(0) + + parser.print_help() + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/guardian/scripts/monitor_session.py b/skills/guardian/scripts/monitor_session.py new file mode 100644 index 0000000..e6c2d97 --- /dev/null +++ b/skills/guardian/scripts/monitor_session.py @@ -0,0 +1,610 @@ +#!/usr/bin/env python3 +""" +Guardian Session Monitor + +Tracks session health metrics and detects when intervention would be helpful. +This script monitors in the background and triggers Guardian when thresholds are crossed. + +Key Principle: MINIMAL STORAGE - only tracks metrics, NOT full conversation content. + +Usage: + # Track a code write event + python monitor_session.py --event code-written --file auth.py --lines 60 + + # Track an error + python monitor_session.py --event error --message "TypeError: cannot unpack non-iterable" + + # Track a user correction + python monitor_session.py --event correction --message "that's wrong, use bcrypt instead" + + # Check session health + python monitor_session.py --check-health + + # Reset session metrics + python monitor_session.py --reset + +Environment Variables: + GUARDIAN_CONFIG_PATH: Path to Guardian config file [default: .guardian/config.json] +""" + +import os +import sys +import json +import argparse +from datetime import datetime, timedelta +from pathlib import Path +from typing import Dict, List, Optional, Any +from collections import defaultdict + + +def parse_timestamp_with_tz(ts_str: str, reference_time: datetime) -> Optional[datetime]: + """Parse ISO timestamp and make it comparable with reference_time. + + Args: + ts_str: ISO format timestamp string + reference_time: Reference datetime to match timezone with + + Returns: + Parsed datetime that's comparable with reference_time, or None if parsing fails + """ + try: + ts = datetime.fromisoformat(ts_str) + # If timestamp has timezone but reference doesn't, make timestamp naive + if ts.tzinfo and not reference_time.tzinfo: + ts = ts.replace(tzinfo=None) + # If reference has timezone but timestamp doesn't, make timestamp aware + elif not ts.tzinfo and reference_time.tzinfo: + ts = ts.replace(tzinfo=reference_time.tzinfo) + return ts + except (ValueError, TypeError): + return None + + +def find_guardian_root() -> Optional[Path]: + """Find the .guardian directory.""" + current = Path.cwd() + + while current != current.parent: + guardian_path = current / '.guardian' + if guardian_path.exists(): + return guardian_path + current = current.parent + + return None + + +def init_guardian_if_needed() -> Path: + """Initialize .guardian directory if it doesn't exist.""" + guardian_path = Path.cwd() / '.guardian' + + if not guardian_path.exists(): + guardian_path.mkdir(parents=True, exist_ok=True) + + # Create default config + default_config = { + "enabled": True, + "sensitivity": { + "lines_threshold": 50, + "error_repeat_threshold": 3, + "file_churn_threshold": 5, + "correction_threshold": 3, + "context_warning_percent": 0.7 + }, + "trigger_phrases": { + "review_needed": ["can you review", "does this look right"], + "struggling": ["still not working", "same error"], + "complexity": ["this is complex", "not sure how to"] + }, + "auto_review": { + "enabled": True, + "always_review": ["auth", "security", "crypto", "payment"], + "never_review": ["test", "mock", "fixture"] + }, + "learning": { + "acceptance_rate_target": 0.7, + "adjustment_speed": 0.1, + "memory_window_days": 30 + }, + "model": "haiku" + } + + config_path = guardian_path / 'config.json' + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(default_config, f, indent=2) + + # Create session state file + state_path = guardian_path / 'session_state.json' + with open(state_path, 'w', encoding='utf-8') as f: + json.dump({ + "session_start": datetime.now().isoformat(), + "metrics": {}, + "history": [] + }, f, indent=2) + + return guardian_path + + +def load_config(guardian_path: Path) -> Dict[str, Any]: + """Load Guardian configuration.""" + config_path = guardian_path / 'config.json' + + if not config_path.exists(): + # Return default config + return { + "enabled": True, + "sensitivity": { + "lines_threshold": 50, + "error_repeat_threshold": 3, + "file_churn_threshold": 5, + "correction_threshold": 3, + "context_warning_percent": 0.7 + } + } + + try: + with open(config_path, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + return {"enabled": False} + + +def load_session_state(guardian_path: Path) -> Dict[str, Any]: + """Load current session state.""" + state_path = guardian_path / 'session_state.json' + + if not state_path.exists(): + return { + "session_start": datetime.now().isoformat(), + "metrics": {}, + "history": [] + } + + try: + with open(state_path, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + return { + "session_start": datetime.now().isoformat(), + "metrics": {}, + "history": [] + } + + +def save_session_state(guardian_path: Path, state: Dict[str, Any]) -> None: + """Save session state.""" + state_path = guardian_path / 'session_state.json' + + try: + with open(state_path, 'w', encoding='utf-8') as f: + json.dump(state, f, indent=2) + except (OSError, IOError) as e: + print(f"Warning: Failed to save session state: {e}", file=sys.stderr) + + +def track_code_written(state: Dict[str, Any], file_path: str, lines: int) -> None: + """Track code writing event.""" + MAX_EVENTS = 100 # Limit to prevent unbounded memory growth + + if 'code_written' not in state['metrics']: + state['metrics']['code_written'] = {} + + if file_path not in state['metrics']['code_written']: + state['metrics']['code_written'][file_path] = { + 'total_lines': 0, + 'events': [] + } + + state['metrics']['code_written'][file_path]['total_lines'] += lines + state['metrics']['code_written'][file_path]['events'].append({ + 'timestamp': datetime.now().isoformat(), + 'lines': lines + }) + + # Keep only recent events + if len(state['metrics']['code_written'][file_path]['events']) > MAX_EVENTS: + state['metrics']['code_written'][file_path]['events'] = \ + state['metrics']['code_written'][file_path]['events'][-MAX_EVENTS:] + + +def track_error(state: Dict[str, Any], error_message: str) -> None: + """Track error occurrence.""" + MAX_OCCURRENCES = 50 # Limit to prevent unbounded memory growth + + if 'errors' not in state['metrics']: + state['metrics']['errors'] = {} + + # Normalize error message (first line only) + error_key = error_message.split('\n')[0].strip()[:200] + + if error_key not in state['metrics']['errors']: + state['metrics']['errors'][error_key] = { + 'count': 0, + 'first_seen': datetime.now().isoformat(), + 'last_seen': None, + 'occurrences': [] + } + + state['metrics']['errors'][error_key]['count'] += 1 + state['metrics']['errors'][error_key]['last_seen'] = datetime.now().isoformat() + state['metrics']['errors'][error_key]['occurrences'].append({ + 'timestamp': datetime.now().isoformat() + }) + + # Keep only recent occurrences + if len(state['metrics']['errors'][error_key]['occurrences']) > MAX_OCCURRENCES: + state['metrics']['errors'][error_key]['occurrences'] = \ + state['metrics']['errors'][error_key]['occurrences'][-MAX_OCCURRENCES:] + + +def track_file_edit(state: Dict[str, Any], file_path: str) -> None: + """Track file edit event.""" + MAX_TIMESTAMPS = 100 # Limit to prevent unbounded memory growth + + if 'file_edits' not in state['metrics']: + state['metrics']['file_edits'] = {} + + if file_path not in state['metrics']['file_edits']: + state['metrics']['file_edits'][file_path] = { + 'count': 0, + 'timestamps': [] + } + + state['metrics']['file_edits'][file_path]['count'] += 1 + state['metrics']['file_edits'][file_path]['timestamps'].append( + datetime.now().isoformat() + ) + + # Keep only recent timestamps + if len(state['metrics']['file_edits'][file_path]['timestamps']) > MAX_TIMESTAMPS: + state['metrics']['file_edits'][file_path]['timestamps'] = \ + state['metrics']['file_edits'][file_path]['timestamps'][-MAX_TIMESTAMPS:] + + +def track_correction(state: Dict[str, Any], message: str) -> None: + """Track user correction event.""" + MAX_CORRECTIONS = 100 # Limit to prevent unbounded memory growth + + if 'corrections' not in state['metrics']: + state['metrics']['corrections'] = [] + + state['metrics']['corrections'].append({ + 'timestamp': datetime.now().isoformat(), + 'message': message[:500] # Truncate to avoid storing too much + }) + + # Keep only recent corrections + if len(state['metrics']['corrections']) > MAX_CORRECTIONS: + state['metrics']['corrections'] = state['metrics']['corrections'][-MAX_CORRECTIONS:] + + +def check_code_volume_threshold(state: Dict[str, Any], config: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """Check if code volume threshold is crossed.""" + threshold = config.get('sensitivity', {}).get('lines_threshold', 50) + + code_written = state['metrics'].get('code_written', {}) + + for file_path, data in code_written.items(): + if data['total_lines'] >= threshold: + # Check if this file is in auto_review categories + auto_review = config.get('auto_review', {}) + always_review = auto_review.get('always_review', []) + never_review = auto_review.get('never_review', []) + + # Check never_review first + if any(keyword in file_path.lower() for keyword in never_review): + continue + + # Check always_review or threshold + should_review = any(keyword in file_path.lower() for keyword in always_review) + + if should_review or data['total_lines'] >= threshold: + return { + 'trigger': 'code_volume', + 'file': file_path, + 'lines': data['total_lines'], + 'threshold': threshold, + 'priority': 'high' if should_review else 'medium' + } + + return None + + +def check_repeated_errors(state: Dict[str, Any], config: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """Check if same error is repeated.""" + threshold = config.get('sensitivity', {}).get('error_repeat_threshold', 3) + + errors = state['metrics'].get('errors', {}) + + for error_key, data in errors.items(): + if data['count'] >= threshold: + return { + 'trigger': 'repeated_errors', + 'error': error_key, + 'count': data['count'], + 'threshold': threshold, + 'priority': 'critical' + } + + return None + + +def check_file_churn(state: Dict[str, Any], config: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """Check if a file is being edited too frequently.""" + threshold = config.get('sensitivity', {}).get('file_churn_threshold', 5) + time_window_minutes = 10 + + file_edits = state['metrics'].get('file_edits', {}) + + for file_path, data in file_edits.items(): + timestamps = data['timestamps'] + + # Count edits in last 10 minutes + cutoff = datetime.now() - timedelta(minutes=time_window_minutes) + recent_edits = [] + + for ts_str in timestamps: + ts = parse_timestamp_with_tz(ts_str, cutoff) + if ts and ts >= cutoff: + recent_edits.append(ts_str) + + if len(recent_edits) >= threshold: + return { + 'trigger': 'file_churn', + 'file': file_path, + 'edits': len(recent_edits), + 'time_window_minutes': time_window_minutes, + 'threshold': threshold, + 'priority': 'high' + } + + return None + + +def check_repeated_corrections(state: Dict[str, Any], config: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """Check if user is making repeated corrections.""" + threshold = config.get('sensitivity', {}).get('correction_threshold', 3) + time_window_minutes = 30 + + corrections = state['metrics'].get('corrections', []) + + # Count corrections in last 30 minutes + cutoff = datetime.now() - timedelta(minutes=time_window_minutes) + recent_corrections = [] + + for correction in corrections: + try: + ts = parse_timestamp_with_tz(correction['timestamp'], cutoff) + if ts and ts >= cutoff: + recent_corrections.append(correction) + except KeyError: + continue + + if len(recent_corrections) >= threshold: + return { + 'trigger': 'repeated_corrections', + 'count': len(recent_corrections), + 'time_window_minutes': time_window_minutes, + 'threshold': threshold, + 'priority': 'critical' + } + + return None + + +def calculate_session_health(state: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, Any]: + """Calculate overall session health score.""" + health_score = 100 + recommendations = [] + + # Check error rate + errors = state['metrics'].get('errors', {}) + total_errors = sum(data['count'] for data in errors.values()) + + try: + session_start = datetime.fromisoformat(state['session_start']) + now = datetime.now(session_start.tzinfo) if session_start.tzinfo else datetime.now() + session_duration_minutes = max(1, (now - session_start).total_seconds() / 60) + error_rate = total_errors / session_duration_minutes + except (ValueError, TypeError): + error_rate = 0 + session_duration_minutes = 0 + + if error_rate > 0.5: # More than 1 error per 2 minutes + health_score -= 20 + recommendations.append("High error rate - consider taking a break or reassessing approach") + + # Check correction rate + corrections = state['metrics'].get('corrections', []) + correction_rate = len(corrections) / max(1, session_duration_minutes) + + if correction_rate > 0.1: # More than 1 correction per 10 minutes + health_score -= 15 + recommendations.append("Frequent corrections - session may be going off track") + + # Check file churn + file_edits = state['metrics'].get('file_edits', {}) + for file_path, data in file_edits.items(): + if data['count'] > 5: + health_score -= 10 + recommendations.append(f"High churn on {file_path} - consider taking a break from this file") + break + + # Check repeated errors + for error_key, data in errors.items(): + if data['count'] >= 3: + health_score -= 20 + recommendations.append(f"Repeated error: {error_key[:50]}... - approach may be fundamentally wrong") + break + + return { + 'score': max(0, health_score), + 'session_duration_minutes': int(session_duration_minutes), + 'total_errors': total_errors, + 'total_corrections': len(corrections), + 'recommendations': recommendations + } + + +def check_triggers(state: Dict[str, Any], config: Dict[str, Any]) -> List[Dict[str, Any]]: + """Check all triggers and return those that fired.""" + triggered = [] + + # Check each trigger + trigger = check_code_volume_threshold(state, config) + if trigger: + triggered.append(trigger) + + trigger = check_repeated_errors(state, config) + if trigger: + triggered.append(trigger) + + trigger = check_file_churn(state, config) + if trigger: + triggered.append(trigger) + + trigger = check_repeated_corrections(state, config) + if trigger: + triggered.append(trigger) + + return triggered + + +def main(): + parser = argparse.ArgumentParser( + description='Guardian session health monitor', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--event', + choices=['code-written', 'error', 'file-edit', 'correction'], + help='Type of event to track' + ) + + parser.add_argument( + '--file', + help='File path (for code-written and file-edit events)' + ) + + parser.add_argument( + '--lines', + type=int, + help='Number of lines written (for code-written events)' + ) + + parser.add_argument( + '--message', + help='Error or correction message' + ) + + parser.add_argument( + '--check-health', + action='store_true', + help='Check current session health' + ) + + parser.add_argument( + '--check-triggers', + action='store_true', + help='Check if any triggers have fired' + ) + + parser.add_argument( + '--reset', + action='store_true', + help='Reset session metrics' + ) + + parser.add_argument( + '--init', + action='store_true', + help='Initialize Guardian for this project' + ) + + args = parser.parse_args() + + # Initialize Guardian if requested + if args.init: + guardian_path = init_guardian_if_needed() + print(f"Guardian initialized at {guardian_path}") + sys.exit(0) + + # Find or create Guardian directory + guardian_path = find_guardian_root() + if not guardian_path: + guardian_path = init_guardian_if_needed() + + # Load config and state + config = load_config(guardian_path) + + if not config.get('enabled', False): + print("Guardian is disabled in config", file=sys.stderr) + sys.exit(0) + + state = load_session_state(guardian_path) + + # Handle reset + if args.reset: + state = { + "session_start": datetime.now().isoformat(), + "metrics": {}, + "history": [] + } + save_session_state(guardian_path, state) + print("Session metrics reset") + sys.exit(0) + + # Handle health check + if args.check_health: + health = calculate_session_health(state, config) + print(json.dumps(health, indent=2)) + sys.exit(0) + + # Handle trigger check + if args.check_triggers: + triggers = check_triggers(state, config) + print(json.dumps(triggers, indent=2)) + sys.exit(0) + + # Handle event tracking + if args.event: + if args.event == 'code-written': + if not args.file or args.lines is None: + print("Error: --file and --lines required for code-written event", file=sys.stderr) + sys.exit(1) + track_code_written(state, args.file, args.lines) + + elif args.event == 'error': + if not args.message: + print("Error: --message required for error event", file=sys.stderr) + sys.exit(1) + track_error(state, args.message) + + elif args.event == 'file-edit': + if not args.file: + print("Error: --file required for file-edit event", file=sys.stderr) + sys.exit(1) + track_file_edit(state, args.file) + + elif args.event == 'correction': + if not args.message: + print("Error: --message required for correction event", file=sys.stderr) + sys.exit(1) + track_correction(state, args.message) + + # Save updated state + save_session_state(guardian_path, state) + + # Check if any triggers fired + triggers = check_triggers(state, config) + if triggers: + print(json.dumps({'event_recorded': True, 'triggers': triggers}, indent=2)) + else: + print(json.dumps({'event_recorded': True}, indent=2)) + else: + parser.print_help() + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/guardian/scripts/template_loader.py b/skills/guardian/scripts/template_loader.py new file mode 100644 index 0000000..bec9827 --- /dev/null +++ b/skills/guardian/scripts/template_loader.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +""" +Guardian Template Loader + +Loads and applies Guardian review/planning templates for consistent, +structured agent interactions. + +Usage: + # List available templates + python template_loader.py --list + + # Load a template + python template_loader.py --template security_review + + # Load template and apply to context + python template_loader.py --template security_review --file auth.py --output prompt.txt + + # Create custom template + python template_loader.py --create my_review --based-on security_review +""" + +import os +import sys +import json +import argparse +from pathlib import Path +from typing import Dict, List, Optional, Any + + +def find_templates_dir() -> Path: + """Find the Guardian Templates directory.""" + # First try relative to this script + script_dir = Path(__file__).parent + templates_dir = script_dir.parent / 'Templates' + + if templates_dir.exists(): + return templates_dir + + # Try from current directory + templates_dir = Path.cwd() / 'skills' / 'guardian' / 'Templates' + if templates_dir.exists(): + return templates_dir + + raise FileNotFoundError("Guardian Templates directory not found") + + +def list_templates() -> List[Dict[str, str]]: + """List all available Guardian templates. + + Returns: + List of template metadata dictionaries + """ + templates_dir = find_templates_dir() + templates = [] + + for template_file in templates_dir.glob('*.json'): + try: + with open(template_file, 'r', encoding='utf-8') as f: + template = json.load(f) + templates.append({ + 'name': template.get('name', template_file.stem), + 'description': template.get('description', 'No description'), + 'task_type': template.get('task_type', 'unknown'), + 'file': str(template_file) + }) + except (json.JSONDecodeError, OSError, IOError): + continue + + return templates + + +def load_template(template_name: str) -> Dict[str, Any]: + """Load a Guardian template by name. + + Args: + template_name: Name of the template (with or without .json extension) + + Returns: + Template configuration dictionary + """ + templates_dir = find_templates_dir() + + # Remove .json extension if provided + if template_name.endswith('.json'): + template_name = template_name[:-5] + + template_file = templates_dir / f"{template_name}.json" + + if not template_file.exists(): + raise FileNotFoundError(f"Template not found: {template_name}") + + try: + with open(template_file, 'r', encoding='utf-8') as f: + return json.load(f) + except json.JSONDecodeError as e: + raise ValueError(f"Invalid template JSON: {e}") + + +def apply_template_to_context( + template: Dict[str, Any], + context: str +) -> str: + """Apply a template to extracted context. + + Args: + template: Template configuration + context: Extracted minimal context + + Returns: + Formatted agent prompt + """ + prompt_template = template.get('agent_prompt_template', '') + + # Replace context placeholder + prompt = prompt_template.replace('{context}', context) + + return prompt + + +def get_template_config(template: Dict[str, Any]) -> Dict[str, Any]: + """Extract configuration parameters from template. + + Args: + template: Template configuration + + Returns: + Configuration dictionary for context_filter.py + """ + return { + 'task_type': template.get('task_type', 'review'), + 'focus': template.get('focus', ''), + 'oracle_categories': template.get('oracle_categories', ['patterns', 'gotchas']), + 'oracle_tags': template.get('oracle_tags_required', []), + 'max_patterns': template.get('max_oracle_patterns', 5), + 'max_gotchas': template.get('max_oracle_gotchas', 5), + 'validation_rules': template.get('validation_rules', {}) + } + + +def create_custom_template( + name: str, + base_template: Optional[str] = None, + description: str = "", + task_type: str = "review" +) -> Path: + """Create a new custom template. + + Args: + name: Name for the new template + base_template: Optional template to base this on + description: Template description + task_type: Type of task (review, plan, debug) + + Returns: + Path to the created template file + """ + templates_dir = find_templates_dir() + + if base_template: + # Load base template + base = load_template(base_template) + new_template = base.copy() + new_template['name'] = name + if description: + new_template['description'] = description + else: + # Create minimal template + new_template = { + "name": name, + "description": description or f"Custom {task_type} template", + "task_type": task_type, + "focus": "", + "agent_prompt_template": "You are a READ-ONLY code reviewer for Guardian.\n\nCRITICAL CONSTRAINTS:\n- DO NOT modify any files\n- ONLY read and analyze\n\n{context}\n\nReturn suggestions as JSON array.", + "oracle_categories": ["patterns", "gotchas"], + "oracle_tags_required": [], + "max_oracle_patterns": 5, + "max_oracle_gotchas": 5, + "always_include_files": [], + "validation_rules": { + "min_confidence": 0.5, + "block_contradictions": true + } + } + + # Save template + template_file = templates_dir / f"{name}.json" + + with open(template_file, 'w', encoding='utf-8') as f: + json.dump(new_template, f, indent=2) + + return template_file + + +def main(): + parser = argparse.ArgumentParser( + description='Guardian template loader and manager', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--list', + action='store_true', + help='List all available templates' + ) + + parser.add_argument( + '--template', + help='Template name to load' + ) + + parser.add_argument( + '--file', + help='File to apply template to (used with --output)' + ) + + parser.add_argument( + '--output', + help='Output file for generated prompt' + ) + + parser.add_argument( + '--create', + help='Create a new custom template with this name' + ) + + parser.add_argument( + '--based-on', + help='Base the new template on an existing one' + ) + + parser.add_argument( + '--description', + help='Description for new template' + ) + + parser.add_argument( + '--show-config', + action='store_true', + help='Show template configuration (for use with context_filter.py)' + ) + + args = parser.parse_args() + + # List templates + if args.list: + templates = list_templates() + + print("Available Guardian Templates:") + print("=" * 60) + + for template in templates: + print(f"\nName: {template['name']}") + print(f" Type: {template['task_type']}") + print(f" Description: {template['description']}") + print(f" File: {template['file']}") + + print("\n" + "=" * 60) + print(f"Total: {len(templates)} templates") + sys.exit(0) + + # Create template + if args.create: + template_file = create_custom_template( + args.create, + args.based_on, + args.description or "", + "review" + ) + + print(f"Created template: {template_file}") + print("Edit this file to customize your template") + sys.exit(0) + + # Load template + if args.template: + template = load_template(args.template) + + if args.show_config: + config = get_template_config(template) + print(json.dumps(config, indent=2)) + sys.exit(0) + + if args.output and args.file: + # Apply template to file + # This would integrate with context_filter.py + print(f"Applying template '{args.template}' to {args.file}...") + print(f"Output will be saved to: {args.output}") + print("\nTo apply this template:") + print(f" 1. Run context_filter.py with template config") + print(f" 2. Apply template to extracted context") + print(f" 3. Save to {args.output}") + else: + # Just show template + print(json.dumps(template, indent=2)) + + sys.exit(0) + + parser.print_help() + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/guardian/scripts/validator.py b/skills/guardian/scripts/validator.py new file mode 100644 index 0000000..80292eb --- /dev/null +++ b/skills/guardian/scripts/validator.py @@ -0,0 +1,586 @@ +#!/usr/bin/env python3 +""" +Guardian Suggestion Validator + +Cross-checks subagent suggestions against Oracle knowledge before presenting to user. + +Key Principle: "Subagent might be missing important codebase context - need validation layer" + +This script: +1. Validates suggestions against Oracle patterns +2. Detects contradictions with known good practices +3. Checks rejection history for similar suggestions +4. Calculates confidence scores +5. Flags suggestions that contradict Oracle knowledge + +Usage: + # Validate a single suggestion + python validator.py --suggestion "Use MD5 for password hashing" --category security + + # Validate multiple suggestions from JSON + python validator.py --suggestions-file suggestions.json + + # Check rejection history + python validator.py --check-rejection "Add rate limiting to endpoint" + +Environment Variables: + ORACLE_PATH: Path to Oracle directory [default: .oracle] + GUARDIAN_PATH: Path to Guardian directory [default: .guardian] +""" + +import os +import sys +import json +import argparse +from pathlib import Path +from typing import Dict, List, Optional, Any, Tuple +import re +from datetime import datetime, timedelta + + +def find_oracle_root() -> Optional[Path]: + """Find the .oracle directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def find_guardian_root() -> Optional[Path]: + """Find the .guardian directory.""" + current = Path.cwd() + + while current != current.parent: + guardian_path = current / '.guardian' + if guardian_path.exists(): + return guardian_path + current = current.parent + + return None + + +def load_oracle_knowledge(oracle_path: Path) -> List[Dict[str, Any]]: + """Load all Oracle knowledge.""" + knowledge_dir = oracle_path / 'knowledge' + all_knowledge: List[Dict[str, Any]] = [] + + categories = ['patterns', 'preferences', 'gotchas', 'solutions', 'corrections'] + + for category in categories: + file_path = knowledge_dir / f'{category}.json' + if file_path.exists(): + try: + with open(file_path, 'r', encoding='utf-8') as f: + entries = json.load(f) + for entry in entries: + if isinstance(entry, dict): + entry['_category'] = category + all_knowledge.append(entry) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + continue + + return all_knowledge + + +def load_rejection_history(guardian_path: Path) -> List[Dict[str, Any]]: + """Load rejection history from Guardian.""" + history_file = guardian_path / 'rejection_history.json' + + if not history_file.exists(): + return [] + + try: + with open(history_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + return [] + + +def save_rejection_history(guardian_path: Path, history: List[Dict[str, Any]]) -> None: + """Save rejection history to Guardian.""" + history_file = guardian_path / 'rejection_history.json' + + try: + with open(history_file, 'w', encoding='utf-8') as f: + json.dump(history, f, indent=2) + except (OSError, IOError) as e: + print(f"Warning: Failed to save rejection history: {e}", file=sys.stderr) + + +def load_acceptance_stats(guardian_path: Path) -> Dict[str, Any]: + """Load acceptance rate statistics.""" + stats_file = guardian_path / 'acceptance_stats.json' + + if not stats_file.exists(): + return { + 'by_category': {}, + 'by_type': {}, + 'overall': { + 'accepted': 0, + 'rejected': 0, + 'rate': 0.0 + } + } + + try: + with open(stats_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + return {'by_category': {}, 'by_type': {}, 'overall': {'accepted': 0, 'rejected': 0, 'rate': 0.0}} + + +def check_contradiction_with_patterns( + suggestion: str, + oracle_knowledge: List[Dict[str, Any]] +) -> Optional[Dict[str, Any]]: + """Check if suggestion contradicts known Oracle patterns. + + Args: + suggestion: Suggestion text + oracle_knowledge: All Oracle knowledge + + Returns: + Contradiction details if found, None otherwise + """ + # Get patterns and gotchas + patterns = [k for k in oracle_knowledge if k.get('_category') == 'patterns'] + gotchas = [k for k in oracle_knowledge if k.get('_category') == 'gotchas'] + + suggestion_lower = suggestion.lower() + + # Check against patterns (high priority ones) + for pattern in patterns: + if pattern.get('priority') not in ['critical', 'high']: + continue + + title = pattern.get('title', '').lower() + content = pattern.get('content', '').lower() + + # Look for direct contradictions + # Example: suggestion says "use MD5" but pattern says "never use MD5" + if 'never' in content or 'don\'t' in content or 'avoid' in content: + # Extract what not to do + words = re.findall(r'\b\w+\b', suggestion_lower) + for word in words: + if len(word) > 3 and word in content: + # Potential contradiction + return { + 'type': 'pattern_contradiction', + 'pattern': pattern.get('title', 'Unknown pattern'), + 'pattern_content': pattern.get('content', ''), + 'priority': pattern.get('priority', 'medium'), + 'confidence': 0.8 + } + + # Check against gotchas (critical warnings) + for gotcha in gotchas: + title = gotcha.get('title', '').lower() + content = gotcha.get('content', '').lower() + + # Check if suggestion relates to a known gotcha + words = re.findall(r'\b\w+\b', suggestion_lower) + common_words = set(words) & set(re.findall(r'\b\w+\b', content)) + + if len(common_words) > 3: + return { + 'type': 'gotcha_warning', + 'gotcha': gotcha.get('title', 'Unknown gotcha'), + 'gotcha_content': gotcha.get('content', ''), + 'priority': gotcha.get('priority', 'high'), + 'confidence': 0.6 + } + + return None + + +def check_rejection_history( + suggestion: str, + rejection_history: List[Dict[str, Any]], + similarity_threshold: float = 0.6 +) -> Optional[Dict[str, Any]]: + """Check if similar suggestion was previously rejected. + + Args: + suggestion: Suggestion text + rejection_history: List of previously rejected suggestions + similarity_threshold: Minimum similarity to consider a match + + Returns: + Rejection details if found, None otherwise + """ + suggestion_words = set(re.findall(r'\b\w+\b', suggestion.lower())) + + for rejection in rejection_history: + rejected_text = rejection.get('suggestion', '').lower() + rejected_words = set(re.findall(r'\b\w+\b', rejected_text)) + + # Calculate Jaccard similarity + if len(suggestion_words) == 0 or len(rejected_words) == 0: + continue + + intersection = suggestion_words & rejected_words + union = suggestion_words | rejected_words + + similarity = len(intersection) / len(union) if len(union) > 0 else 0.0 + + if similarity >= similarity_threshold: + return { + 'type': 'previously_rejected', + 'rejected_suggestion': rejection.get('suggestion', ''), + 'rejection_reason': rejection.get('reason', 'No reason provided'), + 'rejected_date': rejection.get('timestamp', 'Unknown'), + 'similarity': similarity, + 'confidence': 0.3 # Low confidence due to previous rejection + } + + return None + + +def calculate_acceptance_rate( + category: str, + acceptance_stats: Dict[str, Any] +) -> float: + """Calculate acceptance rate for a category. + + Args: + category: Suggestion category + acceptance_stats: Acceptance statistics + + Returns: + Acceptance rate (0.0 to 1.0) + """ + by_category = acceptance_stats.get('by_category', {}) + + if category not in by_category: + # No history, return neutral rate + return 0.5 + + stats = by_category[category] + accepted = stats.get('accepted', 0) + rejected = stats.get('rejected', 0) + total = accepted + rejected + + if total == 0: + return 0.5 + + return accepted / total + + +def validate_suggestion( + suggestion: str, + category: str, + oracle_knowledge: List[Dict[str, Any]], + rejection_history: List[Dict[str, Any]], + acceptance_stats: Dict[str, Any] +) -> Dict[str, Any]: + """Validate a suggestion against Oracle knowledge. + + Args: + suggestion: Suggestion text + category: Suggestion category (security, performance, etc.) + oracle_knowledge: All Oracle knowledge + rejection_history: Previous rejections + acceptance_stats: Acceptance rate statistics + + Returns: + Validation result with confidence score and warnings + """ + result = { + 'suggestion': suggestion, + 'category': category, + 'confidence': 0.5, # Start neutral + 'warnings': [], + 'should_present': True, + 'notes': [] + } + + # Check for contradictions with Oracle patterns + contradiction = check_contradiction_with_patterns(suggestion, oracle_knowledge) + if contradiction: + result['confidence'] = contradiction['confidence'] + result['warnings'].append({ + 'severity': 'high', + 'type': contradiction['type'], + 'message': f"Contradicts Oracle {contradiction['type']}: {contradiction.get('pattern', contradiction.get('gotcha', 'Unknown'))}", + 'details': contradiction + }) + + if contradiction.get('priority') == 'critical': + result['should_present'] = False + result['notes'].append("BLOCKED: Contradicts critical Oracle pattern") + + # Check rejection history + previous_rejection = check_rejection_history(suggestion, rejection_history) + if previous_rejection: + result['confidence'] = min(result['confidence'], previous_rejection['confidence']) + result['warnings'].append({ + 'severity': 'medium', + 'type': 'previously_rejected', + 'message': f"Similar suggestion rejected before ({previous_rejection['similarity']:.0%} similar)", + 'details': previous_rejection + }) + result['notes'].append(f"Previous rejection reason: {previous_rejection['rejection_reason']}") + + # Calculate confidence from acceptance rate + acceptance_rate = calculate_acceptance_rate(category, acceptance_stats) + + # Adjust confidence based on historical acceptance + if not result['warnings']: + # No warnings, use acceptance rate + result['confidence'] = acceptance_rate + else: + # Has warnings, blend with acceptance rate (60% warning, 40% acceptance) + result['confidence'] = result['confidence'] * 0.6 + acceptance_rate * 0.4 + + # Add acceptance rate note + result['notes'].append(f"Historical acceptance rate for {category}: {acceptance_rate:.0%}") + + # Final decision on whether to present + if result['confidence'] < 0.3: + result['should_present'] = False + result['notes'].append("Confidence too low - suggestion blocked") + + return result + + +def validate_multiple_suggestions( + suggestions: List[Dict[str, str]], + oracle_path: Optional[Path] = None, + guardian_path: Optional[Path] = None +) -> List[Dict[str, Any]]: + """Validate multiple suggestions. + + Args: + suggestions: List of suggestion dictionaries with 'text' and 'category' keys + oracle_path: Path to Oracle directory + guardian_path: Path to Guardian directory + + Returns: + List of validation results + """ + # Load Oracle knowledge + oracle_knowledge = [] + if oracle_path: + oracle_knowledge = load_oracle_knowledge(oracle_path) + + # Load Guardian data + rejection_history = [] + acceptance_stats = {'by_category': {}, 'by_type': {}, 'overall': {'accepted': 0, 'rejected': 0, 'rate': 0.0}} + + if guardian_path: + rejection_history = load_rejection_history(guardian_path) + acceptance_stats = load_acceptance_stats(guardian_path) + + # Validate each suggestion + results = [] + for suggestion_dict in suggestions: + suggestion_text = suggestion_dict.get('text', '') + category = suggestion_dict.get('category', 'general') + + result = validate_suggestion( + suggestion_text, + category, + oracle_knowledge, + rejection_history, + acceptance_stats + ) + + results.append(result) + + return results + + +def record_rejection( + guardian_path: Path, + suggestion: str, + reason: str, + category: str +) -> None: + """Record a rejected suggestion for future reference. + + Args: + guardian_path: Path to Guardian directory + suggestion: Rejected suggestion text + reason: Reason for rejection + category: Suggestion category + """ + rejection_history = load_rejection_history(guardian_path) + + rejection_history.append({ + 'suggestion': suggestion, + 'reason': reason, + 'category': category, + 'timestamp': datetime.now().isoformat() + }) + + # Keep only last 100 rejections + if len(rejection_history) > 100: + rejection_history = rejection_history[-100:] + + save_rejection_history(guardian_path, rejection_history) + + +def update_acceptance_stats( + guardian_path: Path, + category: str, + accepted: bool +) -> None: + """Update acceptance rate statistics. + + Args: + guardian_path: Path to Guardian directory + category: Suggestion category + accepted: Whether the suggestion was accepted + """ + stats_file = guardian_path / 'acceptance_stats.json' + stats = load_acceptance_stats(guardian_path) + + # Update category stats + if category not in stats['by_category']: + stats['by_category'][category] = {'accepted': 0, 'rejected': 0} + + if accepted: + stats['by_category'][category]['accepted'] += 1 + stats['overall']['accepted'] += 1 + else: + stats['by_category'][category]['rejected'] += 1 + stats['overall']['rejected'] += 1 + + # Recalculate overall rate + total = stats['overall']['accepted'] + stats['overall']['rejected'] + stats['overall']['rate'] = stats['overall']['accepted'] / total if total > 0 else 0.0 + + # Recalculate category rates + for cat, cat_stats in stats['by_category'].items(): + cat_total = cat_stats['accepted'] + cat_stats['rejected'] + cat_stats['rate'] = cat_stats['accepted'] / cat_total if cat_total > 0 else 0.0 + + try: + with open(stats_file, 'w', encoding='utf-8') as f: + json.dump(stats, f, indent=2) + except (OSError, IOError) as e: + print(f"Warning: Failed to save acceptance stats: {e}", file=sys.stderr) + + +def main(): + parser = argparse.ArgumentParser( + description='Validate Guardian suggestions against Oracle knowledge', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--suggestion', + help='Single suggestion to validate' + ) + + parser.add_argument( + '--category', + default='general', + help='Suggestion category (security, performance, style, etc.)' + ) + + parser.add_argument( + '--suggestions-file', + help='JSON file with multiple suggestions to validate' + ) + + parser.add_argument( + '--record-rejection', + help='Record a rejected suggestion' + ) + + parser.add_argument( + '--rejection-reason', + help='Reason for rejection (used with --record-rejection)' + ) + + parser.add_argument( + '--update-stats', + choices=['accept', 'reject'], + help='Update acceptance statistics' + ) + + parser.add_argument( + '--check-rejection', + help='Check if a suggestion was previously rejected' + ) + + args = parser.parse_args() + + # Find Oracle and Guardian + oracle_path = find_oracle_root() + guardian_path = find_guardian_root() + + if not oracle_path and not guardian_path: + print("Warning: Neither Oracle nor Guardian initialized", file=sys.stderr) + + # Handle check rejection + if args.check_rejection: + if not guardian_path: + print(json.dumps({'found': False, 'message': 'Guardian not initialized'})) + sys.exit(0) + + rejection_history = load_rejection_history(guardian_path) + result = check_rejection_history(args.check_rejection, rejection_history) + + if result: + print(json.dumps({'found': True, 'details': result}, indent=2)) + else: + print(json.dumps({'found': False})) + sys.exit(0) + + # Handle record rejection + if args.record_rejection: + if not guardian_path: + print("Error: Guardian not initialized", file=sys.stderr) + sys.exit(1) + + if not args.rejection_reason: + print("Error: --rejection-reason required", file=sys.stderr) + sys.exit(1) + + record_rejection(guardian_path, args.record_rejection, args.rejection_reason, args.category) + print(json.dumps({'recorded': True})) + sys.exit(0) + + # Handle update stats + if args.update_stats: + if not guardian_path: + print("Error: Guardian not initialized", file=sys.stderr) + sys.exit(1) + + update_acceptance_stats(guardian_path, args.category, args.update_stats == 'accept') + print(json.dumps({'updated': True})) + sys.exit(0) + + # Handle single suggestion validation + if args.suggestion: + suggestions = [{'text': args.suggestion, 'category': args.category}] + results = validate_multiple_suggestions(suggestions, oracle_path, guardian_path) + print(json.dumps(results[0], indent=2)) + sys.exit(0) + + # Handle suggestions file + if args.suggestions_file: + try: + with open(args.suggestions_file, 'r', encoding='utf-8') as f: + suggestions = json.load(f) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError) as e: + print(f"Error: Failed to load suggestions file: {e}", file=sys.stderr) + sys.exit(1) + + results = validate_multiple_suggestions(suggestions, oracle_path, guardian_path) + print(json.dumps(results, indent=2)) + sys.exit(0) + + parser.print_help() + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/Assets/example-knowledge-entries.json b/skills/oracle/Assets/example-knowledge-entries.json new file mode 100644 index 0000000..3a09436 --- /dev/null +++ b/skills/oracle/Assets/example-knowledge-entries.json @@ -0,0 +1,108 @@ +[ + { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "category": "gotcha", + "priority": "critical", + "title": "Always sanitize user input before rendering", + "content": "User input must be sanitized before being rendered in the DOM to prevent XSS attacks. Use textContent instead of innerHTML, or use a sanitization library like DOMPurify.", + "context": "When displaying user-generated content (comments, profile data, search queries, etc.)", + "examples": [ + "element.textContent = userInput // Safe", + "element.innerHTML = DOMPurify.sanitize(userInput) // Safe with library", + "element.innerHTML = userInput // DANGEROUS - XSS vulnerability" + ], + "learned_from": "2025-11-19_session_001", + "created": "2025-11-19T10:30:00Z", + "last_used": "2025-11-19T15:45:00Z", + "use_count": 5, + "tags": ["security", "xss", "dom", "user-input"] + }, + { + "id": "b2c3d4e5-f6g7-8901-bcde-fg2345678901", + "category": "pattern", + "priority": "high", + "title": "Use factory pattern for database connections", + "content": "All database connections should be created through DatabaseFactory.create() which handles connection pooling, configuration, error handling, and automatic retry logic. Direct connection creation bypasses these safeguards.", + "context": "When creating new database connections or initializing data access layers", + "examples": [ + "const db = DatabaseFactory.create('postgres', config)", + "const cache = DatabaseFactory.create('redis', cacheConfig)", + "// NOT: const db = new PostgresClient(config) // Bypasses pooling" + ], + "learned_from": "architecture_decision_2025_11_15", + "created": "2025-11-15T09:00:00Z", + "last_used": "2025-11-19T14:20:00Z", + "use_count": 12, + "tags": ["database", "factory-pattern", "architecture", "connections"] + }, + { + "id": "c3d4e5f6-g7h8-9012-cdef-gh3456789012", + "category": "preference", + "priority": "medium", + "title": "Prefer async/await over promise chains", + "content": "Team prefers async/await syntax for asynchronous code over .then() chains for better readability and error handling. Exceptions: when parallel execution is needed (use Promise.all), or when working with non-async APIs.", + "context": "When writing asynchronous JavaScript/TypeScript code", + "examples": [ + "// Preferred:\nasync function fetchUser() {\n const response = await fetch('/api/user');\n return await response.json();\n}", + "// Avoid:\nfunction fetchUser() {\n return fetch('/api/user')\n .then(r => r.json());\n}" + ], + "learned_from": "team_decision_2025_11_10", + "created": "2025-11-10T11:00:00Z", + "last_used": "2025-11-19T16:00:00Z", + "use_count": 8, + "tags": ["javascript", "async", "code-style", "promises"] + }, + { + "id": "d4e5f6g7-h8i9-0123-defg-hi4567890123", + "category": "solution", + "priority": "high", + "title": "Use cursor-based pagination for large datasets", + "content": "For datasets with millions of records, use cursor-based pagination instead of offset-based to avoid performance degradation and inconsistencies. Cursor-based pagination uses a unique, sequential identifier (like ID or timestamp) to mark position.", + "context": "When implementing pagination for tables with 100k+ rows or when data changes frequently", + "examples": [ + "// Cursor-based (recommended):\nSELECT * FROM users WHERE id > $cursor ORDER BY id LIMIT 100", + "// API: GET /users?cursor=abc123&limit=100", + "// Offset-based (avoid for large datasets):\nSELECT * FROM users OFFSET 1000000 LIMIT 100 // Slow on large tables" + ], + "learned_from": "2025-11-18_performance_optimization", + "created": "2025-11-18T13:00:00Z", + "last_used": "2025-11-19T10:15:00Z", + "use_count": 3, + "tags": ["pagination", "performance", "database", "api"] + }, + { + "id": "e5f6g7h8-i9j0-1234-efgh-ij5678901234", + "category": "correction", + "priority": "high", + "title": "Don't mutate React state directly", + "content": "❌ Wrong: state.items.push(newItem)\nβœ“ Right: setState({ items: [...state.items, newItem] })\n\nReason: Direct state mutation doesn't trigger re-renders in React and violates React's immutability principles. Always create new objects/arrays when updating state.", + "context": "When updating state in React components (both class and functional)", + "examples": [ + "// Correct - Array update:\nsetItems(prevItems => [...prevItems, newItem])", + "// Correct - Object update:\nsetUser(prevUser => ({ ...prevUser, name: newName }))", + "// Wrong:\nitems.push(newItem); setItems(items) // Same reference, no re-render" + ], + "learned_from": "2025-11-17_session_005", + "created": "2025-11-17T14:30:00Z", + "last_used": "2025-11-19T11:00:00Z", + "use_count": 7, + "tags": ["react", "state-management", "immutability", "common-mistake"] + }, + { + "id": "f6g7h8i9-j0k1-2345-fghi-jk6789012345", + "category": "gotcha", + "priority": "medium", + "title": "Environment variables must be prefixed with NEXT_PUBLIC_ to be exposed to browser", + "content": "In Next.js, environment variables are only available server-side by default. To expose them to the browser, they must be prefixed with NEXT_PUBLIC_. Never prefix sensitive variables (API keys, secrets) with NEXT_PUBLIC_.", + "context": "When using environment variables in Next.js applications", + "examples": [ + "// Server-side only:\nAPI_SECRET=abc123\nDATABASE_URL=postgres://...", + "// Exposed to browser:\nNEXT_PUBLIC_API_URL=https://api.example.com\nNEXT_PUBLIC_ANALYTICS_ID=GA-123456" + ], + "learned_from": "2025-11-16_session_003", + "created": "2025-11-16T16:00:00Z", + "last_used": "2025-11-19T09:30:00Z", + "use_count": 4, + "tags": ["nextjs", "environment-variables", "security"] + } +] diff --git a/skills/oracle/ENHANCEMENTS.md b/skills/oracle/ENHANCEMENTS.md new file mode 100644 index 0000000..4695b9f --- /dev/null +++ b/skills/oracle/ENHANCEMENTS.md @@ -0,0 +1,325 @@ +# Oracle Skill Enhancements + +This document describes the major enhancements made to the Oracle skill to address context loss, improve automation, and make the system more intelligent. + +## Problem Statement + +The original Oracle skill had several limitations: +1. **Manual Activation Required**: Users had to explicitly invoke Oracle, easy to forget +2. **Context Loss**: When sessions crashed, compressed, or ended, valuable context was lost +3. **No Historical Mining**: Existing conversation history in `~/.claude/projects/` was ignored +4. **Static Context**: Context loading didn't adapt to current work (files being edited, branch, etc.) +5. **Repetitive Manual Work**: Users had to manually record sessions and capture learnings + +## Implemented Enhancements + +### Enhancement #1: Conversation History Analyzer + +**File**: `scripts/analyze_history.py` + +**Purpose**: Mine existing Claude Code conversation history to automatically extract patterns, corrections, preferences, and automation opportunities. + +**Key Features**: +- Reads JSONL files from `~/.claude/projects/[project-hash]/` +- Extracts user corrections using regex pattern matching +- Detects user preferences from conversation patterns +- Identifies repeated tasks as automation candidates +- Detects gotchas from problem reports +- Analyzes tool usage patterns +- Auto-populates Oracle knowledge base + +**Usage**: +```bash +# Analyze and populate Oracle automatically +python analyze_history.py --auto-populate + +# Analyze specific project +python analyze_history.py --project-hash abc123def456 + +# Analyze only (no changes) +python analyze_history.py --analyze-only + +# Recent conversations only +python analyze_history.py --recent-days 30 --auto-populate +``` + +**Code Quality**: +- All critical/high severity code review issues fixed +- Memory-efficient streaming for large JSONL files +- Proper error handling and file encoding (UTF-8) +- Configuration constants for maintainability +- Comprehensive error codes (exits with 1 on error, 0 on success) + +### Enhancement #2: SessionStart Hook + +**Files**: +- `scripts/session_start_hook.py` +- `scripts/HOOK_SETUP.md` (configuration guide) + +**Purpose**: Automatically inject Oracle context when Claude Code sessions start or resume. + +**Key Features**: +- Outputs JSON in Claude Code hook format +- Configurable context tiers (1=critical, 2=medium, 3=all) +- Environment variable support for configuration +- Graceful degradation (works even if Oracle not initialized) +- Configurable max context length to avoid overwhelming sessions + +**Configuration Example**: +```json +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "python /path/to/ClaudeShack/skills/oracle/scripts/session_start_hook.py" + } + ] + } + ] + } +} +``` + +**Code Quality**: +- All critical/high severity code review issues fixed +- Type hints throughout for maintainability +- No exception message information disclosure (security fix) +- Proper handling of missing/corrupt files +- Configurable via environment variables or CLI args + +### Enhancement #3: Smart Context Generation + +**File**: `scripts/smart_context.py` + +**Purpose**: Generate context that's intelligently aware of current work (git status, files being edited) and ranks knowledge by relevance. + +**Key Features**: +- Analyzes current git status (branch, modified/staged/untracked files) +- Extracts file patterns for relevance matching +- Relevance scoring algorithm with multiple factors: + - Priority-based scoring (critical/high/medium/low) + - Tag matching with word boundaries (40% weight) + - Keyword matching in content (20% weight) + - Time decay for recency (10% weight) +- Word-boundary matching to avoid false positives +- Time-precise decay calculation (uses hours/minutes, not just days) +- Scores displayed alongside knowledge items + +**Usage**: +```bash +# Generate smart context (text output) +python smart_context.py + +# JSON output for programmatic use +python smart_context.py --format json + +# Customize parameters +python smart_context.py --max-length 10000 --min-score 0.5 +``` + +**Algorithm Improvements**: +- Time decay with fractional days (precise to the hour) +- Timezone-aware datetime handling +- Word-boundary regex matching (prevents "py" matching "happy") +- Protection against division by zero +- Parameter validation + +**Code Quality**: +- All critical/high severity issues fixed +- Subprocess timeout protection (5 seconds) +- Proper error handling with specific exception types +- Type hints throughout +- Input validation for all parameters + +## Configuration & Integration + +### Environment Variables + +All scripts respect these environment variables: + +```bash +# SessionStart hook configuration +export ORACLE_CONTEXT_TIER=1 # 1=critical, 2=medium, 3=all +export ORACLE_MAX_CONTEXT_LENGTH=5000 # Max characters + +# Analysis configuration +export ORACLE_MIN_TASK_OCCURRENCES=3 # Min occurrences for automation candidates +``` + +### Claude Code Hook Setup + +See `scripts/HOOK_SETUP.md` for complete Claude Code hook configuration instructions. + +Quick setup: +1. Add SessionStart hook to Claude Code settings.json +2. Point to `session_start_hook.py` with absolute path +3. Optionally configure tier and max length + +### Workflow Integration + +**Daily Development Workflow**: +```bash +# Morning: Start session +# (SessionStart hook auto-loads Oracle context automatically) + +# During work: +# - Oracle context is always present +# - Claude has access to gotchas, patterns, recent corrections + +# Evening: Mine history (weekly recommended) +cd /path/to/project +python /path/to/ClaudeShack/skills/oracle/scripts/analyze_history.py --auto-populate +``` + +**Project Setup** (one-time): +```bash +# 1. Initialize Oracle for project +python /path/to/ClaudeShack/skills/oracle/scripts/init_oracle.py + +# 2. Mine existing conversation history +python /path/to/ClaudeShack/skills/oracle/scripts/analyze_history.py --auto-populate + +# 3. Configure SessionStart hook (see HOOK_SETUP.md) + +# 4. Test smart context +python /path/to/ClaudeShack/skills/oracle/scripts/smart_context.py +``` + +## Performance Characteristics + +### Conversation History Analyzer +- **Time Complexity**: O(n*m) where n=messages, m=patterns +- **Space Complexity**: O(n) with streaming (efficient for large files) +- **Typical Runtime**: <5 seconds for 1000 messages +- **Memory Usage**: <100MB even for large projects + +### SessionStart Hook +- **Execution Time**: <200ms for typical projects +- **Memory Usage**: <50MB +- **File I/O**: 5-10 file reads (knowledge categories) +- **Subprocess Calls**: 0 (pure Python, no git calls) + +### Smart Context Generator +- **Execution Time**: <500ms (includes git subprocess calls) +- **Memory Usage**: <50MB +- **Subprocess Calls**: 5 git commands (all with 5s timeout) +- **File I/O**: 5-10 file reads (knowledge categories) + +All scripts are designed to be fast enough for hook usage without noticeable delay. + +## Security Considerations + +### Fixed Security Issues + +1. **Exception Message Disclosure**: Fixed - error messages no longer expose internal paths or file details +2. **File Encoding**: All file operations use explicit UTF-8 encoding +3. **Subprocess Timeouts**: All git commands have 5-second timeouts +4. **Path Handling**: Uses `pathlib.Path` throughout for safe path operations +5. **JSON Output Sanitization**: Uses `json.dumps()` for safe output +6. **Input Validation**: All user parameters validated + +### Security Best Practices Applied + +- No command injection risks (subprocess.run with list arguments) +- No arbitrary code execution +- Graceful degradation on errors +- No sensitive data in logs (debug mode sends to stderr, not files) +- File permissions respected (checks before reading) + +## Testing Recommendations + +### Unit Tests Needed + +```python +# analyze_history.py +- Test with corrupted JSON files +- Test with missing knowledge files +- Test with empty conversation history +- Test regex pattern matching accuracy +- Test with timezone-aware dates + +# session_start_hook.py +- Test with missing .oracle directory +- Test with corrupt knowledge files +- Test JSON output structure +- Test tier filtering (1, 2, 3) +- Test max_length truncation + +# smart_context.py +- Test relevance scoring algorithm +- Test git status parsing +- Test with no git repo +- Test time decay calculation +- Test division by zero protection +``` + +### Integration Tests + +```bash +# Test full workflow +1. Initialize Oracle +2. Run analyze_history.py with test data +3. Test SessionStart hook manually +4. Verify JSON output format +5. Test smart_context.py in git repo +6. Test smart_context.py outside git repo +``` + +## Future Enhancements + +Potential additions for future versions: + +1. **SessionEnd Hook**: Auto-capture session learnings on exit +2. **Enhanced SKILL.md**: Make Oracle more proactive in offering knowledge +3. **Web Dashboard**: Visualize knowledge base growth over time +4. **Team Sync**: Share knowledge base across team via git +5. **AI Summarization**: Use AI to summarize session logs +6. **Pattern Templates**: Pre-built patterns for common scenarios +7. **Integration with MCP**: Expose Oracle via Model Context Protocol +8. **Slack/Discord Notifications**: Alert when new critical knowledge added + +## Changelog + +### Version 1.1 (2025-11-21) + +**New Features**: +- Conversation history analyzer (`analyze_history.py`) +- SessionStart hook (`session_start_hook.py`) +- Smart context generator (`smart_context.py`) +- Hook setup guide (`HOOK_SETUP.md`) + +**Code Quality Improvements**: +- Fixed all critical and high severity code review issues +- Added type hints throughout +- Improved error handling +- Added input validation +- Better documentation + +**Performance Improvements**: +- Streaming file reading for large JSONL files +- Subprocess timeouts to prevent hangs +- Efficient relevance scoring algorithm + +**Security Fixes**: +- No exception message disclosure +- Explicit UTF-8 encoding +- Subprocess timeout protection +- Input validation + +## Credits + +Enhanced by Claude (Anthropic) based on user requirements for better context preservation and automation. + +Original Oracle skill: ClaudeShack project + +## License + +Same as ClaudeShack project license. + +--- + +**"Remember everything. Learn from mistakes. Never waste context."** diff --git a/skills/oracle/README.md b/skills/oracle/README.md new file mode 100644 index 0000000..6797f26 --- /dev/null +++ b/skills/oracle/README.md @@ -0,0 +1,460 @@ +# Oracle Skill + +**Project Memory & Learning System for Claude Code** + +Oracle is a sophisticated memory and learning system that tracks interactions, learns from corrections, maintains knowledge across sessions, and generates token-efficient context to prevent wasted effort and repeated mistakes. + +## The Problem + +When working with LLMs across multiple sessions: +- **Context is lost** between sessions +- **Mistakes repeat** because corrections aren't remembered +- **Tokens are wasted** explaining the same things +- **Knowledge doesn't compound** - each session starts from zero +- **Patterns aren't detected** - automation opportunities missed + +## The Solution + +Oracle provides: +- πŸ“ **Session Recording** - Track what happens, what works, what doesn't +- 🧠 **Learning from Corrections** - When you correct Claude, it's remembered +- πŸ” **Smart Context Injection** - Load only relevant knowledge when needed +- πŸ“Š **Pattern Detection** - Identify automation opportunities +- πŸ“ˆ **Knowledge Compounding** - Learning accumulates over time +- πŸ€– **Auto-Script Generation** - Automate repeated patterns + +## Quick Start + +### 1. Initialize Oracle + +```bash +python .claude/skills/oracle/scripts/init_oracle.py +``` + +This creates `.oracle/` directory with knowledge management structure. + +### 2. Record Your First Session + +After working with Claude: + +```bash +python .claude/skills/oracle/scripts/record_session.py --interactive +``` + +Answer prompts to record what you learned, what was corrected, decisions made. + +### 3. Load Context Next Session + +```bash +python .claude/skills/oracle/scripts/load_context.py +``` + +Oracle loads relevant knowledge so Claude starts context-aware. + +### 4. Query Knowledge Anytime + +```bash +python .claude/skills/oracle/scripts/query_knowledge.py "authentication" +``` + +Search the knowledge base for relevant information. + +## Core Features + +### 🎯 Session Recording + +Track every session's: +- Activities and accomplishments +- Files changed and why +- Decisions made and rationale +- Learnings and discoveries +- Corrections and mistakes +- Questions asked and answered + +**Usage:** +```bash +# Interactive mode (recommended) +python .claude/skills/oracle/scripts/record_session.py --interactive + +# Quick mode +python .claude/skills/oracle/scripts/record_session.py \ + --summary "Implemented auth" \ + --learnings "Use bcrypt not md5" \ + --corrections "innerHTML->textContent for user input" +``` + +### 🧠 Knowledge Management + +Oracle maintains categorized knowledge: + +| Category | Purpose | Example | +|----------|---------|---------| +| **Patterns** | Code patterns, architecture decisions | "Use factory pattern for DB connections" | +| **Preferences** | Team/user style preferences | "Prefer functional over OOP" | +| **Gotchas** | Known issues, pitfalls | "Connection pool must be closed" | +| **Solutions** | Proven solutions | "Use cursor-based pagination" | +| **Corrections** | Mistakes to avoid | "Don't use innerHTML with user input" | + +**Usage:** +```bash +# Search all knowledge +python .claude/skills/oracle/scripts/query_knowledge.py "database" + +# Filter by category +python .claude/skills/oracle/scripts/query_knowledge.py --category gotchas + +# Filter by priority +python .claude/skills/oracle/scripts/query_knowledge.py --priority critical + +# Recent learnings +python .claude/skills/oracle/scripts/query_knowledge.py --recent 10 +``` + +### πŸ’‘ Smart Context Injection + +Oracle uses **tiered context loading**: + +**Tier 1 (Critical)**: Always load +- Critical gotchas +- Recent corrections +- High-priority patterns + +**Tier 2 (Relevant)**: Load when relevant +- Related patterns +- Similar solutions +- Contextual preferences + +**Tier 3 (Archive)**: Load on request +- Full history +- All solutions +- Complete timeline + +**Usage:** +```bash +# Session start context +python .claude/skills/oracle/scripts/generate_context.py --session-start + +# Task-specific context +python .claude/skills/oracle/scripts/generate_context.py --task "implement API" + +# Update claude.md +python .claude/skills/oracle/scripts/generate_context.py --output claude.md --update +``` + +### πŸ” Pattern Detection & Automation + +Oracle analyzes sessions to find: +- Repeated tasks β†’ automation candidates +- Common corrections β†’ update defaults +- Frequent queries β†’ add to auto-inject +- Token-heavy operations β†’ create scripts + +**Usage:** +```bash +# Analyze patterns +python .claude/skills/oracle/scripts/analyze_patterns.py + +# Generate automation scripts +python .claude/skills/oracle/scripts/analyze_patterns.py --generate-scripts --threshold 3 +``` + +## Integration + +### claude.md Integration + +Add Oracle context to `claude.md`: + +```markdown +## Project Knowledge (Oracle) + + + + +``` + +Update it: +```bash +python .claude/skills/oracle/scripts/generate_context.py --output claude.md --update +``` + +### Session Start Hook + +Auto-load context at session start: + +```bash +# Create hook +cat > .claude/hooks/session-start.sh << 'EOF' +#!/bin/bash +python .claude/skills/oracle/scripts/load_context.py +EOF + +chmod +x .claude/hooks/session-start.sh +``` + +### Git Hook Integration + +Auto-track commits: + +```bash +# Create post-commit hook +cat > .git/hooks/post-commit << 'EOF' +#!/bin/bash +python .claude/skills/oracle/scripts/record_commit.py +EOF + +chmod +x .git/hooks/post-commit +``` + +See `References/integration-guide.md` for complete integration options. + +## Directory Structure + +``` +.oracle/ +β”œβ”€β”€ knowledge/ +β”‚ β”œβ”€β”€ patterns.json # Code patterns +β”‚ β”œβ”€β”€ preferences.json # Team preferences +β”‚ β”œβ”€β”€ gotchas.json # Known issues +β”‚ β”œβ”€β”€ solutions.json # Proven solutions +β”‚ └── corrections.json # Historical corrections +β”œβ”€β”€ sessions/ +β”‚ └── YYYY-MM-DD_HHMMSS_*.md # Session logs +β”œβ”€β”€ timeline/ +β”‚ └── project_timeline.md # Chronological history +β”œβ”€β”€ scripts/ +β”‚ └── auto_*.sh # Generated automation +β”œβ”€β”€ hooks/ +β”‚ └── *.sh # Integration hooks +β”œβ”€β”€ index.json # Fast lookup index +└── README.md # Oracle documentation +``` + +## Scripts Reference + +| Script | Purpose | +|--------|---------| +| `init_oracle.py` | Initialize Oracle for a project | +| `record_session.py` | Record session activities and learnings | +| `query_knowledge.py` | Search knowledge base | +| `generate_context.py` | Generate context summaries | +| `analyze_patterns.py` | Detect patterns and automation opportunities | +| `load_context.py` | Load context (for hooks) | +| `record_commit.py` | Record git commits (for hooks) | + +Run any script with `--help` for detailed options. + +## Workflow Examples + +### Example 1: Daily Development + +```bash +# Morning - load context +python .claude/skills/oracle/scripts/load_context.py + +# Work with Claude... + +# Evening - record session +python .claude/skills/oracle/scripts/record_session.py --interactive +``` + +### Example 2: Bug Fix + +```bash +# Search for related knowledge +python .claude/skills/oracle/scripts/query_knowledge.py "bug keywords" + +# Fix bug with Claude... + +# Record the fix +python .claude/skills/oracle/scripts/record_session.py \ + --summary "Fixed bug in authentication" \ + --learnings "Root cause was connection timeout - added retry logic" +``` + +### Example 3: New Feature + +```bash +# Get context for the feature +python .claude/skills/oracle/scripts/generate_context.py --task "user profile feature" + +# Implement feature... + +# Record decisions and patterns +python .claude/skills/oracle/scripts/record_session.py --interactive +# Document: decisions made, patterns used, learnings +``` + +### Example 4: Weekly Maintenance + +```bash +# Analyze for patterns +python .claude/skills/oracle/scripts/analyze_patterns.py --generate-scripts + +# Review knowledge base +python .claude/skills/oracle/scripts/query_knowledge.py --summary + +# Update claude.md +python .claude/skills/oracle/scripts/generate_context.py --output claude.md --update +``` + +## Best Practices + +### 1. Record Sessions Immediately + +Don't wait - record while details are fresh. + +### 2. Be Specific in Corrections + +❌ "That's wrong" +βœ… "Don't use innerHTML -> use textContent to prevent XSS" + +### 3. Use Consistent Tags + +Review existing tags before creating new ones. + +### 4. Prioritize Ruthlessly + +- **Critical**: Security, data loss, breaking issues +- **High**: Important patterns, frequent gotchas +- **Medium**: Helpful solutions, preferences +- **Low**: Nice-to-know, historical context + +### 5. Leverage Automation + +When Oracle suggests automation, implement it. + +### 6. Review Monthly + +Knowledge bases get stale - review and update regularly. + +### 7. Integration is Key + +Set up hooks so Oracle works automatically. + +## Philosophy + +> "What is learned once should never be forgotten. What works should be remembered. What fails should be avoided." + +Oracle operates on: +- **KISS**: Simple, readable formats +- **Token Efficiency**: Dense storage, strategic recall +- **Learning from Feedback**: Adapt when corrected +- **Progressive Recall**: Load what's needed, when needed +- **Human-Readable**: No special tools required + +## Success Indicators + +βœ… **Oracle is working when:** +- Similar questions get "I remember we..." +- Corrections don't repeat +- Context is relevant without asking +- Knowledge base grows steadily +- Scripts reduce repetitive tasks +- Timeline shows clear project evolution + +❌ **Warning signs:** +- Same corrections repeating +- Duplicate knowledge entries +- Irrelevant context injections +- No automation scripts generated +- Timeline has gaps + +## Documentation + +- **SKILL.md**: Full skill definition and workflows +- **References/knowledge-schema.md**: Knowledge entry structure +- **References/session-log-template.md**: Session recording template +- **References/integration-guide.md**: Integration options and patterns +- **References/pattern-library.md**: Common patterns and anti-patterns + +## Troubleshooting + +### Context Not Loading + +```bash +# Check Oracle exists +ls -la .oracle/ + +# Test manually +python .claude/skills/oracle/scripts/load_context.py --verbose +``` + +### Knowledge Not Relevant + +```bash +# Use task-specific context +python .claude/skills/oracle/scripts/generate_context.py --task "specific task" + +# Review and update tags +python .claude/skills/oracle/scripts/query_knowledge.py --category patterns +``` + +### Too Much Context + +```bash +# Use tier 1 only (critical) +python .claude/skills/oracle/scripts/generate_context.py --tier 1 + +# Review priorities +python .claude/skills/oracle/scripts/query_knowledge.py --priority critical +``` + +## Use Cases + +### Solo Developer + +- Track personal learnings +- Build institutional knowledge +- Automate repeated tasks +- Prevent repeating mistakes + +### Team Project + +- Share knowledge via git +- Onboard new members +- Maintain consistency +- Document tribal knowledge + +### Open Source + +- Help contributors understand patterns +- Document decisions and rationale +- Reduce repetitive questions +- Build comprehensive knowledge base + +### Learning New Technology + +- Record discoveries +- Track corrections +- Build reference materials +- Compound knowledge over time + +## Examples + +See `Assets/` directory for: +- Example knowledge entries +- Sample session logs +- Generated automation scripts + +## Contributing + +Oracle is part of ClaudeShack. Improvements welcome! + +- Suggest new patterns +- Propose script enhancements +- Share successful workflows +- Report issues + +## Version + +**Oracle v1.0** +- Initial release +- Core knowledge management +- Session recording +- Pattern detection +- Context generation +- Automation script generation + +--- + +**"Remember everything. Learn from mistakes. Never waste context."** diff --git a/skills/oracle/References/integration-guide.md b/skills/oracle/References/integration-guide.md new file mode 100644 index 0000000..f4c873e --- /dev/null +++ b/skills/oracle/References/integration-guide.md @@ -0,0 +1,344 @@ +# Oracle Integration Guide + +This guide explains how to integrate Oracle into your development workflow for maximum effectiveness. + +## Integration Methods + +### 1. claude.md Integration (Recommended) + +The `claude.md` file is automatically loaded by Claude Code at session start. Integrating Oracle here ensures context is always available. + +#### Setup + +1. Create or open `claude.md` in your project root: + +```bash +touch claude.md +``` + +2. Add Oracle context section: + +```markdown +# Project Documentation + +## Project Knowledge (Oracle) + + + + +Run to update: +```bash +python .claude/skills/oracle/scripts/generate_context.py --output claude.md --update +``` + + + +## Project Overview + +[Your project description...] +``` + +3. Update Oracle context: + +```bash +python .claude/skills/oracle/scripts/generate_context.py --output claude.md --update +``` + +4. (Optional) Add to your workflow to auto-update after sessions. + +#### Auto-Update + +Add to your session workflow: + +```bash +# After recording a session +python .claude/skills/oracle/scripts/record_session.py --interactive +python .claude/skills/oracle/scripts/generate_context.py --output claude.md --update +``` + +### 2. Session Start Hooks + +Load Oracle context automatically when Claude Code starts. + +#### Setup + +1. Create hooks directory: + +```bash +mkdir -p .claude/hooks +``` + +2. Create session-start hook: + +```bash +cat > .claude/hooks/session-start.sh << 'EOF' +#!/bin/bash +# Load Oracle context at session start + +python .claude/skills/oracle/scripts/load_context.py +EOF + +chmod +x .claude/hooks/session-start.sh +``` + +3. Claude Code will run this hook at session start. + +### 3. Git Hooks Integration + +Track commits in Oracle timeline automatically. + +#### Setup + +1. Create post-commit hook: + +```bash +cat > .git/hooks/post-commit << 'EOF' +#!/bin/bash +# Record commit in Oracle timeline + +python .claude/skills/oracle/scripts/record_commit.py +EOF + +chmod +x .git/hooks/post-commit +``` + +2. Commits are now automatically tracked in `.oracle/timeline/project_timeline.md`. + +### 4. CI/CD Integration + +Update Oracle knowledge as part of your CI/CD pipeline. + +#### Example: GitHub Actions + +```yaml +name: Update Oracle + +on: + push: + branches: [ main ] + +jobs: + update-oracle: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Update Oracle Timeline + run: | + python .claude/skills/oracle/scripts/record_commit.py + + - name: Update claude.md + run: | + python .claude/skills/oracle/scripts/generate_context.py --output claude.md --update + + - name: Commit changes + run: | + git config --local user.email "oracle@bot" + git config --local user.name "Oracle Bot" + git add claude.md + git diff --quiet && git diff --staged --quiet || git commit -m "Update Oracle context [skip ci]" + git push +``` + +## Workflow Patterns + +### Pattern 1: Active Learning + +**For:** Projects where you're learning and making frequent corrections. + +**Workflow:** +1. Work on tasks +2. When corrected, immediately record: `python .claude/skills/oracle/scripts/record_session.py --corrections "wrong->right"` +3. Context auto-updates for next session +4. Corrections are avoided in future + +**Best for:** New projects, learning new technologies + +### Pattern 2: Pattern Detection + +**For:** Mature projects with established patterns. + +**Workflow:** +1. Record sessions regularly +2. Weekly: Run pattern analysis `python .claude/skills/oracle/scripts/analyze_patterns.py --generate-scripts` +3. Review and customize generated scripts +4. Use scripts for repeated tasks + +**Best for:** Established projects, teams with repeated workflows + +### Pattern 3: Knowledge Sharing + +**For:** Team projects where knowledge needs to be shared. + +**Workflow:** +1. Each team member records sessions +2. Knowledge base synced via git +3. `claude.md` updated and committed +4. All team members benefit from shared learning + +**Best for:** Team projects, open source projects + +### Pattern 4: Documentation Sync + +**For:** Projects where documentation must stay current. + +**Workflow:** +1. Record sessions with learnings +2. Use Oracle with documentation skill/tool +3. Auto-generate documentation updates from learnings +4. Review and commit documentation + +**Best for:** Projects requiring up-to-date docs + +## Context Injection Strategies + +### Strategy 1: Minimal Context (Default) + +Load only critical and high-priority items. + +```bash +python .claude/skills/oracle/scripts/generate_context.py --tier 1 +``` + +**Pros:** Low token usage, fast loading +**Cons:** May miss relevant context +**Use when:** Token budget is tight + +### Strategy 2: Relevant Context + +Load context relevant to current task. + +```bash +python .claude/skills/oracle/scripts/generate_context.py --task "implement authentication" +``` + +**Pros:** Highly relevant, moderate token usage +**Cons:** Requires knowing the task upfront +**Use when:** Starting a specific task + +### Strategy 3: Full Context + +Load all available knowledge. + +```bash +python .claude/skills/oracle/scripts/generate_context.py --tier 3 +``` + +**Pros:** Complete picture, no missing context +**Cons:** High token usage, may be overwhelming +**Use when:** Complex tasks, architecture decisions + +## Maintenance + +### Weekly Maintenance + +```bash +# 1. Analyze patterns +python .claude/skills/oracle/scripts/analyze_patterns.py + +# 2. Generate automation scripts +python .claude/skills/oracle/scripts/analyze_patterns.py --generate-scripts + +# 3. Update claude.md +python .claude/skills/oracle/scripts/generate_context.py --output claude.md --update + +# 4. Review knowledge base +python .claude/skills/oracle/scripts/query_knowledge.py --summary +``` + +### Monthly Maintenance + +1. Review corrections - are patterns emerging? +2. Update knowledge priorities +3. Archive old sessions (optional) +4. Review automation scripts - are they being used? +5. Clean up duplicate knowledge entries + +### Knowledge Curation + +Periodically review and curate: + +```bash +# Find rarely used knowledge +python .claude/skills/oracle/scripts/query_knowledge.py --sort used + +# Find old corrections (may be outdated) +python .claude/skills/oracle/scripts/query_knowledge.py --category corrections --sort recent +``` + +## Troubleshooting + +### Oracle Not Loading + +**Problem:** Context not appearing at session start. + +**Solutions:** +1. Check `.claude/hooks/session-start.sh` exists and is executable +2. Verify Oracle is initialized: `ls -la .oracle/` +3. Test manually: `python .claude/skills/oracle/scripts/load_context.py --verbose` + +### Context Too Large + +**Problem:** Too much context being injected. + +**Solutions:** +1. Use tier 1 context: `--tier 1` +2. Increase priorities of only critical items +3. Use task-specific context instead of session-start +4. Review and archive old knowledge + +### Knowledge Not Relevant + +**Problem:** Loaded knowledge doesn't apply to current task. + +**Solutions:** +1. Use task-specific context generation +2. Improve tags on knowledge entries +3. Use more specific queries +4. Update priorities to better reflect importance + +### Scripts Not Generating + +**Problem:** Pattern analysis doesn't find automation candidates. + +**Solutions:** +1. Lower threshold: `--threshold 2` +2. Record more sessions +3. Be consistent in activity descriptions +4. Manually identify patterns and add as automation opportunities + +## Best Practices + +### 1. Record Sessions Consistently + +Don't wait until end of day - record immediately after completing work while details are fresh. + +### 2. Be Specific in Corrections + +Instead of: "That's wrong" +Use: "Don't use innerHTML for user input -> use textContent to prevent XSS" + +### 3. Tag Thoughtfully + +Use consistent, meaningful tags. Review existing tags before creating new ones. + +### 4. Prioritize Ruthlessly + +Not everything is critical. Save high/critical priorities for things that truly matter. + +### 5. Review Regularly + +Knowledge bases become stale. Review and update monthly. + +### 6. Use Automation + +If pattern analysis suggests automation, implement it. Oracle works best when reducing repetitive LLM usage. + +### 7. Share Knowledge + +In team settings, commit Oracle knowledge to git (exclude sensitive session logs via .gitignore). + +--- + +**Integration Version**: 1.0 +**Last Updated**: 2025-11-19 diff --git a/skills/oracle/References/knowledge-schema.md b/skills/oracle/References/knowledge-schema.md new file mode 100644 index 0000000..cf19cf3 --- /dev/null +++ b/skills/oracle/References/knowledge-schema.md @@ -0,0 +1,368 @@ +# Oracle Knowledge Schema + +This document defines the structure and schema for Oracle knowledge entries. + +## Knowledge Entry Structure + +### JSON Format + +```json +{ + "id": "unique-identifier", + "category": "pattern|preference|gotcha|solution|correction", + "priority": "critical|high|medium|low", + "title": "Brief descriptive title (max 100 chars)", + "content": "Detailed information about this knowledge", + "context": "When this knowledge applies", + "examples": [ + "Example 1", + "Example 2" + ], + "learned_from": "session-id or source", + "created": "2025-11-19T10:30:00Z", + "last_used": "2025-11-19T10:30:00Z", + "use_count": 0, + "tags": ["tag1", "tag2", "tag3"] +} +``` + +### Field Descriptions + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | string (UUID) | Yes | Unique identifier for this entry | +| `category` | enum | Yes | Category: pattern, preference, gotcha, solution, or correction | +| `priority` | enum | Yes | Priority level: critical, high, medium, or low | +| `title` | string | Yes | Brief, descriptive title (max 100 characters) | +| `content` | string | Yes | Detailed information, explanation, or description | +| `context` | string | No | When/where this knowledge applies | +| `examples` | array[string] | No | Concrete examples demonstrating the knowledge | +| `learned_from` | string | No | Session ID or source where this was learned | +| `created` | ISO 8601 | Yes | When this entry was created | +| `last_used` | ISO 8601 | No | When this knowledge was last recalled/used | +| `use_count` | integer | No | Number of times this knowledge has been used | +| `tags` | array[string] | No | Tags for categorization and search | + +## Categories Explained + +### Pattern + +**Purpose**: Capture code patterns, architectural decisions, and conventions + +**When to use**: +- Established coding patterns in the project +- Architecture decisions +- Design patterns being used +- Coding conventions and style + +**Example**: +```json +{ + "category": "pattern", + "title": "Use factory pattern for database connections", + "content": "All database connections should be created through DatabaseFactory.create() which handles connection pooling, configuration, and error handling.", + "context": "When creating new database connections", + "examples": [ + "const db = DatabaseFactory.create('postgres')", + "const cache = DatabaseFactory.create('redis')" + ], + "priority": "high", + "tags": ["database", "factory-pattern", "architecture"] +} +``` + +### Preference + +**Purpose**: Capture user/team preferences and stylistic choices + +**When to use**: +- Team coding style preferences +- Tool choices +- Workflow preferences +- Naming conventions + +**Example**: +```json +{ + "category": "preference", + "title": "Prefer functional programming over OOP", + "content": "Team prefers functional programming style with pure functions, immutability, and composition over object-oriented approaches.", + "context": "When designing new features or refactoring code", + "examples": [ + "Use map/filter/reduce instead of for loops", + "Use pure functions without side effects" + ], + "priority": "medium", + "tags": ["coding-style", "functional-programming"] +} +``` + +### Gotcha + +**Purpose**: Capture known issues, pitfalls, edge cases, and things to avoid + +**When to use**: +- Known bugs or quirks +- Common mistakes +- Platform-specific issues +- Performance pitfalls +- Security concerns + +**Example**: +```json +{ + "category": "gotcha", + "title": "Database connection pool must be explicitly closed", + "content": "The database connection pool doesn't close automatically. Failing to call pool.close() in shutdown handlers causes memory leaks and prevents clean exits.", + "context": "When setting up application lifecycle hooks", + "examples": [ + "process.on('SIGTERM', () => pool.close())", + "app.on('shutdown', async () => await pool.close())" + ], + "priority": "critical", + "tags": ["database", "memory-leak", "lifecycle"] +} +``` + +### Solution + +**Purpose**: Capture proven solutions to specific problems + +**When to use**: +- Solutions to problems encountered +- Best practices discovered +- Successful implementations +- Recommended approaches + +**Example**: +```json +{ + "category": "solution", + "title": "Use cursor-based pagination for large datasets", + "content": "For datasets with millions of records, use cursor-based pagination instead of offset-based to avoid performance degradation and inconsistencies.", + "context": "When implementing pagination for large tables", + "examples": [ + "?cursor=xyz&limit=100 instead of ?page=1&limit=100", + "SELECT * FROM users WHERE id > $cursor LIMIT $limit" + ], + "priority": "high", + "tags": ["pagination", "performance", "database"] +} +``` + +### Correction + +**Purpose**: Capture mistakes Claude made and the correct approach + +**When to use**: +- User corrected Claude's approach +- Wrong assumptions were made +- Incorrect implementations +- Learning from mistakes + +**Example**: +```json +{ + "category": "correction", + "title": "Don't use innerHTML for user content", + "content": "❌ Wrong: element.innerHTML = userInput\nβœ“ Right: element.textContent = userInput\n\nReason: innerHTML allows XSS attacks when used with user input.", + "context": "When displaying user-generated content", + "examples": [ + "// Correct: element.textContent = comment.body", + "// Correct: element.appendChild(document.createTextNode(input))" + ], + "priority": "critical", + "tags": ["security", "xss", "dom"] +} +``` + +## Priority Levels + +### Critical + +- Security vulnerabilities +- Data loss risks +- Production-breaking issues +- Must be remembered and applied + +**Examples**: +- Security best practices +- Data integrity rules +- Critical gotchas + +### High + +- Important patterns +- Frequent corrections +- Performance considerations +- Should be remembered and applied + +**Examples**: +- Core architectural patterns +- Common gotchas +- Team preferences + +### Medium + +- Helpful solutions +- General preferences +- Nice-to-know patterns +- Could be remembered + +**Examples**: +- Helper patterns +- Coding style details +- Useful solutions + +### Low + +- Optional information +- Rare cases +- Historical context +- Reference only + +**Examples**: +- Deprecated approaches +- Rarely-used patterns +- Historical decisions + +## Tags Best Practices + +### Effective Tags + +**Technology/Framework**: +- `react`, `typescript`, `python`, `postgres`, `redis` + +**Domain**: +- `authentication`, `api`, `database`, `testing`, `deployment` + +**Type**: +- `security`, `performance`, `bug`, `refactoring` + +**Component**: +- `frontend`, `backend`, `database`, `infrastructure` + +### Tag Naming Conventions + +- Use lowercase +- Use hyphens for multi-word tags (`cursor-based-pagination`) +- Be specific but not too granular +- Reuse existing tags when possible +- Limit to 3-5 tags per entry + +## Index Schema + +The `index.json` file maintains metadata for quick access: + +```json +{ + "created": "2025-11-19T10:00:00Z", + "last_updated": "2025-11-19T15:30:00Z", + "total_entries": 42, + "categories": { + "patterns": 10, + "preferences": 5, + "gotchas": 8, + "solutions": 15, + "corrections": 4 + }, + "sessions": [ + "2025-11-19_session_001", + "2025-11-19_session_002" + ], + "version": "1.0" +} +``` + +## Migration and Versioning + +If the schema changes in future versions: + +1. Update `version` field in index +2. Provide migration scripts +3. Maintain backward compatibility where possible +4. Document breaking changes + +## Validation + +When adding entries programmatically, validate: + +1. **Required fields**: id, category, priority, title, content, created +2. **Valid enums**: category and priority must be from allowed values +3. **Type checking**: Ensure correct data types +4. **UUID format**: id should be valid UUID +5. **ISO 8601 dates**: created and last_used should be valid ISO 8601 + +## Best Practices + +### Writing Good Titles + +βœ… Good: +- "Use bcrypt for password hashing" +- "Database connections must use connection pool" +- "API responses include timestamp and request_id" + +❌ Bad: +- "Passwords" (too vague) +- "This is how we handle database connections and everything related to databases" (too long) +- "Thing about APIs" (not descriptive) + +### Writing Good Content + +βœ… Good: +- Clear, concise explanation +- Includes the "why" not just "what" +- Provides enough detail to apply the knowledge + +❌ Bad: +- Just restating the title +- Too verbose or includes irrelevant details +- Missing the rationale + +### Choosing Priority + +Ask yourself: +- **Critical**: Would ignoring this cause security issues, data loss, or production failures? +- **High**: Would ignoring this cause bugs, poor performance, or violate team standards? +- **Medium**: Would ignoring this make code harder to maintain or less optimal? +- **Low**: Is this just nice to know or historical context? + +### When to Create New Entry vs Update Existing + +**Create new entry** when: +- The knowledge is distinct and separate +- Different context or use case +- Different priority level + +**Update existing entry** when: +- Adding examples to existing knowledge +- Clarifying existing content +- Updating outdated information + +## Example: Complete Entry + +```json +{ + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "category": "gotcha", + "priority": "critical", + "title": "Redis client must handle connection failures", + "content": "Redis connections can fail intermittently in production. The client must implement exponential backoff retry logic and circuit breaker pattern to handle connection failures gracefully. Without this, the application will crash on Redis unavailability.", + "context": "When initializing Redis client or any external service connection", + "examples": [ + "redisClient.on('error', handleError)", + "Use ioredis with retry_strategy option", + "Implement circuit breaker with state: closed, open, half-open" + ], + "learned_from": "2025-11-19_session_003", + "created": "2025-11-19T14:30:00Z", + "last_used": "2025-11-19T15:45:00Z", + "use_count": 3, + "tags": ["redis", "error-handling", "resilience", "circuit-breaker"] +} +``` + +--- + +**Schema Version**: 1.0 +**Last Updated**: 2025-11-19 diff --git a/skills/oracle/References/pattern-library.md b/skills/oracle/References/pattern-library.md new file mode 100644 index 0000000..dcd0a65 --- /dev/null +++ b/skills/oracle/References/pattern-library.md @@ -0,0 +1,443 @@ +# Oracle Pattern Library + +Common patterns, solutions, and best practices for the Oracle knowledge management system. + +## Knowledge Entry Patterns + +### Pattern: Security Critical + +**When:** Recording security-related knowledge + +**Structure:** +```json +{ + "category": "gotcha" or "correction", + "priority": "critical", + "title": "Always [security practice]", + "content": "Security vulnerability description and correct approach", + "context": "When handling [user input/authentication/etc]", + "tags": ["security", "xss|sql-injection|csrf|etc"] +} +``` + +**Example:** +- Input validation +- Authentication bypasses +- Injection vulnerabilities +- Data exposure + +### Pattern: Performance Optimization + +**When:** Recording performance-related learnings + +**Structure:** +```json +{ + "category": "solution", + "priority": "high", + "title": "Use [optimized approach] for [use case]", + "content": "Performance problem and solution with metrics", + "examples": ["Before/after code"], + "tags": ["performance", "optimization"] +} +``` + +**Example:** +- Caching strategies +- Query optimization +- Algorithm improvements + +### Pattern: Common Mistake + +**When:** User corrects a repeated mistake + +**Structure:** +```json +{ + "category": "correction", + "priority": "high", + "title": "Don't [wrong approach]", + "content": "❌ Wrong: [what not to do]\\nβœ“ Right: [correct approach]\\nReason: [why]", + "context": "When [situation]", + "tags": ["common-mistake"] +} +``` + +**Example:** +- API misuse +- Framework gotchas +- Language quirks + +## Session Recording Patterns + +### Pattern: Bug Fix Session + +**When:** Session focused on fixing a bug + +**Template:** +```markdown +## Summary +Fixed [bug description] + +## Activities +- Identified root cause in [location] +- Implemented fix in [location] +- Added tests to prevent regression + +## Changes Made +- File: [path] + - Change: [what changed] + - Reason: Fix for [bug] + +## Learnings +- 🟑 [HIGH] [Root cause and how to prevent] + +## Corrections (if applicable) +- What was wrong: [incorrect assumption/approach] +- What's right: [correct understanding/approach] +``` + +### Pattern: Feature Implementation + +**When:** Adding new functionality + +**Template:** +```markdown +## Summary +Implemented [feature name] + +## Activities +- Designed [component/system] +- Implemented [components] +- Added tests +- Updated documentation + +## Decisions +- Decision: [technical decision made] + - Rationale: [why this approach] + - Alternatives: [other options considered] + +## Learnings +- [Patterns established] +- [Solutions used] +``` + +### Pattern: Refactoring Session + +**When:** Improving existing code + +**Template:** +```markdown +## Summary +Refactored [what] to [improvement] + +## Activities +- Analyzed current implementation +- Refactored [components] +- Verified no breaking changes + +## Changes Made +- [List of files and changes] + +## Learnings +- [Patterns applied] +- [Anti-patterns removed] +``` + +## Query Patterns + +### Pattern: Pre-Task Context + +**When:** Starting a new task + +**Command:** +```bash +python .claude/skills/oracle/scripts/query_knowledge.py --task "implement [feature]" --priority high +``` + +**What it does:** +- Surfaces relevant patterns +- Shows related solutions +- Highlights potential gotchas + +### Pattern: Post-Correction + +**When:** After being corrected + +**Command:** +```bash +python .claude/skills/oracle/scripts/record_session.py --corrections "wrong->right" +python .claude/skills/oracle/scripts/query_knowledge.py --category corrections --recent 10 +``` + +**What it does:** +- Records the correction +- Shows recent corrections to identify patterns + +### Pattern: Weekly Review + +**When:** Regular maintenance + +**Commands:** +```bash +# See what we've learned +python .claude/skills/oracle/scripts/query_knowledge.py --recent 20 + +# Find automation opportunities +python .claude/skills/oracle/scripts/analyze_patterns.py + +# Update documentation +python .claude/skills/oracle/scripts/generate_context.py --output claude.md --update +``` + +## Integration Patterns + +### Pattern: Continuous Learning + +**Setup:** +1. `claude.md` with Oracle section +2. Session start hook loads context +3. After each session: record learnings +4. Weekly: analyze patterns + +**Benefit:** Continuous improvement, knowledge compounds + +### Pattern: Team Knowledge Base + +**Setup:** +1. Oracle in git repository +2. `.gitignore` excludes sensitive session logs +3. `claude.md` committed and shared +4. Team members contribute learnings + +**Benefit:** Shared institutional knowledge + +### Pattern: Documentation Sync + +**Setup:** +1. Record sessions with learnings +2. Oracle context in `claude.md` +3. Use learnings to update docs +4. Keep docs and knowledge in sync + +**Benefit:** Documentation stays current + +## Automation Patterns + +### Pattern: Repeated Command + +**Detection:** +Activity appears 3+ times: "Run tests" + +**Action:** +```bash +# Oracle generates +./auto_run_tests.sh + +# Which contains +#!/bin/bash +npm test +# or pytest +# or cargo test +``` + +**Usage:** Use script instead of asking Claude + +### Pattern: Multi-Step Process + +**Detection:** +Same sequence appears repeatedly: +1. "Build project" +2. "Run linter" +3. "Run tests" + +**Action:** +```bash +# Oracle generates +./auto_pre_commit.sh + +# Which contains +#!/bin/bash +npm run build && npm run lint && npm test +``` + +### Pattern: Context Generation + +**Detection:** +Repeatedly asking about same topic + +**Action:** +Add to claude.md auto-inject: +```markdown +## Authentication Context + +[Relevant knowledge auto-injected] +``` + +## Anti-Patterns (What NOT to Do) + +### Anti-Pattern: Recording Everything + +❌ **Don't:** Record every tiny action +βœ“ **Do:** Record meaningful learnings and corrections + +**Why:** Bloats knowledge base, harder to find relevant info + +### Anti-Pattern: Vague Entries + +❌ **Don't:** "Something about databases" +βœ“ **Do:** "Use connection pooling for database connections to prevent connection exhaustion" + +**Why:** Vague entries aren't useful for recall + +### Anti-Pattern: Duplicate Knowledge + +❌ **Don't:** Create new entry for same knowledge +βœ“ **Do:** Update existing entry or add examples + +**Why:** Duplicates cause confusion and bloat + +### Anti-Pattern: Wrong Priorities + +❌ **Don't:** Mark everything as critical +βœ“ **Do:** Save critical for security, data loss, breaking issues + +**Why:** Dilutes importance, everything seems urgent + +### Anti-Pattern: No Tags + +❌ **Don't:** Skip tags thinking title is enough +βœ“ **Do:** Add 2-4 relevant tags + +**Why:** Tags enable better search and categorization + +### Anti-Pattern: Never Reviewing + +❌ **Don't:** Set and forget +βœ“ **Do:** Review monthly, update priorities, archive old entries + +**Why:** Stale knowledge becomes misleading + +### Anti-Pattern: Ignoring Automations + +❌ **Don't:** Keep asking LLM for same repeated task +βœ“ **Do:** Use or create automation scripts + +**Why:** Wastes tokens and time on deterministic tasks + +## Common Use Cases + +### Use Case 1: Onboarding New Developers + +**Situation:** New team member joining project + +**Solution:** +```bash +# Generate comprehensive onboarding context +python .claude/skills/oracle/scripts/generate_context.py --tier 2 > onboarding.md + +# Include: +# - Critical gotchas +# - Key patterns +# - Team preferences +# - Common solutions +``` + +### Use Case 2: Context Switching + +**Situation:** Coming back to project after time away + +**Solution:** +```bash +# Load session start context +python .claude/skills/oracle/scripts/load_context.py + +# Review recent sessions +python .claude/skills/oracle/scripts/query_knowledge.py --recent 10 + +# Review project timeline +cat .oracle/timeline/project_timeline.md | tail -50 +``` + +### Use Case 3: Bug Investigation + +**Situation:** Investigating a bug + +**Solution:** +```bash +# Search for related issues +python .claude/skills/oracle/scripts/query_knowledge.py "bug-related-keywords" + +# Check if similar bug was fixed before +python .claude/skills/oracle/scripts/query_knowledge.py --category gotchas --tags bug + +# After fix, record to prevent recurrence +python .claude/skills/oracle/scripts/record_session.py --interactive +``` + +### Use Case 4: Architecture Decision + +**Situation:** Making important architectural choice + +**Solution:** +```bash +# Review existing patterns +python .claude/skills/oracle/scripts/query_knowledge.py --category patterns + +# Review past decisions +grep "Decision:" .oracle/sessions/*.md + +# After deciding, record rationale +python .claude/skills/oracle/scripts/record_session.py \ + --summary "Decided to use [approach]" \ + --learnings "Use [approach] because [rationale]" +``` + +## Success Metrics + +Track these to measure Oracle effectiveness: + +### Metric 1: Correction Reduction + +**Measure:** Count corrections per week + +**Target:** Declining trend (learning from mistakes) + +**How:** +```bash +grep -c "## Corrections" .oracle/sessions/*.md +``` + +### Metric 2: Knowledge Reuse + +**Measure:** use_count in knowledge entries + +**Target:** Increasing use counts on valuable entries + +**How:** +```bash +python .claude/skills/oracle/scripts/query_knowledge.py --sort used +``` + +### Metric 3: Automation Adoption + +**Measure:** Generated scripts being used + +**Target:** High usage of automation scripts + +**How:** +Check git history for manual vs scripted operations + +### Metric 4: Context Relevance + +**Measure:** How often injected context is actually useful + +**Target:** High relevance rate + +**How:** +Subjective assessment during sessions + +--- + +**Pattern Library Version**: 1.0 +**Last Updated**: 2025-11-19 diff --git a/skills/oracle/References/session-log-template.md b/skills/oracle/References/session-log-template.md new file mode 100644 index 0000000..0f0308d --- /dev/null +++ b/skills/oracle/References/session-log-template.md @@ -0,0 +1,72 @@ +# Session: YYYY-MM-DD HH:MM + +**Session ID**: `session-id-here` + +## Summary + +[Brief 1-2 sentence summary of what was accomplished this session] + +## Activities + +- [Activity 1] +- [Activity 2] +- [Activity 3] + +## Changes Made + +- **File**: `path/to/file.ts` + - Change: [Description of what changed] + - Reason: [Why this change was made] + +- **File**: `path/to/another/file.py` + - Change: [Description of what changed] + - Reason: [Why this change was made] + +## Decisions + +- **Decision**: [What was decided] + - Rationale: [Why this decision was made] + - Alternatives considered: [What other options were considered] + - Impact: [What this affects] + +## Learnings + +- πŸ”΄ **[CRITICAL]** [Critical learning - security, data loss, etc.] + - Context: [When this applies] + - Applied to: [What this affects] + +- 🟑 **[HIGH]** [Important learning - patterns, gotchas] + - Context: [When this applies] + +- πŸ”΅ **[MEDIUM]** [Helpful learning - solutions, preferences] + +## Corrections + +- **Correction**: [What was wrong β†’ what's right] + - ❌ Wrong: [What Claude did incorrectly] + - βœ“ Right: [The correct approach] + - Context: [When this applies] + - Prevention: [How to avoid in future] + +## Questions Asked + +- **Q**: [Question that was asked] + - **A**: [Answer provided] + - Relevant knowledge: [Links to related knowledge entries] + +## Automation Opportunities + +- **Task**: [Repeated task that could be automated] + - Frequency: [How often this occurs] + - Candidate for: [Script/template/pattern] + - Complexity: [Simple/Medium/Complex] + +## Next Session Preparation + +- [Thing to remember for next time] +- [Context to load at next session start] +- [Follow-up task] + +--- + +*Recorded: YYYY-MM-DDTHH:MM:SSZ* diff --git a/skills/oracle/SKILL.md b/skills/oracle/SKILL.md new file mode 100644 index 0000000..a79191a --- /dev/null +++ b/skills/oracle/SKILL.md @@ -0,0 +1,489 @@ +--- +name: oracle +description: Project memory and learning system that tracks interactions, learns from corrections, maintains knowledge across sessions, and generates token-efficient context. Use when you need to remember project-specific patterns, avoid repeating mistakes, track what works and what doesn't, or maintain institutional knowledge across multiple sessions. Integrates with guardian, wizard, summoner, and style-master. +allowed-tools: Read, Write, Edit, Bash, Glob, Grep +--- + +# Oracle: Project Memory & Learning System + +You are now operating as the **Oracle**, a sophisticated memory and learning system designed to maintain institutional knowledge, learn from corrections, and prevent the waste of context and effort across sessions. + +## Core Philosophy + +**"What is learned once should never be forgotten. What works should be remembered. What fails should be avoided."** + +The Oracle operates on these principles: + +1. **KISS (Keep It Simple, Stupid)**: Simple, readable formats over complex systems +2. **Token Efficiency**: Store knowledge densely, recall strategically +3. **Learning from Feedback**: When corrected, record and adapt +4. **Progressive Recall**: Load only relevant knowledge when needed +5. **Human-Readable**: All knowledge accessible without special tools + +## Core Responsibilities + +### 1. Session Recording + +Track every interaction to build a comprehensive project timeline: + +- **Questions Asked**: What users want to know +- **Changes Made**: What was modified and why +- **Corrections Received**: When Claude got it wrong +- **Successes**: What worked well +- **Failures**: What didn't work and why +- **Decisions**: Why specific approaches were chosen + +### 2. Knowledge Management + +Maintain a structured, searchable knowledge base: + +**Categories**: +- **Patterns**: Code patterns, architectural decisions, conventions +- **Preferences**: User/team preferences, style choices +- **Gotchas**: Known issues, pitfalls, edge cases +- **Solutions**: Proven solutions to common problems +- **Corrections**: Historical mistakes to avoid +- **Context**: Project-specific context and background + +### 3. Learning from Corrections + +When users say "that's wrong" or "don't do it that way": + +1. **Record the Correction**: What was wrong, what's right +2. **Identify the Pattern**: Why did this mistake happen? +3. **Update Knowledge**: Add to knowledge base with high priority +4. **Flag for Recall**: Mark as critical for future sessions +5. **Generate Reminder**: Create context injection for similar situations + +### 4. Strategic Context Injection + +Provide relevant knowledge at the right time: + +- **Session Start**: Load project overview and recent learnings +- **Before Coding**: Recall relevant patterns and gotchas +- **On Similar Tasks**: Surface previous solutions +- **On Corrections**: Show what we learned from past mistakes + +### 5. Timeline & History + +Maintain detailed project lifecycle: + +- **Chronological Log**: What happened when +- **Evolution Tracking**: How decisions evolved over time +- **Contributor Activity**: Who worked on what +- **Knowledge Growth**: How understanding improved + +### 6. Automation Opportunities + +Identify tasks that can be scripted instead of using LLM: + +- **Repeated Patterns**: Same task done multiple times +- **Deterministic Operations**: No decision-making required +- **Token-Heavy Tasks**: Better done by scripts +- **Validation Checks**: Automated quality checks + +## Knowledge Storage Structure + +### Directory Layout + +``` +.oracle/ +β”œβ”€β”€ knowledge/ +β”‚ β”œβ”€β”€ patterns.json # Code patterns and conventions +β”‚ β”œβ”€β”€ preferences.json # User/team preferences +β”‚ β”œβ”€β”€ gotchas.json # Known issues and pitfalls +β”‚ β”œβ”€β”€ solutions.json # Proven solutions +β”‚ └── corrections.json # Historical corrections +β”œβ”€β”€ sessions/ +β”‚ β”œβ”€β”€ 2025-11-19_session_001.md +β”‚ β”œβ”€β”€ 2025-11-19_session_002.md +β”‚ └── ... +β”œβ”€β”€ timeline/ +β”‚ └── project_timeline.md # Chronological history +β”œβ”€β”€ scripts/ +β”‚ └── [auto-generated scripts] +└── index.json # Fast lookup index +``` + +### Knowledge Entry Format + +```json +{ + "id": "unique-id", + "category": "pattern|preference|gotcha|solution|correction", + "priority": "critical|high|medium|low", + "title": "Brief description", + "content": "Detailed information", + "context": "When this applies", + "examples": ["example1", "example2"], + "learned_from": "session-id or source", + "created": "2025-11-19T10:30:00Z", + "last_used": "2025-11-19T10:30:00Z", + "use_count": 5, + "tags": ["tag1", "tag2"] +} +``` + +## Workflow + +### Session Start + +``` +1. Initialize Oracle for this session + ↓ +2. Load project context from .oracle/ + ↓ +3. Review recent sessions and learnings + ↓ +4. Prepare relevant knowledge for injection + ↓ +5. Begin session with context-aware state +``` + +### During Session + +``` +1. Monitor interactions + ↓ +2. Detect corrections or feedback + ↓ +3. Record decisions and changes + ↓ +4. When corrected: + - Record what was wrong + - Record what's right + - Update knowledge base + - Flag for future recall + ↓ +5. When similar context arises: + - Recall relevant knowledge + - Apply learned patterns + - Avoid known mistakes +``` + +### Session End + +``` +1. Summarize session activities + ↓ +2. Extract new learnings + ↓ +3. Update knowledge base + ↓ +4. Update timeline + ↓ +5. Generate context summary for next session + ↓ +6. Identify automation opportunities + ↓ +7. Create scripts if patterns detected +``` + +## Integration Points + +### 1. Claude.md Integration + +Add to your project's `claude.md`: + +```markdown +## Project Knowledge (Oracle) + + +[Auto-injected context from Oracle knowledge base] + +Key Patterns: +- [Critical patterns for this project] + +Recent Learnings: +- [What we learned in recent sessions] + +Known Gotchas: +- [Issues to avoid] + +Preferences: +- [Team/user preferences] + +``` + +Oracle will update this section automatically. + +### 2. Hooks Integration + +Create a `.claude/hooks/session-start.sh`: + +```bash +#!/bin/bash +# Load Oracle context at session start +python .claude/skills/oracle/scripts/load_context.py +``` + +### 3. Pre-Commit Hook + +Create a `.oracle/hooks/pre-commit.sh`: + +```bash +#!/bin/bash +# Record commit in Oracle timeline +python .claude/skills/oracle/scripts/record_commit.py +``` + +## Using Oracle + +### Initialize Oracle for a Project + +```bash +python .claude/skills/oracle/scripts/init_oracle.py +``` + +This creates the `.oracle/` directory structure. + +### Record a Session + +During or after a session: + +```bash +python .claude/skills/oracle/scripts/record_session.py \ + --summary "Implemented user authentication" \ + --learnings "Use bcrypt for password hashing, not md5" \ + --corrections "Don't store passwords in plain text" +``` + +### Query Knowledge + +Find relevant knowledge: + +```bash +# Search by keyword +python .claude/skills/oracle/scripts/query_knowledge.py "authentication" + +# Get specific category +python .claude/skills/oracle/scripts/query_knowledge.py --category patterns + +# Get high-priority items +python .claude/skills/oracle/scripts/query_knowledge.py --priority critical +``` + +### Generate Context + +Create context summary for injection: + +```bash +# For current task +python .claude/skills/oracle/scripts/generate_context.py --task "implement API endpoints" + +# For claude.md +python .claude/skills/oracle/scripts/generate_context.py --output claude.md + +# For session start +python .claude/skills/oracle/scripts/generate_context.py --session-start +``` + +### Analyze Patterns + +Identify automation opportunities: + +```bash +python .claude/skills/oracle/scripts/analyze_patterns.py +``` + +This detects: +- Repeated tasks (candidates for scripts) +- Common corrections (update defaults) +- Frequent queries (add to auto-inject) +- Token-heavy operations (automate) + +## Knowledge Categories Explained + +### Patterns + +**What**: Code patterns, architectural decisions, conventions + +**Examples**: +- "We use factory pattern for creating database connections" +- "API responses always include timestamp and request_id" +- "Error handling uses Result pattern" + +### Preferences + +**What**: User/team style and approach preferences + +**Examples**: +- "Prefer functional programming over OOP" +- "Use explicit variable names, no abbreviations" +- "Write tests before implementation (TDD)" + +### Gotchas + +**What**: Known issues, pitfalls, edge cases + +**Examples**: +- "Database connection pool must be closed or memory leak occurs" +- "Don't use Date() for timestamps, use Date.now()" +- "API rate limit is 100 req/min, need exponential backoff" + +### Solutions + +**What**: Proven solutions to specific problems + +**Examples**: +- "For pagination, use cursor-based not offset-based" +- "Handle race conditions with optimistic locking" +- "Use Redis for session storage, not in-memory" + +### Corrections + +**What**: Mistakes Claude made and the correct approach + +**Examples**: +- "❌ Don't use innerHTML (XSS risk) βœ“ Use textContent" +- "❌ Don't mutate state directly βœ“ Use setState/immutable updates" +- "❌ Don't catch all exceptions βœ“ Catch specific exceptions" + +## Context Injection Strategy + +Oracle uses **tiered context loading** to optimize token usage: + +### Tier 1: Always Load (Critical) +- Project overview (1-2 sentences) +- Critical gotchas (high-priority warnings) +- Recent corrections (last 5 sessions) +- Active patterns (frequently used) + +### Tier 2: Load on Relevance (Contextual) +- Patterns matching current task +- Solutions to similar problems +- Related preferences +- Historical decisions + +### Tier 3: Load on Request (Archive) +- Full session history +- All solutions +- Complete timeline +- Deprecated patterns + +## Automation Script Generation + +When Oracle detects repeated patterns: + +### Detection Criteria + +- Same task performed 3+ times +- Task is deterministic (no decisions) +- Task is token-heavy (>1000 tokens) +- Task has clear inputs/outputs + +### Script Generation + +Oracle creates: + +```bash +# .oracle/scripts/auto_generated_task_name.sh +#!/bin/bash +# Auto-generated by Oracle on 2025-11-19 +# Purpose: [what this does] +# Usage: ./auto_generated_task_name.sh [args] + +[script content] +``` + +And updates knowledge: + +```json +{ + "category": "automation", + "title": "Task can be automated", + "content": "Use .oracle/scripts/auto_generated_task_name.sh instead of asking Claude", + "priority": "high" +} +``` + +## Quality Indicators + +### βœ… Oracle is Working Well When: + +- Similar questions get answered with "I remember we..." +- Corrections don't repeat (learned from mistakes) +- Context is relevant without being asked +- Knowledge base grows steadily +- Scripts reduce repetitive LLM usage +- Timeline provides clear project evolution + +### ❌ Warning Signs: + +- Same corrections happening repeatedly +- Knowledge base has duplicates +- Context injections not relevant +- No automation scripts generated +- Timeline has gaps +- Knowledge queries return no results + +## Session Recording Format + +Each session creates a structured log: + +```markdown +# Session: 2025-11-19 10:30 AM + +## Summary +[What was accomplished this session] + +## Activities +- [Activity 1] +- [Activity 2] + +## Changes Made +- File: path/to/file.ts + - Change: [what changed] + - Reason: [why] + +## Decisions +- Decision: [what was decided] + - Rationale: [why] + - Alternatives considered: [what else] + +## Learnings +- Learning: [what we learned] + - Priority: [critical/high/medium/low] + - Applied to: [what this affects] + +## Corrections +- Correction: [what was wrong β†’ what's right] + - Context: [when this applies] + - Prevention: [how to avoid in future] + +## Questions Asked +- Q: [question] + - A: [answer] + - Relevant knowledge: [related items] + +## Automation Opportunities +- Task: [repeated task] + - Frequency: [how often] + - Candidate for: [script/template/pattern] + +## Next Session Preparation +- [Things to remember for next time] +``` + +## Templates & References + +- **Knowledge Schema**: See `References/knowledge-schema.md` +- **Session Log Template**: See `References/session-log-template.md` +- **Integration Guide**: See `References/integration-guide.md` +- **Pattern Library**: See `References/pattern-library.md` + +## Remember + +> "The Oracle doesn't just remember - it learns, adapts, and prevents waste." + +Your role as Oracle: +1. **Never forget** what's been learned +2. **Always recall** relevant knowledge +3. **Continuously learn** from feedback +4. **Proactively suggest** improvements +5. **Automate** what can be automated +6. **Preserve context** across sessions + +--- + +**Oracle activated. All knowledge preserved. Learning enabled. Context ready.** diff --git a/skills/oracle/scripts/HOOK_SETUP.md b/skills/oracle/scripts/HOOK_SETUP.md new file mode 100644 index 0000000..b0fa9d8 --- /dev/null +++ b/skills/oracle/scripts/HOOK_SETUP.md @@ -0,0 +1,302 @@ +# Oracle SessionStart Hook Setup + +This guide explains how to configure Claude Code to automatically load Oracle context when sessions start. + +## Overview + +The SessionStart hook automatically injects Oracle knowledge into every new or resumed Claude Code session, ensuring Claude always has access to: +- Critical gotchas and warnings +- Recent corrections +- High-priority patterns and solutions +- Project-specific preferences + +## Quick Setup + +### 1. Add Hook to Claude Code Settings + +Edit your Claude Code settings file (location varies by platform): +- **macOS**: `~/Library/Application Support/Claude/settings.json` +- **Linux**: `~/.config/Claude/settings.json` +- **Windows**: `%APPDATA%\Claude\settings.json` + +Add this configuration to the `hooks` section: + +```json +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "python /full/path/to/ClaudeShack/skills/oracle/scripts/session_start_hook.py" + } + ] + } + ] + } +} +``` + +**Important**: Replace `/full/path/to/ClaudeShack` with the actual absolute path to your ClaudeShack directory. + +### 2. Test the Hook + +Test that the hook works by running it manually: + +```bash +cd /path/to/your/project +python /path/to/ClaudeShack/skills/oracle/scripts/session_start_hook.py --debug +``` + +You should see Oracle context output to stderr. If you see "Oracle: Not initialized", run: + +```bash +python /path/to/ClaudeShack/skills/oracle/scripts/init_oracle.py +``` + +### 3. Start a New Session + +Start a new Claude Code session. Oracle context should automatically be injected! + +You'll see something like: + +```markdown +# Oracle Project Knowledge + +Knowledge Base: 25 entries | 5 sessions recorded + +## Key Knowledge + +### Gotchas (Watch Out!) + +- **[CRITICAL]** Database connections must be closed explicitly +- **API rate limit is 100 req/min** + +### Recent Corrections + +- Use textContent instead of innerHTML for user input (XSS prevention) +- Always use async/await, not callbacks +``` + +## Configuration Options + +### Context Tier Levels + +Control how much context is loaded using the `ORACLE_CONTEXT_TIER` environment variable: + +```bash +# In your shell profile (.bashrc, .zshrc, etc.): +export ORACLE_CONTEXT_TIER=1 # Default: Critical + High priority only +export ORACLE_CONTEXT_TIER=2 # Include Medium priority +export ORACLE_CONTEXT_TIER=3 # All knowledge +``` + +Or pass it directly in the hook configuration: + +```json +{ + "type": "command", + "command": "ORACLE_CONTEXT_TIER=2 python /path/to/session_start_hook.py" +} +``` + +Or use the CLI argument: + +```json +{ + "type": "command", + "command": "python /path/to/session_start_hook.py --tier 2" +} +``` + +### Maximum Context Length + +Limit context size to avoid overwhelming the session: + +```bash +export ORACLE_MAX_CONTEXT_LENGTH=5000 # Default: 5000 characters +export ORACLE_MAX_CONTEXT_LENGTH=10000 # More context +export ORACLE_MAX_CONTEXT_LENGTH=2000 # Less context +``` + +Or via CLI: + +```json +{ + "type": "command", + "command": "python /path/to/session_start_hook.py --max-length 10000" +} +``` + +## Advanced Configuration + +### Hook on Resume Only + +To load Oracle context only when resuming sessions (not on new sessions): + +```json +{ + "hooks": { + "SessionStart": [ + { + "matcher": "resume", + "hooks": [ + { + "type": "command", + "command": "python /path/to/session_start_hook.py --source resume" + } + ] + } + ] + } +} +``` + +### Hook on Both Startup and Resume + +```json +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "python /path/to/session_start_hook.py --source startup" + } + ] + }, + { + "matcher": "resume", + "hooks": [ + { + "type": "command", + "command": "python /path/to/session_start_hook.py --source resume" + } + ] + } + ] + } +} +``` + +### Per-Project Configuration + +If you work with multiple projects, you can use different configurations: + +```json +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "pathPattern": "**/my-critical-project/**", + "hooks": [ + { + "type": "command", + "command": "ORACLE_CONTEXT_TIER=1 python /path/to/session_start_hook.py" + } + ] + }, + { + "matcher": "startup", + "pathPattern": "**/my-casual-project/**", + "hooks": [ + { + "type": "command", + "command": "ORACLE_CONTEXT_TIER=3 python /path/to/session_start_hook.py" + } + ] + } + ] + } +} +``` + +## Troubleshooting + +### Hook Not Running + +1. **Check settings file syntax**: Ensure valid JSON (no trailing commas, proper quotes) +2. **Check paths**: Use absolute paths, not relative +3. **Check permissions**: Ensure script is executable (`chmod +x session_start_hook.py`) +4. **Test manually**: Run the script from your project directory + +### No Context Showing + +1. **Verify Oracle is initialized**: Run `ls -la .oracle/` in your project +2. **Check if knowledge exists**: Run `python /path/to/query_knowledge.py --summary` +3. **Test hook in debug mode**: `python session_start_hook.py --debug` + +### Context Too Large + +Reduce context with: +- Lower tier level (`ORACLE_CONTEXT_TIER=1`) +- Smaller max length (`ORACLE_MAX_CONTEXT_LENGTH=3000`) +- Prioritize your knowledge entries (set priority to `low` for less critical items) + +### Context Not Relevant + +The SessionStart hook loads critical/high priority items only. To get task-specific context: + +1. Use the oracle skill manually: `/oracle` (if available) +2. Run: `python /path/to/generate_context.py --task "your task description"` +3. Query specific knowledge: `python /path/to/query_knowledge.py "keywords"` + +## Best Practices + +1. **Keep critical items truly critical**: Only mark security, data loss, and breaking issues as critical +2. **Regular cleanup**: Review and remove outdated knowledge monthly +3. **Use tags**: Tag knowledge for better organization +4. **Record sessions**: Use `record_session.py` after important sessions +5. **Analyze history**: Run `analyze_history.py --auto-populate` weekly to mine conversation history + +## Hook Output Format + +The hook outputs JSON in this format: + +```json +{ + "hookSpecificOutput": { + "hookEventName": "SessionStart", + "additionalContext": "# Oracle Project Knowledge\n\n..." + } +} +``` + +Claude Code reads the `additionalContext` field and injects it into the session context. + +## Verification + +To verify the hook is working: + +1. Start a new session +2. Ask Claude: "What do you know about this project from Oracle?" +3. Claude should reference the injected knowledge + +## Disable Hook Temporarily + +To temporarily disable the hook without removing configuration: + +1. Add a condition to the matcher that won't match +2. Or comment out the hook in settings (use `//` in JSONC format if supported) +3. Or set environment variable: `export ORACLE_HOOK_DISABLED=1` + +## Related Scripts + +- `init_oracle.py` - Initialize Oracle for a project +- `record_session.py` - Record session learnings +- `query_knowledge.py` - Query knowledge base +- `generate_context.py` - Generate context summaries +- `analyze_history.py` - Mine conversation history + +## Support + +For issues or questions: +1. Check the troubleshooting section above +2. Review Claude Code hooks documentation +3. Test the script manually with `--debug` flag +4. Check Claude Code logs for hook execution errors diff --git a/skills/oracle/scripts/analyze_history.py b/skills/oracle/scripts/analyze_history.py new file mode 100755 index 0000000..201f205 --- /dev/null +++ b/skills/oracle/scripts/analyze_history.py @@ -0,0 +1,701 @@ +#!/usr/bin/env python3 +""" +Oracle Conversation History Analyzer + +Analyzes Claude Code conversation history from ~/.claude/projects/ and extracts: +- Patterns and repeated tasks +- Corrections and learnings +- User preferences and gotchas +- Automation opportunities + +This script mines existing conversation data without requiring manual capture. + +Usage: + python analyze_history.py [options] + python analyze_history.py --project-hash abc123 --auto-populate + python analyze_history.py --all-projects --recent-days 30 + python analyze_history.py --analyze-only + +Examples: + python analyze_history.py --auto-populate + python analyze_history.py --project-hash abc123def456 + python analyze_history.py --all-projects --min-confidence 0.7 +""" + +import os +import sys +import json +import argparse +from datetime import datetime, timedelta +from pathlib import Path +from collections import defaultdict, Counter +import re +import uuid + + +CLAUDE_PROJECTS_PATH = Path.home() / '.claude' / 'projects' + +# Configuration constants +CONFIG = { + 'MAX_TITLE_LENGTH': 200, + 'ACTION_CONTEXT_MIN_LEN': 10, + 'ACTION_CONTEXT_MAX_LEN': 50, + 'TOP_TOOLS_TO_REPORT': 20, + 'TOP_CORRECTIONS_TO_ADD': 10, + 'TOP_GOTCHAS_TO_ADD': 10, + 'TOP_TASKS_TO_ADD': 5, + 'MAX_PREFERENCES_TO_ADD': 10, + 'DEFAULT_MIN_TASK_OCCURRENCES': 3, + 'SNIPPET_LENGTH': 80, +} + +# Precompiled regex patterns for performance +CORRECTION_PATTERNS = [ + re.compile(r"(?:that's|thats)\s+(?:wrong|incorrect|not right)", re.IGNORECASE), + re.compile(r"(?:don't|dont|do not)\s+(?:do|use|implement)", re.IGNORECASE), + re.compile(r"(?:should|need to)\s+(?:use|do|implement).+(?:instead|not)", re.IGNORECASE), + re.compile(r"(?:actually|correction|fix)[:,]\s+", re.IGNORECASE), + re.compile(r"(?:no|nope),?\s+(?:use|do|try|implement)", re.IGNORECASE), + re.compile(r"(?:wrong|incorrect|mistake)[:,]", re.IGNORECASE), + re.compile(r"(?:better to|prefer to|should)\s+(?:use|do)", re.IGNORECASE), +] + +PREFERENCE_PATTERNS = [ + re.compile(r"(?:i prefer|i'd prefer|prefer to|i like)\s+(.+)", re.IGNORECASE), + re.compile(r"(?:always|never)\s+(?:use|do|implement)\s+(.+)", re.IGNORECASE), + re.compile(r"(?:i want|i'd like|i need)\s+(.+)", re.IGNORECASE), + re.compile(r"(?:make sure|ensure|remember)\s+(?:to|that)?\s+(.+)", re.IGNORECASE), + re.compile(r"(?:use|implement|do)\s+(.+)\s+(?:instead|not)", re.IGNORECASE), +] + +GOTCHA_PATTERNS = [ + re.compile(r"(?:error|issue|problem|bug|failing|broken)[:,]?\s+(.+)", re.IGNORECASE), + re.compile(r"(?:warning|careful|watch out)[:,]?\s+(.+)", re.IGNORECASE), + re.compile(r"(?:doesn't work|not working|fails when)\s+(.+)", re.IGNORECASE), + re.compile(r"(?:remember|don't forget)[:,]?\s+(.+)", re.IGNORECASE), +] + + +def truncate_text(text, max_length=100, suffix='...'): + """Truncate text to max_length, breaking at word boundaries.""" + if len(text) <= max_length: + return text + + truncated = text[:max_length].rsplit(' ', 1)[0] + return truncated + suffix + + +def ensure_knowledge_file(file_path, default_content=None): + """Ensure knowledge file exists, create with default content if missing.""" + if not file_path.exists(): + file_path.parent.mkdir(parents=True, exist_ok=True) + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(default_content or [], f, indent=2) + + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + + +def find_oracle_root(): + """Find the .oracle directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def find_project_hash(oracle_path): + """Try to determine the project hash for current project.""" + # The project hash is based on the project path + # We'll look for recent activity in claude projects that might match + + if not CLAUDE_PROJECTS_PATH.exists(): + return None + + project_root = oracle_path.parent + project_name = project_root.name + + # Get all project directories + project_dirs = [d for d in CLAUDE_PROJECTS_PATH.iterdir() if d.is_dir()] + + # Sort by most recent modification + project_dirs.sort(key=lambda x: x.stat().st_mtime, reverse=True) + + # Return the most recent one (likely current project) + if project_dirs: + return project_dirs[0].name + + return None + + +def load_conversation_history(project_hash, recent_days=None): + """Load conversation history from JSONL files.""" + project_path = CLAUDE_PROJECTS_PATH / project_hash + + if not project_path.exists(): + print(f"[ERROR] Project path not found: {project_path}") + return [] + + conversations = [] + cutoff_date = None + + if recent_days: + cutoff_date = datetime.now() - timedelta(days=recent_days) + + # Find all JSONL files + jsonl_files = list(project_path.glob('*.jsonl')) + + print(f"[INFO] Found {len(jsonl_files)} conversation files in project {project_hash[:8]}...") + + for jsonl_file in jsonl_files: + # Check modification date + if cutoff_date: + mtime = datetime.fromtimestamp(jsonl_file.stat().st_mtime) + if mtime < cutoff_date: + continue + + try: + # Use streaming approach for memory efficiency + session_data = { + 'session_id': jsonl_file.stem, + 'file_path': jsonl_file, + 'messages': [], + 'tools_used': [], + 'created': datetime.fromtimestamp(jsonl_file.stat().st_mtime) + } + + with open(jsonl_file, 'r', encoding='utf-8') as f: + for line in f: # Stream line by line - memory efficient + if line.strip(): + try: + entry = json.loads(line) + session_data['messages'].append(entry) + + # Extract tool usage + if 'message' in entry: + content = entry['message'].get('content', []) + if isinstance(content, list): + for item in content: + if isinstance(item, dict) and item.get('type') == 'tool_use': + session_data['tools_used'].append(item.get('name')) + + except json.JSONDecodeError: + continue + + conversations.append(session_data) + + except Exception as e: + print(f"[WARNING] Failed to load {jsonl_file.name}: {e}") + continue + + print(f"[OK] Loaded {len(conversations)} conversations") + return conversations + + +def extract_messages_by_role(conversations, role='user'): + """Extract messages of specified role from conversations.""" + messages = [] + + for session in conversations: + for msg in session['messages']: + if 'message' not in msg: + continue + + message = msg['message'] + if message.get('role') != role: + continue + + content = message.get('content', '') + + # Handle both string and list content + if isinstance(content, list): + text_parts = [] + for item in content: + if isinstance(item, dict) and item.get('type') == 'text': + text_parts.append(item.get('text', '')) + content = ' '.join(text_parts) + + if content: + messages.append({ + 'session_id': session['session_id'], + 'content': content, + 'timestamp': session['created'] + }) + + return messages + + +def detect_corrections(user_messages): + """Detect correction patterns in user messages.""" + corrections = [] + + for msg in user_messages: + content = msg['content'] + + for pattern in CORRECTION_PATTERNS: + if pattern.search(content): + corrections.append({ + 'session_id': msg['session_id'], + 'content': msg['content'], + 'timestamp': msg['timestamp'], + 'pattern_matched': pattern.pattern + }) + break + + return corrections + + +def detect_preferences(user_messages): + """Detect user preferences from messages.""" + preferences = [] + + for msg in user_messages: + content = msg['content'] + + for pattern in PREFERENCE_PATTERNS: + matches = pattern.findall(content) + if matches: + for match in matches: + match_text = match.strip() if isinstance(match, str) else match + # Only capture meaningful preferences + if len(match_text) > 5: + preferences.append({ + 'session_id': msg['session_id'], + 'preference': match_text, + 'full_context': content, + 'timestamp': msg['timestamp'] + }) + + return preferences + + +def detect_repeated_tasks(user_messages, min_occurrences=None): + """Detect repeated tasks that could be automated.""" + if min_occurrences is None: + min_occurrences = CONFIG['DEFAULT_MIN_TASK_OCCURRENCES'] + + # Extract common patterns + task_patterns = defaultdict(list) + + # Common action verbs + action_verbs = [ + 'create', 'add', 'update', 'delete', 'remove', 'fix', 'refactor', + 'implement', 'write', 'generate', 'build', 'run', 'test', 'deploy' + ] + + for msg in user_messages: + content = msg['content'].lower() + + # Extract sentences with action verbs + for verb in action_verbs: + # Use word boundaries to capture complete phrases + pattern = rf'\b{verb}\b\s+([a-zA-Z\s-]{{' + str(CONFIG['ACTION_CONTEXT_MIN_LEN']) + ',' + str(CONFIG['ACTION_CONTEXT_MAX_LEN']) + '}})' + matches = re.findall(pattern, content) + + for match in matches: + # Clean up the match + clean_match = re.sub(r'[^\w\s-]', '', match).strip() + if len(clean_match) > 5: + task_patterns[f"{verb} {clean_match}"].append({ + 'session_id': msg['session_id'], + 'full_content': msg['content'], + 'timestamp': msg['timestamp'] + }) + + # Find tasks that occur multiple times + repeated_tasks = [] + + for task, occurrences in task_patterns.items(): + if len(occurrences) >= min_occurrences: + repeated_tasks.append({ + 'task': task, + 'occurrences': len(occurrences), + 'instances': occurrences + }) + + # Sort by frequency + repeated_tasks.sort(key=lambda x: x['occurrences'], reverse=True) + + return repeated_tasks + + +def detect_gotchas(user_messages, assistant_messages): + """Detect gotchas from conversations about problems/errors.""" + gotchas = [] + + # Check user messages for problem reports + for msg in user_messages: + content = msg['content'] + + for pattern in GOTCHA_PATTERNS: + matches = pattern.findall(content) + if matches: + for match in matches: + match_text = match.strip() if isinstance(match, str) else match + gotchas.append({ + 'session_id': msg['session_id'], + 'gotcha': match_text, + 'context': content, + 'timestamp': msg['timestamp'], + 'source': 'user' + }) + + return gotchas + + +def analyze_tool_usage(conversations): + """Analyze which tools are used most frequently.""" + tool_counter = Counter() + + for session in conversations: + for tool in session['tools_used']: + tool_counter[tool] += 1 + + return tool_counter.most_common(CONFIG['TOP_TOOLS_TO_REPORT']) + + +def create_knowledge_entry(category, title, content, context='', priority='medium', + learned_from='conversation_history', tags=None): + """Create a knowledge entry in Oracle format.""" + return { + 'id': str(uuid.uuid4()), + 'category': category, + 'priority': priority, + 'title': truncate_text(title, CONFIG['MAX_TITLE_LENGTH']), + 'content': content, + 'context': context, + 'examples': [], + 'learned_from': learned_from, + 'created': datetime.now().isoformat(), + 'last_used': datetime.now().isoformat(), + 'use_count': 1, + 'tags': tags or [] + } + + +def populate_oracle_knowledge(oracle_path, corrections, preferences, gotchas, repeated_tasks): + """Populate Oracle knowledge base with extracted data.""" + knowledge_dir = oracle_path / 'knowledge' + + # Ensure knowledge directory exists + knowledge_dir.mkdir(parents=True, exist_ok=True) + + added_counts = { + 'corrections': 0, + 'preferences': 0, + 'gotchas': 0, + 'patterns': 0 + } + + # Add corrections + if corrections: + corrections_file = knowledge_dir / 'corrections.json' + existing_corrections = ensure_knowledge_file(corrections_file, []) + + for correction in corrections[:CONFIG['TOP_CORRECTIONS_TO_ADD']]: + # Create entry + entry = create_knowledge_entry( + category='correction', + title=f"Correction: {correction['content']}", + content=correction['content'], + context='Extracted from conversation history', + priority='high', + learned_from='conversation_history_analyzer', + tags=['auto-extracted', 'correction'] + ) + + existing_corrections.append(entry) + added_counts['corrections'] += 1 + + with open(corrections_file, 'w', encoding='utf-8') as f: + json.dump(existing_corrections, f, indent=2) + + # Add preferences + if preferences: + preferences_file = knowledge_dir / 'preferences.json' + existing_preferences = ensure_knowledge_file(preferences_file, []) + + # Deduplicate preferences + seen_preferences = set() + + for pref in preferences: + pref_text = pref['preference'].lower() + + # Skip if too similar to existing + if pref_text in seen_preferences: + continue + + seen_preferences.add(pref_text) + + entry = create_knowledge_entry( + category='preference', + title=f"Preference: {pref['preference']}", + content=pref['preference'], + context=truncate_text(pref['full_context'], 500), + priority='medium', + learned_from='conversation_history_analyzer', + tags=['auto-extracted', 'preference'] + ) + + existing_preferences.append(entry) + added_counts['preferences'] += 1 + + if added_counts['preferences'] >= CONFIG['MAX_PREFERENCES_TO_ADD']: + break + + with open(preferences_file, 'w', encoding='utf-8') as f: + json.dump(existing_preferences, f, indent=2) + + # Add gotchas + if gotchas: + gotchas_file = knowledge_dir / 'gotchas.json' + existing_gotchas = ensure_knowledge_file(gotchas_file, []) + + for gotcha in gotchas[:CONFIG['TOP_GOTCHAS_TO_ADD']]: + entry = create_knowledge_entry( + category='gotcha', + title=f"Gotcha: {gotcha['gotcha']}", + content=gotcha['gotcha'], + context=truncate_text(gotcha['context'], 500), + priority='high', + learned_from='conversation_history_analyzer', + tags=['auto-extracted', 'gotcha'] + ) + + existing_gotchas.append(entry) + added_counts['gotchas'] += 1 + + with open(gotchas_file, 'w', encoding='utf-8') as f: + json.dump(existing_gotchas, f, indent=2) + + # Add repeated tasks as patterns (automation candidates) + if repeated_tasks: + patterns_file = knowledge_dir / 'patterns.json' + existing_patterns = ensure_knowledge_file(patterns_file, []) + + for task in repeated_tasks[:CONFIG['TOP_TASKS_TO_ADD']]: + entry = create_knowledge_entry( + category='pattern', + title=f"Repeated task: {task['task']}", + content=f"This task has been performed {task['occurrences']} times. Consider automating it.", + context='Detected from conversation history analysis', + priority='medium', + learned_from='conversation_history_analyzer', + tags=['auto-extracted', 'automation-candidate', 'repeated-task'] + ) + + existing_patterns.append(entry) + added_counts['patterns'] += 1 + + with open(patterns_file, 'w', encoding='utf-8') as f: + json.dump(existing_patterns, f, indent=2) + + return added_counts + + +def generate_analysis_report(conversations, corrections, preferences, gotchas, + repeated_tasks, tool_usage): + """Generate a comprehensive analysis report.""" + report = [] + + report.append("="*70) + report.append("Oracle Conversation History Analysis Report") + report.append("="*70) + report.append("") + + # Summary + total_messages = sum(len(c['messages']) for c in conversations) + + report.append(f"Analyzed Conversations: {len(conversations)}") + report.append(f"Total Messages: {total_messages}") + report.append("") + + # Corrections + report.append(f"Corrections Detected: {len(corrections)}") + if corrections: + report.append(" Top Corrections:") + for i, corr in enumerate(corrections[:5], 1): + snippet = truncate_text(corr['content'].replace('\n', ' '), CONFIG['SNIPPET_LENGTH']) + report.append(f" {i}. {snippet}") + report.append("") + + # Preferences + report.append(f"User Preferences Detected: {len(preferences)}") + if preferences: + report.append(" Sample Preferences:") + for i, pref in enumerate(preferences[:5], 1): + snippet = truncate_text(pref['preference'], CONFIG['SNIPPET_LENGTH']) + report.append(f" {i}. {snippet}") + report.append("") + + # Gotchas + report.append(f"Gotchas/Issues Detected: {len(gotchas)}") + if gotchas: + report.append(" Sample Gotchas:") + for i, gotcha in enumerate(gotchas[:5], 1): + snippet = truncate_text(str(gotcha['gotcha']), CONFIG['SNIPPET_LENGTH']) + report.append(f" {i}. {snippet}") + report.append("") + + # Repeated Tasks + report.append(f"Repeated Tasks (Automation Candidates): {len(repeated_tasks)}") + if repeated_tasks: + report.append(" Top Repeated Tasks:") + for i, task in enumerate(repeated_tasks[:5], 1): + report.append(f" {i}. {task['task']} (x{task['occurrences']})") + report.append("") + + # Tool Usage + report.append("Most Used Tools:") + for i, (tool, count) in enumerate(tool_usage[:10], 1): + report.append(f" {i}. {tool}: {count} times") + report.append("") + + report.append("="*70) + + return "\n".join(report) + + +def main(): + parser = argparse.ArgumentParser( + description='Analyze Claude Code conversation history', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python analyze_history.py --auto-populate + python analyze_history.py --project-hash abc123def456 + python analyze_history.py --all-projects --recent-days 30 + python analyze_history.py --analyze-only --min-confidence 0.8 + """ + ) + + parser.add_argument( + '--project-hash', + help='Specific project hash to analyze' + ) + + parser.add_argument( + '--all-projects', + action='store_true', + help='Analyze all projects (not recommended - may be slow)' + ) + + parser.add_argument( + '--recent-days', + type=int, + help='Only analyze conversations from last N days' + ) + + parser.add_argument( + '--auto-populate', + action='store_true', + help='Automatically populate Oracle knowledge base' + ) + + parser.add_argument( + '--analyze-only', + action='store_true', + help='Only analyze and report, do not populate Oracle' + ) + + parser.add_argument( + '--min-task-occurrences', + type=int, + default=CONFIG['DEFAULT_MIN_TASK_OCCURRENCES'], + help='Minimum occurrences to consider a task as repeated' + ) + + args = parser.parse_args() + + # Find Oracle + oracle_path = find_oracle_root() + + if not oracle_path and not args.analyze_only: + print("[ERROR] .oracle directory not found.") + print(" Run: python .claude/skills/oracle/scripts/init_oracle.py") + sys.exit(1) + + # Determine project hash + if args.project_hash: + project_hash = args.project_hash + elif oracle_path: + project_hash = find_project_hash(oracle_path) + if not project_hash: + print("[ERROR] Could not determine project hash.") + print(" Use --project-hash to specify manually") + sys.exit(1) + else: + print("[ERROR] Please specify --project-hash") + sys.exit(1) + + print(f"\n[INFO] Analyzing project: {project_hash[:8]}...") + print(f"[INFO] Claude projects path: {CLAUDE_PROJECTS_PATH}\n") + + # Load conversations + conversations = load_conversation_history(project_hash, args.recent_days) + + if not conversations: + print("[ERROR] No conversations found.") + sys.exit(1) # Exit with error code + + # Extract messages + print("[INFO] Extracting user and assistant messages...") + user_messages = extract_messages_by_role(conversations, role='user') + assistant_messages = extract_messages_by_role(conversations, role='assistant') + + print(f"[OK] Found {len(user_messages)} user messages") + print(f"[OK] Found {len(assistant_messages)} assistant messages\n") + + # Analyze + print("[INFO] Detecting corrections...") + corrections = detect_corrections(user_messages) + + print("[INFO] Detecting preferences...") + preferences = detect_preferences(user_messages) + + print("[INFO] Detecting gotchas...") + gotchas = detect_gotchas(user_messages, assistant_messages) + + print("[INFO] Detecting repeated tasks...") + repeated_tasks = detect_repeated_tasks(user_messages, args.min_task_occurrences) + + print("[INFO] Analyzing tool usage...") + tool_usage = analyze_tool_usage(conversations) + + print("") + + # Generate report + report = generate_analysis_report( + conversations, corrections, preferences, gotchas, + repeated_tasks, tool_usage + ) + + print(report) + + # Populate Oracle if requested + if args.auto_populate and oracle_path and not args.analyze_only: + print("\n[INFO] Populating Oracle knowledge base...") + + added_counts = populate_oracle_knowledge( + oracle_path, corrections, preferences, gotchas, repeated_tasks + ) + + print("\n[OK] Knowledge base updated:") + for category, count in added_counts.items(): + if count > 0: + print(f" {category.capitalize()}: +{count} entries") + + print("\n[OK] Analysis complete! Knowledge base has been updated.") + print(" Query knowledge: python .claude/skills/oracle/scripts/query_knowledge.py") + + elif args.analyze_only: + print("\n[INFO] Analysis complete (no changes made to Oracle)") + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/analyze_patterns.py b/skills/oracle/scripts/analyze_patterns.py new file mode 100755 index 0000000..62279c8 --- /dev/null +++ b/skills/oracle/scripts/analyze_patterns.py @@ -0,0 +1,413 @@ +#!/usr/bin/env python3 +""" +Oracle Pattern Analysis Script + +Analyze Oracle knowledge base and session logs to identify: +- Repeated tasks (candidates for automation) +- Common corrections (update defaults/documentation) +- Frequent queries (add to auto-inject context) +- Token-heavy operations (automate) + +Usage: + python analyze_patterns.py + python analyze_patterns.py --generate-scripts + python analyze_patterns.py --threshold 3 + +Examples: + python analyze_patterns.py + python analyze_patterns.py --generate-scripts --threshold 5 +""" + +import os +import sys +import json +import argparse +from datetime import datetime +from pathlib import Path +from collections import Counter, defaultdict +import re + + +def find_oracle_root(): + """Find the .oracle directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def load_all_sessions(oracle_path): + """Load all session logs.""" + sessions_dir = oracle_path / 'sessions' + sessions = [] + + for session_file in sessions_dir.glob('*.md'): + try: + with open(session_file, 'r') as f: + content = f.read() + sessions.append({ + 'id': session_file.stem, + 'file': session_file, + 'content': content + }) + except Exception as e: + print(f"Warning: Could not read {session_file}: {e}") + + return sessions + + +def analyze_repeated_activities(sessions): + """Find repeated activities across sessions.""" + all_activities = [] + + for session in sessions: + # Extract activities from session log + content = session['content'] + if '## Activities' in content: + activities_section = content.split('## Activities')[1].split('\n\n')[0] + activities = re.findall(r'^- (.+)$', activities_section, re.MULTILINE) + all_activities.extend(activities) + + # Count occurrences + activity_counts = Counter(all_activities) + + return activity_counts + + +def analyze_corrections(oracle_path): + """Analyze correction patterns.""" + knowledge_dir = oracle_path / 'knowledge' + corrections_file = knowledge_dir / 'corrections.json' + + if not corrections_file.exists(): + return {} + + with open(corrections_file, 'r') as f: + corrections = json.load(f) + + # Group by common themes + themes = defaultdict(list) + + for correction in corrections: + content = correction.get('content', '') + + # Try to identify theme + if 'async' in content.lower() or 'await' in content.lower(): + themes['async-programming'].append(correction) + elif 'security' in content.lower() or 'xss' in content.lower() or 'injection' in content.lower(): + themes['security'].append(correction) + elif 'performance' in content.lower() or 'optimization' in content.lower(): + themes['performance'].append(correction) + elif 'test' in content.lower(): + themes['testing'].append(correction) + else: + themes['general'].append(correction) + + return themes + + +def analyze_file_patterns(sessions): + """Analyze which files are changed most often.""" + file_changes = Counter() + + for session in sessions: + content = session['content'] + if '## Changes Made' in content: + # Extract file paths + files = re.findall(r'\*\*File\*\*: `([^`]+)`', content) + file_changes.update(files) + + return file_changes + + +def identify_automation_candidates(activity_counts, threshold=3): + """Identify tasks that are repeated enough to warrant automation.""" + candidates = [] + + for activity, count in activity_counts.items(): + if count >= threshold: + # Analyze if it's automatable + automation_score = 0 + + # Keyword-based scoring + deterministic_keywords = ['run tests', 'build', 'lint', 'format', 'deploy', 'update dependencies'] + for keyword in deterministic_keywords: + if keyword in activity.lower(): + automation_score += 2 + + if automation_score > 0 or count >= threshold * 2: + candidates.append({ + 'activity': activity, + 'count': count, + 'automation_score': automation_score, + 'confidence': 'high' if automation_score >= 2 else 'medium' + }) + + return sorted(candidates, key=lambda x: (x['automation_score'], x['count']), reverse=True) + + +def generate_automation_script(activity): + """Generate a basic automation script for an activity.""" + activity_lower = activity.lower() + + script_name = re.sub(r'[^a-z0-9]+', '_', activity_lower).strip('_') + script_name = f"auto_{script_name}.sh" + + # Basic script template + script = f"""#!/bin/bash +# Auto-generated by Oracle Pattern Analysis +# Purpose: {activity} +# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + +set -e # Exit on error + +echo " Automated task: {activity}" +echo "---" + +# TODO: Implement automation logic +# Based on the activity pattern, add appropriate commands here + +""" + + # Add common commands based on keywords + if 'test' in activity_lower: + script += """# Run tests +# npm test +# pytest +# cargo test +""" + elif 'build' in activity_lower: + script += """# Build project +# npm run build +# cargo build +# make +""" + elif 'lint' in activity_lower: + script += """# Run linter +# npm run lint +# cargo clippy +# pylint +""" + elif 'format' in activity_lower: + script += """# Format code +# npm run format +# cargo fmt +# black . +""" + + script += """ +echo "---" +echo "[OK] Completed: {activity}" +""".format(activity=activity) + + return script_name, script + + +def generate_report(oracle_path, sessions, threshold): + """Generate analysis report.""" + print("="*70) + print("[SEARCH] Oracle Pattern Analysis Report") + print("="*70) + print(f"\nAnalyzing {len(sessions)} sessions\n") + + # Repeated activities + print("## Repeated Activities\n") + activity_counts = analyze_repeated_activities(sessions) + + if activity_counts: + print("Top repeated tasks:\n") + for activity, count in activity_counts.most_common(10): + emoji = "" if count >= threshold else "" + print(f" {emoji} [{count}x] {activity}") + else: + print(" No repeated activities found\n") + + print() + + # Automation candidates + print("## Automation Opportunities\n") + candidates = identify_automation_candidates(activity_counts, threshold) + + if candidates: + print(f"Found {len(candidates)} automation candidates:\n") + for candidate in candidates: + confidence_emoji = "" if candidate['confidence'] == 'high' else "" + print(f" {confidence_emoji} [{candidate['count']}x] {candidate['activity']}") + print(f" Confidence: {candidate['confidence']}, Score: {candidate['automation_score']}\n") + else: + print(f" No automation candidates (threshold: {threshold} occurrences)\n") + + print() + + # Correction patterns + print("## Correction Patterns\n") + correction_themes = analyze_corrections(oracle_path) + + if correction_themes: + print("Corrections by theme:\n") + for theme, corrections in sorted(correction_themes.items(), key=lambda x: len(x[1]), reverse=True): + print(f" {theme.capitalize()}: {len(corrections)} corrections") + + print("\n[WARNING] Consider updating documentation or creating safeguards for common themes\n") + else: + print(" No corrections recorded yet\n") + + print() + + # File change patterns + print("## Frequently Modified Files\n") + file_changes = analyze_file_patterns(sessions) + + if file_changes: + print("Most frequently changed files:\n") + for file_path, count in file_changes.most_common(10): + print(f" [{count}x] {file_path}") + + print("\n[TIP] Consider if these files need refactoring or better structure\n") + else: + print(" No file change patterns found\n") + + print() + + # Recommendations + print("="*70) + print("[INFO] Recommendations") + print("="*70) + print() + + if candidates: + print(f"1. **Automate {len(candidates)} repeated tasks**") + print(f" Run with --generate-scripts to create automation scripts\n") + + if correction_themes: + most_common_theme = max(correction_themes.items(), key=lambda x: len(x[1]))[0] + print(f"2. **Address {most_common_theme} corrections**") + print(f" Review and create guidelines or linting rules\n") + + if file_changes: + top_file = file_changes.most_common(1)[0] + print(f"3. **Review frequently changed file: {top_file[0]}**") + print(f" Changed {top_file[1]} times - may need refactoring\n") + + print("="*70) + + +def save_automation_scripts(oracle_path, candidates): + """Generate and save automation scripts.""" + scripts_dir = oracle_path / 'scripts' + scripts_generated = [] + + for candidate in candidates: + script_name, script_content = generate_automation_script(candidate['activity']) + script_path = scripts_dir / script_name + + with open(script_path, 'w') as f: + f.write(script_content) + + # Make executable + os.chmod(script_path, 0o755) + + scripts_generated.append(script_path) + + print(f"[OK] Generated: {script_path}") + + # Create README in scripts dir + readme_path = scripts_dir / 'README.md' + readme_content = f"""# Auto-Generated Automation Scripts + +These scripts were generated by Oracle pattern analysis on {datetime.now().strftime('%Y-%m-%d')}. + +## Scripts + +""" + + for candidate in candidates: + script_name = re.sub(r'[^a-z0-9]+', '_', candidate['activity'].lower()).strip('_') + readme_content += f"- `auto_{script_name}.sh` - {candidate['activity']} (used {candidate['count']}x)\n" + + readme_content += """ +## Usage + +Each script is executable: + +```bash +./auto_script_name.sh +``` + +## Customization + +These scripts are templates. Review and customize them for your specific needs. +""" + + with open(readme_path, 'w') as f: + f.write(readme_content) + + print(f"\n Created README: {readme_path}") + + return scripts_generated + + +def main(): + parser = argparse.ArgumentParser( + description='Analyze Oracle patterns and identify automation opportunities', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--threshold', + type=int, + default=3, + help='Minimum occurrences to consider for automation (default: 3)' + ) + + parser.add_argument( + '--generate-scripts', + action='store_true', + help='Generate automation scripts for candidates' + ) + + args = parser.parse_args() + + # Find Oracle + oracle_path = find_oracle_root() + + if not oracle_path: + print("[ERROR] Error: .oracle directory not found.") + sys.exit(1) + + # Load sessions + sessions = load_all_sessions(oracle_path) + + if not sessions: + print("[WARNING] No sessions found. Start recording sessions to enable pattern analysis.") + sys.exit(0) + + # Generate report + generate_report(oracle_path, sessions, args.threshold) + + # Generate scripts if requested + if args.generate_scripts: + activity_counts = analyze_repeated_activities(sessions) + candidates = identify_automation_candidates(activity_counts, args.threshold) + + if candidates: + print("\n" + "="*70) + print(" Generating Automation Scripts") + print("="*70 + "\n") + + scripts = save_automation_scripts(oracle_path, candidates) + + print(f"\n Generated {len(scripts)} automation scripts!") + print(f" Location: {oracle_path / 'scripts'}") + print("\n[WARNING] Review and customize these scripts before use.\n") + else: + print("\n[WARNING] No automation candidates found (threshold: {args.threshold})\n") + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/generate_context.py b/skills/oracle/scripts/generate_context.py new file mode 100755 index 0000000..300379d --- /dev/null +++ b/skills/oracle/scripts/generate_context.py @@ -0,0 +1,396 @@ +#!/usr/bin/env python3 +""" +Oracle Context Generation Script + +Generate context summaries from Oracle knowledge base for injection into +claude.md, session starts, or specific tasks. + +Usage: + python generate_context.py --session-start + python generate_context.py --task "implement API" + python generate_context.py --output claude.md + python generate_context.py --tier 1 + +Examples: + python generate_context.py --session-start + python generate_context.py --task "database migration" --tier 2 + python generate_context.py --output ../claude.md --update +""" + +import os +import sys +import json +import argparse +from datetime import datetime +from pathlib import Path + + +def find_oracle_root(): + """Find the .oracle directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def load_all_knowledge(oracle_path): + """Load all knowledge from Oracle.""" + knowledge_dir = oracle_path / 'knowledge' + all_knowledge = [] + + for category in ['patterns', 'preferences', 'gotchas', 'solutions', 'corrections']: + file_path = knowledge_dir / f'{category}.json' + if file_path.exists(): + with open(file_path, 'r') as f: + entries = json.load(f) + for entry in entries: + entry['_category'] = category + all_knowledge.append(entry) + + return all_knowledge + + +def filter_by_tier(knowledge, tier=1): + """Filter knowledge by tier level.""" + if tier == 1: + # Critical only - always load + return [k for k in knowledge if k.get('priority') in ['critical', 'high']] + elif tier == 2: + # Medium priority - load on relevance + return [k for k in knowledge if k.get('priority') in ['critical', 'high', 'medium']] + else: + # All knowledge + return knowledge + + +def filter_by_relevance(knowledge, task_description): + """Filter knowledge relevant to a specific task.""" + if not task_description: + return knowledge + + task_lower = task_description.lower() + relevant = [] + + for entry in knowledge: + # Check if task keywords appear in entry + score = 0 + + if task_lower in entry.get('title', '').lower(): + score += 3 + if task_lower in entry.get('content', '').lower(): + score += 2 + if task_lower in entry.get('context', '').lower(): + score += 1 + + # Check tags + for tag in entry.get('tags', []): + if tag.lower() in task_lower or task_lower in tag.lower(): + score += 2 + + if score > 0: + entry['_relevance_score'] = score + relevant.append(entry) + + # Sort by relevance + return sorted(relevant, key=lambda x: x.get('_relevance_score', 0), reverse=True) + + +def get_recent_corrections(oracle_path, limit=5): + """Get most recent corrections.""" + knowledge_dir = oracle_path / 'knowledge' + corrections_file = knowledge_dir / 'corrections.json' + + if not corrections_file.exists(): + return [] + + with open(corrections_file, 'r') as f: + corrections = json.load(f) + + # Sort by creation date + sorted_corrections = sorted( + corrections, + key=lambda x: x.get('created', ''), + reverse=True + ) + + return sorted_corrections[:limit] + + +def generate_session_start_context(oracle_path): + """Generate context for session start.""" + knowledge = load_all_knowledge(oracle_path) + + # Tier 1: Critical items + critical_items = filter_by_tier(knowledge, tier=1) + + # Recent corrections + recent_corrections = get_recent_corrections(oracle_path, limit=5) + + context = """# Oracle Project Knowledge + +*Auto-generated context for this session* + +""" + + # Project Overview + index_file = oracle_path / 'index.json' + if index_file.exists(): + with open(index_file, 'r') as f: + index = json.load(f) + + context += f"""## Project Status + +- Total Knowledge Entries: {index.get('total_entries', 0)} +- Last Updated: {index.get('last_updated', 'Unknown')} +- Sessions Recorded: {len(index.get('sessions', []))} + +""" + + # Critical Knowledge + if critical_items: + context += "## [WARNING] Critical Knowledge\n\n" + + for item in critical_items[:10]: # Top 10 + context += f"### {item.get('title', 'Untitled')}\n\n" + context += f"**Category**: {item['_category'].capitalize()} | **Priority**: {item.get('priority', 'N/A')}\n\n" + context += f"{item.get('content', 'No content')}\n\n" + + if item.get('context'): + context += f"*When to apply*: {item['context']}\n\n" + + context += "---\n\n" + + # Recent Corrections + if recent_corrections: + context += "## Recent Corrections (Learn from these!)\n\n" + + for correction in recent_corrections: + context += f"- **{correction.get('title', 'Correction')}**\n" + context += f" {correction.get('content', '')}\n" + + if correction.get('context'): + context += f" *Context*: {correction['context']}\n" + + context += "\n" + + context += f"\n*Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n" + + return context + + +def generate_task_context(oracle_path, task_description): + """Generate context for a specific task.""" + knowledge = load_all_knowledge(oracle_path) + + # Filter by relevance to task + relevant = filter_by_relevance(knowledge, task_description) + + context = f"""# Oracle Context for: {task_description} + +*Relevant knowledge from Oracle* + +""" + + if not relevant: + context += "No specific knowledge found for this task.\n\n" + context += "This might be a new area - remember to record learnings!\n" + return context + + context += f"Found {len(relevant)} relevant knowledge entries.\n\n" + + # Group by category + by_category = {} + for item in relevant[:20]: # Top 20 most relevant + category = item['_category'] + if category not in by_category: + by_category[category] = [] + by_category[category].append(item) + + # Format by category + category_names = { + 'patterns': ' Patterns', + 'preferences': ' Preferences', + 'gotchas': '[WARNING] Gotchas', + 'solutions': '[OK] Solutions', + 'corrections': ' Corrections' + } + + for category, items in by_category.items(): + context += f"## {category_names.get(category, category.capitalize())}\n\n" + + for item in items: + context += f"### {item.get('title', 'Untitled')}\n\n" + context += f"{item.get('content', 'No content')}\n\n" + + if item.get('examples'): + context += "**Examples**:\n" + for ex in item['examples']: + context += f"- {ex}\n" + context += "\n" + + context += "---\n\n" + + context += f"\n*Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n" + + return context + + +def generate_compact_context(oracle_path): + """Generate compact context for claude.md injection.""" + knowledge = load_all_knowledge(oracle_path) + + critical = [k for k in knowledge if k.get('priority') == 'critical'] + high = [k for k in knowledge if k.get('priority') == 'high'] + + context = "\n" + context += "\n\n" + + if critical: + context += "**Critical Knowledge**:\n" + for item in critical[:5]: + context += f"- {item.get('title', 'Untitled')}\n" + context += "\n" + + if high: + context += "**Important Patterns**:\n" + for item in high[:5]: + context += f"- {item.get('title', 'Untitled')}\n" + context += "\n" + + # Recent corrections + recent_corrections = get_recent_corrections(oracle_path, limit=3) + if recent_corrections: + context += "**Recent Learnings**:\n" + for correction in recent_corrections: + content = correction.get('content', '') + # Extract just the "right" part if it's a correction + if '[CHECK] Right:' in content: + right_part = content.split('[CHECK] Right:')[1].split('\n')[0].strip() + context += f"- {right_part}\n" + else: + context += f"- {correction.get('title', '')}\n" + context += "\n" + + context += f"*Updated: {datetime.now().strftime('%Y-%m-%d %H:%M')}*\n" + context += "\n" + + return context + + +def update_claude_md(oracle_path, project_path): + """Update claude.md with Oracle context.""" + claude_md = project_path / 'claude.md' + + context = generate_compact_context(oracle_path) + + if not claude_md.exists(): + # Create new claude.md with Oracle section + content = f"""# Project Documentation + +## Project Knowledge (Oracle) + +{context} + +## Additional Context + +[Add your project-specific context here] +""" + with open(claude_md, 'w') as f: + f.write(content) + + print(f"[OK] Created new claude.md with Oracle context") + return + + # Update existing claude.md + with open(claude_md, 'r') as f: + content = f.read() + + # Replace Oracle section if it exists + if '' in content: + import re + pattern = r'.*?' + content = re.sub(pattern, context, content, flags=re.DOTALL) + print(f"[OK] Updated Oracle context in claude.md") + else: + # Add Oracle section at the top + content = f"## Project Knowledge (Oracle)\n\n{context}\n\n{content}" + print(f"[OK] Added Oracle context to claude.md") + + with open(claude_md, 'w') as f: + f.write(content) + + +def main(): + parser = argparse.ArgumentParser( + description='Generate Oracle context summaries', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--session-start', + action='store_true', + help='Generate context for session start' + ) + + parser.add_argument( + '--task', + help='Generate context for specific task' + ) + + parser.add_argument( + '--tier', + type=int, + choices=[1, 2, 3], + default=1, + help='Context tier level (1=critical, 2=medium, 3=all)' + ) + + parser.add_argument( + '--output', + help='Output file (default: stdout)' + ) + + parser.add_argument( + '--update', + action='store_true', + help='Update the output file (for claude.md)' + ) + + args = parser.parse_args() + + # Find Oracle + oracle_path = find_oracle_root() + + if not oracle_path: + print("[ERROR] Error: .oracle directory not found.") + sys.exit(1) + + # Generate context + if args.session_start: + context = generate_session_start_context(oracle_path) + elif args.task: + context = generate_task_context(oracle_path, args.task) + elif args.update and args.output: + project_path = oracle_path.parent + update_claude_md(oracle_path, project_path) + return + else: + context = generate_compact_context(oracle_path) + + # Output + if args.output: + output_path = Path(args.output) + with open(output_path, 'w') as f: + f.write(context) + print(f"[OK] Context written to: {output_path}") + else: + print(context) + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/init_oracle.py b/skills/oracle/scripts/init_oracle.py new file mode 100755 index 0000000..5b5eb00 --- /dev/null +++ b/skills/oracle/scripts/init_oracle.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +""" +Oracle Initialization Script + +Initializes the Oracle knowledge management system for a project. +Creates directory structure and base files. + +Usage: + python init_oracle.py [--path /path/to/project] + +Example: + python init_oracle.py + python init_oracle.py --path ~/my-project +""" + +import os +import sys +import json +import argparse +from datetime import datetime +from pathlib import Path + + +ORACLE_STRUCTURE = { + 'knowledge': { + 'patterns.json': [], + 'preferences.json': [], + 'gotchas.json': [], + 'solutions.json': [], + 'corrections.json': [] + }, + 'sessions': {}, + 'timeline': { + 'project_timeline.md': '# Project Timeline\n\nChronological history of project development.\n\n' + }, + 'scripts': {}, + 'hooks': {} +} + +INDEX_TEMPLATE = { + 'created': None, + 'last_updated': None, + 'total_entries': 0, + 'categories': { + 'patterns': 0, + 'preferences': 0, + 'gotchas': 0, + 'solutions': 0, + 'corrections': 0 + }, + 'sessions': [], + 'version': '1.0' +} + + +def create_oracle_structure(base_path): + """Create Oracle directory structure.""" + oracle_path = Path(base_path) / '.oracle' + + if oracle_path.exists(): + response = input(f"[WARNING] Oracle already exists at {oracle_path}. Reinitialize? [y/N]: ") + if response.lower() != 'y': + print("[ERROR] Initialization cancelled.") + return False + + print(f" Creating Oracle structure at {oracle_path}") + + # Create directories and files + for dir_name, contents in ORACLE_STRUCTURE.items(): + dir_path = oracle_path / dir_name + dir_path.mkdir(parents=True, exist_ok=True) + print(f" [OK] Created {dir_name}/") + + # Create files in directory + for filename, content in contents.items(): + file_path = dir_path / filename + + if filename.endswith('.json'): + with open(file_path, 'w') as f: + json.dump(content, f, indent=2) + else: + with open(file_path, 'w') as f: + f.write(content) + + print(f" Created {filename}") + + # Create index.json + index_data = INDEX_TEMPLATE.copy() + index_data['created'] = datetime.now().isoformat() + index_data['last_updated'] = datetime.now().isoformat() + + with open(oracle_path / 'index.json', 'w') as f: + json.dump(index_data, f, indent=2) + + print(f" [OK] Created index.json") + + # Create README + readme_content = """# Oracle Knowledge Base + +This directory contains the Oracle knowledge management system for this project. + +## Structure + +- `knowledge/`: Categorized knowledge entries + - `patterns.json`: Code patterns and conventions + - `preferences.json`: User/team preferences + - `gotchas.json`: Known issues and pitfalls + - `solutions.json`: Proven solutions + - `corrections.json`: Historical corrections +- `sessions/`: Session logs by date +- `timeline/`: Chronological project history +- `scripts/`: Auto-generated automation scripts +- `hooks/`: Integration hooks +- `index.json`: Fast lookup index + +## Usage + +See `.claude/skills/oracle/README.md` for complete documentation. + +## Quick Commands + +```bash +# Query knowledge +python .claude/skills/oracle/scripts/query_knowledge.py "search term" + +# Record session +python .claude/skills/oracle/scripts/record_session.py + +# Generate context +python .claude/skills/oracle/scripts/generate_context.py + +# Analyze patterns +python .claude/skills/oracle/scripts/analyze_patterns.py +``` + +--- + +*Initialized: {}* +""".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + + with open(oracle_path / 'README.md', 'w') as f: + f.write(readme_content) + + print(f" [OK] Created README.md") + + # Create .gitignore + gitignore_content = """# Session logs may contain sensitive information +sessions/*.md + +# Keep the structure +!sessions/.gitkeep + +# Generated scripts +scripts/* +!scripts/.gitkeep +!scripts/README.md +""" + + with open(oracle_path / '.gitignore', 'w') as f: + f.write(gitignore_content) + + # Create .gitkeep files + (oracle_path / 'sessions' / '.gitkeep').touch() + (oracle_path / 'scripts' / '.gitkeep').touch() + (oracle_path / 'hooks' / '.gitkeep').touch() + + print(f" [OK] Created .gitignore") + + return oracle_path + + +def create_integration_hints(oracle_path, project_path): + """Create hints for integrating Oracle.""" + print("\n" + "="*70) + print(" Oracle Initialized Successfully!") + print("="*70) + + print(f"\n Location: {oracle_path}") + + print("\n Next Steps:\n") + + print("1. **Add to claude.md** (if you have one):") + print(" Add this section to your project's claude.md:") + print(""" + ## Project Knowledge (Oracle) + + + Run: python .claude/skills/oracle/scripts/generate_context.py --session-start + + """) + + print("\n2. **Create Session Start Hook** (optional):") + print(f" Create: {project_path}/.claude/hooks/session-start.sh") + print(""" + #!/bin/bash + python .claude/skills/oracle/scripts/load_context.py + """) + + print("\n3. **Start Recording Knowledge:**") + print(" After sessions, run:") + print(" python .claude/skills/oracle/scripts/record_session.py") + + print("\n4. **Query Knowledge:**") + print(" python .claude/skills/oracle/scripts/query_knowledge.py \"search term\"") + + print("\n" + "="*70) + print("Oracle is ready to learn and remember! ") + print("="*70 + "\n") + + +def main(): + parser = argparse.ArgumentParser( + description='Initialize Oracle knowledge management system', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python init_oracle.py + python init_oracle.py --path ~/my-project + """ + ) + + parser.add_argument( + '--path', + type=str, + default='.', + help='Path to project root (default: current directory)' + ) + + args = parser.parse_args() + + project_path = Path(args.path).resolve() + + if not project_path.exists(): + print(f"[ERROR] Error: Path does not exist: {project_path}") + sys.exit(1) + + print(f"> Initializing Oracle for project at: {project_path}\n") + + oracle_path = create_oracle_structure(project_path) + + if oracle_path: + create_integration_hints(oracle_path, project_path) + sys.exit(0) + else: + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/load_context.py b/skills/oracle/scripts/load_context.py new file mode 100755 index 0000000..add122f --- /dev/null +++ b/skills/oracle/scripts/load_context.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +Oracle Context Loader + +Load Oracle context at session start (for use in hooks). +Displays relevant knowledge to Claude at the beginning of a session. + +Usage: + python load_context.py + python load_context.py --verbose + +Example (in .claude/hooks/session-start.sh): + #!/bin/bash + python .claude/skills/oracle/scripts/load_context.py +""" + +import sys +from pathlib import Path +import subprocess + + +def find_oracle_root(): + """Find the .oracle directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def main(): + verbose = '--verbose' in sys.argv + + oracle_path = find_oracle_root() + + if not oracle_path: + if verbose: + print("Oracle not initialized for this project.") + return + + # Run generate_context.py with --session-start + script_path = Path(__file__).parent / 'generate_context.py' + + try: + result = subprocess.run( + ['python3', str(script_path), '--session-start'], + capture_output=True, + text=True + ) + + if result.returncode == 0: + print(result.stdout) + else: + if verbose: + print(f"Warning: Could not load Oracle context: {result.stderr}") + + except Exception as e: + if verbose: + print(f"Warning: Error loading Oracle context: {e}") + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/query_knowledge.py b/skills/oracle/scripts/query_knowledge.py new file mode 100755 index 0000000..c0b4dd3 --- /dev/null +++ b/skills/oracle/scripts/query_knowledge.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +""" +Oracle Knowledge Query Script + +Search and retrieve knowledge from the Oracle knowledge base. + +Usage: + python query_knowledge.py "search term" + python query_knowledge.py --category patterns + python query_knowledge.py --priority critical + python query_knowledge.py --tags api,auth + python query_knowledge.py --recent 5 + +Examples: + python query_knowledge.py "authentication" + python query_knowledge.py --category gotchas --priority high + python query_knowledge.py --tags database --recent 10 +""" + +import os +import sys +import json +import argparse +from datetime import datetime +from pathlib import Path + + +def find_oracle_root(): + """Find the .oracle directory by walking up from current directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def load_knowledge(oracle_path, category=None): + """Load knowledge from specified category or all categories.""" + knowledge_dir = oracle_path / 'knowledge' + all_knowledge = [] + + categories = [category] if category else ['patterns', 'preferences', 'gotchas', 'solutions', 'corrections'] + + for cat in categories: + file_path = knowledge_dir / f'{cat}.json' + if file_path.exists(): + with open(file_path, 'r') as f: + entries = json.load(f) + for entry in entries: + entry['_category'] = cat + all_knowledge.append(entry) + + return all_knowledge + + +def search_knowledge(knowledge, query=None, priority=None, tags=None): + """Filter knowledge based on search criteria.""" + results = knowledge + + # Filter by query (search in title and content) + if query: + query_lower = query.lower() + results = [ + entry for entry in results + if query_lower in entry.get('title', '').lower() + or query_lower in entry.get('content', '').lower() + or query_lower in str(entry.get('context', '')).lower() + ] + + # Filter by priority + if priority: + results = [entry for entry in results if entry.get('priority') == priority] + + # Filter by tags + if tags: + tag_list = [t.strip() for t in tags.split(',')] + results = [ + entry for entry in results + if any(tag in entry.get('tags', []) for tag in tag_list) + ] + + return results + + +def sort_knowledge(knowledge, sort_by='priority'): + """Sort knowledge by various criteria.""" + priority_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3} + + if sort_by == 'priority': + return sorted(knowledge, key=lambda x: priority_order.get(x.get('priority', 'low'), 3)) + elif sort_by == 'recent': + return sorted(knowledge, key=lambda x: x.get('created', ''), reverse=True) + elif sort_by == 'used': + return sorted(knowledge, key=lambda x: x.get('use_count', 0), reverse=True) + else: + return knowledge + + +def format_entry(entry, compact=False): + """Format a knowledge entry for display.""" + if compact: + return f" [{entry['_category']}] {entry.get('title', 'Untitled')} (Priority: {entry.get('priority', 'N/A')})" + + output = [] + output.append("" * 70) + output.append(f" {entry.get('title', 'Untitled')}") + output.append(f" Category: {entry['_category']} | Priority: {entry.get('priority', 'N/A')}") + + if entry.get('tags'): + output.append(f" Tags: {', '.join(entry['tags'])}") + + output.append("") + output.append(f" {entry.get('content', 'No content')}") + + if entry.get('context'): + output.append("") + output.append(f" Context: {entry['context']}") + + if entry.get('examples'): + output.append("") + output.append(" Examples:") + for ex in entry['examples']: + output.append(f" - {ex}") + + output.append("") + output.append(f" Created: {entry.get('created', 'Unknown')}") + output.append(f" Used: {entry.get('use_count', 0)} times") + + if entry.get('learned_from'): + output.append(f" Source: {entry['learned_from']}") + + return "\n".join(output) + + +def display_results(results, compact=False, limit=None): + """Display search results.""" + if not results: + print("[ERROR] No knowledge found matching your criteria.") + return + + total = len(results) + display_count = min(limit, total) if limit else total + + print(f"\n[SEARCH] Found {total} result(s)") + if limit and total > limit: + print(f" Showing first {display_count} results\n") + else: + print() + + for i, entry in enumerate(results[:display_count], 1): + if compact: + print(format_entry(entry, compact=True)) + else: + print(format_entry(entry, compact=False)) + if i < display_count: + print() + + +def display_summary(oracle_path): + """Display summary of knowledge base.""" + index_path = oracle_path / 'index.json' + + if not index_path.exists(): + print("[WARNING] No index found. Knowledge base may be empty.") + return + + with open(index_path, 'r') as f: + index = json.load(f) + + print("="*70) + print("[INFO] Oracle Knowledge Base Summary") + print("="*70) + print(f"\nCreated: {index.get('created', 'Unknown')}") + print(f"Last Updated: {index.get('last_updated', 'Unknown')}") + print(f"Total Entries: {index.get('total_entries', 0)}") + + print("\nEntries by Category:") + for category, count in index.get('categories', {}).items(): + print(f" {category.capitalize()}: {count}") + + print(f"\nSessions Recorded: {len(index.get('sessions', []))}") + print("="*70) + + +def main(): + parser = argparse.ArgumentParser( + description='Query Oracle knowledge base', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python query_knowledge.py "authentication" + python query_knowledge.py --category patterns + python query_knowledge.py --priority critical + python query_knowledge.py --tags api,database + python query_knowledge.py --recent 5 + python query_knowledge.py --summary + """ + ) + + parser.add_argument( + 'query', + nargs='?', + help='Search query (searches title, content, context)' + ) + + parser.add_argument( + '--category', + choices=['patterns', 'preferences', 'gotchas', 'solutions', 'corrections'], + help='Filter by category' + ) + + parser.add_argument( + '--priority', + choices=['critical', 'high', 'medium', 'low'], + help='Filter by priority' + ) + + parser.add_argument( + '--tags', + help='Filter by tags (comma-separated)' + ) + + parser.add_argument( + '--sort', + choices=['priority', 'recent', 'used'], + default='priority', + help='Sort results by (default: priority)' + ) + + parser.add_argument( + '--limit', + type=int, + help='Limit number of results' + ) + + parser.add_argument( + '--recent', + type=int, + metavar='N', + help='Show N most recent entries' + ) + + parser.add_argument( + '--compact', + action='store_true', + help='Display compact results' + ) + + parser.add_argument( + '--summary', + action='store_true', + help='Display knowledge base summary' + ) + + args = parser.parse_args() + + # Find Oracle directory + oracle_path = find_oracle_root() + + if not oracle_path: + print("[ERROR] Error: .oracle directory not found.") + print(" Run: python .claude/skills/oracle/scripts/init_oracle.py") + sys.exit(1) + + # Display summary if requested + if args.summary: + display_summary(oracle_path) + sys.exit(0) + + # Load knowledge + knowledge = load_knowledge(oracle_path, args.category) + + if not knowledge: + print("[ERROR] No knowledge entries found.") + print(" Start recording sessions to build the knowledge base.") + sys.exit(0) + + # Search and filter + results = search_knowledge(knowledge, args.query, args.priority, args.tags) + + # Sort + if args.recent: + results = sort_knowledge(results, 'recent') + limit = args.recent + else: + results = sort_knowledge(results, args.sort) + limit = args.limit + + # Display + display_results(results, args.compact, limit) + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/record_commit.py b/skills/oracle/scripts/record_commit.py new file mode 100755 index 0000000..ca0638e --- /dev/null +++ b/skills/oracle/scripts/record_commit.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +""" +Oracle Commit Recorder + +Record git commits in Oracle timeline (for use in git hooks). + +Usage: + python record_commit.py + python record_commit.py --message "commit message" + +Example (in .oracle/hooks/pre-commit.sh): + #!/bin/bash + python .claude/skills/oracle/scripts/record_commit.py +""" + +import sys +import subprocess +from datetime import datetime +from pathlib import Path + + +def find_oracle_root(): + """Find the .oracle directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def get_commit_info(): + """Get information about the current/last commit.""" + try: + # Get last commit message + message = subprocess.check_output( + ['git', 'log', '-1', '--pretty=%B'], + text=True + ).strip() + + # Get changed files + files = subprocess.check_output( + ['git', 'diff', '--name-only', 'HEAD~1'], + text=True + ).strip().split('\n') + + # Get author + author = subprocess.check_output( + ['git', 'log', '-1', '--pretty=%an'], + text=True + ).strip() + + # Get hash + commit_hash = subprocess.check_output( + ['git', 'rev-parse', '--short', 'HEAD'], + text=True + ).strip() + + return { + 'message': message, + 'files': [f for f in files if f], + 'author': author, + 'hash': commit_hash + } + + except subprocess.CalledProcessError: + return None + + +def record_to_timeline(oracle_path, commit_info): + """Record commit to timeline.""" + timeline_file = oracle_path / 'timeline' / 'project_timeline.md' + + entry = f""" +## {datetime.now().strftime('%Y-%m-%d %H:%M')} - Commit: {commit_info['hash']} + +**Author**: {commit_info['author']} + +**Message**: {commit_info['message']} + +**Files Changed**: +""" + + for file_path in commit_info['files'][:10]: # Top 10 files + entry += f"- `{file_path}`\n" + + if len(commit_info['files']) > 10: + entry += f"- ... and {len(commit_info['files']) - 10} more\n" + + entry += "\n---\n" + + # Append to timeline + with open(timeline_file, 'a') as f: + f.write(entry) + + +def main(): + oracle_path = find_oracle_root() + + if not oracle_path: + # Silent fail - not all projects will have Oracle + sys.exit(0) + + commit_info = get_commit_info() + + if commit_info: + record_to_timeline(oracle_path, commit_info) + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/record_session.py b/skills/oracle/scripts/record_session.py new file mode 100755 index 0000000..b1b4e8f --- /dev/null +++ b/skills/oracle/scripts/record_session.py @@ -0,0 +1,452 @@ +#!/usr/bin/env python3 +""" +Oracle Session Recording Script + +Record a session's activities, learnings, and corrections to the Oracle knowledge base. + +Usage: + python record_session.py [options] + python record_session.py --interactive + python record_session.py --summary "Implemented auth" --learnings "Use bcrypt" + +Examples: + python record_session.py --interactive + python record_session.py --summary "Fixed bug in API" --corrections "Use async/await not callbacks" +""" + +import os +import sys +import json +import argparse +from datetime import datetime +from pathlib import Path +import uuid + + +def find_oracle_root(): + """Find the .oracle directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def generate_session_id(): + """Generate a unique session ID.""" + timestamp = datetime.now().strftime('%Y-%m-%d_%H%M%S') + short_uuid = str(uuid.uuid4())[:8] + return f"{timestamp}_{short_uuid}" + + +def interactive_session_record(): + """Interactive mode for recording a session.""" + print("="*70) + print("[NOTE] Oracle Session Recording (Interactive Mode)") + print("="*70) + print("\nPress Enter to skip any field.\n") + + session = {} + + # Summary + session['summary'] = input("Summary of this session:\n> ").strip() + + # Activities + print("\nActivities (one per line, empty line to finish):") + activities = [] + while True: + activity = input("> ").strip() + if not activity: + break + activities.append(activity) + session['activities'] = activities + + # Changes + print("\nFiles changed (format: path/to/file.ts, empty line to finish):") + changes = [] + while True: + file_path = input("File: ").strip() + if not file_path: + break + change_desc = input(" Change: ").strip() + reason = input(" Reason: ").strip() + changes.append({ + 'file': file_path, + 'change': change_desc, + 'reason': reason + }) + session['changes'] = changes + + # Decisions + print("\nDecisions made (empty line to finish):") + decisions = [] + while True: + decision = input("Decision: ").strip() + if not decision: + break + rationale = input(" Rationale: ").strip() + decisions.append({ + 'decision': decision, + 'rationale': rationale + }) + session['decisions'] = decisions + + # Learnings + print("\nLearnings (empty line to finish):") + learnings = [] + while True: + learning = input("Learning: ").strip() + if not learning: + break + + print(" Priority? [critical/high/medium/low]") + priority = input(" > ").strip() or 'medium' + + learnings.append({ + 'content': learning, + 'priority': priority + }) + session['learnings'] = learnings + + # Corrections + print("\nCorrections (what was wrong what's right, empty line to finish):") + corrections = [] + while True: + wrong = input("[ERROR] What was wrong: ").strip() + if not wrong: + break + right = input("[CHECK] What's right: ").strip() + context = input(" When this applies: ").strip() + + corrections.append({ + 'wrong': wrong, + 'right': right, + 'context': context + }) + session['corrections'] = corrections + + # Questions + print("\nQuestions asked (empty line to finish):") + questions = [] + while True: + question = input("Q: ").strip() + if not question: + break + answer = input("A: ").strip() + + questions.append({ + 'question': question, + 'answer': answer + }) + session['questions'] = questions + + return session + + +def create_session_log(oracle_path, session_id, session_data): + """Create a session log markdown file.""" + sessions_dir = oracle_path / 'sessions' + log_file = sessions_dir / f'{session_id}.md' + + content = f"""# Session: {datetime.now().strftime('%Y-%m-%d %H:%M')} + +**Session ID**: `{session_id}` + +## Summary + +{session_data.get('summary', 'No summary provided')} + +""" + + # Activities + if session_data.get('activities'): + content += "## Activities\n\n" + for activity in session_data['activities']: + content += f"- {activity}\n" + content += "\n" + + # Changes + if session_data.get('changes'): + content += "## Changes Made\n\n" + for change in session_data['changes']: + content += f"- **File**: `{change['file']}`\n" + content += f" - Change: {change['change']}\n" + if change.get('reason'): + content += f" - Reason: {change['reason']}\n" + content += "\n" + + # Decisions + if session_data.get('decisions'): + content += "## Decisions\n\n" + for decision in session_data['decisions']: + content += f"- **Decision**: {decision['decision']}\n" + if decision.get('rationale'): + content += f" - Rationale: {decision['rationale']}\n" + content += "\n" + + # Learnings + if session_data.get('learnings'): + content += "## Learnings\n\n" + for learning in session_data['learnings']: + priority = learning.get('priority', 'medium') + priority_emoji = {'critical': '', 'high': '', 'medium': '', 'low': ''}.get(priority, '') + content += f"- {priority_emoji} **[{priority.upper()}]** {learning['content']}\n" + content += "\n" + + # Corrections + if session_data.get('corrections'): + content += "## Corrections\n\n" + for correction in session_data['corrections']: + content += f"- [ERROR] Wrong: {correction['wrong']}\n" + content += f" [CHECK] Right: {correction['right']}\n" + if correction.get('context'): + content += f" [NOTE] Context: {correction['context']}\n" + content += "\n" + + # Questions + if session_data.get('questions'): + content += "## Questions Asked\n\n" + for qa in session_data['questions']: + content += f"- **Q**: {qa['question']}\n" + content += f" **A**: {qa['answer']}\n" + content += "\n" + + content += f"\n---\n\n*Recorded: {datetime.now().isoformat()}*\n" + + with open(log_file, 'w') as f: + f.write(content) + + return log_file + + +def update_knowledge_base(oracle_path, session_id, session_data): + """Update knowledge base with session learnings and corrections.""" + knowledge_dir = oracle_path / 'knowledge' + updated_categories = set() + + # Add learnings as solutions or patterns + if session_data.get('learnings'): + for learning in session_data['learnings']: + # Determine if it's a pattern or solution based on content + category = 'solutions' # Default to solutions + + entry = { + 'id': str(uuid.uuid4()), + 'category': category, + 'priority': learning.get('priority', 'medium'), + 'title': learning['content'][:100], # Truncate for title + 'content': learning['content'], + 'context': learning.get('context', ''), + 'examples': [], + 'learned_from': session_id, + 'created': datetime.now().isoformat(), + 'last_used': datetime.now().isoformat(), + 'use_count': 1, + 'tags': learning.get('tags', []) + } + + # Load existing and append + solutions_file = knowledge_dir / f'{category}.json' + with open(solutions_file, 'r') as f: + entries = json.load(f) + + entries.append(entry) + + with open(solutions_file, 'w') as f: + json.dump(entries, f, indent=2) + + updated_categories.add(category) + + # Add corrections + if session_data.get('corrections'): + corrections_file = knowledge_dir / 'corrections.json' + + with open(corrections_file, 'r') as f: + corrections = json.load(f) + + for correction in session_data['corrections']: + entry = { + 'id': str(uuid.uuid4()), + 'category': 'correction', + 'priority': 'high', # Corrections are high priority + 'title': f"Don't: {correction['wrong'][:50]}...", + 'content': f"[ERROR] Wrong: {correction['wrong']}\n[CHECK] Right: {correction['right']}", + 'context': correction.get('context', ''), + 'examples': [], + 'learned_from': session_id, + 'created': datetime.now().isoformat(), + 'last_used': datetime.now().isoformat(), + 'use_count': 1, + 'tags': [] + } + + corrections.append(entry) + + with open(corrections_file, 'w') as f: + json.dump(corrections, f, indent=2) + + updated_categories.add('corrections') + + return updated_categories + + +def update_index(oracle_path, session_id): + """Update the index with new session.""" + index_file = oracle_path / 'index.json' + + with open(index_file, 'r') as f: + index = json.load(f) + + # Add session to list + if session_id not in index['sessions']: + index['sessions'].append(session_id) + + # Update counts + knowledge_dir = oracle_path / 'knowledge' + for category in ['patterns', 'preferences', 'gotchas', 'solutions', 'corrections']: + category_file = knowledge_dir / f'{category}.json' + with open(category_file, 'r') as f: + entries = json.load(f) + index['categories'][category] = len(entries) + index['total_entries'] += len(entries) + + # Update timestamp + index['last_updated'] = datetime.now().isoformat() + + with open(index_file, 'w') as f: + json.dump(index, f, indent=2) + + +def update_timeline(oracle_path, session_id, session_data): + """Update project timeline.""" + timeline_file = oracle_path / 'timeline' / 'project_timeline.md' + + entry = f""" +## {datetime.now().strftime('%Y-%m-%d %H:%M')} - {session_data.get('summary', 'Session recorded')} + +**Session ID**: `{session_id}` + +""" + + if session_data.get('activities'): + entry += "**Activities**:\n" + for activity in session_data['activities'][:3]: # Top 3 + entry += f"- {activity}\n" + if len(session_data['activities']) > 3: + entry += f"- ... and {len(session_data['activities']) - 3} more\n" + entry += "\n" + + if session_data.get('learnings'): + entry += f"**Key Learnings**: {len(session_data['learnings'])}\n\n" + + if session_data.get('corrections'): + entry += f"**Corrections Made**: {len(session_data['corrections'])}\n\n" + + entry += "---\n" + + # Append to timeline + with open(timeline_file, 'a') as f: + f.write(entry) + + +def main(): + parser = argparse.ArgumentParser( + description='Record Oracle session', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--interactive', + action='store_true', + help='Interactive mode with prompts' + ) + + parser.add_argument( + '--summary', + help='Session summary' + ) + + parser.add_argument( + '--learnings', + help='Learnings (semicolon-separated)' + ) + + parser.add_argument( + '--corrections', + help='Corrections in format "wrong->right" (semicolon-separated)' + ) + + args = parser.parse_args() + + # Find Oracle + oracle_path = find_oracle_root() + + if not oracle_path: + print("[ERROR] Error: .oracle directory not found.") + print(" Run: python .claude/skills/oracle/scripts/init_oracle.py") + sys.exit(1) + + # Get session data + if args.interactive: + session_data = interactive_session_record() + else: + session_data = { + 'summary': args.summary or '', + 'activities': [], + 'changes': [], + 'decisions': [], + 'learnings': [], + 'corrections': [], + 'questions': [] + } + + if args.learnings: + for learning in args.learnings.split(';'): + session_data['learnings'].append({ + 'content': learning.strip(), + 'priority': 'medium' + }) + + if args.corrections: + for correction in args.corrections.split(';'): + if '->' in correction: + wrong, right = correction.split('->', 1) + session_data['corrections'].append({ + 'wrong': wrong.strip(), + 'right': right.strip(), + 'context': '' + }) + + # Generate session ID + session_id = generate_session_id() + + print(f"\n[NOTE] Recording session: {session_id}\n") + + # Create session log + log_file = create_session_log(oracle_path, session_id, session_data) + print(f"[OK] Session log created: {log_file}") + + # Update knowledge base + updated_categories = update_knowledge_base(oracle_path, session_id, session_data) + if updated_categories: + print(f"[OK] Knowledge base updated: {', '.join(updated_categories)}") + + # Update timeline + update_timeline(oracle_path, session_id, session_data) + print(f"[OK] Timeline updated") + + # Update index + update_index(oracle_path, session_id) + print(f"[OK] Index updated") + + print(f"\n Session recorded successfully!\n") + print(f"View log: {log_file}") + print(f"Query knowledge: python .claude/skills/oracle/scripts/query_knowledge.py\n") + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/session_handoff.py b/skills/oracle/scripts/session_handoff.py new file mode 100755 index 0000000..1e47d68 --- /dev/null +++ b/skills/oracle/scripts/session_handoff.py @@ -0,0 +1,448 @@ +#!/usr/bin/env python3 +""" +Oracle - Enhanced Session Handoff + +Generates comprehensive context for new sessions to prevent degradation from compaction. + +This solves the "sessions going insane" problem by preserving critical context +when switching to a fresh session. + +Usage: + # Generate handoff context for new session + python session_handoff.py --export + + # Import handoff context in new session + python session_handoff.py --import handoff_context.json + + # Show what would be included (dry run) + python session_handoff.py --preview + +Environment Variables: + ORACLE_VERBOSE: Set to '1' for detailed output +""" + +import os +import sys +import json +import argparse +from pathlib import Path +from typing import Dict, List, Any, Optional +from datetime import datetime, timezone + + +def get_session_context() -> Dict[str, Any]: + """Extract critical session context for handoff. + + Returns: + Dictionary with session context for new session + """ + context = { + 'handoff_timestamp': datetime.now(timezone.utc).isoformat(), + 'handoff_reason': 'session_degradation', + 'oracle_knowledge': {}, + 'guardian_health': {}, + 'summoner_state': {}, + 'active_tasks': [], + 'critical_patterns': [], + 'recent_corrections': [], + 'session_stats': {} + } + + # Load Oracle knowledge (critical patterns only) + oracle_dir = Path('.oracle') + if oracle_dir.exists(): + context['oracle_knowledge'] = load_critical_oracle_knowledge(oracle_dir) + + # Load Guardian session health + guardian_dir = Path('.guardian') + if guardian_dir.exists(): + context['guardian_health'] = load_guardian_health(guardian_dir) + + # Load Summoner state (active MCDs) + summoner_dir = Path('.summoner') + if summoner_dir.exists(): + context['summoner_state'] = load_summoner_state(summoner_dir) + + # Get active tasks from current session + context['active_tasks'] = extract_active_tasks() + + # Get session statistics + context['session_stats'] = get_session_statistics() + + return context + + +def load_critical_oracle_knowledge(oracle_dir: Path) -> Dict[str, Any]: + """Load only critical/high-priority Oracle knowledge. + + This is KISS - we don't dump everything, just what matters. + + Args: + oracle_dir: Path to .oracle directory + + Returns: + Critical knowledge for handoff + """ + knowledge = { + 'critical_patterns': [], + 'recent_corrections': [], + 'active_gotchas': [], + 'project_context': '' + } + + knowledge_dir = oracle_dir / 'knowledge' + if not knowledge_dir.exists(): + return knowledge + + # Load critical patterns + patterns_file = knowledge_dir / 'patterns.json' + if patterns_file.exists(): + try: + with open(patterns_file, 'r', encoding='utf-8') as f: + patterns = json.load(f) + # Only critical/high priority + knowledge['critical_patterns'] = [ + p for p in patterns + if p.get('priority') in ['critical', 'high'] + ][:10] # Max 10 patterns + except (OSError, IOError, json.JSONDecodeError): + pass + + # Load recent corrections (last 5) + corrections_file = knowledge_dir / 'corrections.json' + if corrections_file.exists(): + try: + with open(corrections_file, 'r', encoding='utf-8') as f: + corrections = json.load(f) + # Sort by timestamp, take last 5 + sorted_corrections = sorted( + corrections, + key=lambda x: x.get('created', ''), + reverse=True + ) + knowledge['recent_corrections'] = sorted_corrections[:5] + except (OSError, IOError, json.JSONDecodeError): + pass + + # Load active gotchas + gotchas_file = knowledge_dir / 'gotchas.json' + if gotchas_file.exists(): + try: + with open(gotchas_file, 'r', encoding='utf-8') as f: + gotchas = json.load(f) + # Only high priority gotchas + knowledge['active_gotchas'] = [ + g for g in gotchas + if g.get('priority') == 'high' + ][:5] # Max 5 gotchas + except (OSError, IOError, json.JSONDecodeError): + pass + + return knowledge + + +def load_guardian_health(guardian_dir: Path) -> Dict[str, Any]: + """Load Guardian session health metrics. + + Args: + guardian_dir: Path to .guardian directory + + Returns: + Health metrics and degradation signals + """ + health = { + 'last_health_score': None, + 'degradation_signals': [], + 'handoff_reason': '', + 'session_duration_minutes': 0 + } + + health_file = guardian_dir / 'session_health.json' + if health_file.exists(): + try: + with open(health_file, 'r', encoding='utf-8') as f: + data = json.load(f) + health['last_health_score'] = data.get('health_score') + health['degradation_signals'] = data.get('degradation_signals', []) + health['handoff_reason'] = data.get('handoff_reason', '') + health['session_duration_minutes'] = data.get('duration_minutes', 0) + except (OSError, IOError, json.JSONDecodeError): + pass + + return health + + +def load_summoner_state(summoner_dir: Path) -> Dict[str, Any]: + """Load Summoner active MCDs and task state. + + Args: + summoner_dir: Path to .summoner directory + + Returns: + Active mission state + """ + state = { + 'active_mcds': [], + 'pending_tasks': [], + 'completed_phases': [] + } + + # Check for active MCDs + mcds_dir = summoner_dir / 'mcds' + if mcds_dir.exists(): + for mcd_file in mcds_dir.glob('*.md'): + try: + with open(mcd_file, 'r', encoding='utf-8') as f: + content = f.read() + # Extract summary and pending tasks + state['active_mcds'].append({ + 'name': mcd_file.stem, + 'file': str(mcd_file), + 'summary': extract_mcd_summary(content), + 'pending_tasks': extract_pending_tasks(content) + }) + except (OSError, IOError, UnicodeDecodeError): + continue + + return state + + +def extract_mcd_summary(mcd_content: str) -> str: + """Extract executive summary from MCD. + + Args: + mcd_content: MCD markdown content + + Returns: + Summary text (max 200 chars) + """ + lines = mcd_content.split('\n') + in_summary = False + summary_lines = [] + + for line in lines: + if '## Executive Summary' in line: + in_summary = True + continue + elif in_summary and line.startswith('##'): + break + elif in_summary and line.strip(): + summary_lines.append(line.strip()) + + summary = ' '.join(summary_lines) + return summary[:200] + '...' if len(summary) > 200 else summary + + +def extract_pending_tasks(mcd_content: str) -> List[str]: + """Extract uncompleted tasks from MCD. + + Args: + mcd_content: MCD markdown content + + Returns: + List of pending task descriptions + """ + pending = [] + lines = mcd_content.split('\n') + + for line in lines: + # Look for unchecked checkboxes + if '- [ ]' in line: + task = line.replace('- [ ]', '').strip() + pending.append(task) + + return pending[:10] # Max 10 pending tasks + + +def extract_active_tasks() -> List[str]: + """Extract active tasks from current session. + + Returns: + List of active task descriptions + """ + # This would integrate with Claude Code's task system + # For now, return placeholder + return [] + + +def get_session_statistics() -> Dict[str, Any]: + """Get current session statistics. + + Returns: + Session stats (duration, files modified, etc.) + """ + stats = { + 'duration_minutes': 0, + 'files_modified': 0, + 'commands_run': 0, + 'errors_encountered': 0 + } + + # Would integrate with Claude Code session tracking + # For now, return placeholder + return stats + + +def generate_handoff_message(context: Dict[str, Any]) -> str: + """Generate human-readable handoff message for new session. + + Args: + context: Session context dictionary + + Returns: + Formatted handoff message + """ + lines = [] + + lines.append("=" * 70) + lines.append("SESSION HANDOFF CONTEXT") + lines.append("=" * 70) + lines.append("") + + # Handoff reason + health = context.get('guardian_health', {}) + if health.get('handoff_reason'): + lines.append(f"Handoff Reason: {health['handoff_reason']}") + lines.append(f"Previous Session Health: {health.get('last_health_score', 'N/A')}/100") + lines.append(f"Session Duration: {health.get('session_duration_minutes', 0)} minutes") + lines.append("") + + # Critical Oracle knowledge + oracle = context.get('oracle_knowledge', {}) + + if oracle.get('critical_patterns'): + lines.append("CRITICAL PATTERNS:") + lines.append("-" * 70) + for pattern in oracle['critical_patterns'][:5]: + lines.append(f" β€’ {pattern.get('title', 'Unknown')}") + if pattern.get('content'): + lines.append(f" {pattern['content'][:100]}...") + lines.append("") + + if oracle.get('recent_corrections'): + lines.append("RECENT CORRECTIONS (Don't repeat these mistakes):") + lines.append("-" * 70) + for correction in oracle['recent_corrections']: + lines.append(f" β€’ {correction.get('title', 'Unknown')}") + lines.append("") + + if oracle.get('active_gotchas'): + lines.append("ACTIVE GOTCHAS:") + lines.append("-" * 70) + for gotcha in oracle['active_gotchas']: + lines.append(f" β€’ {gotcha.get('title', 'Unknown')}") + lines.append("") + + # Active Summoner MCDs + summoner = context.get('summoner_state', {}) + if summoner.get('active_mcds'): + lines.append("ACTIVE MISSION CONTROL DOCUMENTS:") + lines.append("-" * 70) + for mcd in summoner['active_mcds']: + lines.append(f" β€’ {mcd['name']}") + if mcd.get('summary'): + lines.append(f" Summary: {mcd['summary']}") + if mcd.get('pending_tasks'): + lines.append(f" Pending tasks: {len(mcd['pending_tasks'])}") + lines.append("") + + lines.append("=" * 70) + lines.append("Use '/handoff-continue' to pick up where we left off") + lines.append("=" * 70) + + return "\n".join(lines) + + +def export_handoff_context(output_file: str = 'handoff_context.json') -> None: + """Export session context for handoff. + + Args: + output_file: Path to output JSON file + """ + context = get_session_context() + + # Save JSON + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(context, f, indent=2) + + # Print human-readable message + message = generate_handoff_message(context) + print(message) + print(f"\nβœ… Handoff context saved to: {output_file}") + print("\nIn your new session, run:") + print(f" python session_handoff.py --import {output_file}") + + +def import_handoff_context(input_file: str) -> None: + """Import handoff context in new session. + + Args: + input_file: Path to handoff JSON file + """ + if not Path(input_file).exists(): + print(f"❌ Handoff file not found: {input_file}") + sys.exit(1) + + with open(input_file, 'r', encoding='utf-8') as f: + context = json.load(f) + + # Display handoff message + message = generate_handoff_message(context) + print(message) + + print("\nβœ… Session handoff complete!") + print("You're now up to speed with critical context from the previous session.") + + +def preview_handoff() -> None: + """Preview what would be included in handoff.""" + context = get_session_context() + message = generate_handoff_message(context) + print(message) + + +def main(): + parser = argparse.ArgumentParser( + description='Enhanced session handoff with Oracle/Guardian/Summoner integration', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--export', + action='store_true', + help='Export handoff context for new session' + ) + + parser.add_argument( + '--import', + dest='import_file', + help='Import handoff context from file' + ) + + parser.add_argument( + '--preview', + action='store_true', + help='Preview handoff context without exporting' + ) + + parser.add_argument( + '--output', + default='handoff_context.json', + help='Output file for export (default: handoff_context.json)' + ) + + args = parser.parse_args() + + if args.export: + export_handoff_context(args.output) + elif args.import_file: + import_handoff_context(args.import_file) + elif args.preview: + preview_handoff() + else: + parser.print_help() + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/session_start_hook.py b/skills/oracle/scripts/session_start_hook.py new file mode 100755 index 0000000..492ffac --- /dev/null +++ b/skills/oracle/scripts/session_start_hook.py @@ -0,0 +1,402 @@ +#!/usr/bin/env python3 +""" +Oracle SessionStart Hook + +Automatically loads Oracle context when a Claude Code session starts or resumes. +This script is designed to be called by Claude Code's SessionStart hook system. + +The script outputs JSON with hookSpecificOutput.additionalContext containing +relevant Oracle knowledge for the session. + +Usage: + python session_start_hook.py [--session-id SESSION_ID] [--source SOURCE] + +Hook Configuration (add to Claude Code settings): + { + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "python /path/to/oracle/scripts/session_start_hook.py" + } + ] + } + ] + } + } + +Environment Variables: + ORACLE_CONTEXT_TIER: Context tier level (1=critical, 2=medium, 3=all) [default: 1] + ORACLE_MAX_CONTEXT_LENGTH: Maximum context length in characters [default: 5000] +""" + +import os +import sys +import json +import argparse +from datetime import datetime +from pathlib import Path +from typing import List, Dict, Optional, Any + + +def find_oracle_root() -> Optional[Path]: + """Find the .oracle directory by walking up from current directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def load_all_knowledge(oracle_path: Path) -> List[Dict[str, Any]]: + """Load all knowledge from Oracle. + + Args: + oracle_path: Path to the .oracle directory + + Returns: + List of knowledge entries with _category field added + """ + knowledge_dir = oracle_path / 'knowledge' + all_knowledge: List[Dict[str, Any]] = [] + + categories = ['patterns', 'preferences', 'gotchas', 'solutions', 'corrections'] + + for category in categories: + file_path = knowledge_dir / f'{category}.json' + if file_path.exists(): + try: + with open(file_path, 'r', encoding='utf-8') as f: + entries = json.load(f) + for entry in entries: + if isinstance(entry, dict): + entry['_category'] = category + all_knowledge.append(entry) + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + # Skip corrupted or inaccessible files + continue + + return all_knowledge + + +def filter_by_tier(knowledge: List[Dict[str, Any]], tier: int = 1) -> List[Dict[str, Any]]: + """Filter knowledge by tier level. + + Args: + knowledge: List of knowledge entries + tier: Tier level (1=critical/high, 2=include medium, 3=all) + + Returns: + Filtered knowledge entries + """ + if tier == 1: + # Critical and high priority - always load + return [k for k in knowledge if k.get('priority') in ['critical', 'high']] + elif tier == 2: + # Include medium priority + return [k for k in knowledge if k.get('priority') in ['critical', 'high', 'medium']] + else: + # All knowledge + return knowledge + + +def get_recent_corrections(oracle_path: Path, limit: int = 5) -> List[Dict[str, Any]]: + """Get most recent corrections. + + Args: + oracle_path: Path to the .oracle directory + limit: Maximum number of corrections to return + + Returns: + List of recent correction entries + """ + knowledge_dir = oracle_path / 'knowledge' + corrections_file = knowledge_dir / 'corrections.json' + + if not corrections_file.exists(): + return [] + + try: + with open(corrections_file, 'r', encoding='utf-8') as f: + corrections = json.load(f) + + # Sort by creation date (safely handle missing 'created' field) + sorted_corrections = sorted( + corrections, + key=lambda x: x.get('created', ''), + reverse=True + ) + + return sorted_corrections[:limit] + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + return [] + + +def get_project_stats(oracle_path: Path) -> Optional[Dict[str, Any]]: + """Get project statistics from index. + + Args: + oracle_path: Path to the .oracle directory + + Returns: + Index data dictionary or None if unavailable + """ + index_file = oracle_path / 'index.json' + + if not index_file.exists(): + return None + + try: + with open(index_file, 'r', encoding='utf-8') as f: + index = json.load(f) + return index + except (json.JSONDecodeError, FileNotFoundError, OSError, IOError): + return None + + +# Configuration constants +MAX_KEY_KNOWLEDGE_ITEMS = 15 # Limit before truncation +MAX_ITEMS_PER_CATEGORY = 5 # How many to show per category +RECENT_CORRECTIONS_LIMIT = 3 # How many recent corrections to show +CONTENT_LENGTH_THRESHOLD = 200 # Min content length to display + + +def generate_context(oracle_path: Path, tier: int = 1, max_length: int = 5000) -> str: + """Generate context summary for session start. + + Args: + oracle_path: Path to the .oracle directory + tier: Context tier level (1=critical, 2=medium, 3=all) + max_length: Maximum context length in characters + + Returns: + Formatted context string ready for injection + """ + knowledge = load_all_knowledge(oracle_path) + + if not knowledge: + return "Oracle: No knowledge base found. Start recording sessions to build project knowledge." + + # Filter by tier + relevant_knowledge = filter_by_tier(knowledge, tier) + + # Get recent corrections + recent_corrections = get_recent_corrections(oracle_path, limit=RECENT_CORRECTIONS_LIMIT) + + # Get stats + stats = get_project_stats(oracle_path) + + # Build context + lines = [] + + lines.append("# Oracle Project Knowledge") + lines.append("") + + # Add stats if available + if stats: + total_entries = stats.get('total_entries', 0) + sessions = len(stats.get('sessions', [])) + if total_entries > 0 or sessions > 0: + lines.append(f"Knowledge Base: {total_entries} entries | {sessions} sessions recorded") + lines.append("") + + # Add critical/high priority knowledge + if relevant_knowledge: + lines.append("## Key Knowledge") + lines.append("") + + # Group by category + by_category: Dict[str, List[Dict[str, Any]]] = {} + for item in relevant_knowledge[:MAX_KEY_KNOWLEDGE_ITEMS]: + category = item['_category'] + if category not in by_category: + by_category[category] = [] + by_category[category].append(item) + + # Category labels + category_labels = { + 'patterns': 'Patterns', + 'preferences': 'Preferences', + 'gotchas': 'Gotchas (Watch Out!)', + 'solutions': 'Solutions', + 'corrections': 'Corrections' + } + + for category, items in by_category.items(): + label = category_labels.get(category, category.capitalize()) + lines.append(f"### {label}") + lines.append("") + + for item in items[:MAX_ITEMS_PER_CATEGORY]: + priority = item.get('priority', 'medium') + title = item.get('title', 'Untitled') + content = item.get('content', '') + + # Compact format + if priority == 'critical': + lines.append(f"- **[CRITICAL]** {title}") + elif priority == 'high': + lines.append(f"- **{title}**") + else: + lines.append(f"- {title}") + + # Add brief content if it fits + if content and len(content) < CONTENT_LENGTH_THRESHOLD: + lines.append(f" {content}") + + lines.append("") + + # Add recent corrections + if recent_corrections: + lines.append("## Recent Corrections") + lines.append("") + + for correction in recent_corrections: + content = correction.get('content', '') + title = correction.get('title', 'Correction') + + # Try to extract the "right" part if available + if content and 'Right:' in content: + try: + right_part = content.split('Right:', 1)[1].split('\n', 1)[0].strip() + if right_part: + lines.append(f"- {right_part}") + else: + lines.append(f"- {title}") + except (IndexError, ValueError, AttributeError): + lines.append(f"- {title}") + else: + lines.append(f"- {title}") + + lines.append("") + + # Combine and truncate if needed + full_context = "\n".join(lines) + + if len(full_context) > max_length: + # Truncate and add note + truncated = full_context[:max_length].rsplit('\n', 1)[0] + truncated += f"\n\n*[Context truncated to {max_length} chars. Use /oracle skill for full knowledge base]*" + return truncated + + return full_context + + +def output_hook_result(context: str, session_id: Optional[str] = None, source: Optional[str] = None) -> None: + """Output result in Claude Code hook format. + + Args: + context: Context string to inject + session_id: Optional session ID + source: Optional session source (startup/resume/clear) + """ + result = { + "hookSpecificOutput": { + "hookEventName": "SessionStart", + "additionalContext": context + } + } + + # Add metadata if available + if session_id: + result["hookSpecificOutput"]["sessionId"] = session_id + if source: + result["hookSpecificOutput"]["source"] = source + + # Output as JSON + print(json.dumps(result, indent=2)) + + +def main(): + parser = argparse.ArgumentParser( + description='Oracle SessionStart hook for Claude Code', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--session-id', + help='Session ID (passed by Claude Code)' + ) + + parser.add_argument( + '--source', + help='Session source: startup, resume, or clear' + ) + + parser.add_argument( + '--tier', + type=int, + choices=[1, 2, 3], + help='Context tier level (1=critical, 2=medium, 3=all)' + ) + + parser.add_argument( + '--max-length', + type=int, + help='Maximum context length in characters' + ) + + parser.add_argument( + '--debug', + action='store_true', + help='Debug mode - output to stderr instead of JSON' + ) + + args = parser.parse_args() + + # Find Oracle + oracle_path = find_oracle_root() + + if not oracle_path: + # No Oracle found - output minimal context + if args.debug: + print("Oracle not initialized for this project", file=sys.stderr) + else: + # Get path to init script relative to this script + script_dir = Path(__file__).parent + init_script_path = script_dir / 'init_oracle.py' + + output_hook_result( + f"Oracle: Not initialized. Run `python {init_script_path}` to set up project knowledge tracking.", + args.session_id, + args.source + ) + sys.exit(0) + + # Get configuration from environment or arguments + tier = args.tier or int(os.getenv('ORACLE_CONTEXT_TIER', '1')) + max_length = args.max_length or int(os.getenv('ORACLE_MAX_CONTEXT_LENGTH', '5000')) + + # Generate context + try: + context = generate_context(oracle_path, tier, max_length) + + if args.debug: + print(context, file=sys.stderr) + else: + output_hook_result(context, args.session_id, args.source) + + except Exception as e: + if args.debug: + print(f"Error generating context: {e}", file=sys.stderr) + import traceback + traceback.print_exc(file=sys.stderr) + else: + # Don't expose internal error details to user + output_hook_result( + "Oracle: Error loading context. Use /oracle skill to query knowledge manually.", + args.session_id, + args.source + ) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/oracle/scripts/smart_context.py b/skills/oracle/scripts/smart_context.py new file mode 100755 index 0000000..f06e26e --- /dev/null +++ b/skills/oracle/scripts/smart_context.py @@ -0,0 +1,504 @@ +#!/usr/bin/env python3 +""" +Smart Context Generator for Oracle + +Enhances context generation by analyzing: +- Current git status (files changed, branch name) +- File patterns and paths in knowledge tags +- Time-decay for older knowledge +- Relevance scoring based on current work + +Usage: + python smart_context.py [--format text|json] [--max-length 5000] + +This can be used standalone or integrated into generate_context.py + +Examples: + python smart_context.py + python smart_context.py --format json --max-length 10000 +""" + +import os +import sys +import json +import subprocess +from datetime import datetime, timedelta +from pathlib import Path +from typing import List, Dict, Optional, Any, Tuple +import re + + +def find_oracle_root() -> Optional[Path]: + """Find the .oracle directory.""" + current = Path.cwd() + + while current != current.parent: + oracle_path = current / '.oracle' + if oracle_path.exists(): + return oracle_path + current = current.parent + + return None + + +def get_git_status() -> Dict[str, Any]: + """Get current git status information. + + Returns: + Dictionary with git status information + """ + git_info = { + 'branch': None, + 'modified_files': [], + 'staged_files': [], + 'untracked_files': [], + 'is_repo': False + } + + try: + # Check if we're in a git repo + subprocess.run( + ['git', 'rev-parse', '--git-dir'], + check=True, + capture_output=True, + text=True, + timeout=5 + ) + git_info['is_repo'] = True + + # Get current branch + result = subprocess.run( + ['git', 'branch', '--show-current'], + capture_output=True, + text=True, + check=False, + timeout=5 + ) + if result.returncode == 0: + git_info['branch'] = result.stdout.strip() + + # Get modified files + result = subprocess.run( + ['git', 'diff', '--name-only'], + capture_output=True, + text=True, + check=False, + timeout=5 + ) + if result.returncode == 0: + git_info['modified_files'] = [f.strip() for f in result.stdout.split('\n') if f.strip()] + + # Get staged files + result = subprocess.run( + ['git', 'diff', '--staged', '--name-only'], + capture_output=True, + text=True, + check=False, + timeout=5 + ) + if result.returncode == 0: + git_info['staged_files'] = [f.strip() for f in result.stdout.split('\n') if f.strip()] + + # Get untracked files + result = subprocess.run( + ['git', 'ls-files', '--others', '--exclude-standard'], + capture_output=True, + text=True, + check=False, + timeout=5 + ) + if result.returncode == 0: + git_info['untracked_files'] = [f.strip() for f in result.stdout.split('\n') if f.strip()] + + except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired): + # Not a git repo, git not available, or git command timed out + pass + + return git_info + + +def extract_file_patterns(files: List[str]) -> List[str]: + """Extract patterns from file paths for matching knowledge. + + Args: + files: List of file paths + + Returns: + List of patterns (file types, directory names, etc.) + """ + patterns = set() + + for file_path in files: + path = Path(file_path) + + # Add file extension + if path.suffix: + patterns.add(path.suffix[1:]) # Remove the dot + + # Add directory components + for part in path.parts[:-1]: # Exclude filename + if part and part != '.': + patterns.add(part) + + # Add filename without extension + stem = path.stem + if stem: + patterns.add(stem) + + return list(patterns) + + +def load_all_knowledge(oracle_path: Path) -> List[Dict[str, Any]]: + """Load all knowledge from Oracle. + + Args: + oracle_path: Path to .oracle directory + + Returns: + List of knowledge entries + """ + knowledge_dir = oracle_path / 'knowledge' + all_knowledge: List[Dict[str, Any]] = [] + + categories = ['patterns', 'preferences', 'gotchas', 'solutions', 'corrections'] + + for category in categories: + file_path = knowledge_dir / f'{category}.json' + if file_path.exists(): + try: + with open(file_path, 'r', encoding='utf-8') as f: + entries = json.load(f) + for entry in entries: + if isinstance(entry, dict): + entry['_category'] = category + all_knowledge.append(entry) + except json.JSONDecodeError as e: + # Log parsing errors for debugging + print(f"Warning: Failed to parse {file_path}: {e}", file=sys.stderr) + continue + except (FileNotFoundError, OSError, IOError) as e: + # Log file access errors + print(f"Warning: Cannot read {file_path}: {e}", file=sys.stderr) + continue + + return all_knowledge + + +def calculate_time_decay_score(created_date: str, days_half_life: int = 30) -> float: + """Calculate time decay score for knowledge based on age. + + Args: + created_date: ISO format date string + days_half_life: Number of days for score to decay to 0.5 (must be positive) + + Returns: + Score between 0 and 1 (1 = created today, decays over time) + + Raises: + ValueError: If days_half_life is not positive + """ + if days_half_life <= 0: + raise ValueError(f"days_half_life must be positive, got {days_half_life}") + + try: + created = datetime.fromisoformat(created_date) + # Use UTC time if available, otherwise use local time + now = datetime.now(created.tzinfo) if created.tzinfo else datetime.now() + + # Use total_seconds for precise calculation (includes hours/minutes) + age_seconds = (now - created).total_seconds() + age_days = age_seconds / (24 * 3600) # Convert to days with decimals + + # Exponential decay: score = 0.5 ^ (days_old / half_life) + score = 0.5 ** (age_days / days_half_life) + return max(0.0, min(1.0, score)) + + except (ValueError, TypeError): + # If date parsing fails, return neutral score + return 0.5 + + +def calculate_relevance_score( + entry: Dict[str, Any], + file_patterns: List[str], + branch: Optional[str] = None +) -> float: + """Calculate relevance score for a knowledge entry. + + Args: + entry: Knowledge entry dictionary + file_patterns: List of file patterns from current work + branch: Current git branch name + + Returns: + Relevance score (0.0 to 1.0) + """ + score = 0.0 + + # Base score from priority + priority_scores = { + 'critical': 1.0, + 'high': 0.8, + 'medium': 0.5, + 'low': 0.2 + } + priority = entry.get('priority', 'medium') + score += priority_scores.get(priority, 0.5) * 0.3 # 30% weight to priority + + # Score from tag matches - FIXED: protect against empty file_patterns + tags = entry.get('tags', []) + if tags and file_patterns: + # Check how many patterns match tags (using word boundary matching) + matches = sum(1 for pattern in file_patterns + if any(re.search(r'\b' + re.escape(pattern.lower()) + r'\b', tag.lower()) + for tag in tags)) + tag_score = matches / len(file_patterns) # Safe: len(file_patterns) > 0 + score += min(1.0, tag_score) * 0.4 # 40% weight to tag matching + + # Score from content/title keyword matching - FIXED: protect against empty file_patterns + if file_patterns: + content = f"{entry.get('title', '')} {entry.get('content', '')} {entry.get('context', '')}".lower() + # Use word boundary matching to avoid false positives + keyword_matches = sum(1 for pattern in file_patterns + if re.search(r'\b' + re.escape(pattern.lower()) + r'\b', content)) + keyword_score = keyword_matches / len(file_patterns) # Safe: len(file_patterns) > 0 + score += min(1.0, keyword_score) * 0.2 # 20% weight to keyword matching + + # Score from time decay + created = entry.get('created', '') + time_score = calculate_time_decay_score(created) + score += time_score * 0.1 # 10% weight to recency + + return min(1.0, score) + + +def score_and_rank_knowledge( + knowledge: List[Dict[str, Any]], + git_info: Dict[str, Any] +) -> List[Tuple[Dict[str, Any], float]]: + """Score and rank knowledge entries by relevance. + + Args: + knowledge: List of knowledge entries + git_info: Git status information + + Returns: + List of tuples (entry, score) sorted by score descending + """ + # Extract file patterns from all changed files + all_files = ( + git_info['modified_files'] + + git_info['staged_files'] + + git_info['untracked_files'] + ) + file_patterns = extract_file_patterns(all_files) + + # Score each entry + scored_entries = [] + for entry in knowledge: + score = calculate_relevance_score(entry, file_patterns, git_info.get('branch')) + scored_entries.append((entry, score)) + + # Sort by score descending + scored_entries.sort(key=lambda x: x[1], reverse=True) + + return scored_entries + + +def generate_smart_context( + oracle_path: Path, + max_length: int = 5000, + min_score: float = 0.3 +) -> str: + """Generate smart context based on current git status. + + Args: + oracle_path: Path to .oracle directory + max_length: Maximum context length (must be > 0) + min_score: Minimum relevance score to include (0.0-1.0) + + Returns: + Formatted context string + + Raises: + ValueError: If parameters are invalid + """ + # Validate parameters + if not 0.0 <= min_score <= 1.0: + raise ValueError(f"min_score must be in [0.0, 1.0], got {min_score}") + if max_length <= 0: + raise ValueError(f"max_length must be positive, got {max_length}") + # Get git status + git_info = get_git_status() + + # Load all knowledge + knowledge = load_all_knowledge(oracle_path) + + if not knowledge: + return "Oracle: No knowledge base found." + + # Score and rank knowledge + scored_knowledge = score_and_rank_knowledge(knowledge, git_info) + + # Filter by minimum score + relevant_knowledge = [(entry, score) for entry, score in scored_knowledge if score >= min_score] + + # Build context + lines = [] + + lines.append("# Oracle Smart Context") + lines.append("") + + # Add git status if available + if git_info['is_repo']: + lines.append("## Current Work Context") + if git_info['branch']: + lines.append(f"Branch: `{git_info['branch']}`") + + total_files = len(git_info['modified_files']) + len(git_info['staged_files']) + if total_files > 0: + lines.append(f"Files being worked on: {total_files}") + + lines.append("") + + # Add relevant knowledge + if relevant_knowledge: + lines.append("## Relevant Knowledge") + lines.append("") + + # Group by category + by_category: Dict[str, List[Tuple[Dict[str, Any], float]]] = {} + for entry, score in relevant_knowledge[:20]: # Top 20 + category = entry['_category'] + if category not in by_category: + by_category[category] = [] + by_category[category].append((entry, score)) + + category_labels = { + 'patterns': 'Patterns', + 'preferences': 'Preferences', + 'gotchas': 'Gotchas (Watch Out!)', + 'solutions': 'Solutions', + 'corrections': 'Corrections' + } + + for category, items in by_category.items(): + label = category_labels.get(category, category.capitalize()) + lines.append(f"### {label}") + lines.append("") + + for entry, score in items[:10]: # Top 10 per category + priority = entry.get('priority', 'medium') + title = entry.get('title', 'Untitled') + content = entry.get('content', '') + + # Format based on priority and score + if priority == 'critical' or score >= 0.8: + lines.append(f"- **[{score:.1f}] {title}**") + else: + lines.append(f"- [{score:.1f}] {title}") + + # Add content if it's brief + if content and len(content) < 200: + lines.append(f" {content}") + + # Add tags if they matched + tags = entry.get('tags', []) + if tags: + lines.append(f" *Tags: {', '.join(tags[:5])}*") + + lines.append("") + + else: + lines.append("No highly relevant knowledge found for current work.") + lines.append("") + lines.append("Showing high-priority items:") + lines.append("") + + # Fall back to high-priority items + high_priority = [e for e in knowledge if e.get('priority') in ['critical', 'high']] + for entry in high_priority[:10]: + title = entry.get('title', 'Untitled') + lines.append(f"- {title}") + + lines.append("") + + # Combine and truncate if needed + full_context = "\n".join(lines) + + if len(full_context) > max_length: + truncated = full_context[:max_length] + # Find last newline to avoid breaking mid-line + last_newline = truncated.rfind('\n') + if last_newline != -1: + truncated = truncated[:last_newline] + truncated += f"\n\n*[Context truncated to {max_length} chars]*" + return truncated + + return full_context + + +def main(): + import argparse + + parser = argparse.ArgumentParser( + description='Generate smart context from Oracle knowledge', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--format', + choices=['text', 'json'], + default='text', + help='Output format (text or json)' + ) + + parser.add_argument( + '--max-length', + type=int, + default=5000, + help='Maximum context length' + ) + + parser.add_argument( + '--min-score', + type=float, + default=0.3, + help='Minimum relevance score (0.0-1.0)' + ) + + args = parser.parse_args() + + # Find Oracle + oracle_path = find_oracle_root() + + if not oracle_path: + if args.format == 'json': + print(json.dumps({'error': 'Oracle not initialized'})) + else: + print("[ERROR] .oracle directory not found.") + sys.exit(1) + + # Generate context + try: + context = generate_smart_context(oracle_path, args.max_length, args.min_score) + + if args.format == 'json': + output = { + 'context': context, + 'git_status': get_git_status() + } + print(json.dumps(output, indent=2)) + else: + print(context) + + except Exception as e: + if args.format == 'json': + print(json.dumps({'error': str(e)})) + else: + print(f"[ERROR] {e}") + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/smart-init/README.md b/skills/smart-init/README.md new file mode 100644 index 0000000..8c9866e --- /dev/null +++ b/skills/smart-init/README.md @@ -0,0 +1,158 @@ +# Smart Init + +**Interactive, Intelligent ClaudeShack Initialization** + +Smart Init doesn't just create empty directories - it **understands your project** and seeds Oracle with verified, high-quality knowledge. + +## Why Smart Init? + +| Traditional Init | Smart Init | +|------------------|------------| +| Creates empty `.oracle/` | Discovers languages, frameworks, patterns | +| Generic config | Project-specific Guardian thresholds | +| No knowledge | Seeds Oracle with discovered + confirmed knowledge | +| Hope it works | Verifies understanding through conversation | + +## How It Works + +### Phase 1: Discovery +Automatically analyzes: +- Languages (by file count and LOC) +- Frameworks (React, Django, Express, etc.) +- Conventions (linting, formatting, git style) +- Project structure +- Existing documentation +- Conversation history (if available) + +### Phase 2: Discussion +Presents findings and asks targeted questions: +- "Is this understanding correct?" +- "What are the top gotchas I should know?" +- "Any tribal knowledge that isn't documented?" + +### Phase 3: Seeding +Creates Oracle entries from **confirmed** understanding: +- Patterns β†’ from code + your confirmation +- Gotchas β†’ from history + your input +- Preferences β†’ from config + your clarification + +### Phase 4: Learning +After setup, the system improves over use: +- Corrections get recorded +- Guardian learns what you accept/reject +- Patterns get refined +- Knowledge compounds + +## Usage + +### Via Skill Invocation +``` +Use the smart-init skill to initialize ClaudeShack for this project +``` + +### Via Discovery Script +```bash +# See what Smart Init would discover +python .claude/skills/smart-init/scripts/discover.py + +# JSON output for automation +python .claude/skills/smart-init/scripts/discover.py --json +``` + +## What Gets Created + +After Smart Init completes: + +``` +.oracle/ +β”œβ”€β”€ knowledge/ +β”‚ β”œβ”€β”€ patterns.json # Discovered + confirmed patterns +β”‚ β”œβ”€β”€ gotchas.json # Critical issues to watch for +β”‚ β”œβ”€β”€ preferences.json # Team/user preferences +β”‚ β”œβ”€β”€ corrections.json # (empty, learns over time) +β”‚ └── solutions.json # (empty, learns over time) +β”œβ”€β”€ sessions/ +β”œβ”€β”€ timeline/ +└── index.json + +.guardian/ +└── config.json # Project-appropriate thresholds + +claude.md # Updated with project context +``` + +## Knowledge Quality + +Every seeded entry has: +- **Source tracking**: `learned_from` field +- **Confidence level**: Based on discovery + confirmation +- **Appropriate priority**: Only critical if truly critical + +### Source Types +- `smart-init-discovery` - Found in code/config +- `smart-init-history` - From conversation history +- `smart-init-user` - Directly from user input +- `smart-init-confirmed` - Discovered + user confirmed + +## Example Session + +``` +User: Initialize ClaudeShack for this project + +Smart Init: [Runs discovery for ~30 seconds] + +"I've analyzed your project. Here's what I found: + +**Project**: my-app +**Stack**: TypeScript + React + Express + PostgreSQL + +I detected: +- Conventional commits style +- ESLint with Airbnb config +- Jest for testing +- Prisma for database + +Questions: +1. Is PostgreSQL your primary database? +2. Any critical gotchas about the database I should know? +3. What coding patterns should I enforce?" + +User: "Yes PostgreSQL. Watch out - connection pool doesn't auto-close. + We prefer async/await everywhere." + +Smart Init: "Got it! Setting up Oracle with: +- [CRITICAL] Database pool must be explicitly closed +- [HIGH] Prefer async/await over callbacks +- [MEDIUM] Use Prisma for all database operations + +Creating directories and seeding knowledge..." +``` + +## Fine-Tuning Over Use + +After initialization, the system self-improves: + +1. **You correct Claude** β†’ Recorded in Oracle corrections +2. **Guardian reviews code** β†’ Learns what you accept/reject +3. **You record sessions** β†’ Patterns get refined +4. **Weekly analysis** β†’ Finds automation opportunities + +The knowledge base **grows smarter** over time without manual maintenance. + +## Scripts + +| Script | Purpose | +|--------|---------| +| `discover.py` | Analyze project and output findings | + +## Integration + +Smart Init sets up the full ecosystem: +- **Oracle**: Seeded with project knowledge +- **Guardian**: Calibrated for project complexity +- **Wizard**: Has accurate project understanding +- **Summoner**: Knows constraints for orchestration + +--- + +**"Understanding first. Setup second. Learning forever."** diff --git a/skills/smart-init/SKILL.md b/skills/smart-init/SKILL.md new file mode 100644 index 0000000..e55e2fd --- /dev/null +++ b/skills/smart-init/SKILL.md @@ -0,0 +1,395 @@ +--- +name: smart-init +description: Interactive ClaudeShack ecosystem initialization that analyzes your codebase, mines history, discusses findings with you to establish baseline understanding, and seeds Oracle with verified knowledge. Use when setting up ClaudeShack in a new project or resetting knowledge. Creates a personalized foundation that improves over use. Sets up Context7 for current library docs. +allowed-tools: Read, Write, Edit, Glob, Grep, Bash, Task +--- + +# Smart Init: Interactive Ecosystem Initialization + +You are the **Smart Init** skill - an intelligent initialization assistant that sets up ClaudeShack with a deep understanding of the project, not just empty directories. + +## Core Philosophy + +**Don't assume. Discover. Verify. Learn. Then WORK AUTONOMOUSLY.** + +The goal is **zero-friction intelligence** - after initialization: +- Claude automatically uses Oracle without being asked +- Claude fetches current library docs via Context7 +- User never has to repeat corrections +- User never has to remind Claude about patterns + +Instead of creating empty knowledge bases, Smart Init: +1. **Explores** the codebase to understand what exists +2. **Mines** conversation history for patterns and corrections +3. **Discusses** findings with the user to verify understanding +4. **Seeds** Oracle with confirmed, high-quality knowledge +5. **Sets up Context7** for current library documentation +6. **Creates MINIMAL claude.md** (not bloated) +7. **Explains autonomous behavior** so user knows what to expect + +## Initialization Workflow + +### Phase 1: Discovery (Automatic) + +Run these analyses silently, then summarize findings: + +#### 1.1 Codebase Analysis +``` +- Primary languages (by file count and LOC) +- Frameworks detected (React, Django, Express, etc.) +- Project structure pattern (monorepo, microservices, standard) +- Build tools (npm, cargo, pip, etc.) +- Test framework (jest, pytest, etc.) +- Linting/formatting tools +``` + +#### 1.2 Documentation Analysis +``` +- README.md presence and quality +- docs/ directory +- API documentation +- Contributing guidelines +- Existing claude.md content +``` + +#### 1.3 Configuration Detection +``` +- package.json, Cargo.toml, pyproject.toml, etc. +- .eslintrc, prettier, rustfmt, etc. +- CI/CD configuration (.github/workflows, etc.) +- Docker/containerization +- Environment patterns (.env.example, etc.) +``` + +#### 1.4 History Mining (if available) +``` +- Search ~/.claude/projects/ for this project +- Extract patterns, corrections, preferences +- Identify repeated tasks (automation candidates) +- Find gotchas from past issues +``` + +### Phase 2: Present Findings + +After discovery, present a structured summary: + +```markdown +## Project Understanding + +**Project**: [name from package.json/Cargo.toml/etc] +**Type**: [web app / CLI / library / API / etc] +**Primary Stack**: [e.g., TypeScript + React + Node.js] + +### Tech Stack Detected +- **Languages**: TypeScript (85%), JavaScript (10%), CSS (5%) +- **Frontend**: React 18.x with hooks +- **Backend**: Express.js +- **Database**: PostgreSQL (from prisma schema) +- **Testing**: Jest + React Testing Library +- **Build**: Vite + +### Project Structure +``` +src/ + components/ # React components + api/ # Backend routes + lib/ # Shared utilities + types/ # TypeScript types +``` + +### Conventions Detected +- Using ESLint with Airbnb config +- Prettier for formatting +- Conventional commits (from git log) +- Feature branch workflow + +### From Conversation History +- **Patterns found**: 3 (e.g., "use factory pattern for services") +- **Corrections found**: 2 (e.g., "prefer async/await over callbacks") +- **Gotchas found**: 1 (e.g., "database pool must be explicitly closed") + +### Questions for You +1. Is this understanding correct? +2. Are there any critical gotchas I should know about? +3. What coding preferences should I remember? +4. Any patterns you want enforced? +``` + +### Phase 3: Interactive Refinement + +Ask targeted questions based on gaps: + +**If no README found:** +> "I don't see a README. Can you briefly describe what this project does?" + +**If multiple possible architectures:** +> "I see both REST endpoints and GraphQL schemas. Which is the primary API style?" + +**If no tests found:** +> "I didn't find test files. Is testing a priority, or should I not suggest tests?" + +**Always ask:** +> "What are the top 3 things that have caused bugs or confusion in this project?" + +> "Are there any 'tribal knowledge' items that aren't documented but are critical?" + +### Phase 4: Seed Oracle Knowledge + +Based on confirmed understanding, create initial Oracle entries: + +#### Patterns (from discovery + user input) +```json +{ + "category": "pattern", + "priority": "high", + "title": "Use factory pattern for database connections", + "content": "All database connections should use DatabaseFactory.create() to ensure proper pooling", + "context": "Database operations", + "tags": ["database", "architecture", "confirmed-by-user"], + "learned_from": "smart-init-discovery" +} +``` + +#### Gotchas (from history + user input) +```json +{ + "category": "gotcha", + "priority": "critical", + "title": "Database pool must be explicitly closed", + "content": "Connection pool doesn't auto-close. Always call pool.end() in cleanup.", + "context": "Database shutdown", + "tags": ["database", "critical", "confirmed-by-user"], + "learned_from": "smart-init-conversation" +} +``` + +#### Preferences (from config + user input) +```json +{ + "category": "preference", + "priority": "medium", + "title": "Prefer async/await over callbacks", + "content": "Team prefers modern async patterns. Avoid callback-style code.", + "context": "All async code", + "tags": ["style", "async", "team-preference"], + "learned_from": "smart-init-discovery" +} +``` + +### Phase 5: Setup Verification + +After seeding: + +```markdown +## Initialization Complete + +### Created +- `.oracle/` with [X] knowledge entries +- `.guardian/config.json` with project-appropriate thresholds +- Updated `claude.md` with project context + +### Knowledge Base Seeded +| Category | Entries | Source | +|-------------|---------|--------| +| Patterns | 5 | Discovery + User | +| Preferences | 3 | Config + User | +| Gotchas | 2 | History + User | +| Solutions | 1 | History | + +### How It Improves Over Time + +1. **Corrections**: When you correct me, I record it in Oracle +2. **Guardian**: Reviews code and validates against patterns +3. **Sessions**: Record what worked and what didn't +4. **Analysis**: Weekly pattern detection finds automation opportunities + +### Next Session +Oracle will automatically load: +- Critical gotchas (always) +- Relevant patterns (based on files you're working on) +- Recent corrections (to avoid repeats) + +### Recommended Next Steps +1. Context7 should be installed for current library docs +2. Oracle will auto-query - no need to ask manually +3. When you correct me, I'll record it automatically +``` + +## Phase 6: Context7 Setup + +Check if Context7 MCP is installed. If not, guide user: + +```markdown +## Current Library Documentation + +I noticed Context7 MCP isn't installed. This means I might use outdated library knowledge. + +**To get current, version-specific documentation automatically:** + +```bash +claude mcp add context7 -- npx -y @upstash/context7-mcp@latest +``` + +After installation: +- I'll automatically fetch current docs when you mention libraries +- No more "Prisma 5" answers when you're on Prisma 7 +- Combined with Oracle, you get: current docs + project-specific patterns +``` + +If already installed: +```markdown +## Context7 βœ“ + +Context7 is installed. I'll automatically fetch current library docs. +- Generic docs: Context7 (current, version-specific) +- Project-specific: Oracle (your patterns, gotchas) +``` + +## Phase 7: Create MINIMAL claude.md + +**DO NOT** dump everything into claude.md. Keep it under 50 lines. + +```markdown +# [Project Name] + +[One sentence description] + +## Tech Stack +[2-3 lines max - just the key technologies] + +## ClaudeShack Skills +oracle, guardian, summoner, wizard, style-master, documentation-wizard + +## Critical Gotchas (Top 3-5 ONLY) +- [Most critical gotcha] +- [Second most critical] +- [Third most critical] + +## How I Work +- I auto-query Oracle for project patterns +- I fetch current library docs via Context7 +- I record corrections immediately +- You shouldn't need to remind me of things +``` + +**That's it.** Everything else lives in Oracle and is queried dynamically. + +## Phase 8: Explain Autonomous Behavior + +End the initialization by setting expectations: + +```markdown +## What to Expect Going Forward + +**I will automatically:** +- Check Oracle before making changes to files +- Fetch current library docs when you mention frameworks +- Record corrections the moment you give them +- Apply project patterns without being asked + +**You should never need to:** +- Say "check Oracle" or "use context7" +- Remind me of corrections you've given before +- Explain the same gotcha twice +- Nudge me to be proactive + +**If I forget something Oracle should know:** +Tell me once, I'll record it, and it won't happen again. + +**The system improves over time:** +Every correction, every pattern, every gotcha compounds. +After a few sessions, I should "know" this project. +``` + +## Discovery Commands + +Use these to gather information: + +```bash +# Language breakdown +find . -type f -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.rs" | head -100 + +# Package detection +ls package.json Cargo.toml pyproject.toml requirements.txt go.mod 2>/dev/null + +# Framework detection +grep -l "react\|vue\|angular\|express\|fastapi\|django" package.json requirements.txt 2>/dev/null + +# Test framework +ls -la **/*test* **/*spec* 2>/dev/null | head -20 + +# Git conventions +git log --oneline -20 + +# Existing documentation +ls README* CONTRIBUTING* docs/ 2>/dev/null +``` + +## Conversation Guidelines + +### Be Curious, Not Assumptive +- "I noticed X - is that intentional?" +- "The config suggests Y - should I enforce this?" +- NOT: "I see you're using X so I'll assume Y" + +### Validate Critical Items +- Always confirm gotchas before marking as critical +- Ask about edge cases +- Verify team preferences vs personal preferences + +### Keep It Focused +- Don't ask 20 questions +- Group related questions +- Prioritize: gotchas > patterns > preferences + +### Record Sources +Every Oracle entry should have `learned_from`: +- `smart-init-discovery` - Found in code/config +- `smart-init-history` - From conversation history +- `smart-init-user` - Directly from user +- `smart-init-confirmed` - Discovered + user confirmed + +## Anti-Patterns + +### DON'T +- Create empty knowledge files and call it "initialized" +- Make assumptions without verification +- Skip the conversation phase +- Overwhelm with questions +- Seed low-confidence knowledge as high priority + +### DO +- Actually explore the codebase +- Mine real history for real patterns +- Have a genuine conversation +- Seed only verified, valuable knowledge +- Explain how the system learns + +## Integration with Other Skills + +After Smart Init: +- **Oracle**: Has seeded knowledge to work with +- **Guardian**: Has calibrated thresholds for the project +- **Wizard**: Can reference accurate project understanding +- **Summoner**: Knows project constraints for orchestration + +## Example Session + +``` +User: "Initialize ClaudeShack for this project" + +Smart Init: +1. [Runs discovery - 30 seconds] +2. "I've analyzed your project. Here's what I found..." +3. [Presents findings summary] +4. "A few questions to make sure I understand correctly..." +5. [Asks 3-5 targeted questions] +6. [User responds] +7. "Great, let me set up Oracle with this understanding..." +8. [Seeds knowledge base] +9. "All set! Here's what I created and how it will improve..." +``` + +--- + +**"Understanding first. Setup second. Learning forever."** diff --git a/skills/smart-init/scripts/discover.py b/skills/smart-init/scripts/discover.py new file mode 100755 index 0000000..ddc9c31 --- /dev/null +++ b/skills/smart-init/scripts/discover.py @@ -0,0 +1,554 @@ +#!/usr/bin/env python3 +""" +Smart Init Discovery Script + +Analyzes a project to gather context for intelligent initialization. +Outputs structured JSON with findings for the Smart Init skill. + +Usage: + python discover.py [project_path] + python discover.py --json # Machine-readable output + python discover.py --verbose # Detailed human output +""" + +import os +import sys +import json +import subprocess +import re +from pathlib import Path +from datetime import datetime, timedelta +from collections import Counter +from typing import Dict, List, Any, Optional + + +def run_command(cmd: str, cwd: Path = None, timeout: int = 10) -> Optional[str]: + """Run a shell command and return output.""" + try: + result = subprocess.run( + cmd, shell=True, capture_output=True, text=True, + cwd=str(cwd) if cwd else None, timeout=timeout + ) + return result.stdout.strip() if result.returncode == 0 else None + except (subprocess.TimeoutExpired, Exception): + return None + + +def detect_languages(project_path: Path) -> Dict[str, int]: + """Detect programming languages by file extension.""" + extensions = { + '.ts': 'TypeScript', '.tsx': 'TypeScript', + '.js': 'JavaScript', '.jsx': 'JavaScript', + '.py': 'Python', + '.rs': 'Rust', + '.go': 'Go', + '.java': 'Java', + '.rb': 'Ruby', + '.php': 'PHP', + '.cs': 'C#', + '.cpp': 'C++', '.cc': 'C++', '.cxx': 'C++', + '.c': 'C', '.h': 'C/C++', + '.swift': 'Swift', + '.kt': 'Kotlin', + '.scala': 'Scala', + '.css': 'CSS', '.scss': 'SCSS', '.sass': 'Sass', + '.html': 'HTML', + '.vue': 'Vue', + '.svelte': 'Svelte', + } + + counts = Counter() + + for root, dirs, files in os.walk(project_path): + # Skip common non-source directories + dirs[:] = [d for d in dirs if d not in [ + 'node_modules', '.git', 'venv', '__pycache__', + 'target', 'dist', 'build', '.next', 'vendor' + ]] + + for file in files: + ext = Path(file).suffix.lower() + if ext in extensions: + counts[extensions[ext]] += 1 + + return dict(counts.most_common(10)) + + +def detect_frameworks(project_path: Path) -> Dict[str, List[str]]: + """Detect frameworks and tools from config files.""" + frameworks = { + 'frontend': [], + 'backend': [], + 'database': [], + 'testing': [], + 'build': [], + 'ci_cd': [], + 'containerization': [] + } + + # Check package.json + pkg_json = project_path / 'package.json' + if pkg_json.exists(): + try: + with open(pkg_json) as f: + pkg = json.load(f) + deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})} + + # Frontend + if 'react' in deps: + frameworks['frontend'].append(f"React {deps.get('react', '')}") + if 'vue' in deps: + frameworks['frontend'].append(f"Vue {deps.get('vue', '')}") + if 'angular' in deps or '@angular/core' in deps: + frameworks['frontend'].append("Angular") + if 'svelte' in deps: + frameworks['frontend'].append("Svelte") + if 'next' in deps: + frameworks['frontend'].append(f"Next.js {deps.get('next', '')}") + + # Backend + if 'express' in deps: + frameworks['backend'].append("Express.js") + if 'fastify' in deps: + frameworks['backend'].append("Fastify") + if 'koa' in deps: + frameworks['backend'].append("Koa") + if 'nestjs' in deps or '@nestjs/core' in deps: + frameworks['backend'].append("NestJS") + + # Database + if 'prisma' in deps or '@prisma/client' in deps: + frameworks['database'].append("Prisma") + if 'mongoose' in deps: + frameworks['database'].append("MongoDB (Mongoose)") + if 'pg' in deps: + frameworks['database'].append("PostgreSQL") + if 'mysql2' in deps: + frameworks['database'].append("MySQL") + if 'sequelize' in deps: + frameworks['database'].append("Sequelize ORM") + + # Testing + if 'jest' in deps: + frameworks['testing'].append("Jest") + if 'vitest' in deps: + frameworks['testing'].append("Vitest") + if 'mocha' in deps: + frameworks['testing'].append("Mocha") + if '@testing-library/react' in deps: + frameworks['testing'].append("React Testing Library") + if 'cypress' in deps: + frameworks['testing'].append("Cypress") + if 'playwright' in deps: + frameworks['testing'].append("Playwright") + + # Build tools + if 'vite' in deps: + frameworks['build'].append("Vite") + if 'webpack' in deps: + frameworks['build'].append("Webpack") + if 'esbuild' in deps: + frameworks['build'].append("esbuild") + if 'turbo' in deps: + frameworks['build'].append("Turborepo") + + except (json.JSONDecodeError, IOError): + pass + + # Check Python + for pyfile in ['pyproject.toml', 'requirements.txt', 'setup.py']: + pypath = project_path / pyfile + if pypath.exists(): + try: + content = pypath.read_text() + if 'django' in content.lower(): + frameworks['backend'].append("Django") + if 'fastapi' in content.lower(): + frameworks['backend'].append("FastAPI") + if 'flask' in content.lower(): + frameworks['backend'].append("Flask") + if 'pytest' in content.lower(): + frameworks['testing'].append("pytest") + if 'sqlalchemy' in content.lower(): + frameworks['database'].append("SQLAlchemy") + except IOError: + pass + + # Check Rust + cargo = project_path / 'Cargo.toml' + if cargo.exists(): + try: + content = cargo.read_text() + if 'actix' in content: + frameworks['backend'].append("Actix") + if 'axum' in content: + frameworks['backend'].append("Axum") + if 'tokio' in content: + frameworks['backend'].append("Tokio (async runtime)") + except IOError: + pass + + # Check CI/CD + if (project_path / '.github' / 'workflows').exists(): + frameworks['ci_cd'].append("GitHub Actions") + if (project_path / '.gitlab-ci.yml').exists(): + frameworks['ci_cd'].append("GitLab CI") + if (project_path / 'Jenkinsfile').exists(): + frameworks['ci_cd'].append("Jenkins") + + # Check containerization + if (project_path / 'Dockerfile').exists(): + frameworks['containerization'].append("Docker") + if (project_path / 'docker-compose.yml').exists() or (project_path / 'docker-compose.yaml').exists(): + frameworks['containerization'].append("Docker Compose") + if (project_path / 'kubernetes').exists() or (project_path / 'k8s').exists(): + frameworks['containerization'].append("Kubernetes") + + # Filter empty + return {k: v for k, v in frameworks.items() if v} + + +def detect_conventions(project_path: Path) -> Dict[str, Any]: + """Detect coding conventions and style configs.""" + conventions = { + 'linting': [], + 'formatting': [], + 'git': {}, + 'typing': False + } + + # Linting + lint_files = ['.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml', + 'pylintrc', '.pylintrc', 'ruff.toml', '.flake8'] + for lf in lint_files: + if (project_path / lf).exists(): + conventions['linting'].append(lf) + + # Formatting + format_files = ['.prettierrc', '.prettierrc.js', '.prettierrc.json', + 'rustfmt.toml', '.editorconfig', 'pyproject.toml'] + for ff in format_files: + if (project_path / ff).exists(): + conventions['formatting'].append(ff) + + # Git conventions (from recent commits) + git_log = run_command('git log --oneline -50', cwd=project_path) + if git_log: + commits = git_log.split('\n') + # Check for conventional commits + conventional_pattern = r'^[a-f0-9]+ (feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:' + conventional_count = sum(1 for c in commits if re.match(conventional_pattern, c)) + if conventional_count > len(commits) * 0.5: + conventions['git']['style'] = 'conventional-commits' + + # Check branch pattern + branch = run_command('git branch --show-current', cwd=project_path) + if branch: + conventions['git']['current_branch'] = branch + + # TypeScript/typing + if (project_path / 'tsconfig.json').exists(): + conventions['typing'] = 'TypeScript' + elif (project_path / 'py.typed').exists() or (project_path / 'mypy.ini').exists(): + conventions['typing'] = 'Python type hints' + + return conventions + + +def detect_project_structure(project_path: Path) -> Dict[str, Any]: + """Detect project structure pattern.""" + structure = { + 'type': 'unknown', + 'key_directories': [], + 'entry_points': [] + } + + # Check for monorepo indicators + if (project_path / 'packages').exists() or (project_path / 'apps').exists(): + structure['type'] = 'monorepo' + if (project_path / 'packages').exists(): + structure['key_directories'].append('packages/') + if (project_path / 'apps').exists(): + structure['key_directories'].append('apps/') + + # Check for standard patterns + src = project_path / 'src' + if src.exists(): + structure['key_directories'].append('src/') + structure['type'] = 'standard' + + # Detect src subdirectories + for subdir in ['components', 'pages', 'api', 'lib', 'utils', + 'hooks', 'services', 'models', 'controllers', 'views']: + if (src / subdir).exists(): + structure['key_directories'].append(f'src/{subdir}/') + + # Entry points + entry_files = ['index.ts', 'index.js', 'main.ts', 'main.js', + 'main.py', 'app.py', 'main.rs', 'lib.rs', 'main.go'] + for ef in entry_files: + for match in project_path.rglob(ef): + rel_path = str(match.relative_to(project_path)) + if 'node_modules' not in rel_path and 'target' not in rel_path: + structure['entry_points'].append(rel_path) + break + + return structure + + +def detect_documentation(project_path: Path) -> Dict[str, Any]: + """Detect existing documentation.""" + docs = { + 'readme': None, + 'contributing': None, + 'docs_directory': False, + 'api_docs': False, + 'claude_md': None + } + + # README + for readme in ['README.md', 'README.rst', 'README.txt', 'readme.md']: + readme_path = project_path / readme + if readme_path.exists(): + docs['readme'] = readme + # Check quality (rough estimate by size) + size = readme_path.stat().st_size + if size > 5000: + docs['readme_quality'] = 'detailed' + elif size > 1000: + docs['readme_quality'] = 'basic' + else: + docs['readme_quality'] = 'minimal' + break + + # Contributing + for contrib in ['CONTRIBUTING.md', 'contributing.md', 'CONTRIBUTE.md']: + if (project_path / contrib).exists(): + docs['contributing'] = contrib + break + + # Docs directory + docs['docs_directory'] = (project_path / 'docs').exists() + + # API docs + docs['api_docs'] = any([ + (project_path / 'docs' / 'api').exists(), + (project_path / 'api-docs').exists(), + (project_path / 'openapi.yaml').exists(), + (project_path / 'openapi.json').exists(), + (project_path / 'swagger.yaml').exists(), + ]) + + # claude.md + claude_md = project_path / 'claude.md' + if claude_md.exists(): + docs['claude_md'] = 'exists' + content = claude_md.read_text() + if 'ClaudeShack' in content: + docs['claude_md'] = 'has-claudeshack' + + return docs + + +def mine_history(project_path: Path) -> Dict[str, Any]: + """Mine Claude Code conversation history for patterns.""" + history = { + 'found': False, + 'patterns': [], + 'corrections': [], + 'gotchas': [], + 'preferences': [] + } + + # Determine Claude projects directory + if sys.platform == 'darwin': + projects_dir = Path.home() / 'Library' / 'Application Support' / 'Claude' / 'projects' + elif sys.platform == 'win32': + projects_dir = Path(os.environ.get('APPDATA', '')) / 'Claude' / 'projects' + else: + projects_dir = Path.home() / '.claude' / 'projects' + + if not projects_dir.exists(): + return history + + # Try to find project hash + project_name = project_path.name.lower() + + for project_hash_dir in projects_dir.iterdir(): + if not project_hash_dir.is_dir(): + continue + + # Look for JSONL files + for jsonl_file in project_hash_dir.glob('*.jsonl'): + try: + with open(jsonl_file, 'r', encoding='utf-8') as f: + content = f.read() + + # Simple pattern matching + if project_name in content.lower() or str(project_path) in content: + history['found'] = True + + # Look for corrections (simple heuristic) + correction_patterns = [ + r"no,?\s+(use|prefer|don't|never|always)", + r"actually,?\s+(it's|that's|we)", + r"that's\s+(wrong|incorrect|not right)", + ] + + for pattern in correction_patterns: + matches = re.findall(pattern, content, re.IGNORECASE) + if matches: + history['corrections'].append(f"Found {len(matches)} potential corrections") + break + + # Don't process too much + break + + except (IOError, UnicodeDecodeError): + continue + + return history + + +def get_project_name(project_path: Path) -> str: + """Get project name from config files or directory.""" + # Try package.json + pkg_json = project_path / 'package.json' + if pkg_json.exists(): + try: + with open(pkg_json) as f: + return json.load(f).get('name', project_path.name) + except: + pass + + # Try Cargo.toml + cargo = project_path / 'Cargo.toml' + if cargo.exists(): + try: + content = cargo.read_text() + match = re.search(r'name\s*=\s*"([^"]+)"', content) + if match: + return match.group(1) + except: + pass + + # Try pyproject.toml + pyproject = project_path / 'pyproject.toml' + if pyproject.exists(): + try: + content = pyproject.read_text() + match = re.search(r'name\s*=\s*"([^"]+)"', content) + if match: + return match.group(1) + except: + pass + + return project_path.name + + +def discover(project_path: Path) -> Dict[str, Any]: + """Run full discovery on a project.""" + return { + 'project_name': get_project_name(project_path), + 'project_path': str(project_path), + 'discovered_at': datetime.now().isoformat(), + 'languages': detect_languages(project_path), + 'frameworks': detect_frameworks(project_path), + 'conventions': detect_conventions(project_path), + 'structure': detect_project_structure(project_path), + 'documentation': detect_documentation(project_path), + 'history': mine_history(project_path) + } + + +def format_human_readable(discovery: Dict[str, Any]) -> str: + """Format discovery results for human reading.""" + output = [] + output.append("=" * 60) + output.append(f"Project Discovery: {discovery['project_name']}") + output.append("=" * 60) + + # Languages + if discovery['languages']: + output.append("\n## Languages") + total = sum(discovery['languages'].values()) + for lang, count in discovery['languages'].items(): + pct = (count / total) * 100 + output.append(f" - {lang}: {count} files ({pct:.0f}%)") + + # Frameworks + if discovery['frameworks']: + output.append("\n## Tech Stack") + for category, items in discovery['frameworks'].items(): + if items: + output.append(f" **{category.replace('_', ' ').title()}**: {', '.join(items)}") + + # Conventions + conv = discovery['conventions'] + if conv['linting'] or conv['formatting']: + output.append("\n## Conventions") + if conv['linting']: + output.append(f" - Linting: {', '.join(conv['linting'])}") + if conv['formatting']: + output.append(f" - Formatting: {', '.join(conv['formatting'])}") + if conv.get('typing'): + output.append(f" - Typing: {conv['typing']}") + if conv.get('git', {}).get('style'): + output.append(f" - Git: {conv['git']['style']}") + + # Structure + struct = discovery['structure'] + output.append(f"\n## Structure: {struct['type']}") + if struct['key_directories']: + output.append(f" Key dirs: {', '.join(struct['key_directories'][:5])}") + + # Documentation + docs = discovery['documentation'] + output.append("\n## Documentation") + if docs['readme']: + output.append(f" - README: {docs['readme']} ({docs.get('readme_quality', 'unknown')})") + if docs['docs_directory']: + output.append(" - docs/ directory: Yes") + if docs['claude_md']: + output.append(f" - claude.md: {docs['claude_md']}") + + # History + hist = discovery['history'] + if hist['found']: + output.append("\n## Conversation History") + output.append(" Found existing Claude conversations for this project") + if hist['corrections']: + output.append(f" - {hist['corrections'][0]}") + + output.append("\n" + "=" * 60) + return "\n".join(output) + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='Discover project context') + parser.add_argument('project_path', nargs='?', default='.', + help='Project path (default: current directory)') + parser.add_argument('--json', action='store_true', + help='Output as JSON') + parser.add_argument('--verbose', '-v', action='store_true', + help='Verbose output') + + args = parser.parse_args() + project_path = Path(args.project_path).resolve() + + if not project_path.exists(): + print(f"Error: Path does not exist: {project_path}", file=sys.stderr) + sys.exit(1) + + discovery = discover(project_path) + + if args.json: + print(json.dumps(discovery, indent=2)) + else: + print(format_human_readable(discovery)) + + +if __name__ == '__main__': + main() diff --git a/skills/style-master/Assets/design-tokens/tokens.json b/skills/style-master/Assets/design-tokens/tokens.json new file mode 100644 index 0000000..9f36889 --- /dev/null +++ b/skills/style-master/Assets/design-tokens/tokens.json @@ -0,0 +1,34 @@ +{ + "color": { + "primary": { + "50": "#eff6ff", + "500": "#3b82f6", + "900": "#1e3a8a" + }, + "gray": { + "50": "#f9fafb", + "500": "#6b7280", + "900": "#111827" + } + }, + "spacing": { + "xs": "0.25rem", + "sm": "0.5rem", + "md": "1rem", + "lg": "1.5rem", + "xl": "2rem" + }, + "typography": { + "fontFamily": { + "sans": "'Inter', sans-serif", + "mono": "'Fira Code', monospace" + }, + "fontSize": { + "xs": "0.75rem", + "sm": "0.875rem", + "base": "1rem", + "lg": "1.125rem", + "xl": "1.25rem" + } + } +} diff --git a/skills/style-master/README.md b/skills/style-master/README.md new file mode 100644 index 0000000..45b8a95 --- /dev/null +++ b/skills/style-master/README.md @@ -0,0 +1,217 @@ +# Style Master Skill + +**Expert CSS & Frontend Styling Specialist** + +Style Master is your go-to expert for all things CSS, design systems, and frontend styling. It analyzes codebases, maintains style guides, suggests improvements, and ensures beautiful, consistent, accessible UIs. + +## What It Does + +### πŸ” **Codebase Analysis** +- Detects styling approach (Tailwind, CSS-in-JS, Sass, etc.) +- Extracts design tokens (colors, spacing, typography) +- Identifies patterns and inconsistencies +- Assesses accessibility and performance + +### πŸ“š **Style Guide Maintenance** +- Generates living style guides from your code +- Documents design tokens and component patterns +- Keeps guidelines up-to-date +- Integrates with Oracle to remember preferences + +### πŸ’‘ **Suggestions & Improvements** +- Modernization opportunities (Grid, custom properties, etc.) +- Performance optimizations +- Accessibility enhancements +- Consistency improvements + +### 🎨 **Expert Styling** +- Modern CSS techniques (Container queries, Grid, Flexbox) +- Framework expertise (Tailwind, styled-components, etc.) +- Design system development +- Dark mode and theming support + +## Quick Start + +### Analyze Your Codebase + +```bash +python .claude/skills/style-master/scripts/analyze_styles.py --detailed +``` + +### Generate Style Guide + +```bash +python .claude/skills/style-master/scripts/generate_styleguide.py --output docs/STYLEGUIDE.md +``` + +### Validate Consistency + +```bash +python .claude/skills/style-master/scripts/validate_consistency.py +``` + +### Get Suggestions + +```bash +python .claude/skills/style-master/scripts/suggest_improvements.py +``` + +## Use Cases + +### 1. Start a New Project +``` +Use the style master skill to set up a design system for our new React app. + +[Proposes modern approach with Tailwind + CSS custom properties] +[Generates initial style guide] +[Sets up design tokens] +``` + +### 2. Maintain Consistency +``` +Analyze our styles and ensure everything follows our design system. + +[Scans codebase] +[Finds 5 colors not in design tokens] +[Suggests consolidation] +``` + +### 3. Modernize Legacy Styles +``` +Help modernize our CSS from floats to modern layouts. + +[Analyzes current CSS] +[Proposes Grid/Flexbox migration] +[Works with Summoner for large refactor] +``` + +### 4. Create Components +``` +Style a card component with our design system. + +[Loads style guide] +[Uses design tokens] +[Creates responsive, accessible card] +[Documents in style guide] +``` + +## Integration with Other Skills + +### With Oracle 🧠 +- Remembers your style preferences +- Records component patterns +- Tracks design decisions +- Avoids repeated style mistakes + +### With Summoner πŸ§™ +- Coordinates large styling refactors +- Multi-phase design system rollouts +- Complex component library updates + +### With Documentation Wizard πŸ“ +- Syncs style guide with documentation +- Auto-updates component docs +- Keeps examples current + +## Modern Techniques + +### Container Queries +```css +.card { + container-type: inline-size; +} + +@container (min-width: 400px) { + .card { display: grid; } +} +``` + +### CSS Custom Properties +```css +:root { + --color-primary: #007bff; +} + +[data-theme="dark"] { + --color-primary: #0d6efd; +} +``` + +### Modern Layouts +```css +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); +} +``` + +## Accessibility First + +Style Master ensures: +- βœ… WCAG AA/AAA color contrast +- βœ… Visible focus indicators +- βœ… Keyboard navigation support +- βœ… Screen reader compatibility +- βœ… `prefers-reduced-motion` respect + +## Framework Support + +- **Tailwind CSS**: Theme config, plugins, optimization +- **CSS-in-JS**: styled-components, Emotion +- **Sass/SCSS**: Modern patterns, organization +- **CSS Modules**: Component-scoped styles +- **UI Libraries**: Material UI, Chakra UI, shadcn/ui + +## Scripts Reference + +| Script | Purpose | +|--------|---------| +| `analyze_styles.py` | Analyze codebase styling approach and patterns | +| `generate_styleguide.py` | Create living style guide from code | +| `validate_consistency.py` | Check adherence to design tokens | +| `suggest_improvements.py` | Suggest modernization and optimizations | + +## Example Workflow + +```bash +# 1. Analyze current state +python .claude/skills/style-master/scripts/analyze_styles.py --detailed + +# 2. Generate style guide +python .claude/skills/style-master/scripts/generate_styleguide.py + +# 3. Review and customize STYLEGUIDE.md + +# 4. Validate consistency +python .claude/skills/style-master/scripts/validate_consistency.py + +# 5. Get improvement suggestions +python .claude/skills/style-master/scripts/suggest_improvements.py + +# 6. Use Style Master skill in Claude Code +# "Use the style master skill to implement the new design system" +``` + +## Philosophy + +**"Form follows function, but both deserve excellence."** + +- Consistency is king +- Maintainability matters +- Performance counts +- Accessibility first +- Modern but pragmatic + +## Success Indicators + +βœ… **Style Master is working when:** +- Visual consistency across app +- Up-to-date style guide +- No duplicate styles +- WCAG compliance +- Optimized performance +- Design tokens used consistently + +--- + +**Style Master v1.0** - Beautiful, consistent, accessible interfaces diff --git a/skills/style-master/References/style-guide-template.md b/skills/style-master/References/style-guide-template.md new file mode 100644 index 0000000..3060519 --- /dev/null +++ b/skills/style-master/References/style-guide-template.md @@ -0,0 +1,56 @@ +# {Project Name} Style Guide + +**Last Updated**: {Date} + +## Design Tokens + +### Colors + +#### Primary Colors +- `--color-primary`: #007bff +- `--color-primary-hover`: #0056b3 +- `--color-primary-active`: #004085 + +#### Semantic Colors +- `--color-success`: #28a745 +- `--color-warning`: #ffc107 +- `--color-error`: #dc3545 +- `--color-info`: #17a2b8 + +#### Neutral Colors +- `--color-gray-50`: #f8f9fa +- `--color-gray-900`: #212529 + +### Typography + +- `--font-sans`: 'Inter', system-ui, sans-serif +- `--font-mono`: 'Fira Code', monospace +- `--font-size-xs`: 0.75rem +- `--font-size-base`: 1rem +- `--font-size-xl`: 1.25rem +- `--line-height-tight`: 1.25 +- `--line-height-normal`: 1.5 + +### Spacing + +- `--spacing-xs`: 0.25rem (4px) +- `--spacing-sm`: 0.5rem (8px) +- `--spacing-md`: 1rem (16px) +- `--spacing-lg`: 1.5rem (24px) +- `--spacing-xl`: 2rem (32px) + +### Breakpoints + +- sm: 640px +- md: 768px +- lg: 1024px +- xl: 1280px +- 2xl: 1536px + +## Components + +[Document component patterns here] + +## Guidelines + +[Project-specific guidelines] diff --git a/skills/style-master/SKILL.md b/skills/style-master/SKILL.md new file mode 100644 index 0000000..9fa5b77 --- /dev/null +++ b/skills/style-master/SKILL.md @@ -0,0 +1,607 @@ +--- +name: style-master +description: Expert CSS and frontend styling specialist that analyzes codebases, maintains style guides, suggests improvements, and stays current with modern design patterns. Use when working on frontend styling, creating design systems, ensuring visual consistency, or need expert CSS/styling guidance. Integrates with oracle, summoner, and wizard. +allowed-tools: Read, Write, Edit, Glob, Grep, Bash +--- + +# Style Master: CSS & Frontend Styling Expert + +You are now operating as the **Style Master**, an expert in CSS, design systems, and frontend styling who ensures beautiful, consistent, maintainable, and modern user interfaces. + +## Core Philosophy + +**"Form follows function, but both deserve excellence."** + +Style Master operates on these principles: + +1. **Consistency is King**: Visual consistency creates professional UIs +2. **Maintainability Matters**: Styles should be DRY, scalable, and organized +3. **Performance Counts**: Beautiful AND fast +4. **Accessibility First**: Styles that work for everyone +5. **Modern but Pragmatic**: Use modern techniques, but know when simple is better +6. **Adaptive Learning**: Learn project preferences and evolve with trends + +## Core Responsibilities + +### 1. Codebase Style Analysis + +When analyzing a frontend codebase: + +**Discovery Phase**: +- Identify styling approach (CSS, Sass, CSS-in-JS, Tailwind, etc.) +- Map component structure and patterns +- Detect design tokens (colors, spacing, typography) +- Find inconsistencies and anti-patterns +- Assess accessibility compliance +- Evaluate performance implications + +**Output**: Comprehensive style audit report + +### 2. Style Guide Maintenance + +Maintain a living style guide that documents: + +**Design Tokens**: +- Color palette (primary, secondary, semantic colors) +- Typography scale (fonts, sizes, weights, line heights) +- Spacing system (margins, padding, gaps) +- Breakpoints (responsive design) +- Shadows, borders, radius, animations + +**Component Patterns**: +- Button styles and variants +- Form elements +- Cards, modals, tooltips +- Navigation patterns +- Layout patterns + +**Guidelines**: +- Naming conventions +- File organization +- Best practices +- Accessibility requirements + +### 3. Enhancement & Suggestions + +Proactively suggest improvements: + +**Modernization**: +- Container queries over media queries +- CSS custom properties for theming +- Modern layout (Grid, Flexbox) +- CSS nesting (where supported) +- Logical properties for i18n + +**Optimization**: +- Remove unused styles +- Consolidate duplicate rules +- Improve specificity +- Reduce bundle size +- Critical CSS extraction + +**Accessibility**: +- Color contrast ratios (WCAG AA/AAA) +- Focus indicators +- Screen reader compatibility +- Keyboard navigation support +- Motion preferences (prefers-reduced-motion) + +**Best Practices**: +- BEM or consistent naming methodology +- Mobile-first responsive design +- Component-scoped styles +- Design token usage +- Dark mode support + +### 4. Style Development + +When creating new styles: + +**Approach**: +1. Understand the design intent or mockup +2. Identify existing patterns to reuse +3. Use design tokens for consistency +4. Consider all states (hover, focus, active, disabled) +5. Ensure responsiveness across breakpoints +6. Test accessibility +7. Optimize for performance + +**Modern Techniques**: +- CSS Grid for 2D layouts +- Flexbox for 1D layouts +- CSS custom properties for theming +- CSS logical properties for i18n +- Container queries for component-level responsiveness +- CSS cascade layers for specificity management +- View transitions API for smooth animations +- Subgrid for nested layouts + +### 5. Framework Expertise + +Adapt to any styling approach: + +**Vanilla CSS/Sass**: +- BEM methodology +- ITCSS architecture +- Utility-first patterns +- CSS modules + +**Tailwind CSS**: +- Utility composition +- Custom theme configuration +- Plugin development +- Optimization strategies + +**CSS-in-JS**: +- Styled Components patterns +- Emotion best practices +- Runtime vs build-time approaches +- TypeScript integration + +**UI Frameworks**: +- Material UI customization +- Chakra UI theming +- shadcn/ui component styling +- Radix UI primitive styling + +### 6. Design System Development + +Create and maintain design systems: + +**Foundations**: +- Design token architecture +- Color system (palettes, semantic colors) +- Typography system (type scale, font loading) +- Spacing system (consistent rhythm) +- Motion system (animations, transitions) + +**Components**: +- Atomic design methodology +- Component variants and states +- Composition patterns +- Documentation examples + +**Tooling**: +- Storybook integration +- Design token management +- Automated visual regression testing +- Style guide generation + +## Workflow + +### Initial Analysis + +``` +1. Scan codebase for styling files + ↓ +2. Identify styling approach and frameworks + ↓ +3. Extract design tokens and patterns + ↓ +4. Analyze consistency and quality + ↓ +5. Generate audit report + ↓ +6. Record findings to Oracle (if available) +``` + +### Style Guide Creation + +``` +1. Analyze existing styles and components + ↓ +2. Extract and organize design tokens + ↓ +3. Document component patterns + ↓ +4. Define guidelines and conventions + ↓ +5. Generate living style guide + ↓ +6. Set up automated updates +``` + +### Enhancement Workflow + +``` +1. Receive enhancement request + ↓ +2. Load style guide and project patterns + ↓ +3. Load Oracle preferences (if available) + ↓ +4. Propose solutions with examples + ↓ +5. Implement with modern best practices + ↓ +6. Ensure accessibility and performance + ↓ +7. Update style guide + ↓ +8. Record patterns to Oracle +``` + +## Integration with Other Skills + +### With Oracle (Project Memory) + +**Store in Oracle**: +- Style preferences (e.g., "Prefer Tailwind utilities over custom CSS") +- Component patterns used in this project +- Accessibility requirements +- Performance thresholds +- Design token decisions + +**Example Oracle Entry**: +```json +{ + "category": "pattern", + "priority": "high", + "title": "Use CSS custom properties for theme values", + "content": "All theme-able values (colors, spacing) must use CSS custom properties (--color-primary, --spacing-md) to enable dark mode and dynamic theming.", + "context": "When adding new styled components", + "tags": ["css", "theming", "design-tokens"] +} +``` + +### With Summoner (Orchestration) + +For complex styling tasks, Summoner can coordinate: + +**Example**: Redesign entire component library +1. Summoner creates Mission Control Document +2. Style Master analyzes current state +3. Summoner summons specialists: + - Style Master: New design system + - Component Expert: Component refactoring + - Accessibility Specialist: WCAG compliance + - Performance Expert: Optimization +4. Style Master updates style guide +5. Oracle records new patterns + +### With Documentation Wizard + +**Collaboration**: +- Style Master provides style guide content +- Documentation Wizard maintains it in docs +- Synced automatically on changes + +## Style Guide Structure + +### Living Style Guide Format + +```markdown +# [Project Name] Style Guide + +## Design Tokens + +### Colors +- Primary: `--color-primary: #007bff` +- Secondary: `--color-secondary: #6c757d` +- [Semantic colors, states, etc.] + +### Typography +- Font Family: `--font-sans: 'Inter', sans-serif` +- Type Scale: [Scale details] + +### Spacing +- Base: `--spacing-base: 1rem` +- Scale: [4, 8, 12, 16, 24, 32, 48, 64, 96] + +### Breakpoints +- sm: 640px +- md: 768px +- lg: 1024px +- xl: 1280px + +## Components + +### Button +[Variants, states, usage examples] + +### Form Elements +[Input, select, checkbox, radio patterns] + +## Guidelines + +### Naming Conventions +[BEM, utility-first, or project conventions] + +### File Organization +[Structure and architecture] + +### Accessibility +[WCAG compliance requirements] +``` + +## Modern CSS Techniques + +### Container Queries + +Use for component-level responsiveness: + +```css +.card { + container-type: inline-size; +} + +@container (min-width: 400px) { + .card__content { + display: grid; + grid-template-columns: 1fr 1fr; + } +} +``` + +### CSS Custom Properties + +For theming and maintainability: + +```css +:root { + --color-primary: #007bff; + --color-primary-hover: #0056b3; + --spacing-md: 1rem; +} + +[data-theme="dark"] { + --color-primary: #0d6efd; + --color-primary-hover: #0a58ca; +} +``` + +### Logical Properties + +For internationalization: + +```css +/* Instead of margin-left */ +.element { + margin-inline-start: 1rem; + padding-block: 2rem; +} +``` + +### Modern Layouts + +CSS Grid and Flexbox: + +```css +/* Responsive grid without media queries */ +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; +} +``` + +### Cascade Layers + +For specificity management: + +```css +@layer reset, base, components, utilities; + +@layer components { + .button { /* Component styles */ } +} + +@layer utilities { + .mt-4 { margin-top: 1rem; } +} +``` + +## Accessibility Standards + +### WCAG Compliance Checklist + +**Color Contrast**: +- Normal text: Minimum 4.5:1 (AA), 7:1 (AAA) +- Large text: Minimum 3:1 (AA), 4.5:1 (AAA) +- UI components: Minimum 3:1 + +**Focus Indicators**: +- Visible focus states on all interactive elements +- Minimum 3:1 contrast ratio for focus indicators + +**Motion**: +- Respect `prefers-reduced-motion` +- Provide alternatives to motion-based UI + +**Typography**: +- Scalable font sizes (rem/em, not px) +- Sufficient line height (1.5+ for body text) +- Adequate letter spacing + +## Performance Optimization + +### CSS Performance Checklist + +- [ ] Remove unused CSS (PurgeCSS, etc.) +- [ ] Minimize CSS bundle size +- [ ] Extract critical CSS for above-the-fold content +- [ ] Use CSS containment for complex layouts +- [ ] Avoid expensive properties (box-shadow, filter) on animations +- [ ] Use `content-visibility` for off-screen content +- [ ] Optimize font loading (font-display, preload) +- [ ] Use CSS custom properties instead of Sass variables where possible + +### Loading Strategies + +```html + + + + + +``` + +## Trend Awareness + +### Current Trends (2025) + +**Techniques**: +- Container queries for component responsiveness +- View transitions API for smooth page transitions +- CSS nesting in native CSS +- `:has()` selector for parent styling +- Subgrid for nested grid layouts +- CSS cascade layers for specificity control + +**Design Trends**: +- Glassmorphism (frosted glass effects) +- Neumorphism (soft UI) +- Brutalism (raw, bold design) +- Gradient mesh backgrounds +- Micro-interactions and animations +- Dark mode as default consideration + +**Frameworks**: +- Tailwind CSS v4 with Oxide engine +- Vanilla Extract for type-safe styles +- Panda CSS for design tokens +- UnoCSS for atomic CSS +- shadcn/ui component patterns + +### Evolution Tracking + +Stay current by: +- Monitoring CSS Working Group specs +- Following design system leaders +- Tracking framework updates +- Analyzing trending designs +- Learning from popular sites + +## Scripts & Tools + +### Analysis Script + +```bash +python .claude/skills/style-master/scripts/analyze_styles.py +``` + +**Outputs**: +- Styling approach detection +- Design token extraction +- Consistency report +- Improvement suggestions + +### Style Guide Generator + +```bash +python .claude/skills/style-master/scripts/generate_styleguide.py +``` + +**Outputs**: +- Living style guide document +- Design token files +- Component examples +- Usage guidelines + +### Consistency Validator + +```bash +python .claude/skills/style-master/scripts/validate_consistency.py +``` + +**Checks**: +- Color usage consistency +- Spacing adherence to system +- Typography compliance +- Naming convention violations + +### Improvement Suggester + +```bash +python .claude/skills/style-master/scripts/suggest_improvements.py +``` + +**Suggests**: +- Modernization opportunities +- Performance optimizations +- Accessibility enhancements +- Best practice violations + +## Templates & References + +- **Style Guide Template**: See `References/style-guide-template.md` +- **CSS Patterns**: See `References/css-patterns.md` +- **Design Token Schema**: See `References/design-token-schema.md` +- **Component Library**: See `Assets/component-templates/` +- **Framework Guides**: See `References/framework-guides/` + +## Success Indicators + +βœ… **Style Master is succeeding when**: +- Visual consistency across the application +- Style guide is up-to-date and referenced +- No duplicate or conflicting styles +- Accessibility standards met +- Performance optimized +- Modern techniques adopted appropriately +- Design tokens used consistently +- Team follows established patterns + +❌ **Warning signs**: +- Inconsistent styling between pages/components +- Growing stylesheet without organization +- Accessibility violations +- Poor performance scores +- Outdated techniques +- No design token usage +- Inline styles proliferating + +## Example Interactions + +### Analyze Existing Styles + +**User**: "Analyze the styling in our React app" + +**Style Master**: +1. Scans for styling files +2. Detects: Tailwind CSS + custom CSS +3. Extracts: Color palette, spacing system +4. Finds: 3 shades of blue used inconsistently +5. Reports: "You're using Tailwind but have custom CSS duplicating utilities. Suggest consolidating to Tailwind config for consistency." + +### Create Component Styles + +**User**: "Style a card component with image, title, description, and action button" + +**Style Master**: +1. Loads project style guide +2. Uses existing design tokens +3. Creates responsive, accessible card +4. Provides hover/focus states +5. Ensures dark mode support +6. Updates style guide with new component + +### Modernize Codebase + +**User**: "Help modernize our CSS" + +**Style Master**: +1. Analyzes current CSS +2. Identifies: Float-based layouts, pixels, old vendor prefixes +3. Suggests: CSS Grid, rem units, drop old prefixes +4. Proposes migration plan +5. Works with Summoner for large-scale refactor + +## Remember + +> "Great styling is invisible - users just know it feels right." + +Your role as Style Master: +1. **Ensure consistency** through design systems +2. **Maintain quality** through standards and patterns +3. **Stay modern** but pragmatic +4. **Optimize performance** without sacrificing beauty +5. **Champion accessibility** in every design +6. **Document everything** in the style guide +7. **Learn and adapt** to project preferences + +--- + +**Style Master activated. Ready to create beautiful, consistent, accessible interfaces.** diff --git a/skills/style-master/scripts/analyze_styles.py b/skills/style-master/scripts/analyze_styles.py new file mode 100755 index 0000000..816094d --- /dev/null +++ b/skills/style-master/scripts/analyze_styles.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +""" +Style Master - Codebase Style Analyzer + +Analyzes a frontend codebase to understand styling approach, extract design tokens, +identify patterns, and detect issues. + +Usage: + python analyze_styles.py [--path /path/to/project] + python analyze_styles.py --detailed + python analyze_styles.py --export report.json + +Examples: + python analyze_styles.py + python analyze_styles.py --path ../my-app --detailed +""" + +import os +import sys +import json +import re +import argparse +from pathlib import Path +from collections import Counter, defaultdict + + +def find_style_files(root_path): + """Find all styling-related files in the project.""" + style_extensions = { + '.css', '.scss', '.sass', '.less', + '.module.css', '.module.scss', + '.styled.js', '.styled.ts', '.styled.jsx', '.styled.tsx' + } + + style_files = { + 'css': [], + 'scss': [], + 'sass': [], + 'less': [], + 'css_modules': [], + 'css_in_js': [], + 'tailwind_config': [], + 'other': [] + } + + for root, dirs, files in os.walk(root_path): + # Skip common ignore directories + dirs[:] = [d for d in dirs if d not in {'node_modules', '.git', 'dist', 'build', '.next', 'out'}] + + for file in files: + file_path = Path(root) / file + + if file == 'tailwind.config.js' or file == 'tailwind.config.ts': + style_files['tailwind_config'].append(file_path) + elif file.endswith('.module.css') or file.endswith('.module.scss'): + style_files['css_modules'].append(file_path) + elif '.styled.' in file: + style_files['css_in_js'].append(file_path) + elif file.endswith('.css'): + style_files['css'].append(file_path) + elif file.endswith('.scss'): + style_files['scss'].append(file_path) + elif file.endswith('.sass'): + style_files['sass'].append(file_path) + elif file.endswith('.less'): + style_files['less'].append(file_path) + + return style_files + + +def detect_styling_approach(style_files, root_path): + """Detect the primary styling approach used.""" + approaches = [] + + if style_files['tailwind_config']: + approaches.append('Tailwind CSS') + + if style_files['css_in_js']: + approaches.append('CSS-in-JS (Styled Components/Emotion)') + + if style_files['css_modules']: + approaches.append('CSS Modules') + + if style_files['scss'] or style_files['sass']: + approaches.append('Sass/SCSS') + + if style_files['less']: + approaches.append('Less') + + if style_files['css'] and not style_files['css_modules']: + approaches.append('Vanilla CSS') + + # Check for UI frameworks + package_json = Path(root_path) / 'package.json' + if package_json.exists(): + try: + with open(package_json, 'r') as f: + data = json.load(f) + deps = {**data.get('dependencies', {}), **data.get('devDependencies', {})} + + if '@mui/material' in deps or '@material-ui/core' in deps: + approaches.append('Material UI') + if '@chakra-ui/react' in deps: + approaches.append('Chakra UI') + if 'styled-components' in deps: + approaches.append('Styled Components') + if '@emotion/react' in deps or '@emotion/styled' in deps: + approaches.append('Emotion') + except: + pass + + return approaches if approaches else ['Unknown'] + + +def extract_colors(content): + """Extract color values from CSS content.""" + colors = [] + + # Hex colors + hex_pattern = r'#(?:[0-9a-fA-F]{3}){1,2}\b' + colors.extend(re.findall(hex_pattern, content)) + + # RGB/RGBA + rgb_pattern = r'rgba?\([^)]+\)' + colors.extend(re.findall(rgb_pattern, content)) + + # HSL/HSLA + hsl_pattern = r'hsla?\([^)]+\)' + colors.extend(re.findall(hsl_pattern, content)) + + # CSS custom properties with color-like names + custom_prop_pattern = r'--(?:color|bg|background|border|text)[^:;\s]+:\s*([^;]+)' + colors.extend(re.findall(custom_prop_pattern, content)) + + return colors + + +def extract_spacing_values(content): + """Extract spacing values (margin, padding, gap).""" + spacing = [] + + # Match spacing properties + spacing_pattern = r'(?:margin|padding|gap)[^:]*:\s*([^;]+)' + matches = re.findall(spacing_pattern, content) + + for match in matches: + # Extract numeric values + values = re.findall(r'\d+(?:\.\d+)?(?:px|rem|em|%|vh|vw)', match) + spacing.extend(values) + + return spacing + + +def extract_css_custom_properties(content): + """Extract CSS custom properties (variables).""" + pattern = r'--([\w-]+):\s*([^;]+)' + return dict(re.findall(pattern, content)) + + +def analyze_file_content(file_path): + """Analyze content of a single style file.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + return { + 'colors': extract_colors(content), + 'spacing': extract_spacing_values(content), + 'custom_properties': extract_css_custom_properties(content), + 'size': len(content), + 'lines': content.count('\n') + 1 + } + except Exception as e: + return {'error': str(e)} + + +def generate_report(style_files, root_path, detailed=False): + """Generate analysis report.""" + print("="*70) + print(" Style Master - Codebase Analysis") + print("="*70) + print(f"\nAnalyzing: {root_path}\n") + + # Styling approach + approaches = detect_styling_approach(style_files, root_path) + print("## Styling Approach\n") + for approach in approaches: + print(f" [CHECK] {approach}") + print() + + # File counts + total_files = sum(len(files) for files in style_files.values()) + print(f"## Files Found: {total_files}\n") + for file_type, files in style_files.items(): + if files: + print(f" {file_type}: {len(files)}") + print() + + # Detailed analysis + if detailed: + print("## Design Token Analysis\n") + + all_colors = [] + all_spacing = [] + all_custom_props = {} + total_size = 0 + total_lines = 0 + + # Analyze all CSS files + for file_type, files in style_files.items(): + if file_type in {'css', 'scss', 'sass', 'less', 'css_modules'}: + for file_path in files: + analysis = analyze_file_content(file_path) + if 'error' not in analysis: + all_colors.extend(analysis['colors']) + all_spacing.extend(analysis['spacing']) + all_custom_props.update(analysis['custom_properties']) + total_size += analysis['size'] + total_lines += analysis['lines'] + + # Color analysis + if all_colors: + color_counts = Counter(all_colors) + print(f"**Colors Found**: {len(color_counts)} unique colors") + print(f"\nMost used colors:") + for color, count in color_counts.most_common(10): + print(f" {color}: used {count} times") + print() + + # Spacing analysis + if all_spacing: + spacing_counts = Counter(all_spacing) + print(f"**Spacing Values**: {len(spacing_counts)} unique values") + print(f"\nMost used spacing:") + for value, count in spacing_counts.most_common(10): + print(f" {value}: used {count} times") + print() + + # Custom properties + if all_custom_props: + print(f"**CSS Custom Properties**: {len(all_custom_props)} defined") + print("\nExamples:") + for prop, value in list(all_custom_props.items())[:10]: + print(f" --{prop}: {value}") + print() + + # Size stats + print(f"**Total CSS Size**: {total_size:,} bytes ({total_size / 1024:.1f} KB)") + print(f"**Total Lines**: {total_lines:,}") + print() + + # Suggestions + print("## Suggestions\n") + + suggestions = [] + + if not any(style_files.values()): + suggestions.append("[WARNING] No style files found. Consider adding styling to your project.") + + if 'Tailwind CSS' not in approaches and 'CSS-in-JS' not in approaches: + suggestions.append("[TIP] Consider modern approaches like Tailwind CSS or CSS-in-JS") + + if style_files['css'] and not style_files['css_modules']: + suggestions.append("[TIP] Consider CSS Modules to avoid global namespace pollution") + + if not style_files['tailwind_config'] and len(all_colors) > 20: + suggestions.append("[WARNING] Many color values detected - consider establishing a color system") + + if len(all_spacing) > 30: + suggestions.append("[WARNING] Many spacing values - consider a consistent spacing scale") + + if detailed and not all_custom_props: + suggestions.append("[TIP] No CSS custom properties found - consider using them for theming") + + for suggestion in suggestions: + print(f" {suggestion}") + + if not suggestions: + print(" [OK] Styling approach looks good!") + + print("\n" + "="*70) + + return { + 'approaches': approaches, + 'file_counts': {k: len(v) for k, v in style_files.items()}, + 'colors': len(all_colors) if detailed else None, + 'spacing_values': len(all_spacing) if detailed else None, + 'custom_properties': len(all_custom_props) if detailed else None, + 'suggestions': suggestions + } + + +def main(): + parser = argparse.ArgumentParser( + description='Analyze codebase styling', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--path', + type=str, + default='.', + help='Path to project root (default: current directory)' + ) + + parser.add_argument( + '--detailed', + action='store_true', + help='Perform detailed analysis (slower but more comprehensive)' + ) + + parser.add_argument( + '--export', + type=str, + help='Export report to JSON file' + ) + + args = parser.parse_args() + + root_path = Path(args.path).resolve() + + if not root_path.exists(): + print(f"[ERROR] Error: Path does not exist: {root_path}") + sys.exit(1) + + # Find style files + style_files = find_style_files(root_path) + + # Generate report + report_data = generate_report(style_files, root_path, args.detailed) + + # Export if requested + if args.export: + with open(args.export, 'w') as f: + json.dump(report_data, f, indent=2) + print(f"\n Report exported to: {args.export}") + + +if __name__ == '__main__': + main() diff --git a/skills/style-master/scripts/generate_styleguide.py b/skills/style-master/scripts/generate_styleguide.py new file mode 100755 index 0000000..75f8b6d --- /dev/null +++ b/skills/style-master/scripts/generate_styleguide.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +""" +Style Master - Style Guide Generator + +Generates a living style guide based on codebase analysis. + +Usage: + python generate_styleguide.py [--path /path/to/project] + python generate_styleguide.py --output STYLEGUIDE.md + python generate_styleguide.py --format markdown|json + +Examples: + python generate_styleguide.py + python generate_styleguide.py --output docs/STYLEGUIDE.md +""" + +import os +import sys +import json +import argparse +from pathlib import Path +from datetime import datetime +import re +from collections import Counter + + +def load_template(): + """Load style guide template.""" + template_path = Path(__file__).parent.parent / 'References' / 'style-guide-template.md' + + if template_path.exists(): + with open(template_path, 'r') as f: + return f.read() + + # Fallback basic template + return """# {project_name} Style Guide + +*Generated: {date}* + +## Design Tokens + +### Colors + +{colors} + +### Typography + +{typography} + +### Spacing + +{spacing} + +## Components + +{components} + +## Guidelines + +{guidelines} + +--- + +*This is a living document. Update as the design system evolves.* +""" + + +def detect_project_name(root_path): + """Detect project name from package.json.""" + package_json = root_path / 'package.json' + + if package_json.exists(): + try: + with open(package_json, 'r') as f: + data = json.load(f) + return data.get('name', root_path.name) + except: + pass + + return root_path.name + + +def extract_design_tokens_from_tailwind(root_path): + """Extract design tokens from Tailwind config.""" + config_files = ['tailwind.config.js', 'tailwind.config.ts'] + + for config_file in config_files: + config_path = root_path / config_file + if config_path.exists(): + try: + with open(config_path, 'r') as f: + content = f.read() + + return { + 'source': 'Tailwind Config', + 'colors': 'See tailwind.config.js theme.extend.colors', + 'spacing': 'Using Tailwind default spacing scale', + 'typography': 'See tailwind.config.js theme.extend.fontFamily' + } + except: + pass + + return None + + +def extract_css_variables(root_path): + """Extract CSS custom properties from CSS files.""" + css_vars = {} + + for css_file in root_path.rglob('*.css'): + if 'node_modules' in str(css_file): + continue + + try: + with open(css_file, 'r') as f: + content = f.read() + + # Find CSS custom properties + pattern = r'--([\w-]+):\s*([^;]+)' + matches = re.findall(pattern, content) + + for var_name, var_value in matches: + css_vars[var_name] = var_value.strip() + except: + continue + + return css_vars + + +def categorize_css_variables(css_vars): + """Categorize CSS variables by type.""" + categorized = { + 'colors': {}, + 'spacing': {}, + 'typography': {}, + 'other': {} + } + + color_keywords = ['color', 'bg', 'background', 'border', 'text', 'primary', 'secondary', 'accent'] + spacing_keywords = ['spacing', 'margin', 'padding', 'gap', 'size'] + typo_keywords = ['font', 'text', 'heading', 'body', 'line-height', 'letter-spacing'] + + for var_name, var_value in css_vars.items(): + var_lower = var_name.lower() + + if any(keyword in var_lower for keyword in color_keywords): + categorized['colors'][var_name] = var_value + elif any(keyword in var_lower for keyword in spacing_keywords): + categorized['spacing'][var_name] = var_value + elif any(keyword in var_lower for keyword in typo_keywords): + categorized['typography'][var_name] = var_value + else: + categorized['other'][var_name] = var_value + + return categorized + + +def format_tokens_section(tokens_dict, title): + """Format design tokens into markdown.""" + if not tokens_dict: + return f"*No {title.lower()} tokens defined yet.*\n" + + output = "" + for var_name, var_value in sorted(tokens_dict.items())[:20]: # Limit to 20 + output += f"- `--{var_name}`: {var_value}\n" + + if len(tokens_dict) > 20: + output += f"\n*...and {len(tokens_dict) - 20} more*\n" + + return output + + +def generate_guidelines(root_path): + """Generate guidelines based on detected patterns.""" + guidelines = [] + + # Check for Tailwind + if (root_path / 'tailwind.config.js').exists() or (root_path / 'tailwind.config.ts').exists(): + guidelines.append(""" +### Tailwind CSS Usage + +- **Prefer Tailwind utilities** over custom CSS where possible +- **Use @apply sparingly** - only for frequently repeated patterns +- **Extend the theme** in tailwind.config.js for custom values +- **Use arbitrary values** (e.g., `w-[123px]`) only when necessary + """) + + # Check for CSS Modules + css_modules = list(root_path.rglob('*.module.css')) + list(root_path.rglob('*.module.scss')) + if css_modules: + guidelines.append(""" +### CSS Modules + +- **One module per component** - keep styles colocated +- **Use camelCase** for class names in modules +- **Avoid global styles** unless absolutely necessary +- **Compose classes** to avoid duplication + """) + + # Default guidelines + guidelines.append(""" +### General Principles + +- **Mobile-first**: Design for mobile, enhance for desktop +- **Accessibility**: Ensure WCAG AA compliance minimum +- **Performance**: Optimize for fast loading and rendering +- **Consistency**: Use design tokens and established patterns +- **Dark mode**: Support both light and dark themes where applicable + """) + + return "\n".join(guidelines) + + +def generate_markdown_styleguide(root_path): + """Generate markdown style guide.""" + project_name = detect_project_name(root_path) + date = datetime.now().strftime('%Y-%m-%d') + + # Extract design tokens + tailwind_tokens = extract_design_tokens_from_tailwind(root_path) + css_vars = extract_css_variables(root_path) + categorized = categorize_css_variables(css_vars) + + # Build sections + colors_section = format_tokens_section(categorized['colors'], 'Colors') + spacing_section = format_tokens_section(categorized['spacing'], 'Spacing') + typography_section = format_tokens_section(categorized['typography'], 'Typography') + + if tailwind_tokens: + colors_section = f"**Using Tailwind CSS**\n\nSee `tailwind.config.js` for the complete color palette.\n\n{colors_section}" + + components_section = """ +### Button + +*Component documentation to be added* + +### Card + +*Component documentation to be added* + +### Form Elements + +*Component documentation to be added* + +*Add component documentation as your design system grows.* + """ + + guidelines_section = generate_guidelines(root_path) + + # Build full guide + styleguide = f"""# {project_name} Style Guide + +**Last Updated**: {date} + +*This is a living document that evolves with the project.* + +--- + +## Overview + +This style guide documents the design system, patterns, and conventions used in {project_name}. + +## Design Tokens + +Design tokens are the visual design atoms of the design system. They define colors, typography, spacing, and other fundamental values. + +### Colors + +{colors_section} + +### Typography + +{typography_section} + +### Spacing + +{spacing_section} + +## Components + +Document common component patterns here. + +{components_section} + +## Guidelines + +{guidelines_section} + +## Accessibility + +### Color Contrast + +- Ensure all text has minimum 4.5:1 contrast ratio (WCAG AA) +- Large text (18pt+) requires minimum 3:1 contrast ratio + +### Focus States + +- All interactive elements must have visible focus indicators +- Focus indicators must have 3:1 contrast ratio with background + +### Keyboard Navigation + +- All functionality available via keyboard +- Logical tab order +- Skip links for main content + +## Performance + +### CSS Best Practices + +- Remove unused styles in production +- Use CSS containment for complex layouts +- Optimize font loading with `font-display: swap` +- Minimize use of expensive properties in animations + +## Resources + +- [Design System Documentation](./docs/design-system.md) +- [Component Library](./src/components/) +- [Accessibility Guidelines](https://www.w3.org/WAI/WCAG21/quickref/) + +--- + +*Generated by Style Master on {date}* +""" + + return styleguide + + +def main(): + parser = argparse.ArgumentParser( + description='Generate project style guide', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--path', + type=str, + default='.', + help='Path to project root (default: current directory)' + ) + + parser.add_argument( + '--output', + type=str, + default='STYLEGUIDE.md', + help='Output file path (default: STYLEGUIDE.md)' + ) + + parser.add_argument( + '--format', + choices=['markdown', 'json'], + default='markdown', + help='Output format (default: markdown)' + ) + + args = parser.parse_args() + + root_path = Path(args.path).resolve() + + if not root_path.exists(): + print(f"[ERROR] Error: Path does not exist: {root_path}") + sys.exit(1) + + print(f"[NOTE] Generating style guide for: {root_path}") + + # Generate style guide + if args.format == 'markdown': + styleguide = generate_markdown_styleguide(root_path) + + output_path = Path(args.output) + with open(output_path, 'w') as f: + f.write(styleguide) + + print(f"[OK] Style guide created: {output_path}") + print(f"\n Review and customize the generated style guide") + print(f" Add component examples, update guidelines, refine tokens\n") + else: + # JSON format (for programmatic use) + css_vars = extract_css_variables(root_path) + categorized = categorize_css_variables(css_vars) + + data = { + 'project': detect_project_name(root_path), + 'generated': datetime.now().isoformat(), + 'tokens': categorized + } + + output_path = Path(args.output).with_suffix('.json') + with open(output_path, 'w') as f: + json.dump(data, f, indent=2) + + print(f"[OK] Style guide data exported: {output_path}") + + +if __name__ == '__main__': + main() diff --git a/skills/style-master/scripts/suggest_improvements.py b/skills/style-master/scripts/suggest_improvements.py new file mode 100644 index 0000000..1fe2777 --- /dev/null +++ b/skills/style-master/scripts/suggest_improvements.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Style improvement suggester.""" +import sys +from pathlib import Path + +def analyze_and_suggest(root_path): + """Analyze styles and suggest improvements.""" + suggestions = [] + + # Check for modern CSS features + css_files = list(root_path.rglob('*.css')) + if css_files: + content = ''.join([f.read_text() for f in css_files[:10] if 'node_modules' not in str(f)]) + + if 'float:' in content: + suggestions.append("[TIP] Consider replacing float layouts with Flexbox or Grid") + if 'px' in content and 'rem' not in content: + suggestions.append("[TIP] Consider using rem units for better accessibility") + if '@media' in content and '@container' not in content: + suggestions.append("[TIP] Consider container queries for component-level responsiveness") + if not re.search(r'--[\w-]+:', content): + suggestions.append("[TIP] Consider using CSS custom properties for theming") + + return suggestions + +def main(): + root = Path(sys.argv[1] if len(sys.argv) > 1 else '.').resolve() + print(" Analyzing for improvement opportunities...\n") + + suggestions = analyze_and_suggest(root) + for s in suggestions: + print(f" {s}") + + if not suggestions: + print(" [OK] No immediate improvements suggested!") + +if __name__ == '__main__': + main() diff --git a/skills/style-master/scripts/validate_consistency.py b/skills/style-master/scripts/validate_consistency.py new file mode 100644 index 0000000..6f2d1d4 --- /dev/null +++ b/skills/style-master/scripts/validate_consistency.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""Style consistency validator - checks for design token adherence.""" +import sys +from pathlib import Path +import re +from collections import Counter + +def validate_colors(root_path): + """Check for color consistency.""" + colors = [] + for css_file in root_path.rglob('*.css'): + if 'node_modules' not in str(css_file): + try: + content = css_file.read_text() + colors.extend(re.findall(r'#(?:[0-9a-fA-F]{3}){1,2}\b', content)) + except: pass + + counts = Counter(colors) + print(f" Colors: {len(counts)} unique colors found") + if len(counts) > 20: + print(f" [WARNING] Consider consolidating to a color system") + return counts + +def main(): + root = Path(sys.argv[1] if len(sys.argv) > 1 else '.').resolve() + print("[SEARCH] Validating style consistency...\n") + validate_colors(root) + print("\n[OK] Validation complete") + +if __name__ == '__main__': + main() diff --git a/skills/summoner/README.md b/skills/summoner/README.md new file mode 100644 index 0000000..969ac81 --- /dev/null +++ b/skills/summoner/README.md @@ -0,0 +1,307 @@ +# Summoner Skill + +**Multi-Agent Orchestration for Complex Tasks** + +The Summoner skill transforms Claude Code into a sophisticated project orchestrator, breaking down complex tasks into manageable units and coordinating specialized agents to deliver high-quality, production-ready code. + +## What is the Summoner? + +The Summoner is a meta-skill that excels at: + +- **Task Decomposition**: Breaking complex requirements into atomic, well-defined tasks +- **Context Management**: Preserving all necessary context while avoiding bloat +- **Agent Orchestration**: Summoning and coordinating specialized agents +- **Quality Assurance**: Ensuring DRY, CLEAN, SOLID principles throughout +- **Risk Mitigation**: Preventing assumptions, scope creep, and breaking changes + +## When to Use + +### βœ… Use Summoner For: + +- **Multi-component features** (3+ files/components) +- **Large refactoring projects** (architectural changes) +- **Migration projects** (API versions, frameworks, databases) +- **Complex bug fixes** (multiple related issues) +- **New system implementations** (auth, payments, etc.) + +### ❌ Don't Use Summoner For: + +- Single file changes +- Simple bug fixes +- Straightforward feature additions +- Routine maintenance +- Quick patches + +## How It Works + +``` +1. Task Analysis + ↓ +2. Create Mission Control Document (MCD) + ↓ +3. Decompose into Phases & Tasks + ↓ +4. For Each Task: + - Summon Specialized Agent + - Provide Bounded Context + - Monitor & Validate + ↓ +5. Integration & Quality Control + ↓ +6. Deliver Production-Ready Code +``` + +## Quick Start + +### 1. Activate the Skill + +Simply request it in Claude Code: + +``` +Use the summoner skill to implement user authentication with OAuth2 +``` + +### 2. Or Explicitly Reference + +``` +I need to refactor our API layer to use GraphQL. This is a complex task that +will touch multiple services. Can you use the Summoner skill to orchestrate this? +``` + +## Components + +### πŸ“„ Templates + +- **`mission-control-template.md`**: Master planning document +- **`agent-spec-template.md`**: Agent assignment specifications +- **`quality-gates.md`**: Comprehensive quality checklist + +### πŸ”§ Scripts + +- **`init_mission.py`**: Initialize new Mission Control Documents +- **`validate_quality.py`**: Interactive quality gate validation + +### πŸ“š References + +All templates and quality standards are in the `References/` directory. + +## Directory Structure + +``` +summoner/ +β”œβ”€β”€ SKILL.md # Main skill definition +β”œβ”€β”€ README.md # This file +β”œβ”€β”€ scripts/ +β”‚ β”œβ”€β”€ init_mission.py # MCD initializer +β”‚ └── validate_quality.py # Quality validator +β”œβ”€β”€ References/ +β”‚ β”œβ”€β”€ mission-control-template.md +β”‚ β”œβ”€β”€ agent-spec-template.md +β”‚ └── quality-gates.md +└── Assets/ + └── (reserved for future templates) +``` + +## Example Workflow + +### Scenario: Implement Real-Time Notifications + +1. **Activate Summoner** + ``` + Use the summoner skill to add real-time notifications to our app + using WebSockets. This needs to work across web and mobile clients. + ``` + +2. **Summoner Creates MCD** + - Analyzes requirements + - Creates `mission-real-time-notifications.md` + - Breaks down into phases and tasks + +3. **Phase 1: Backend Infrastructure** + - Task 1.1: WebSocket server setup (Backend Agent) + - Task 1.2: Message queue integration (Backend Agent) + - Task 1.3: Authentication middleware (Security Agent) + +4. **Phase 2: Client Integration** + - Task 2.1: Web client WebSocket handler (Frontend Agent) + - Task 2.2: Mobile client integration (Mobile Agent) + - Task 2.3: Reconnection logic (Frontend/Mobile Agents) + +5. **Phase 3: Testing & Polish** + - Task 3.1: Integration tests (QA Agent) + - Task 3.2: Load testing (Performance Agent) + - Task 3.3: Documentation (Documentation Agent) + +6. **Quality Control** + - Validate all quality gates + - Integration testing + - Final review + +## Key Features + +### 🎯 Context Preservation + +Every task in the MCD includes: +- Exact context needed (no more, no less) +- Clear inputs and outputs +- Explicit dependencies +- Validation criteria + +### πŸ›‘οΈ Quality Enforcement + +Three levels of quality gates: +- **Task-level**: DRY, testing, documentation +- **Phase-level**: Integration, CLEAN, performance, security +- **Project-level**: SOLID, architecture, production readiness + +### πŸ“Š Progress Tracking + +Mission Control Document provides: +- Real-time progress updates +- Risk register +- Decision log +- Integration checklist + +### 🚫 Zero Slop Policy + +The Summoner prevents: +- Assumption-driven development +- Context bloat +- Scope creep +- Breaking changes without migration paths +- Code duplication +- Untested code + +## Using the Scripts + +### Initialize a Mission + +```bash +python .claude/skills/summoner/scripts/init_mission.py "Add User Authentication" +``` + +Creates `mission-add-user-authentication.md` ready for editing. + +### Validate Quality + +```bash +# Interactive validation +python .claude/skills/summoner/scripts/validate_quality.py --level task --interactive + +# Print checklist for manual review +python .claude/skills/summoner/scripts/validate_quality.py --level project +``` + +## Quality Standards + +### DRY (Don't Repeat Yourself) +- No code duplication +- Shared logic extracted +- Single source of truth for data + +### CLEAN Code +- **C**lear: Easy to understand +- **L**imited: Single responsibility +- **E**xpressive: Intent-revealing names +- **A**bstracted: Proper abstraction levels +- **N**eat: Well-organized structure + +### SOLID Principles +- **S**ingle Responsibility +- **O**pen/Closed +- **L**iskov Substitution +- **I**nterface Segregation +- **D**ependency Inversion + +See `References/quality-gates.md` for complete checklists. + +## Best Practices + +### 1. Front-Load Planning + +Spend time on the MCD before coding. A well-planned mission executes smoothly. + +### 2. Bounded Context + +Give each agent exactly what they need. Too much context is as bad as too little. + +### 3. Validate Early, Validate Often + +Run quality gates at task completion, not just at the end. + +### 4. Document Decisions + +Use the Decision Log in the MCD to record why choices were made. + +### 5. Update the MCD + +Keep the MCD current as the project evolves. It's a living document. + +## Troubleshooting + +### Agent Asking for Already-Provided Context + +**Problem**: Agent requests information that's in the MCD. + +**Solution**: The agent spec wasn't clear enough. Update the agent spec template to explicitly reference the MCD sections. + +### Quality Gates Failing + +**Problem**: Code doesn't pass quality checks. + +**Solution**: +1. Identify which gate failed +2. Create a remediation task +3. Assign to appropriate agent +4. Revalidate after fix + +### Scope Creep + +**Problem**: Tasks growing beyond original boundaries. + +**Solution**: +1. Pause execution +2. Review MCD success criteria +3. Either add new tasks or trim scope +4. Update MCD and proceed + +### Integration Issues + +**Problem**: Components don't work together. + +**Solution**: +1. Review interface definitions in MCD +2. Check if agents followed specs +3. Add integration tests +4. Document the interface contract better + +## Examples + +See the `examples/` directory in the main ClaudeShack repo for: +- Complete Mission Control Documents +- Real-world orchestration scenarios +- Quality gate validation reports + +## Contributing + +Ideas for improving the Summoner skill? +- Suggest new templates +- Propose quality gates +- Share success stories +- Report issues + +## Version History + +- **v1.0** (2025-11-19): Initial release + - Mission Control Document system + - Quality gates framework + - Agent orchestration workflows + - Supporting scripts and templates + +## License + +Part of the ClaudeShack skill collection. See main repository for licensing. + +--- + +**"Context is precious. Orchestration is power. Quality is non-negotiable."** diff --git a/skills/summoner/References/agent-spec-template.md b/skills/summoner/References/agent-spec-template.md new file mode 100644 index 0000000..160fc55 --- /dev/null +++ b/skills/summoner/References/agent-spec-template.md @@ -0,0 +1,316 @@ +# Agent Specification Template + +Use this template when summoning specialized agents to ensure they have exactly what they need - no more, no less. + +--- + +## Agent Specification: [AGENT NAME/ID] + +**Created**: [DATE] +**Summoner**: [Who summoned this agent] +**Status**: [Active | Complete | Blocked] + +--- + +## Agent Profile + +### Specialization +[What this agent is expert in - e.g., "Frontend React Developer", "Database Optimization Specialist", "Security Auditor"] + +### Assigned Tasks +- Task 1.2: [Task Name] +- Task 1.3: [Task Name] +- [List all tasks assigned to this agent] + +### Expected Completion +[Date/time or "After Task X.Y completes"] + +--- + +## Context Package + +### What This Agent Needs to Know + +[Provide ONLY the context necessary for their tasks. Link to full docs rather than duplicating.] + +**Project Overview (Brief)**: +[2-3 sentences about the overall project - just enough to understand their role] + +**Their Role**: +[1-2 sentences about what they're responsible for in the bigger picture] + +**Specific Context**: +``` +[The actual detailed context needed for their tasks: +- Relevant architecture decisions +- Tech stack specifics +- Existing patterns to follow +- Constraints to respect +- Examples to reference] +``` + +### What This Agent Does NOT Need + +[Explicitly list what context you're NOT providing to avoid bloat:] +- ❌ [Irrelevant context 1] +- ❌ [Irrelevant context 2] +- ❌ [Information they can look up themselves] + +--- + +## Task Details + +### Task [ID]: [Name] + +**Objective**: +[Clear statement of what needs to be accomplished] + +**Current State**: +``` +[What exists now - relevant files, implementations, issues] +File: path/to/file.ts:123 +Current implementation: [brief description] +Problem: [what needs to change] +``` + +**Desired End State**: +``` +[What should exist after this task] +- Deliverable 1 +- Deliverable 2 +- Tests passing +- Documentation updated +``` + +**Acceptance Criteria**: +- [ ] Criterion 1 (specific, testable) +- [ ] Criterion 2 (specific, testable) +- [ ] All tests pass +- [ ] Quality gates pass +- [ ] Documentation complete + +**Constraints**: +- Must: [Things that must be done] +- Must NOT: [Things to avoid] +- Should: [Preferences/best practices] + +**Reference Files**: +- `path/to/relevant/file.ts` - [Why this is relevant] +- `path/to/example.ts:45-67` - [What pattern to follow] +- `docs/architecture.md` - [Link to full docs] + +--- + +## Inputs & Dependencies + +### Inputs Provided +[What this agent is receiving to start their work:] +- βœ… Input 1: [Description and location] +- βœ… Input 2: [Description and location] + +### Dependencies +[What must be complete before this agent can start:] +- Task X.Y: [Name] - Status: [Complete/In Progress] +- Decision Z: [Description] - Status: [Decided/Pending] + +### Blockers +[Current blockers if any:] +- ❌ [Blocker description] - Owner: [Who's resolving] +- OR: None - Ready to proceed + +--- + +## Outputs Expected + +### Primary Deliverables +1. **[Deliverable 1]** + - Format: [e.g., "Modified file at path/to/file.ts"] + - Requirements: [Specific requirements] + - Validation: [How to verify it's correct] + +2. **[Deliverable 2]** + - Format: [...] + - Requirements: [...] + - Validation: [...] + +### Secondary Deliverables +- [ ] Tests for new functionality +- [ ] Documentation updates +- [ ] Updated MCD if any changes to plan +- [ ] Quality gate sign-off + +### Handoff Protocol +[How to hand off to next agent or back to summoner:] +``` +1. Complete all deliverables +2. Run quality gate checklist +3. Document any deviations from plan +4. Update MCD progress tracking +5. Report completion with summary +``` + +--- + +## Quality Standards + +### Code Quality +- [ ] Follows DRY principle +- [ ] Follows CLEAN code practices +- [ ] Follows SOLID principles (applicable ones) +- [ ] Consistent with project style +- [ ] Properly documented + +### Testing +- [ ] Unit tests written +- [ ] Integration tests if applicable +- [ ] All tests passing +- [ ] Edge cases covered + +### Security +- [ ] No vulnerabilities introduced +- [ ] Input validation +- [ ] Proper error handling +- [ ] No sensitive data exposed + +### Performance +- [ ] Meets performance requirements +- [ ] No unnecessary operations +- [ ] Efficient algorithms +- [ ] Resources properly managed + +--- + +## Communication Protocol + +### Status Updates +**Frequency**: [e.g., "After each task completion" or "Daily"] +**Format**: [How to report - e.g., "Comment in MCD"] +**Content**: [What to include - progress, blockers, questions] + +### Questions/Clarifications +**How to Ask**: [Process for getting clarifications] +**Response SLA**: [When to expect answers] +**Escalation**: [When and how to escalate] + +### Completion Report +When done, provide: +```markdown +## Completion Report: [Agent Name] + +### Summary +[1-2 sentences on what was accomplished] + +### Deliverables +- βœ… Deliverable 1: [Location/description] +- βœ… Deliverable 2: [Location/description] + +### Quality Gates +- βœ… Code Quality: PASS +- βœ… Testing: PASS +- βœ… Documentation: PASS + +### Deviations from Plan +- [None] OR +- [Deviation 1 - why it happened - impact] + +### Blockers Encountered +- [None] OR +- [Blocker 1 - how it was resolved] + +### Recommendations +[Any suggestions for next phases or improvements] + +### Next Steps +[What should happen next] +``` + +--- + +## Tools & Resources + +### Tools Available +- [Tool/Framework 1]: [Purpose] +- [Tool/Framework 2]: [Purpose] +- [Testing framework]: [How to run tests] +- [Linter]: [How to check style] + +### Reference Documentation +- [Link to tech docs] +- [Link to internal docs] +- [Link to examples] +- [Link to style guide] + +### Example Code +[Paste or link to example code that shows the pattern to follow] +```typescript +// Example of preferred pattern +function examplePattern() { + // This is how we do things in this project +} +``` + +--- + +## Emergency Contacts + +**Summoner**: [How to reach the summoner] +**Technical Lead**: [If different from summoner] +**Domain Expert**: [For domain-specific questions] +**Blocker Resolution**: [Who to contact if blocked] + +--- + +## Success Indicators + +βœ… **This agent is succeeding if:** +- Delivering on time +- No out-of-scope work +- Quality gates passing +- No blockers or blockers being resolved quickly +- Clear communication + +❌ **Warning signs:** +- Asking for context that was already provided +- Scope creep +- Quality gate failures +- Long periods of silence +- Assumptions not validated + +--- + +## Agent Activation + +**Summoning Command**: +``` +Using the Task tool with subagent_type="general-purpose": + +"You are a [SPECIALIZATION] agent. Your mission is to [OBJECTIVE]. + +Context: [PROVIDE CONTEXT PACKAGE] + +Your tasks: +[LIST TASKS WITH DETAILS] + +Deliverables expected: +[LIST DELIVERABLES] + +Quality standards: +[REFERENCE QUALITY GATES] + +Report back when complete with a completion report." +``` + +**Estimated Duration**: [Time estimate] +**Complexity**: [Low/Medium/High] +**Priority**: [P0/P1/P2/P3] + +--- + +## Notes + +[Any additional notes, special considerations, or context that doesn't fit elsewhere] + +--- + +**Template Version**: 1.0 +**Last Updated**: [Date] diff --git a/skills/summoner/References/mission-control-template.md b/skills/summoner/References/mission-control-template.md new file mode 100644 index 0000000..41a4887 --- /dev/null +++ b/skills/summoner/References/mission-control-template.md @@ -0,0 +1,280 @@ +# Mission Control: [TASK NAME] + +**Created**: [DATE] +**Status**: [Planning | In Progress | Integration | Complete] +**Summoner**: [Agent/User Name] + +--- + +## Executive Summary + +[Provide a concise 1-2 paragraph overview of the entire initiative. Include: +- What is being built/changed +- Why it's important +- High-level approach +- Expected impact] + +--- + +## Success Criteria + +Define what "done" looks like: + +- [ ] **Criterion 1**: [Specific, measurable success indicator] +- [ ] **Criterion 2**: [Specific, measurable success indicator] +- [ ] **Criterion 3**: [Specific, measurable success indicator] +- [ ] **All tests passing**: Unit, integration, and e2e tests pass +- [ ] **Documentation complete**: All changes documented +- [ ] **Quality gates passed**: DRY, CLEAN, SOLID principles followed + +--- + +## Context & Constraints + +### Technical Context + +**Current Architecture:** +[Brief description of relevant architecture, tech stack, patterns in use] + +**Relevant Existing Implementations:** +- `path/to/file.ts:123` - [What this does and why it's relevant] +- `path/to/other/file.ts:456` - [What this does and why it's relevant] + +**Technology Stack:** +- [Framework/Library 1] +- [Framework/Library 2] +- [Database/Store] +- [Other relevant tech] + +### Business Context + +**User Impact:** +[How this affects end users] + +**Priority:** +[High/Medium/Low and why] + +**Stakeholders:** +[Who cares about this and why] + +### Constraints + +**Performance:** +- [Specific performance requirements] + +**Compatibility:** +- [Browser support, API versions, etc.] + +**Security:** +- [Security considerations and requirements] + +**Timeline:** +- [Any time constraints] + +**Other:** +- [Any other constraints or limitations] + +--- + +## Task Index + +### Phase 1: [PHASE NAME - e.g., "Foundation & Setup"] + +#### Task 1.1: [Specific Task Name] + +**Agent Type**: [e.g., Backend Engineer, Frontend Specialist, DevOps] + +**Responsibility**: +[Clear, bounded description of what this agent is responsible for. Use active voice.] + +**Context Needed**: +``` +[ONLY the specific context this agent needs. Reference sections above or external docs. +DO NOT duplicate large amounts of text - point to it instead.] +``` + +**Inputs**: +- [What must exist before this task can start] +- [Files, data, decisions, or outputs from other tasks] + +**Outputs**: +- [ ] [Specific deliverable 1] +- [ ] [Specific deliverable 2] +- [ ] [Tests for this component] + +**Validation Criteria**: +``` +How to verify this task is complete and correct: +- [ ] Validation point 1 +- [ ] Validation point 2 +- [ ] Tests pass +- [ ] Code review checklist items +``` + +**Dependencies**: +- None (for first task) OR +- Requires: Task X.Y to be complete +- Blocked by: [What's blocking this if anything] + +**Estimated Complexity**: [Low/Medium/High] + +--- + +#### Task 1.2: [Next Task Name] + +[Repeat structure above] + +--- + +### Phase 2: [NEXT PHASE NAME] + +[Continue with tasks for next phase] + +--- + +## Quality Gates + +### Code Quality Standards + +- [ ] **DRY (Don't Repeat Yourself)** + - No duplicated logic or code blocks + - Shared functionality extracted into reusable utilities + - Configuration centralized + +- [ ] **CLEAN Code** + - Meaningful variable and function names + - Functions do one thing well + - Comments explain WHY, not WHAT + - Consistent formatting and style + +- [ ] **SOLID Principles** + - Single Responsibility: Each module/class has one reason to change + - Open/Closed: Open for extension, closed for modification + - Liskov Substitution: Subtypes are substitutable for base types + - Interface Segregation: No client forced to depend on unused methods + - Dependency Inversion: Depend on abstractions, not concretions + +- [ ] **Security** + - No injection vulnerabilities (SQL, XSS, Command, etc.) + - Proper authentication and authorization + - Sensitive data properly handled + - Dependencies checked for vulnerabilities + +- [ ] **Performance** + - Meets stated performance requirements + - No unnecessary computations or renders + - Efficient algorithms and data structures + - Proper resource cleanup + +### Process Quality Standards + +- [ ] **Testing** + - Unit tests for all new functions/components + - Integration tests for component interactions + - E2E tests for critical user paths + - Edge cases covered + - All tests passing + +- [ ] **Documentation** + - Public APIs documented + - Complex logic explained + - README updated if needed + - Migration guide if breaking changes + +- [ ] **Integration** + - No breaking changes (or explicitly documented with migration path) + - Backwards compatible where possible + - All integrations tested + - Dependencies updated + +- [ ] **Code Review** + - Self-review completed + - Peer review if applicable + - All review comments addressed + +--- + +## Agent Roster + +### [Agent Role/Name 1] + +**Specialization**: [What domain expertise this agent brings] + +**Assigned Tasks**: +- Task 1.1 +- Task 2.3 + +**Context Provided**: +- Section: [Reference to MCD sections this agent needs] +- Files: [Key files this agent will work with] +- External Docs: [Any external documentation needed] + +**Communication Protocol**: +- Reports to: [Who/what] +- Updates: [When and how to provide status updates] +- Blockers: [How to escalate blockers] + +--- + +### [Agent Role/Name 2] + +[Repeat structure above] + +--- + +## Risk Register + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|------------| +| [Risk description] | Low/Med/High | Low/Med/High | [How we're mitigating this] | +| [Risk description] | Low/Med/High | Low/Med/High | [How we're mitigating this] | + +--- + +## Progress Tracking + +### Phase 1: [PHASE NAME] +- [x] Task 1.1: [Name] - βœ… Complete +- [ ] Task 1.2: [Name] - πŸ”„ In Progress +- [ ] Task 1.3: [Name] - ⏸️ Blocked by X +- [ ] Task 1.4: [Name] - ⏳ Pending + +### Phase 2: [PHASE NAME] +- [ ] Task 2.1: [Name] - ⏳ Pending + +--- + +## Decision Log + +| Date | Decision | Rationale | Impact | +|------|----------|-----------|--------| +| [DATE] | [What was decided] | [Why this decision] | [What this affects] | + +--- + +## Integration Checklist + +Final integration before marking complete: + +- [ ] All tasks completed and validated +- [ ] All tests passing (unit, integration, e2e) +- [ ] No breaking changes or migration guide provided +- [ ] Performance benchmarks met +- [ ] Security review passed +- [ ] Documentation complete +- [ ] Quality gates all green +- [ ] Stakeholder acceptance (if applicable) + +--- + +## Lessons Learned + +[To be filled at completion - what went well, what could improve for next time] + +--- + +## References + +- [Link to relevant docs] +- [Link to design docs] +- [Link to related issues/PRs] diff --git a/skills/summoner/References/quality-gates.md b/skills/summoner/References/quality-gates.md new file mode 100644 index 0000000..7da65dc --- /dev/null +++ b/skills/summoner/References/quality-gates.md @@ -0,0 +1,282 @@ +# Quality Gates Checklist + +This document provides detailed quality gates for validating work at task, phase, and project levels. + +--- + +## Task-Level Quality Gates + +Run these checks after completing each individual task: + +### βœ… Functional Requirements +- [ ] All specified outputs delivered +- [ ] Functionality works as described +- [ ] Edge cases handled +- [ ] Error cases handled gracefully +- [ ] No regression in existing functionality + +### βœ… Code Quality +- [ ] Code is readable and self-documenting +- [ ] Variable/function names are meaningful +- [ ] No magic numbers or strings +- [ ] No commented-out code (unless explicitly documented why) +- [ ] Consistent code style with project + +### βœ… DRY (Don't Repeat Yourself) +- [ ] No duplicated logic +- [ ] Shared functionality extracted to utilities +- [ ] Constants defined once, referenced everywhere +- [ ] No copy-paste code blocks + +### βœ… Testing +- [ ] Unit tests written for new code +- [ ] Tests cover happy path +- [ ] Tests cover edge cases +- [ ] Tests cover error conditions +- [ ] All tests pass +- [ ] Test names clearly describe what they test + +### βœ… Documentation +- [ ] Complex logic has explanatory comments +- [ ] Public APIs documented (JSDoc, docstrings, etc.) +- [ ] README updated if user-facing changes +- [ ] Breaking changes documented + +--- + +## Phase-Level Quality Gates + +Run these checks after completing a phase (group of related tasks): + +### βœ… Integration +- [ ] All components integrate correctly +- [ ] Data flows between components as expected +- [ ] No integration bugs +- [ ] APIs between components are clean +- [ ] Interfaces are well-defined + +### βœ… CLEAN Principles +- [ ] **C**lear: Code is easy to understand +- [ ] **L**imited: Functions/methods have single responsibility +- [ ] **E**xpressive: Naming reveals intent +- [ ] **A**bstracted: Proper level of abstraction +- [ ] **N**eat: Organized, well-structured code + +### βœ… Performance +- [ ] No obvious performance issues +- [ ] Efficient algorithms used +- [ ] No unnecessary computations +- [ ] Resources properly managed (memory, connections, etc.) +- [ ] Meets stated performance requirements + +### βœ… Security +- [ ] No injection vulnerabilities (SQL, XSS, Command, etc.) +- [ ] Input validation in place +- [ ] Output encoding where needed +- [ ] Authentication/authorization checked +- [ ] Sensitive data not logged or exposed +- [ ] Dependencies have no known vulnerabilities + +--- + +## Project-Level Quality Gates + +Run these checks before marking the entire project complete: + +### βœ… SOLID Principles + +#### Single Responsibility Principle +- [ ] Each class/module has one reason to change +- [ ] Each function does one thing well +- [ ] No god objects or god functions +- [ ] Responsibilities clearly separated + +#### Open/Closed Principle +- [ ] Open for extension (can add new behavior) +- [ ] Closed for modification (don't change existing code) +- [ ] Use abstractions (interfaces, base classes) for extension points +- [ ] Configuration over hardcoding + +#### Liskov Substitution Principle +- [ ] Subtypes can replace base types without breaking +- [ ] Derived classes don't weaken preconditions +- [ ] Derived classes don't strengthen postconditions +- [ ] Inheritance is "is-a" relationship, not "has-a" + +#### Interface Segregation Principle +- [ ] Interfaces are focused and cohesive +- [ ] No client forced to depend on methods it doesn't use +- [ ] Many small interfaces > one large interface +- [ ] Clients see only methods they need + +#### Dependency Inversion Principle +- [ ] High-level modules don't depend on low-level modules +- [ ] Both depend on abstractions +- [ ] Abstractions don't depend on details +- [ ] Details depend on abstractions +- [ ] Dependencies injected, not hardcoded + +### βœ… Architecture Quality +- [ ] Architecture supports future growth +- [ ] Clear separation of concerns +- [ ] Proper layering (presentation, business logic, data) +- [ ] No architectural violations +- [ ] Design patterns used appropriately + +### βœ… Testing Coverage +- [ ] Unit test coverage meets threshold (e.g., 80%) +- [ ] Integration tests for key workflows +- [ ] E2E tests for critical user paths +- [ ] All tests passing consistently +- [ ] No flaky tests + +### βœ… Documentation Completeness +- [ ] README is current and accurate +- [ ] API documentation complete +- [ ] Architecture documented +- [ ] Setup/installation instructions clear +- [ ] Troubleshooting guide if applicable +- [ ] Inline documentation for complex code + +### βœ… Production Readiness +- [ ] No breaking changes (or migration guide provided) +- [ ] Error handling comprehensive +- [ ] Logging appropriate (not too much, not too little) +- [ ] Monitoring/observability in place +- [ ] Configuration externalized +- [ ] Secrets/credentials properly managed + +### βœ… User Impact +- [ ] User-facing features work as expected +- [ ] UX is intuitive +- [ ] Error messages are helpful +- [ ] Performance is acceptable to users +- [ ] Accessibility considerations addressed (if applicable) + +--- + +## Quality Gate Severity Levels + +When a quality gate fails, assess severity: + +### πŸ”΄ Critical (MUST FIX) +- Security vulnerabilities +- Data loss or corruption +- Breaking changes without migration path +- Production crashes or errors +- Major performance degradation + +### 🟑 Warning (SHOULD FIX) +- SOLID principle violations +- Missing tests for complex logic +- Poor performance (but not critical) +- Missing documentation +- Code duplication + +### 🟒 Info (NICE TO FIX) +- Minor style inconsistencies +- Optimization opportunities +- Refactoring suggestions +- Documentation enhancements + +--- + +## Remediation Process + +When quality gates fail: + +1. **Document the Issue** + - What gate failed + - Severity level + - Impact assessment + +2. **Create Remediation Task** + - Add to task index + - Assign to appropriate agent + - Provide context and acceptance criteria + +3. **Revalidate** + - After fix, re-run quality gate + - Ensure no new issues introduced + - Update MCD with results + +4. **Learn** + - Why did this get through? + - How to prevent in future? + - Update checklist if needed + +--- + +## Automated Checks + +Where possible, automate quality gates: + +### Recommended Tools + +**Linting:** +- ESLint (JavaScript/TypeScript) +- Pylint/Flake8 (Python) +- RuboCop (Ruby) +- Clippy (Rust) + +**Testing:** +- Jest, Vitest (JavaScript) +- pytest (Python) +- RSpec (Ruby) +- cargo test (Rust) + +**Security:** +- npm audit, yarn audit +- Snyk +- OWASP Dependency-Check +- Trivy + +**Coverage:** +- Istanbul/nyc (JavaScript) +- Coverage.py (Python) +- SimpleCov (Ruby) +- Tarpaulin (Rust) + +**Type Checking:** +- TypeScript +- mypy (Python) +- Sorbet (Ruby) + +--- + +## Sign-Off Template + +```markdown +## Quality Gate Sign-Off + +**Task/Phase/Project**: [Name] +**Date**: [Date] +**Reviewed By**: [Agent/Person] + +### Results +- βœ… Functional Requirements: PASS +- βœ… Code Quality: PASS +- βœ… DRY: PASS +- βœ… Testing: PASS +- βœ… Documentation: PASS +- βœ… SOLID Principles: PASS +- βœ… Security: PASS +- βœ… Performance: PASS + +### Issues Found +- [None] OR +- [Issue 1 - Severity - Status] +- [Issue 2 - Severity - Status] + +### Recommendation +- [ ] Approved - Ready to proceed +- [ ] Approved with conditions - [List conditions] +- [ ] Rejected - [List blockers] + +### Notes +[Any additional notes or observations] +``` + +--- + +**Remember: Quality is not negotiable. It's faster to build it right than to fix it later.** diff --git a/skills/summoner/SKILL.md b/skills/summoner/SKILL.md new file mode 100644 index 0000000..a2d1f93 --- /dev/null +++ b/skills/summoner/SKILL.md @@ -0,0 +1,293 @@ +--- +name: summoner +description: Multi-agent orchestration skill for complex tasks requiring coordination, decomposition, and quality control. Use for large implementations, refactoring projects, multi-component features, or work requiring multiple specialized agents. Excels at preventing context bloat and ensuring SOLID principles. Integrates with oracle, guardian, and wizard. +allowed-tools: Read, Write, Edit, Glob, Grep, Task, Bash +--- + +# Summoner: Multi-Agent Orchestration Skill + +You are now operating as the **Summoner**, a meta-orchestrator designed to handle complex, multi-faceted tasks through intelligent decomposition and specialized agent coordination. + +## Core Responsibilities + +### 1. Task Analysis & Decomposition + +When given a complex task: + +1. **Analyze Scope**: Understand the full scope, requirements, constraints, and success criteria +2. **Identify Dependencies**: Map out technical and logical dependencies between components +3. **Decompose Atomically**: Break down into highly specific, atomic tasks that can be independently validated +4. **Preserve Context**: Ensure each subtask has all necessary context without duplication + +### 2. Mission Control Document Creation + +Create a **Mission Control Document** (MCD) as a markdown file that serves as the single source of truth: + +**Structure:** +```markdown +# Mission Control: [Task Name] + +## Executive Summary +[1-2 paragraph overview of the entire initiative] + +## Success Criteria +- [ ] Criterion 1 +- [ ] Criterion 2 +... + +## Context & Constraints +### Technical Context +[Relevant tech stack, architecture patterns, existing implementations] + +### Business Context +[Why this matters, user impact, priority] + +### Constraints +[Performance requirements, compatibility, security, etc.] + +## Task Index + +### Phase 1: [Phase Name] +#### Task 1.1: [Specific Task Name] +- **Agent Type**: [e.g., Backend Developer, Frontend Specialist, QA Engineer] +- **Responsibility**: [Clear, bounded responsibility] +- **Context**: [Specific context needed for THIS task only] +- **Inputs**: [What this task needs to start] +- **Outputs**: [What this task must produce] +- **Validation**: [How to verify success] +- **Dependencies**: [What must be completed first] + +[Repeat for each task...] + +## Quality Gates + +### Code Quality +- [ ] DRY: No code duplication +- [ ] CLEAN: Readable, maintainable code +- [ ] SOLID: Proper abstractions and separation of concerns +- [ ] Security: No vulnerabilities introduced +- [ ] Performance: Meets performance requirements + +### Process Quality +- [ ] All tests pass +- [ ] Documentation updated +- [ ] No breaking changes (or explicitly documented) +- [ ] Code reviewed for best practices + +## Agent Roster + +### [Agent Name/Role] +- **Specialization**: [What they're expert in] +- **Assigned Tasks**: [Task IDs] +- **Context Provided**: [References to MCD sections] +``` + +### 3. Agent Summoning & Coordination + +For each task or group of related tasks: + +1. **Summon Specialized Agent**: Use the Task tool to create an agent with specific expertise +2. **Provide Bounded Context**: Give ONLY the context needed for their specific tasks +3. **Clear Handoff Protocol**: Define what success looks like and how to hand off to next agent +4. **Quality Validation**: Review output against quality gates before proceeding + +### 4. Quality Control & Integration + +After each phase: + +1. **Validate Outputs**: Check against quality gates and success criteria +2. **Integration Check**: Ensure components work together correctly +3. **Context Sync**: Update MCD with any learnings or changes +4. **Risk Assessment**: Identify any blockers or risks that emerged + +## Operating Principles + +### Minimize Context Bloat +- **Progressive Disclosure**: Load only what's needed, when it's needed +- **Reference by Location**: Point to existing documentation rather than duplicating +- **Summarize vs. Copy**: Summarize large contexts; provide full details only when necessary + +### Eliminate Assumptions +- **Explicit Over Implicit**: Make all assumptions explicit in the MCD +- **Validation Points**: Build in checkpoints to validate assumptions +- **Question Everything**: Challenge vague requirements before decomposition + +### Enforce Quality +- **Definition of Done**: Each task has clear completion criteria +- **No Slop**: Reject outputs that don't meet quality standards +- **Continuous Review**: Quality checks at task, phase, and project levels + +## Workflow + +``` +1. Receive Complex Task + ↓ +2. Create Mission Control Document + ↓ +3. For Each Phase: + a. For Each Task: + - Summon Specialized Agent + - Provide Bounded Context + - Monitor Execution + - Validate Output + b. Phase Integration Check + c. Update MCD + ↓ +4. Final Integration & Validation + ↓ +5. Deliverable + Updated Documentation +``` + +## Summoner vs Guardian vs Wizard + +### Summoner (YOU - Task Orchestration) +**Purpose**: Coordinate multiple agents for complex, multi-component tasks + +**When to Use**: +- Large feature spanning 3+ components +- Multi-phase refactoring projects +- Complex research requiring multiple specialized agents +- Migration projects with many dependencies +- Coordinating documentation research (with Wizard) + +**Key Traits**: +- **Proactive**: Plans ahead, orchestrates workflows +- **Multi-Agent**: Coordinates multiple specialists +- **Mission Control**: Creates MCD as single source of truth +- **Parallel Work**: Can run agents in parallel when dependencies allow + +**Example**: "Build REST API with auth, rate limiting, caching, and WebSocket support" β†’ Summoner decomposes into 5 subtasks, assigns to specialized agents, coordinates execution + +### Guardian (Quality Gates) +**Purpose**: Monitor session health, detect issues, review code automatically + +**When to Use**: +- Automatic code review (when 50+ lines written) +- Detecting repeated errors (same error 3+ times) +- Session health monitoring (context bloat, file churn) +- Security/performance audits (using templates) + +**Key Traits**: +- **Reactive**: Triggers based on thresholds +- **Single-Agent**: Spawns one focused Haiku reviewer +- **Minimal Context**: Only passes relevant code + Oracle patterns +- **Validation**: Cross-checks suggestions against Oracle + +**Example**: You write 60 lines of auth code β†’ Guardian automatically triggers security review β†’ Presents suggestions with confidence scores + +### Wizard (Documentation Maintenance) +**Purpose**: Keep documentation accurate, up-to-date, and comprehensive + +**When to Use**: +- Updating README for new features +- Generating skill documentation +- Validating documentation accuracy +- Syncing docs across files + +**Key Traits**: +- **Research-First**: Uses Oracle + conversation history + code analysis +- **No Hallucinations**: Facts only, with references +- **Uses Both**: Summoner for research coordination, Guardian for doc review +- **Accuracy Focused**: Verifies all claims against code + +**Example**: "Document the Guardian skill" β†’ Wizard uses Summoner to coordinate research agents β†’ Generates comprehensive docs β†’ Guardian validates accuracy + +### When to Use Which + +**Use Summoner When:** +- βœ… Task has 3+ distinct components +- βœ… Need to coordinate multiple specialists +- βœ… Complex research requiring different expertise +- βœ… Multi-phase execution with dependencies +- βœ… Wizard needs comprehensive research coordination + +**Use Guardian When:** +- βœ… Need automatic quality checks +- βœ… Code review for security/performance +- βœ… Session is degrading (errors, churn, corrections) +- βœ… Validating Wizard's documentation against code + +**Use Wizard When:** +- βœ… Documentation needs updating +- βœ… New feature needs documenting +- βœ… Need to verify documentation accuracy +- βœ… Cross-referencing docs with code + +**Use Together:** +``` +User: "Comprehensively document the Guardian skill" + +Wizard: "This is complex research - using Summoner" + ↓ +Summoner creates Mission Control Document with tasks: + Task 1: Analyze all Guardian scripts + Task 2: Search Oracle for Guardian patterns + Task 3: Search conversation history for Guardian design + ↓ +Summoner coordinates 3 research agents in parallel + ↓ +Summoner synthesizes findings into structured data + ↓ +Wizard generates comprehensive documentation with references + ↓ +Guardian reviews documentation for accuracy and quality + ↓ +Wizard applies Guardian's suggestions + ↓ +Final accurate, comprehensive documentation +``` + +## When to Use This Skill + +**Ideal For:** +- Features touching 3+ components/systems +- Large refactoring efforts +- Migration projects +- Complex bug fixes requiring multiple fixes +- New architectural implementations +- Comprehensive research coordination (for Wizard) +- Any task where coordination overhead > execution overhead + +**Not Needed For:** +- Single-file changes +- Straightforward bug fixes +- Simple feature additions +- Routine maintenance +- Simple code reviews (use Guardian) +- Simple documentation updates (use Wizard directly) + +## Templates & Scripts + +- **MCD Template**: See `References/mission-control-template.md` +- **Quality Checklist**: See `References/quality-gates.md` +- **Agent Specification**: See `References/agent-spec-template.md` + +## Success Indicators + +βœ… **You're succeeding when:** +- No agent needs to ask for context that should have been provided +- Each agent completes tasks without scope creep +- Integration is smooth with minimal rework +- Quality gates pass on first check +- No "surprise" requirements emerge late + +❌ **Warning signs:** +- Agents making assumptions not in MCD +- Repeated context requests +- Integration failures +- Quality gate failures +- Scope creep within tasks + +## Remember + +> "The context window is a public good. Use it wisely." + +Your job is not to do the work yourself, but to **orchestrate specialists** who do their best work when given: +1. Clear, bounded responsibilities +2. Precise context (no more, no less) +3. Explicit success criteria +4. Trust to execute within their domain + +--- + +**Summoner activated. Ready to orchestrate excellence.** diff --git a/skills/summoner/scripts/init_mission.py b/skills/summoner/scripts/init_mission.py new file mode 100755 index 0000000..aaf5705 --- /dev/null +++ b/skills/summoner/scripts/init_mission.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +Mission Control Document Initializer + +This script creates a new Mission Control Document from the template +with proper naming and initial metadata. + +Usage: + python init_mission.py "Task Name" [output_dir] + +Example: + python init_mission.py "Add User Authentication" + python init_mission.py "Refactor API Layer" ./missions +""" + +import sys +import os +from datetime import datetime +from pathlib import Path + + +def slugify(text): + """Convert text to a safe filename slug.""" + # Remove special characters and replace spaces with hyphens + slug = text.lower() + slug = ''.join(c if c.isalnum() or c in ' -_' else '' for c in slug) + slug = '-'.join(slug.split()) + return slug + + +def create_mission_control(task_name, output_dir='.'): + """Create a new Mission Control Document.""" + + # Load template + template_path = Path(__file__).parent.parent / 'References' / 'mission-control-template.md' + + if not template_path.exists(): + print(f"[ERROR] Error: Template not found at {template_path}") + return False + + with open(template_path, 'r') as f: + template = f.read() + + # Replace placeholders + date_str = datetime.now().strftime('%Y-%m-%d') + content = template.replace('[TASK NAME]', task_name) + content = content.replace('[DATE]', date_str) + content = content.replace('[Planning | In Progress | Integration | Complete]', 'Planning') + + # Create output filename + slug = slugify(task_name) + filename = f"mission-{slug}.md" + output_path = Path(output_dir) / filename + + # Ensure output directory exists + output_path.parent.mkdir(parents=True, exist_ok=True) + + # Write file + with open(output_path, 'w') as f: + f.write(content) + + print(f"[OK] Mission Control Document created: {output_path}") + print(f"\n Next steps:") + print(f" 1. Open {output_path}") + print(f" 2. Fill in Executive Summary and Context") + print(f" 3. Define Success Criteria") + print(f" 4. Break down into Tasks") + print(f" 5. Summon agents and begin orchestration!") + + return True + + +def main(): + if len(sys.argv) < 2: + print("Usage: python init_mission.py \"Task Name\" [output_dir]") + print("\nExample:") + print(" python init_mission.py \"Add User Authentication\"") + print(" python init_mission.py \"Refactor API Layer\" ./missions") + sys.exit(1) + + task_name = sys.argv[1] + output_dir = sys.argv[2] if len(sys.argv) > 2 else '.' + + success = create_mission_control(task_name, output_dir) + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main() diff --git a/skills/summoner/scripts/validate_quality.py b/skills/summoner/scripts/validate_quality.py new file mode 100755 index 0000000..640890c --- /dev/null +++ b/skills/summoner/scripts/validate_quality.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +""" +Quality Gates Validator + +This script helps validate code against quality gates defined in the +quality-gates.md reference document. + +Usage: + python validate_quality.py [--level task|phase|project] [--interactive] + +Example: + python validate_quality.py --level task --interactive + python validate_quality.py --level project +""" + +import sys +import argparse +from enum import Enum + + +class Level(Enum): + TASK = 'task' + PHASE = 'phase' + PROJECT = 'project' + + +class Severity(Enum): + CRITICAL = '' + WARNING = '' + INFO = '' + + +# Quality Gate Definitions +TASK_GATES = { + 'Functional Requirements': [ + 'All specified outputs delivered', + 'Functionality works as described', + 'Edge cases handled', + 'Error cases handled gracefully', + 'No regression in existing functionality' + ], + 'Code Quality': [ + 'Code is readable and self-documenting', + 'Variable/function names are meaningful', + 'No magic numbers or strings', + 'No commented-out code', + 'Consistent code style with project' + ], + 'DRY': [ + 'No duplicated logic', + 'Shared functionality extracted to utilities', + 'Constants defined once', + 'No copy-paste code blocks' + ], + 'Testing': [ + 'Unit tests written for new code', + 'Tests cover happy path', + 'Tests cover edge cases', + 'Tests cover error conditions', + 'All tests pass' + ], + 'Documentation': [ + 'Complex logic has explanatory comments', + 'Public APIs documented', + 'README updated if needed', + 'Breaking changes documented' + ] +} + +PHASE_GATES = { + 'Integration': [ + 'All components integrate correctly', + 'Data flows between components as expected', + 'No integration bugs', + 'APIs between components are clean', + 'Interfaces are well-defined' + ], + 'CLEAN Principles': [ + 'Clear: Code is easy to understand', + 'Limited: Functions have single responsibility', + 'Expressive: Naming reveals intent', + 'Abstracted: Proper level of abstraction', + 'Neat: Organized, well-structured code' + ], + 'Performance': [ + 'No obvious performance issues', + 'Efficient algorithms used', + 'No unnecessary computations', + 'Resources properly managed', + 'Meets stated performance requirements' + ], + 'Security': [ + 'No injection vulnerabilities', + 'Input validation in place', + 'Output encoding where needed', + 'Authentication/authorization checked', + 'Sensitive data not logged or exposed', + 'Dependencies have no known vulnerabilities' + ] +} + +PROJECT_GATES = { + 'SOLID - Single Responsibility': [ + 'Each class/module has one reason to change', + 'Each function does one thing well', + 'No god objects or god functions', + 'Responsibilities clearly separated' + ], + 'SOLID - Open/Closed': [ + 'Open for extension (can add new behavior)', + 'Closed for modification', + 'Use abstractions for extension points', + 'Configuration over hardcoding' + ], + 'SOLID - Liskov Substitution': [ + 'Subtypes can replace base types without breaking', + 'Derived classes don\'t weaken preconditions', + 'Inheritance is "is-a" relationship' + ], + 'SOLID - Interface Segregation': [ + 'Interfaces are focused and cohesive', + 'No client forced to depend on unused methods', + 'Many small interfaces over one large interface' + ], + 'SOLID - Dependency Inversion': [ + 'High-level modules don\'t depend on low-level modules', + 'Both depend on abstractions', + 'Dependencies injected, not hardcoded' + ], + 'Testing Coverage': [ + 'Unit test coverage meets threshold', + 'Integration tests for key workflows', + 'E2E tests for critical user paths', + 'All tests passing consistently', + 'No flaky tests' + ], + 'Documentation Completeness': [ + 'README is current and accurate', + 'API documentation complete', + 'Architecture documented', + 'Setup instructions clear', + 'Troubleshooting guide available' + ], + 'Production Readiness': [ + 'No breaking changes or migration guide provided', + 'Error handling comprehensive', + 'Logging appropriate', + 'Configuration externalized', + 'Secrets properly managed' + ] +} + + +def get_gates_for_level(level): + """Get quality gates for specified level.""" + if level == Level.TASK: + return TASK_GATES + elif level == Level.PHASE: + return {**TASK_GATES, **PHASE_GATES} + elif level == Level.PROJECT: + return {**TASK_GATES, **PHASE_GATES, **PROJECT_GATES} + + +def interactive_validation(gates): + """Run interactive validation.""" + results = {} + total_checks = sum(len(checks) for checks in gates.values()) + current = 0 + + print("\n" + "="*70) + print("[SEARCH] Quality Gates Validation") + print("="*70) + print(f"\nTotal checks: {total_checks}") + print("\nFor each check, respond: y (yes/pass), n (no/fail), s (skip)\n") + + for category, checks in gates.items(): + print(f"\n{''*70}") + print(f" {category}") + print(f"{''*70}") + + category_results = [] + + for check in checks: + current += 1 + while True: + response = input(f" [{current}/{total_checks}] {check}? [y/n/s]: ").lower().strip() + + if response in ['y', 'yes']: + print(f" [OK] Pass") + category_results.append(('pass', check)) + break + elif response in ['n', 'no']: + print(f" [ERROR] Fail") + category_results.append(('fail', check)) + break + elif response in ['s', 'skip']: + print(f" Skip") + category_results.append(('skip', check)) + break + else: + print(" Invalid input. Use y/n/s") + + results[category] = category_results + + return results + + +def print_summary(results, level): + """Print validation summary.""" + total_pass = sum(1 for cat in results.values() for status, _ in cat if status == 'pass') + total_fail = sum(1 for cat in results.values() for status, _ in cat if status == 'fail') + total_skip = sum(1 for cat in results.values() for status, _ in cat if status == 'skip') + total_checks = total_pass + total_fail + total_skip + + print("\n" + "="*70) + print("[INFO] VALIDATION SUMMARY") + print("="*70) + + print(f"\nLevel: {level.value.upper()}") + print(f"\nResults:") + print(f" [OK] Passed: {total_pass}/{total_checks}") + print(f" [ERROR] Failed: {total_fail}/{total_checks}") + print(f" Skipped: {total_skip}/{total_checks}") + + if total_fail > 0: + print(f"\n FAILED CHECKS:") + for category, checks in results.items(): + failed = [(status, check) for status, check in checks if status == 'fail'] + if failed: + print(f"\n {category}:") + for _, check in failed: + print(f" [ERROR] {check}") + + print("\n" + "="*70) + + if total_fail == 0 and total_skip == 0: + print(" ALL QUALITY GATES PASSED!") + print("="*70) + return True + elif total_fail == 0: + print(f"[WARNING] All checked gates passed, but {total_skip} checks were skipped.") + print("="*70) + return True + else: + print(f"[ERROR] {total_fail} QUALITY GATES FAILED") + print("[TOOL] Please address failed checks before proceeding.") + print("="*70) + return False + + +def main(): + parser = argparse.ArgumentParser( + description='Validate code against quality gates', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python validate_quality.py --level task --interactive + python validate_quality.py --level project + python validate_quality.py --level phase --interactive + """ + ) + + parser.add_argument( + '--level', + type=str, + choices=['task', 'phase', 'project'], + default='task', + help='Validation level (default: task)' + ) + + parser.add_argument( + '--interactive', + action='store_true', + help='Run interactive validation' + ) + + args = parser.parse_args() + + level = Level(args.level) + gates = get_gates_for_level(level) + + if args.interactive: + results = interactive_validation(gates) + success = print_summary(results, level) + sys.exit(0 if success else 1) + else: + # Non-interactive mode - just print checklist + print(f"\n{'='*70}") + print(f"Quality Gates Checklist - {level.value.upper()} Level") + print(f"{'='*70}\n") + + for category, checks in gates.items(): + print(f"\n{category}:") + for check in checks: + print(f" [ ] {check}") + + print(f"\n{'='*70}") + print("Run with --interactive flag for guided validation") + print(f"{'='*70}\n") + + +if __name__ == '__main__': + main() diff --git a/skills/wizard/SKILL.md b/skills/wizard/SKILL.md new file mode 100644 index 0000000..ef1fe7a --- /dev/null +++ b/skills/wizard/SKILL.md @@ -0,0 +1,671 @@ +--- +name: wizard +description: Documentation wizard that intelligently maintains, updates, and generates accurate documentation. Uses Oracle knowledge, searches conversation history, and spawns focused research agents. No hallucinations - only facts with references. Integrates with oracle, summoner, and guardian. +allowed-tools: Read, Write, Edit, Glob, Grep, Task +--- + +# Documentation Wizard: Intelligent Documentation Maintenance + +You are the **Documentation Wizard** - an intelligent documentation maintainer that keeps ClaudeShack documentation accurate, up-to-date, and comprehensive. + +## Core Principles + +1. **Facts Only**: No hallucinations, assumptions, or made-up information +2. **Always Reference**: Link to code, files, commits, or conversations as proof +3. **Oracle-Powered**: Leverage Oracle knowledge for patterns and corrections +4. **Research-First**: Use focused agents to gather accurate information +5. **Cross-Validation**: Verify claims against actual code +6. **Consistency**: Keep documentation synchronized across all files +7. **Completeness**: Cover all features, skills, and workflows + +## Wizard Responsibilities + +### 1. Documentation Maintenance + +**Auto-Detect Outdated Documentation:** +- Compare README against actual skills directory +- Find undocumented features by scanning code +- Detect version mismatches +- Identify dead links or incorrect paths + +**Update Documentation:** +- Skill README files +- Main project README +- CONTRIBUTING.md +- API documentation +- Usage examples + +### 2. Documentation Generation + +**Generate Documentation for:** +- New skills (from SKILL.md + code analysis) +- New Guardian templates +- New features or major changes +- Migration guides +- API references + +**Documentation Structure:** +```markdown +# Skill/Feature Name + +## Overview +[What it does - one sentence] + +## Use Cases +[Real-world scenarios - verified from code] + +## Installation/Setup +[Exact steps - tested] + +## Usage +[Concrete examples - executable] + +## API Reference +[Functions, parameters, returns - extracted from code] + +## Integration +[How it works with other skills - references to code] + +## Examples +[Full working examples - tested] + +## Troubleshooting +[Common issues from Oracle gotchas/corrections] + +## References +[Links to code, commits, issues] +``` + +### 3. Research & Verification + +**Research Process:** +1. **Search Oracle** for existing knowledge about the topic +2. **Search Conversation History** for past discussions/decisions +3. **Analyze Code** to verify current implementation +4. **Spawn Research Agents** (via Summoner/Guardian) for deep dives +5. **Cross-Reference** findings across all sources +6. **Validate** claims with actual code execution (when safe) + +**Never Document:** +- Planned features (unless clearly marked as roadmap) +- Assumed behavior (verify with code) +- Outdated information (check git history) +- Unimplemented functionality + +## Wizard Workflow + +### Automatic Documentation Update Workflow + +``` +1. Detect Changes + ↓ (git diff, new files, modified SKILL.md) +2. Analyze Impact + ↓ (What docs are affected?) +3. Research & Gather Facts + ↓ (Oracle + Code + History search) +4. Spawn Focused Research Agents (if needed) + ↓ (Summoner: Coordinate multi-agent research) + ↓ (Guardian: Validate accuracy) +5. Generate/Update Documentation + ↓ (With references and examples) +6. Cross-Validate + ↓ (Check consistency across all docs) +7. Present Changes for Review + ↓ (Show diffs with justification) +8. Record in Oracle + ↓ (Store documentation patterns) +``` + +### Manual Documentation Request Workflow + +``` +User: "Document the Guardian security review template" + +Wizard: +1. Search Oracle for Guardian + security + template knowledge +2. Read Guardian template file (security_review.json) +3. Read Guardian Templates/README.md for context +4. Search conversation history for design decisions +5. Spawn research agent: + - Task: "Analyze security_review.json and extract all features" + - Context: Template file only (minimal) + - Expected: JSON with features, configuration, usage +6. Validate findings against actual template +7. Generate documentation with: + - Feature description (from template) + - Usage example (executable) + - Configuration options (from JSON schema) + - Integration points (from Oracle patterns) + - References (links to template file, commits) +8. Present for approval +``` + +## Integration with Other Skills + +### Oracle Integration + +**Query Oracle for:** +- Documentation patterns (what format works best) +- Common gotchas (include in troubleshooting) +- Corrections (what was wrong before) +- Preferences (how user likes docs structured) + +**Store in Oracle:** +- Documentation decisions (why we chose this format) +- Effective examples (what users found helpful) +- Common questions (FAQ material) + +**Example:** +```python +# Search Oracle for documentation patterns +patterns = oracle.search("documentation patterns", category="patterns") + +# Use patterns to guide doc generation +for pattern in patterns: + if "always include examples" in pattern: + include_examples = True +``` + +### Guardian Integration + +**Use Guardian to:** +- **Review generated documentation** for quality + - Check for broken links + - Verify code examples compile/run + - Ensure accuracy against code + - Detect hallucinations + +**Example:** +``` +Wizard generates README update + ↓ +Guardian reviews with "documentation_review" template + ↓ +Guardian validates: + - All code examples are valid Python + - All file paths exist + - All references point to real commits + - No unverified claims + ↓ +Returns suggestions for fixes + ↓ +Wizard applies fixes +``` + +### Summoner Integration + +**Use Summoner to:** +- **Coordinate multi-agent research** for comprehensive docs + - One agent analyzes code + - One agent searches history + - One agent checks Oracle + - Summoner synthesizes findings + +**Example:** +``` +Wizard needs to document complex feature with many components + +Summoner spawns 3 research agents in parallel: + Agent 1: Analyze authentication code + Agent 2: Search Oracle for auth patterns + Agent 3: Find auth-related conversation history + +Summoner synthesizes: + - Code analysis β†’ API reference + - Oracle patterns β†’ Best practices section + - Conversation history β†’ Design rationale + +Wizard receives complete, cross-validated facts +``` + +## Conversation History Search + +**Access to Claude Conversation History:** + +Wizard can search cached conversations in `~/.claude/projects/` for: +- Design decisions and rationale +- Implementation discussions +- User preferences and feedback +- Bug reports and fixes +- Feature requests and specs + +**Search Strategy:** +```python +# Search conversation history for specific topic +def search_conversation_history(topic, project_hash): + """Search JSONL conversation files for topic mentions.""" + + # Load conversations from ~/.claude/projects/[project-hash]/ + conversations = load_jsonl_files(f"~/.claude/projects/{project_hash}/") + + # Extract relevant messages + relevant = [] + for conv in conversations: + for msg in conv['messages']: + if topic.lower() in msg.get('content', '').lower(): + relevant.append({ + 'session_id': conv['session_id'], + 'timestamp': msg['timestamp'], + 'content': msg['content'], + 'role': msg['role'] + }) + + return relevant +``` + +**What to Extract:** +- User requirements: "I want X to do Y" +- Design decisions: "We chose approach A because B" +- Implementation notes: "Changed from X to Y due to Z" +- Corrections: "That's wrong, it should be..." +- Preferences: "I prefer this format..." + +## Research Agent Templates + +### Code Analysis Agent (Read-Only) + +```python +agent_prompt = """You are a READ-ONLY code analyzer for Documentation Wizard. + +CRITICAL CONSTRAINTS: +- DO NOT modify any files +- ONLY read and analyze code +- Return factual findings with line number references + +Your task: Analyze {file_path} and extract: +1. All public functions with signatures +2. All classes with their methods +3. All configuration options +4. All dependencies and integrations +5. All error handling patterns + +{file_content} + +Return JSON: +{{ + "functions": [ + {{ + "name": "function_name", + "signature": "def function_name(param1: type, param2: type) -> return_type", + "docstring": "extracted docstring", + "line_number": 123 + }} + ], + "classes": [...], + "config_options": [...], + "dependencies": [...], + "error_patterns": [...] +}} + +Include line numbers for all findings. +""" +``` + +### History Search Agent (Read-Only) + +```python +agent_prompt = """You are a READ-ONLY conversation history analyzer for Documentation Wizard. + +CRITICAL CONSTRAINTS: +- DO NOT modify any files +- ONLY read conversation history +- Extract factual information with session references + +Your task: Search conversation history for discussions about {topic} + +{conversation_excerpts} + +Return JSON: +{{ + "design_decisions": [ + {{ + "decision": "We chose approach X", + "rationale": "because Y", + "session_id": "abc123", + "timestamp": "2025-01-15T10:30:00Z" + }} + ], + "user_requirements": [...], + "implementation_notes": [...], + "corrections": [...] +}} + +Include session IDs for reference. +""" +``` + +## Documentation Templates + +### Skill Documentation Template + +```markdown +# {Skill Name} + +**Status**: {Production | Beta | Experimental} +**Last Updated**: {YYYY-MM-DD} +**Version**: {X.Y.Z} + +## Overview + +{One-sentence description from SKILL.md} + +## Problem Statement + +{What problem does this skill solve?} +{Verified from design discussions in conversation history} + +## Use Cases + +{List of real-world use cases - verified from code or user feedback} + +1. **{Use Case 1}**: {Description} +2. **{Use Case 2}**: {Description} + +## Installation + +{Exact installation steps - tested} + +## Quick Start + +```bash +{Minimal working example - executable} +``` + +## Core Features + +### Feature 1: {Name} + +**What it does**: {Description from code analysis} + +**How it works**: +1. {Step 1 - reference to code} +2. {Step 2 - reference to code} + +**Example**: +```python +{Working code example - line numbers from actual file} +``` + +**References**: {Link to code, commit, or issue} + +## API Reference + +{Auto-generated from code analysis - includes type hints, parameters, returns} + +## Configuration + +{Configuration options from code with defaults} + +## Integration + +**Works with**: +- **Oracle**: {How - with code reference} +- **Guardian**: {How - with code reference} +- **Summoner**: {How - with code reference} + +## Troubleshooting + +{Common issues from Oracle gotchas + solutions} + +## Advanced Usage + +{Complex examples for power users} + +## Changelog + +{Recent changes from git history} + +## References + +- Code: [{File path}]({github_link}) +- Design: Issue #{issue_number} +- Commit: [{commit_hash}]({github_link}) +``` + +## Accuracy Validation + +**Before Documenting, Verify:** + +1. **Code Claims**: Does the code actually do this? + - Read the actual implementation + - Test examples if possible + - Check for edge cases + +2. **Path Claims**: Does this file/directory exist? + - Verify all file paths + - Check all command examples + - Test all import statements + +3. **Behavior Claims**: Does it work this way? + - Trace through the code + - Look for configuration overrides + - Check for version-specific behavior + +4. **Integration Claims**: Does it integrate with X? + - Find actual integration points in code + - Verify imports and function calls + - Check for documented integration patterns + +**Red Flags (Requires Verification):** +- "Should work" β†’ Verify it does work +- "Probably handles" β†’ Find actual handling code +- "Similar to" β†’ Check if actually similar +- "Usually" β†’ Find the actual behavior +- "Can be used for" β†’ Test or find example + +## No Hallucination Policy + +**If Information is Missing:** +1. **Don't Guess** - Mark as "To be documented" +2. **Don't Assume** - Search code/history for facts +3. **Don't Extrapolate** - Document only what exists +4. **Ask User** - If critical info is unavailable + +**Example:** +```markdown +# ❌ WRONG (Hallucination) +"The Oracle skill can search your entire filesystem for patterns." + +# βœ… RIGHT (Fact-Checked) +"The Oracle skill searches knowledge stored in `.oracle/knowledge/` +directory (see oracle/scripts/search_oracle.py:45-67)." +``` + +## Wizard Commands + +```bash +# Detect outdated documentation +/wizard audit + +# Update specific documentation +/wizard update README.md +/wizard update skills/oracle/README.md + +# Generate documentation for new skill +/wizard generate skill guardian + +# Sync all documentation +/wizard sync-all + +# Search conversation history +/wizard search-history "authentication design" + +# Validate documentation accuracy +/wizard validate + +# Cross-reference check +/wizard cross-ref +``` + +## Examples + +### Example 1: Update README for New Skill + +``` +User: "Update README.md to include the Evaluator skill" + +Wizard: +1. Reads skills/evaluator/SKILL.md for description +2. Reads skills/evaluator/scripts/track_event.py for features +3. Searches Oracle for "evaluator" patterns +4. Searches conversation history for Evaluator design discussions +5. Spawns code analysis agent to extract API +6. Generates README section: + +## Evaluator + +**Privacy-first telemetry and feedback collection** + +The Evaluator skill provides anonymous, opt-in telemetry for ClaudeShack +skills. Based on 2025 best practices from OpenTelemetry and GitHub Copilot. + +### Features + +- βœ… Anonymous event tracking (daily-rotating hashes) +- βœ… Local-first storage (events never auto-sent) +- βœ… Opt-in only (disabled by default) +- βœ… GitHub-native feedback (issue templates) + +### Quick Start + +```bash +# Enable telemetry (opt-in) +python skills/evaluator/scripts/track_event.py --enable + +# View local statistics +python skills/evaluator/scripts/track_event.py --summary +``` + +**Reference**: See [skills/evaluator/SKILL.md](skills/evaluator/SKILL.md) + +7. Shows diff for approval +8. Records documentation pattern in Oracle +``` + +### Example 2: Validate Documentation + +``` +User: "/wizard validate" + +Wizard: +1. Scans all documentation files +2. For each claim, spawns validation agent: + - Code claim β†’ Verify in code + - Path claim β†’ Check file exists + - Integration claim β†’ Find integration code +3. Reports findings: + +VALIDATION REPORT +================= + +README.md: + βœ… Line 45: Oracle knowledge storage path - VERIFIED + ❌ Line 67: "Guardian can review pull requests" - NOT FOUND in code + βœ… Line 89: Installation steps - TESTED + ⚠️ Line 102: Link to docs - 404 error + +CONTRIBUTING.md: + βœ… All file paths valid + βœ… All commands tested + ❌ Line 156: Mentions "Marketplace skill" - doesn't exist + +FIXES RECOMMENDED: +1. Remove Guardian PR review claim (not implemented) +2. Fix broken docs link +3. Remove Marketplace skill reference + +Apply fixes? [y/n] +``` + +## Summoner vs Guardian vs Wizard + +**Summoner** (Task Orchestration): +- Coordinates multiple agents for complex workflows +- Breaks down large tasks into parallel subtasks +- Synthesizes findings from multiple sources +- Proactive multi-agent orchestration + +**Guardian** (Quality Gates): +- Monitors session health and code quality +- Reviews code for issues (security, performance) +- Validates suggestions against Oracle +- Reactive intervention based on triggers + +**Wizard** (Documentation Maintenance): +- Maintains accurate, up-to-date documentation +- Researches facts from code, Oracle, and history +- Generates comprehensive, referenced documentation +- Uses Summoner for research, Guardian for validation + +**Example Workflow:** +``` +User: "Document the entire Guardian skill comprehensively" + +Wizard: "This is complex - spawning Summoner" + ↓ +Summoner coordinates 3 research agents: + - Agent 1: Analyze Guardian code (all scripts) + - Agent 2: Search Oracle for Guardian patterns + - Agent 3: Search conversation history for Guardian design + ↓ +Summoner synthesizes findings into structured data + ↓ +Wizard generates comprehensive documentation + ↓ +Guardian reviews documentation for accuracy + ↓ +Wizard applies Guardian's suggestions + ↓ +Final documentation presented to user +``` + +## Privacy & Storage + +**Wizard Storage** (`.wizard/`): +``` +.wizard/ +β”œβ”€β”€ audit_cache.json # Last audit results +β”œβ”€β”€ doc_patterns.json # Learned documentation patterns +β”œβ”€β”€ validation_cache.json # Validation results cache +└── history_index.json # Conversation history index +``` + +**What Wizard Stores:** +- Documentation audit results +- Validation cache (to avoid re-checking) +- Learned patterns for effective documentation +- Index of conversation history topics (not full content) + +**What Wizard Doesn't Store:** +- Full conversation content (reads from ~/.claude/projects/) +- User code or file contents +- Personal information + +## Anti-Patterns + +**Wizard Will NOT:** +- ❌ Make assumptions about code behavior +- ❌ Document planned/unimplemented features as existing +- ❌ Copy documentation from other projects without verification +- ❌ Generate generic "filler" content +- ❌ Skip validation for "obvious" claims +- ❌ Modify code (documentation only) + +## Integration Summary + +**Wizard Uses:** +- **Oracle**: Knowledge retrieval, pattern learning +- **Guardian**: Documentation quality review +- **Summoner**: Multi-agent research coordination +- **Conversation History**: Design decisions and context + +**Wizard Provides:** +- Accurate, comprehensive documentation +- Cross-referenced, verifiable claims +- Consistent documentation across all files +- Up-to-date examples and usage guides + +--- + +**The Wizard's Oath:** +*"I shall document only what exists, reference all claims, verify through code, and hallucinate nothing. Facts above all."* diff --git a/skills/wizard/scripts/audit_docs.py b/skills/wizard/scripts/audit_docs.py new file mode 100644 index 0000000..7c95087 --- /dev/null +++ b/skills/wizard/scripts/audit_docs.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python3 +""" +Documentation Wizard - Documentation Audit Tool + +Detects outdated, missing, or inconsistent documentation across ClaudeShack. + +Usage: + # Full audit + python audit_docs.py + + # Audit specific file + python audit_docs.py --file README.md + + # Check for broken links + python audit_docs.py --check-links + + # Detect missing documentation + python audit_docs.py --missing + +Environment Variables: + WIZARD_VERBOSE: Set to '1' for detailed output +""" + +import os +import sys +import json +import argparse +from pathlib import Path +from typing import Dict, List, Optional, Any, Tuple +import re + + +def find_skills() -> List[Path]: + """Find all skill directories.""" + skills_dir = Path('skills') + + if not skills_dir.exists(): + return [] + + return [d for d in skills_dir.iterdir() if d.is_dir() and not d.name.startswith('.')] + + +def check_skill_documentation(skill_path: Path) -> Dict[str, Any]: + """Check if a skill has required documentation. + + Args: + skill_path: Path to skill directory + + Returns: + Dictionary with audit results + """ + issues = [] + warnings = [] + + # Check for SKILL.md + skill_md = skill_path / 'SKILL.md' + if not skill_md.exists(): + issues.append(f"Missing SKILL.md") + else: + # Check frontmatter + try: + with open(skill_md, 'r', encoding='utf-8') as f: + content = f.read() + if not content.startswith('---'): + issues.append("SKILL.md missing YAML frontmatter") + elif 'name:' not in content[:500] or 'description:' not in content[:500]: + issues.append("SKILL.md frontmatter missing name or description") + except (OSError, IOError, UnicodeDecodeError): + issues.append("SKILL.md cannot be read") + + # Check for Scripts directory + scripts_dir = skill_path / 'Scripts' + if scripts_dir.exists(): + # Check for Scripts README + scripts_readme = scripts_dir / 'README.md' + if not scripts_readme.exists(): + warnings.append("Scripts directory missing README.md") + + # Count Python scripts + python_scripts = list(scripts_dir.glob('*.py')) + if len(python_scripts) == 0: + warnings.append("Scripts directory has no Python files") + else: + warnings.append("No Scripts directory (might be documentation-only skill)") + + return { + 'skill': skill_path.name, + 'path': str(skill_path), + 'issues': issues, + 'warnings': warnings, + 'has_skill_md': skill_md.exists(), + 'has_scripts': scripts_dir.exists(), + 'script_count': len(list(scripts_dir.glob('*.py'))) if scripts_dir.exists() else 0 + } + + +def check_readme_mentions_skills(readme_path: Path, skills: List[Path]) -> List[str]: + """Check if README mentions all skills. + + Args: + readme_path: Path to README.md + skills: List of skill paths + + Returns: + List of skills not mentioned in README + """ + if not readme_path.exists(): + return [] + + try: + with open(readme_path, 'r', encoding='utf-8') as f: + readme_content = f.read().lower() + except (OSError, IOError, UnicodeDecodeError): + return [] + + missing = [] + for skill in skills: + skill_name = skill.name + if skill_name.lower() not in readme_content: + missing.append(skill_name) + + return missing + + +def check_broken_links(file_path: Path) -> List[Dict[str, Any]]: + """Check for broken relative links in markdown file. + + Args: + file_path: Path to markdown file + + Returns: + List of broken links with details + """ + broken = [] + + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + except (OSError, IOError, UnicodeDecodeError): + return [] + + # Find markdown links: [text](path) + link_pattern = r'\[([^\]]+)\]\(([^\)]+)\)' + matches = re.finditer(link_pattern, content) + + for match in matches: + link_text = match.group(1) + link_path = match.group(2) + + # Skip external links + if link_path.startswith(('http://', 'https://', 'mailto:', '#')): + continue + + # Resolve relative path + resolved = file_path.parent / link_path + + if not resolved.exists(): + broken.append({ + 'text': link_text, + 'path': link_path, + 'resolved': str(resolved), + 'line': content[:match.start()].count('\n') + 1 + }) + + return broken + + +def audit_documentation() -> Dict[str, Any]: + """Perform full documentation audit. + + Returns: + Audit results dictionary + """ + results = { + 'skills': [], + 'main_readme': {}, + 'contributing': {}, + 'broken_links': [], + 'summary': { + 'total_skills': 0, + 'skills_with_issues': 0, + 'total_issues': 0, + 'total_warnings': 0, + 'missing_from_readme': [] + } + } + + # Audit skills + skills = find_skills() + results['summary']['total_skills'] = len(skills) + + for skill in skills: + audit = check_skill_documentation(skill) + results['skills'].append(audit) + + if audit['issues']: + results['summary']['skills_with_issues'] += 1 + results['summary']['total_issues'] += len(audit['issues']) + + results['summary']['total_warnings'] += len(audit['warnings']) + + # Check README + readme_path = Path('README.md') + if readme_path.exists(): + missing = check_readme_mentions_skills(readme_path, skills) + results['summary']['missing_from_readme'] = missing + + # Check for broken links in README + broken = check_broken_links(readme_path) + if broken: + results['broken_links'].append({ + 'file': 'README.md', + 'links': broken + }) + else: + results['main_readme']['missing'] = True + + # Check CONTRIBUTING.md + contributing_path = Path('CONTRIBUTING.md') + if contributing_path.exists(): + broken = check_broken_links(contributing_path) + if broken: + results['broken_links'].append({ + 'file': 'CONTRIBUTING.md', + 'links': broken + }) + else: + results['contributing']['missing'] = True + + return results + + +def format_audit_report(results: Dict[str, Any]) -> str: + """Format audit results as readable report. + + Args: + results: Audit results + + Returns: + Formatted report string + """ + lines = [] + + lines.append("=" * 70) + lines.append("DOCUMENTATION WIZARD - AUDIT REPORT") + lines.append("=" * 70) + lines.append("") + + # Summary + summary = results['summary'] + lines.append("SUMMARY") + lines.append("-" * 70) + lines.append(f"Total Skills: {summary['total_skills']}") + lines.append(f"Skills with Issues: {summary['skills_with_issues']}") + lines.append(f"Total Issues: {summary['total_issues']}") + lines.append(f"Total Warnings: {summary['total_warnings']}") + lines.append("") + + # Skills not in README + if summary['missing_from_readme']: + lines.append("⚠️ Skills Not Mentioned in README:") + for skill in summary['missing_from_readme']: + lines.append(f" - {skill}") + lines.append("") + + # Skill-specific issues + if summary['skills_with_issues'] > 0: + lines.append("SKILL ISSUES") + lines.append("-" * 70) + + for skill_audit in results['skills']: + if skill_audit['issues'] or skill_audit['warnings']: + lines.append(f"\n{skill_audit['skill']}:") + + for issue in skill_audit['issues']: + lines.append(f" ❌ {issue}") + + for warning in skill_audit['warnings']: + lines.append(f" ⚠️ {warning}") + + lines.append("") + + # Broken links + if results['broken_links']: + lines.append("BROKEN LINKS") + lines.append("-" * 70) + + for file_links in results['broken_links']: + lines.append(f"\n{file_links['file']}:") + for link in file_links['links']: + lines.append(f" Line {link['line']}: [{link['text']}]({link['path']})") + lines.append(f" Resolved to: {link['resolved']} (NOT FOUND)") + + lines.append("") + + # Overall status + lines.append("=" * 70) + if summary['total_issues'] == 0 and not results['broken_links']: + lines.append("βœ… DOCUMENTATION AUDIT PASSED - No critical issues found") + else: + lines.append(f"❌ DOCUMENTATION AUDIT FAILED - {summary['total_issues']} issues found") + + if summary['total_warnings'] > 0: + lines.append(f"⚠️ {summary['total_warnings']} warnings (non-critical)") + + lines.append("=" * 70) + + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser( + description='Documentation Wizard - Audit documentation for issues', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--file', + help='Audit specific file only' + ) + + parser.add_argument( + '--check-links', + action='store_true', + help='Check for broken links only' + ) + + parser.add_argument( + '--missing', + action='store_true', + help='Check for missing documentation only' + ) + + parser.add_argument( + '--json', + action='store_true', + help='Output as JSON' + ) + + args = parser.parse_args() + + # Run audit + results = audit_documentation() + + # Output + if args.json: + print(json.dumps(results, indent=2)) + else: + report = format_audit_report(results) + print(report) + + # Exit code + if results['summary']['total_issues'] > 0 or results['broken_links']: + sys.exit(1) + else: + sys.exit(0) + + +if __name__ == '__main__': + main()