@caddis/cli 0.2.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundles/antigravity-plugin/agents/claude-md-curator.md +1 -1
- package/bundles/antigravity-plugin/agents/knowledge-transfer.md +17 -32
- package/bundles/antigravity-plugin/caddis_statusline.py +825 -0
- package/bundles/antigravity-plugin/claude-md/folder-agents.md.tmpl +21 -0
- package/bundles/antigravity-plugin/guard_agy.py +60 -9
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_gate.py +524 -0
- package/bundles/antigravity-plugin/scripts/caddis_inventory.py +439 -0
- package/bundles/antigravity-plugin/scripts/caddis_migrate_dir.py +270 -0
- package/bundles/antigravity-plugin/scripts/caddis_spawn.py +300 -0
- package/bundles/antigravity-plugin/scripts/caddis_statusline.py +825 -0
- package/bundles/antigravity-plugin/scripts/caddis_tidy.py +366 -0
- package/bundles/antigravity-plugin/scripts/check_doc_coverage.py +924 -0
- package/bundles/antigravity-plugin/scripts/claude-deepseek.ps1 +4 -0
- package/bundles/antigravity-plugin/scripts/claude-deepseek.sh +3 -0
- package/bundles/antigravity-plugin/scripts/claude-glm.ps1 +4 -0
- package/bundles/antigravity-plugin/scripts/claude-glm.sh +3 -0
- package/bundles/antigravity-plugin/scripts/claude-oss.ps1 +64 -0
- package/bundles/antigravity-plugin/scripts/claude-oss.sh +60 -0
- package/bundles/antigravity-plugin/scripts/claudster_config.py +121 -0
- package/bundles/antigravity-plugin/scripts/claudster_doctor.py +356 -0
- package/bundles/antigravity-plugin/scripts/claudster_init.py +400 -0
- package/bundles/antigravity-plugin/scripts/claudster_migrate_rules.py +300 -0
- package/bundles/antigravity-plugin/scripts/enforcement_inventory.py +261 -0
- package/bundles/antigravity-plugin/scripts/hook_log.py +156 -0
- package/bundles/antigravity-plugin/scripts/new-machine.ps1 +170 -0
- package/bundles/antigravity-plugin/scripts/oss_ask.py +98 -0
- package/bundles/antigravity-plugin/scripts/oss_model.py +174 -0
- package/bundles/antigravity-plugin/scripts/oss_review.py +718 -0
- package/bundles/antigravity-plugin/scripts/setup_project_ai.py +1432 -0
- package/bundles/antigravity-plugin/scripts/usage_review.py +1323 -0
- package/bundles/antigravity-plugin/session_end_agy.py +52 -8
- package/bundles/antigravity-plugin/skills/_registry.md +1 -0
- package/bundles/antigravity-plugin/skills/advisory-hub/SKILL.md +44 -0
- package/bundles/antigravity-plugin/skills/code-review/SKILL.md +11 -0
- package/bundles/antigravity-plugin/skills/fastapi-dev/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +21 -0
- package/bundles/antigravity-plugin/skills/gate-review/SKILL.md +11 -0
- package/bundles/antigravity-plugin/skills/implement/SKILL.md +36 -0
- package/bundles/antigravity-plugin/skills/security-review/SKILL.md +9 -0
- package/bundles/antigravity-plugin/skills/ship/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/statusline/SKILL.md +105 -0
- package/bundles/antigravity-plugin/skills/use-model/SKILL.md +15 -3
- package/bundles/antigravity-plugin/skills/validate-phase/SKILL.md +31 -0
- package/bundles/antigravity-plugin/warm_start_agy.py +117 -11
- package/bundles/antigravity-plugin-extras/plugin.json +1 -1
- package/bundles/antigravity-plugin-extras/skills/architecture-design/SKILL.md +1 -1
- package/bundles/antigravity-plugin-extras/skills/caching-patterns/SKILL.md +1 -1
- package/bundles/antigravity-plugin-extras/skills/webapp-development/SKILL.md +1 -1
- package/bundles/codex/.codex/config.toml.example +36 -0
- package/bundles/codex/.codex/skills/coding/api-design/SKILL.md +249 -0
- package/bundles/codex/.codex/skills/coding/backend-development/SKILL.md +305 -0
- package/bundles/codex/.codex/skills/coding/code-review/SKILL.md +140 -0
- package/bundles/codex/.codex/skills/coding/codebase-audit/SKILL.md +204 -0
- package/bundles/codex/.codex/skills/coding/dashboard-perf-sweep/SKILL.md +132 -0
- package/bundles/codex/.codex/skills/coding/dashboard-perf-sweep/scripts/perf_sweep.py +249 -0
- package/bundles/codex/.codex/skills/coding/error-handling/SKILL.md +260 -0
- package/bundles/codex/.codex/skills/coding/fastapi-dev/SKILL.md +300 -0
- package/bundles/codex/.codex/skills/coding/javascript-typescript/SKILL.md +142 -0
- package/bundles/codex/.codex/skills/coding/python/SKILL.md +388 -0
- package/bundles/codex/.codex/skills/coding/refactoring/SKILL.md +486 -0
- package/bundles/codex/.codex/skills/coding/security-review/SKILL.md +205 -0
- package/bundles/codex/.codex/skills/coding/sql/SKILL.md +321 -0
- package/bundles/codex/.codex/skills/data/database-design/SKILL.md +177 -0
- package/bundles/codex/.codex/skills/data/db-diagram/SKILL.md +148 -0
- package/bundles/codex/.codex/skills/data/db-diagram/scripts/sql_to_graph.py +1212 -0
- package/bundles/codex/.codex/skills/devops/ci-cd-pipeline/SKILL.md +315 -0
- package/bundles/codex/.codex/skills/devops/gh-cli/SKILL.md +195 -0
- package/bundles/codex/.codex/skills/devops/git-commit/SKILL.md +285 -0
- package/bundles/codex/.codex/skills/devops/using-git-worktrees/SKILL.md +217 -0
- package/bundles/codex/.codex/skills/devops/windows-deployment/SKILL.md +880 -0
- package/bundles/codex/.codex/skills/docs/code-documentation/SKILL.md +271 -0
- package/bundles/codex/.codex/skills/docs/explainer-doc/SKILL.md +97 -0
- package/bundles/codex/.codex/skills/docs/technical-writing/SKILL.md +286 -0
- package/bundles/codex/.codex/skills/docs/writing-plans/SKILL.md +387 -0
- package/bundles/codex/.codex/skills/frontend/css-architecture/SKILL.md +305 -0
- package/bundles/codex/.codex/skills/frontend/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
- package/bundles/codex/.codex/skills/frontend/frontend-design/SKILL.md +193 -0
- package/bundles/codex/.codex/skills/frontend/frontend-design/references/my-tech-stack.md +127 -0
- package/bundles/codex/.codex/skills/frontend/mockup/SKILL.md +242 -0
- package/bundles/codex/.codex/skills/frontend/react-best-practices/SKILL.md +269 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/README.md +404 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/SKILL.md +459 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/examples/generic-components.md +579 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/examples/server-components.md +579 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/event-handlers.md +574 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/hooks.md +456 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/react-19-patterns.md +638 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/react-router.md +1002 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/tanstack-router.md +587 -0
- package/bundles/codex/.codex/skills/frontend/ui-review/SKILL.md +176 -0
- package/bundles/codex/.codex/skills/frontend/ui-review/references/framework-fixes.md +471 -0
- package/bundles/codex/.codex/skills/frontend/ui-review/references/visual-checklist.md +236 -0
- package/bundles/codex/.codex/skills/frontend/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
- package/bundles/codex/.codex/skills/frontend/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
- package/bundles/codex/.codex/skills/frontend/warm-editorial-ui/SKILL.md +732 -0
- package/bundles/codex/.codex/skills/media/draw-io/SKILL.md +162 -0
- package/bundles/codex/.codex/skills/media/draw-io/references/aws-icons.md +677 -0
- package/bundles/codex/.codex/skills/media/draw-io/references/layout-guidelines.md +142 -0
- package/bundles/codex/.codex/skills/media/draw-io/references/troubleshooting.md +118 -0
- package/bundles/codex/.codex/skills/media/draw-io/references/workflows.md +103 -0
- package/bundles/codex/.codex/skills/media/draw-io/scripts/convert-drawio-to-png.sh +25 -0
- package/bundles/codex/.codex/skills/media/draw-io/scripts/find_aws_icon.py +79 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/SKILL.md +292 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/advanced-features.md +556 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/architecture-diagrams.md +192 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/c4-diagrams.md +410 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/class-diagrams.md +361 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/erd-diagrams.md +510 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/flowcharts.md +450 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/sequence-diagrams.md +394 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/troubleshooting.md +335 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/workflows.md +418 -0
- package/bundles/codex/.codex/skills/media/particle-art/SKILL.md +243 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/canvas-utils.ts +171 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/dot-field.template.tsx +203 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/flow-field.template.tsx +263 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/node-shape.template.tsx +261 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/shape-sampler.ts +281 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/stipple-morph.template.tsx +167 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/stipple.template.tsx +175 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/trail-ghost.template.tsx +266 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/usage-examples.md +320 -0
- package/bundles/codex/.codex/skills/testing/playwright/API_REFERENCE.md +653 -0
- package/bundles/codex/.codex/skills/testing/playwright/SKILL.md +454 -0
- package/bundles/codex/.codex/skills/testing/playwright/lib/helpers.js +441 -0
- package/bundles/codex/.codex/skills/testing/playwright/package.json +26 -0
- package/bundles/codex/.codex/skills/testing/playwright/run.js +228 -0
- package/bundles/codex/.codex/skills/testing/tdd-workflow/SKILL.md +188 -0
- package/bundles/codex/.codex/skills/testing/test-strategy/SKILL.md +155 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/LICENSE.txt +202 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/SKILL.md +97 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/examples/console_logging.py +35 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/examples/element_discovery.py +40 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/examples/static_html_automation.py +33 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/scripts/with_server.py +106 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/SKILL.md +227 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/context-template.md +214 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/hub-handbook.md +233 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/report-contract.md +94 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/succession.md +118 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/SKILL.md +500 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/agents/best-practices-referencer.md +263 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/agents/codebase-context-builder.md +326 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/agents/task-intent-analyzer.md +245 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/anti-patterns.md +571 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/before-after-examples.md +1114 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/best-practices-guide.md +513 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/common-workflows.md +692 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/prompt-patterns.md +547 -0
- package/bundles/codex/.codex/skills/workflow/brainstorming/SKILL.md +57 -0
- package/bundles/codex/.codex/skills/workflow/context-curator/SKILL.md +157 -0
- package/bundles/codex/.codex/skills/workflow/golden-plan/SKILL.md +586 -0
- package/bundles/codex/.codex/skills/workflow/preflight/SKILL.md +440 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/LICENSE.txt +202 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/SKILL.md +491 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/agents/analyzer.md +274 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/agents/comparator.md +202 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/agents/grader.md +223 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/assets/eval_review.html +146 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/eval-viewer/generate_review.py +471 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/eval-viewer/viewer.html +1325 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/references/schemas.md +430 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/__init__.py +0 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/generate_report.py +326 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/improve_description.py +247 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/package_skill.py +136 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/quick_validate.py +103 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/run_eval.py +310 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/run_loop.py +328 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/utils.py +47 -0
- package/bundles/codex/AGENTS.md +146 -0
- package/bundles/manifest.json +4 -3
- package/dist/claude-deepseek.js +544 -0
- package/dist/claude-glm.js +544 -0
- package/dist/claude-oss.js +544 -0
- package/dist/cli.js +542 -90
- package/package.json +5 -2
- package/bundles/antigravity-plugin/statusline-command-agy.sh +0 -91
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +0 -94
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cold/warm HTTP timing harness for the dashboard-perf-sweep skill.
|
|
3
|
+
|
|
4
|
+
Given a base URL and a list of endpoints, measures a COLD request (first hit) and a WARM
|
|
5
|
+
request (immediately after) per endpoint, safely: a timeout or connection error on one endpoint
|
|
6
|
+
is recorded as a finding, never a crash of the whole sweep (DASHBOARD-PERFORMANCE-STANDARD.md
|
|
7
|
+
Rule 6 gate-1 mechanics; the huge cold/warm gap without a cache in between is itself diagnostic
|
|
8
|
+
of a query-shape problem, not a cache-TTL one).
|
|
9
|
+
|
|
10
|
+
This harness does NOT start dev servers or know anything about a specific app -- that's the
|
|
11
|
+
`run` skill's job. It only measures a URL that is already reachable. stdlib-only (urllib), no
|
|
12
|
+
install, matching the other caddis tool scripts (oss_review.py etc.).
|
|
13
|
+
|
|
14
|
+
CLI:
|
|
15
|
+
python perf_sweep.py --base-url http://localhost:8000 --endpoints endpoints.json
|
|
16
|
+
python perf_sweep.py --base-url http://localhost:8000 --endpoints endpoints.json --output report.json
|
|
17
|
+
python perf_sweep.py --base-url http://localhost:8000 --endpoints endpoints.json --gate1-seconds 1.0
|
|
18
|
+
|
|
19
|
+
endpoints.json shape (a JSON list):
|
|
20
|
+
[
|
|
21
|
+
{"name": "Historical Trends chart", "method": "GET", "path": "/api/trends/daily"},
|
|
22
|
+
{"name": "IW grid", "method": "POST", "path": "/raworkbench/investigation/query",
|
|
23
|
+
"json": {"filters": {}}, "headers": {"Authorization": "Bearer ..."}}
|
|
24
|
+
]
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
32
|
+
import urllib.error
|
|
33
|
+
import urllib.request
|
|
34
|
+
from dataclasses import asdict, dataclass, field
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
DEFAULT_TIMEOUT_SECONDS = 30.0
|
|
38
|
+
DEFAULT_GATE1_SECONDS = 1.0 # DASHBOARD-PERFORMANCE-STANDARD.md Rule 6
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class RequestResult:
|
|
43
|
+
ok: bool
|
|
44
|
+
status: int | None = None
|
|
45
|
+
elapsed_ms: float | None = None
|
|
46
|
+
error: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class EndpointResult:
|
|
51
|
+
name: str
|
|
52
|
+
method: str
|
|
53
|
+
path: str
|
|
54
|
+
cold: RequestResult
|
|
55
|
+
warm: RequestResult | None = None
|
|
56
|
+
gate1_breach: bool = False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class SweepReport:
|
|
61
|
+
base_url: str
|
|
62
|
+
gate1_seconds: float
|
|
63
|
+
endpoints: list[EndpointResult] = field(default_factory=list)
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> dict[str, Any]:
|
|
66
|
+
return {
|
|
67
|
+
"base_url": self.base_url,
|
|
68
|
+
"gate1_seconds": self.gate1_seconds,
|
|
69
|
+
"endpoints": [
|
|
70
|
+
{
|
|
71
|
+
"name": e.name,
|
|
72
|
+
"method": e.method,
|
|
73
|
+
"path": e.path,
|
|
74
|
+
"cold": asdict(e.cold),
|
|
75
|
+
"warm": asdict(e.warm) if e.warm else None,
|
|
76
|
+
"gate1_breach": e.gate1_breach,
|
|
77
|
+
}
|
|
78
|
+
for e in self.endpoints
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _do_request(
|
|
84
|
+
url: str,
|
|
85
|
+
method: str,
|
|
86
|
+
*,
|
|
87
|
+
json_body: dict[str, Any] | None = None,
|
|
88
|
+
headers: dict[str, str] | None = None,
|
|
89
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
90
|
+
) -> RequestResult:
|
|
91
|
+
"""One timed HTTP request. Never raises -- a timeout/connection/HTTP error becomes a
|
|
92
|
+
RequestResult with ok=False and a human-readable error, which is itself a sweep finding."""
|
|
93
|
+
data = None
|
|
94
|
+
req_headers = dict(headers or {})
|
|
95
|
+
if json_body is not None:
|
|
96
|
+
data = json.dumps(json_body).encode("utf-8")
|
|
97
|
+
req_headers.setdefault("Content-Type", "application/json")
|
|
98
|
+
|
|
99
|
+
req = urllib.request.Request(url, data=data, headers=req_headers, method=method)
|
|
100
|
+
start = time.perf_counter()
|
|
101
|
+
try:
|
|
102
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
103
|
+
resp.read()
|
|
104
|
+
elapsed_ms = (time.perf_counter() - start) * 1000.0
|
|
105
|
+
return RequestResult(ok=True, status=resp.status, elapsed_ms=elapsed_ms)
|
|
106
|
+
except urllib.error.HTTPError as exc:
|
|
107
|
+
# An HTTP error status still completed a round trip -- timing is a real finding.
|
|
108
|
+
elapsed_ms = (time.perf_counter() - start) * 1000.0
|
|
109
|
+
return RequestResult(ok=False, status=exc.code, elapsed_ms=elapsed_ms, error=f"HTTP {exc.code}")
|
|
110
|
+
except urllib.error.URLError as exc:
|
|
111
|
+
elapsed_ms = (time.perf_counter() - start) * 1000.0
|
|
112
|
+
return RequestResult(ok=False, elapsed_ms=elapsed_ms, error=f"connection error: {exc.reason}")
|
|
113
|
+
except TimeoutError:
|
|
114
|
+
elapsed_ms = (time.perf_counter() - start) * 1000.0
|
|
115
|
+
return RequestResult(ok=False, elapsed_ms=elapsed_ms, error=f"timeout after {timeout}s")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def sweep_endpoint(
|
|
119
|
+
base_url: str,
|
|
120
|
+
endpoint: dict[str, Any],
|
|
121
|
+
*,
|
|
122
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
123
|
+
gate1_seconds: float = DEFAULT_GATE1_SECONDS,
|
|
124
|
+
warm: bool = True,
|
|
125
|
+
extra_headers: dict[str, str] | None = None,
|
|
126
|
+
) -> EndpointResult:
|
|
127
|
+
method = endpoint.get("method", "GET").upper()
|
|
128
|
+
path = endpoint["path"]
|
|
129
|
+
url = base_url.rstrip("/") + path
|
|
130
|
+
headers = dict(extra_headers or {})
|
|
131
|
+
headers.update(endpoint.get("headers", {}))
|
|
132
|
+
json_body = endpoint.get("json")
|
|
133
|
+
|
|
134
|
+
cold = _do_request(url, method, json_body=json_body, headers=headers, timeout=timeout)
|
|
135
|
+
warm_result = None
|
|
136
|
+
if warm:
|
|
137
|
+
warm_result = _do_request(url, method, json_body=json_body, headers=headers, timeout=timeout)
|
|
138
|
+
|
|
139
|
+
gate1_breach = bool(cold.ok and cold.elapsed_ms is not None and cold.elapsed_ms / 1000.0 > gate1_seconds)
|
|
140
|
+
return EndpointResult(
|
|
141
|
+
name=endpoint.get("name", path),
|
|
142
|
+
method=method,
|
|
143
|
+
path=path,
|
|
144
|
+
cold=cold,
|
|
145
|
+
warm=warm_result,
|
|
146
|
+
gate1_breach=gate1_breach,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def sweep(
|
|
151
|
+
base_url: str,
|
|
152
|
+
endpoints: list[dict[str, Any]],
|
|
153
|
+
*,
|
|
154
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
155
|
+
gate1_seconds: float = DEFAULT_GATE1_SECONDS,
|
|
156
|
+
warm: bool = True,
|
|
157
|
+
extra_headers: dict[str, str] | None = None,
|
|
158
|
+
) -> SweepReport:
|
|
159
|
+
report = SweepReport(base_url=base_url, gate1_seconds=gate1_seconds)
|
|
160
|
+
for endpoint in endpoints:
|
|
161
|
+
report.endpoints.append(
|
|
162
|
+
sweep_endpoint(
|
|
163
|
+
base_url,
|
|
164
|
+
endpoint,
|
|
165
|
+
timeout=timeout,
|
|
166
|
+
gate1_seconds=gate1_seconds,
|
|
167
|
+
warm=warm,
|
|
168
|
+
extra_headers=extra_headers,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
return report
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def format_report(report: SweepReport) -> str:
|
|
175
|
+
lines = [f"Sweep: {report.base_url} (gate 1 = {report.gate1_seconds}s cold, uncached)", ""]
|
|
176
|
+
for e in report.endpoints:
|
|
177
|
+
if not e.cold.ok:
|
|
178
|
+
lines.append(f" [ERROR] {e.method:5s} {e.path:45s} {e.cold.error}")
|
|
179
|
+
continue
|
|
180
|
+
flag = "SLOW " if e.gate1_breach else " "
|
|
181
|
+
cold_s = f"{e.cold.elapsed_ms:8.1f}ms"
|
|
182
|
+
warm_s = f"{e.warm.elapsed_ms:8.1f}ms" if e.warm and e.warm.ok else "n/a"
|
|
183
|
+
gap = ""
|
|
184
|
+
if e.warm and e.warm.ok and e.cold.elapsed_ms and e.warm.elapsed_ms:
|
|
185
|
+
ratio = e.cold.elapsed_ms / max(e.warm.elapsed_ms, 0.01)
|
|
186
|
+
if ratio > 3:
|
|
187
|
+
gap = f" (cold/warm {ratio:.1f}x -- likely query-shape, not cache-TTL)"
|
|
188
|
+
lines.append(f" {flag}{e.method:5s} {e.path:45s} cold={cold_s} warm={warm_s}{gap}")
|
|
189
|
+
breaches = [e for e in report.endpoints if e.gate1_breach]
|
|
190
|
+
errors = [e for e in report.endpoints if not e.cold.ok]
|
|
191
|
+
problems = breaches + errors
|
|
192
|
+
lines.append("")
|
|
193
|
+
lines.append(
|
|
194
|
+
f"{len(problems)}/{len(report.endpoints)} endpoint(s) have a problem "
|
|
195
|
+
f"({len(breaches)} slow, {len(errors)} errored/timed out -- a timeout is a WORSE "
|
|
196
|
+
f"signal than a gate-1 breach, not a lesser one)."
|
|
197
|
+
)
|
|
198
|
+
if problems:
|
|
199
|
+
lines.append("Not itself a rs-kit verdict -- apply the shape checklist (SKILL.md) next.")
|
|
200
|
+
return "\n".join(lines)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _parse_header(value: str) -> tuple[str, str]:
|
|
204
|
+
if ":" not in value:
|
|
205
|
+
raise argparse.ArgumentTypeError(f"--header must be 'Key: Value', got {value!r}")
|
|
206
|
+
key, _, val = value.partition(":")
|
|
207
|
+
return key.strip(), val.strip()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def main(argv: list[str] | None = None) -> int:
|
|
211
|
+
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
212
|
+
parser.add_argument("--base-url", required=True, help="e.g. http://localhost:8703")
|
|
213
|
+
parser.add_argument("--endpoints", required=True, help="path to a JSON file (see module docstring)")
|
|
214
|
+
parser.add_argument("--timeout", type=float, default=DEFAULT_TIMEOUT_SECONDS)
|
|
215
|
+
parser.add_argument("--gate1-seconds", type=float, default=DEFAULT_GATE1_SECONDS)
|
|
216
|
+
parser.add_argument("--no-warm", action="store_true", help="skip the warm (second) request")
|
|
217
|
+
parser.add_argument("--header", action="append", default=[], type=_parse_header,
|
|
218
|
+
help="extra header applied to every request, repeatable: --header 'Cookie: session=...'")
|
|
219
|
+
parser.add_argument("--output", help="also write the JSON report to this path")
|
|
220
|
+
args = parser.parse_args(argv)
|
|
221
|
+
|
|
222
|
+
with open(args.endpoints, encoding="utf-8") as f:
|
|
223
|
+
endpoints = json.load(f)
|
|
224
|
+
if not isinstance(endpoints, list):
|
|
225
|
+
print(f"[FAIL] {args.endpoints} must contain a JSON list of endpoint objects", file=sys.stderr)
|
|
226
|
+
return 2
|
|
227
|
+
|
|
228
|
+
report = sweep(
|
|
229
|
+
args.base_url,
|
|
230
|
+
endpoints,
|
|
231
|
+
timeout=args.timeout,
|
|
232
|
+
gate1_seconds=args.gate1_seconds,
|
|
233
|
+
warm=not args.no_warm,
|
|
234
|
+
extra_headers=dict(args.header),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
print(format_report(report))
|
|
238
|
+
if args.output:
|
|
239
|
+
with open(args.output, "w", encoding="utf-8") as f:
|
|
240
|
+
json.dump(report.to_dict(), f, indent=2)
|
|
241
|
+
print(f"\nWrote {args.output}")
|
|
242
|
+
|
|
243
|
+
breaches = [e for e in report.endpoints if e.gate1_breach]
|
|
244
|
+
errors = [e for e in report.endpoints if not e.cold.ok]
|
|
245
|
+
return 1 if (breaches or errors) else 0
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
if __name__ == "__main__":
|
|
249
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: error-handling
|
|
3
|
+
description: Error handling patterns for Python and TypeScript applications. Use when designing error hierarchies, implementing retry logic, building error boundaries, or establishing logging strategies. Covers custom exceptions, result types, circuit breakers, and user-facing error messages.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Error Handling Skill
|
|
7
|
+
|
|
8
|
+
Errors are not exceptional — they are part of the contract. This skill defines how to classify, propagate, and recover from errors consistently.
|
|
9
|
+
|
|
10
|
+
## 1. When to Apply This Skill
|
|
11
|
+
|
|
12
|
+
**Trigger conditions:**
|
|
13
|
+
- Designing error handling for a new service or feature
|
|
14
|
+
- "How should we handle errors in this module?"
|
|
15
|
+
- Building retry logic or circuit breakers
|
|
16
|
+
- Reviewing code with bare `except:` or swallowed errors
|
|
17
|
+
- Establishing error handling conventions for a project
|
|
18
|
+
|
|
19
|
+
## 2. Error Classification
|
|
20
|
+
|
|
21
|
+
Every error falls into one of three categories. Handle each differently.
|
|
22
|
+
|
|
23
|
+
| Category | Retryable? | User Action? | Example |
|
|
24
|
+
|----------|-----------|-------------|---------|
|
|
25
|
+
| **Transient** | Yes | None (auto-retry) | Network timeout, DB connection dropped, rate limit |
|
|
26
|
+
| **Operational** | No | Inform user | Invalid input, resource not found, insufficient permissions |
|
|
27
|
+
| **Fatal** | No | Alert team | Corrupted state, config missing, unrecoverable assertion |
|
|
28
|
+
|
|
29
|
+
## 3. Python Exception Hierarchy
|
|
30
|
+
|
|
31
|
+
Build domain-specific exceptions. Never raise raw `Exception`.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
class AppError(Exception):
|
|
35
|
+
"""Base error for the application."""
|
|
36
|
+
def __init__(self, message: str, code: str | None = None):
|
|
37
|
+
self.message = message
|
|
38
|
+
self.code = code or self.__class__.__name__
|
|
39
|
+
super().__init__(self.message)
|
|
40
|
+
|
|
41
|
+
# Operational errors (expected, handled gracefully)
|
|
42
|
+
class NotFoundError(AppError):
|
|
43
|
+
"""Resource does not exist."""
|
|
44
|
+
|
|
45
|
+
class ValidationError(AppError):
|
|
46
|
+
"""Input validation failed."""
|
|
47
|
+
|
|
48
|
+
class AuthorizationError(AppError):
|
|
49
|
+
"""User not authorized for this action."""
|
|
50
|
+
|
|
51
|
+
# Transient errors (retry-eligible)
|
|
52
|
+
class TransientError(AppError):
|
|
53
|
+
"""Temporary failure — safe to retry."""
|
|
54
|
+
|
|
55
|
+
class ExternalServiceError(TransientError):
|
|
56
|
+
"""External API call failed."""
|
|
57
|
+
|
|
58
|
+
class DatabaseConnectionError(TransientError):
|
|
59
|
+
"""Database connection lost."""
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Usage Patterns
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
# ✅ GOOD: Specific exception with context
|
|
66
|
+
def get_user(user_id: str) -> User:
|
|
67
|
+
user = repo.find_by_id(user_id)
|
|
68
|
+
if user is None:
|
|
69
|
+
raise NotFoundError(f"User {user_id} not found")
|
|
70
|
+
return user
|
|
71
|
+
|
|
72
|
+
# ✅ GOOD: Catch specific, re-raise as domain error
|
|
73
|
+
def fetch_external_data(url: str) -> dict:
|
|
74
|
+
try:
|
|
75
|
+
response = httpx.get(url, timeout=10)
|
|
76
|
+
response.raise_for_status()
|
|
77
|
+
return response.json()
|
|
78
|
+
except httpx.TimeoutException as e:
|
|
79
|
+
raise ExternalServiceError(f"Timeout calling {url}") from e
|
|
80
|
+
except httpx.HTTPStatusError as e:
|
|
81
|
+
raise ExternalServiceError(f"HTTP {e.response.status_code} from {url}") from e
|
|
82
|
+
|
|
83
|
+
# ❌ BAD: Bare except swallows everything
|
|
84
|
+
try:
|
|
85
|
+
result = process()
|
|
86
|
+
except:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
# ❌ BAD: Catching too broadly
|
|
90
|
+
try:
|
|
91
|
+
result = process()
|
|
92
|
+
except Exception:
|
|
93
|
+
logger.error("Something went wrong")
|
|
94
|
+
return None # Hides the real error
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 4. TypeScript Error Patterns
|
|
98
|
+
|
|
99
|
+
### Result Type (for expected failures)
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
type Result<T, E = Error> =
|
|
103
|
+
| { ok: true; value: T }
|
|
104
|
+
| { ok: false; error: E };
|
|
105
|
+
|
|
106
|
+
function parseConfig(raw: string): Result<Config, ValidationError> {
|
|
107
|
+
try {
|
|
108
|
+
const parsed = JSON.parse(raw);
|
|
109
|
+
if (!isValidConfig(parsed)) {
|
|
110
|
+
return { ok: false, error: new ValidationError("Invalid config shape") };
|
|
111
|
+
}
|
|
112
|
+
return { ok: true, value: parsed as Config };
|
|
113
|
+
} catch {
|
|
114
|
+
return { ok: false, error: new ValidationError("Invalid JSON") };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Usage — caller must handle both cases
|
|
119
|
+
const result = parseConfig(rawInput);
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
showError(result.error.message);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
useConfig(result.value);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Error Boundaries (React)
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
'use client';
|
|
131
|
+
import { Component, type ReactNode } from 'react';
|
|
132
|
+
|
|
133
|
+
interface Props { children: ReactNode; fallback: ReactNode; }
|
|
134
|
+
interface State { hasError: boolean; }
|
|
135
|
+
|
|
136
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
137
|
+
state: State = { hasError: false };
|
|
138
|
+
|
|
139
|
+
static getDerivedStateFromError(): State {
|
|
140
|
+
return { hasError: true };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
|
144
|
+
console.error('Component error:', error, info.componentStack);
|
|
145
|
+
// Report to error tracking service
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
render() {
|
|
149
|
+
return this.state.hasError ? this.props.fallback : this.props.children;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 5. Retry with Exponential Backoff
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import asyncio
|
|
158
|
+
import random
|
|
159
|
+
from loguru import logger
|
|
160
|
+
|
|
161
|
+
async def retry_with_backoff(
|
|
162
|
+
fn,
|
|
163
|
+
max_retries: int = 3,
|
|
164
|
+
base_delay: float = 1.0,
|
|
165
|
+
max_delay: float = 30.0,
|
|
166
|
+
retryable_exceptions: tuple = (TransientError,),
|
|
167
|
+
):
|
|
168
|
+
"""Retry a function with exponential backoff and jitter."""
|
|
169
|
+
for attempt in range(max_retries + 1):
|
|
170
|
+
try:
|
|
171
|
+
return await fn()
|
|
172
|
+
except retryable_exceptions as e:
|
|
173
|
+
if attempt == max_retries:
|
|
174
|
+
logger.error(f"All {max_retries} retries exhausted: {e}")
|
|
175
|
+
raise
|
|
176
|
+
delay = min(base_delay * (2 ** attempt), max_delay)
|
|
177
|
+
jitter = random.uniform(0, delay * 0.1)
|
|
178
|
+
logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay:.1f}s")
|
|
179
|
+
await asyncio.sleep(delay + jitter)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## 6. FastAPI Error Handling
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from fastapi import FastAPI, Request
|
|
186
|
+
from fastapi.responses import JSONResponse
|
|
187
|
+
from loguru import logger
|
|
188
|
+
|
|
189
|
+
app = FastAPI()
|
|
190
|
+
|
|
191
|
+
@app.exception_handler(NotFoundError)
|
|
192
|
+
async def not_found_handler(request: Request, exc: NotFoundError):
|
|
193
|
+
return JSONResponse(status_code=404, content={"error": exc.code, "message": exc.message})
|
|
194
|
+
|
|
195
|
+
@app.exception_handler(ValidationError)
|
|
196
|
+
async def validation_handler(request: Request, exc: ValidationError):
|
|
197
|
+
return JSONResponse(status_code=422, content={"error": exc.code, "message": exc.message})
|
|
198
|
+
|
|
199
|
+
@app.exception_handler(AuthorizationError)
|
|
200
|
+
async def auth_handler(request: Request, exc: AuthorizationError):
|
|
201
|
+
return JSONResponse(status_code=403, content={"error": exc.code, "message": exc.message})
|
|
202
|
+
|
|
203
|
+
@app.exception_handler(Exception)
|
|
204
|
+
async def unhandled_handler(request: Request, exc: Exception):
|
|
205
|
+
logger.exception(f"Unhandled error on {request.method} {request.url}")
|
|
206
|
+
# Never expose internal details to the client
|
|
207
|
+
return JSONResponse(status_code=500, content={"error": "InternalError", "message": "An internal error occurred."})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## 7. Logging Errors Correctly
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from loguru import logger
|
|
214
|
+
|
|
215
|
+
# ✅ GOOD: Log with context, not just the message
|
|
216
|
+
logger.error(f"Failed to process order {order_id}: {e}", order_id=order_id, customer_id=customer_id)
|
|
217
|
+
|
|
218
|
+
# ✅ GOOD: Use exception() to capture stack trace
|
|
219
|
+
try:
|
|
220
|
+
process_order(order)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.exception(f"Order processing failed for {order.id}")
|
|
223
|
+
raise
|
|
224
|
+
|
|
225
|
+
# ❌ BAD: Logging sensitive data
|
|
226
|
+
logger.error(f"Auth failed for {username} with password {password}")
|
|
227
|
+
|
|
228
|
+
# ❌ BAD: Logging without context
|
|
229
|
+
logger.error("Something failed")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 8. Error Message Guidelines
|
|
233
|
+
|
|
234
|
+
| Audience | Include | Exclude |
|
|
235
|
+
|----------|---------|---------|
|
|
236
|
+
| **End user** | What happened, what to do next | Stack traces, internal IDs, SQL errors |
|
|
237
|
+
| **Developer (logs)** | Full context, request ID, stack trace | User passwords, tokens, PII |
|
|
238
|
+
| **API consumer** | Error code, human message, field-level detail | Server internals, file paths |
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# User-facing message
|
|
242
|
+
"Unable to load your dashboard. Please try again in a few minutes."
|
|
243
|
+
|
|
244
|
+
# API response
|
|
245
|
+
{"error": "ValidationError", "message": "Email format is invalid", "field": "email"}
|
|
246
|
+
|
|
247
|
+
# Log entry
|
|
248
|
+
"Validation failed on POST /api/users: email='not-an-email' — rejected by regex validator"
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## 9. Anti-Patterns
|
|
252
|
+
|
|
253
|
+
| Anti-Pattern | Problem | Fix |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| Bare `except:` / `except Exception:` | Catches KeyboardInterrupt, SystemExit | Catch specific exceptions |
|
|
256
|
+
| `return None` on error | Caller has no idea what went wrong | Raise typed exception |
|
|
257
|
+
| Logging and re-raising | Double logging, noisy | Log at the boundary, not at every layer |
|
|
258
|
+
| String error codes | Typos, no autocomplete | Enum or class-based error codes |
|
|
259
|
+
| Swallowing errors in background tasks | Silent failures, data loss | Log + alert + dead-letter queue |
|
|
260
|
+
| Generic "Something went wrong" | User can't self-serve | Specific message + action hint |
|