novahiz 0.1.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/LICENSE +202 -0
- package/NOTICE.md +46 -0
- package/README.md +123 -0
- package/adapters/README.md +13 -0
- package/adapters/opencode/agent/novahiz-agent.md +31 -0
- package/adapters/opencode/commands/novahiz-clean.md +15 -0
- package/adapters/opencode/commands/novahiz-doctor.md +9 -0
- package/adapters/opencode/commands/novahiz-plan.md +24 -0
- package/adapters/opencode/commands/novahiz-status.md +11 -0
- package/adapters/opencode/instructions.md +69 -0
- package/adapters/opencode/novahiz.ts +266 -0
- package/bin/novahiz.cmd +2 -0
- package/bin/novahiz.mjs +7 -0
- package/bundled-skills/AGENTS.md +50 -0
- package/bundled-skills/LICENSE +21 -0
- package/bundled-skills/README.md +202 -0
- package/bundled-skills/SKILL.md +313 -0
- package/bundled-skills/a11y-audit/SKILL.md +211 -0
- package/bundled-skills/a11y-audit/assets/sample-component.tsx +51 -0
- package/bundled-skills/a11y-audit/expected_outputs/sample-contrast-output.txt +39 -0
- package/bundled-skills/a11y-audit/expected_outputs/sample-scan-output.json +34 -0
- package/bundled-skills/a11y-audit/expected_outputs/sample-scan-report.md +63 -0
- package/bundled-skills/a11y-audit/references/aria-patterns.md +223 -0
- package/bundled-skills/a11y-audit/references/audit-report-template.md +46 -0
- package/bundled-skills/a11y-audit/references/ci-cd-integration.md +127 -0
- package/bundled-skills/a11y-audit/references/color-contrast-guide.md +83 -0
- package/bundled-skills/a11y-audit/references/examples-by-framework.md +312 -0
- package/bundled-skills/a11y-audit/references/framework-a11y-patterns.md +661 -0
- package/bundled-skills/a11y-audit/references/testing-checklist.md +41 -0
- package/bundled-skills/a11y-audit/references/wcag-22-new-criteria.md +79 -0
- package/bundled-skills/a11y-audit/references/wcag-quick-ref.md +113 -0
- package/bundled-skills/a11y-audit/scripts/a11y_scanner.py +684 -0
- package/bundled-skills/a11y-audit/scripts/contrast_checker.py +504 -0
- package/bundled-skills/ab-testing/SKILL.md +353 -0
- package/bundled-skills/ab-testing/evals/evals.json +105 -0
- package/bundled-skills/ab-testing/references/sample-size-guide.md +263 -0
- package/bundled-skills/ab-testing/references/test-templates.md +277 -0
- package/bundled-skills/ad-creative/SKILL.md +425 -0
- package/bundled-skills/ad-creative/assets/creative-review-template.html +400 -0
- package/bundled-skills/ad-creative/evals/evals.json +217 -0
- package/bundled-skills/ad-creative/references/creative-review-page.md +106 -0
- package/bundled-skills/ad-creative/references/creative-roadmap.md +118 -0
- package/bundled-skills/ad-creative/references/generative-tools.md +637 -0
- package/bundled-skills/ad-creative/references/hook-system.md +115 -0
- package/bundled-skills/ad-creative/references/imessage-video-ads.md +201 -0
- package/bundled-skills/ad-creative/references/meta-creative-formats.md +114 -0
- package/bundled-skills/ad-creative/references/motion-video-ads.md +126 -0
- package/bundled-skills/ad-creative/references/platform-specs.md +213 -0
- package/bundled-skills/ad-creative/references/short-form-video-specs.md +213 -0
- package/bundled-skills/ad-creative/references/static-ad-templates.md +308 -0
- package/bundled-skills/ads/SKILL.md +499 -0
- package/bundled-skills/ads/evals/evals.json +157 -0
- package/bundled-skills/ads/references/abm-playbook.md +93 -0
- package/bundled-skills/ads/references/ad-copy-templates.md +207 -0
- package/bundled-skills/ads/references/audience-targeting.md +243 -0
- package/bundled-skills/ads/references/audit-guardrails.md +83 -0
- package/bundled-skills/ads/references/b2b-paid-playbook.md +115 -0
- package/bundled-skills/ads/references/conversion-tracking.md +361 -0
- package/bundled-skills/ads/references/creative-research-automation.md +103 -0
- package/bundled-skills/ads/references/google-ads-audit-checklist.md +84 -0
- package/bundled-skills/ads/references/google-search-playbook.md +120 -0
- package/bundled-skills/ads/references/linkedin-b2b-playbook.md +107 -0
- package/bundled-skills/ads/references/meta-decision-system.md +176 -0
- package/bundled-skills/ads/references/payback-period.md +69 -0
- package/bundled-skills/ads/references/platform-setup-checklists.md +277 -0
- package/bundled-skills/ads/references/rsa-output-spec.md +88 -0
- package/bundled-skills/adversarial-reviewer/SKILL.md +247 -0
- package/bundled-skills/agent-device/SKILL.md +20 -0
- package/bundled-skills/ai-seo/SKILL.md +488 -0
- package/bundled-skills/ai-seo/evals/evals.json +135 -0
- package/bundled-skills/ai-seo/references/agent-readiness.md +57 -0
- package/bundled-skills/ai-seo/references/citations-vs-recommendations.md +85 -0
- package/bundled-skills/ai-seo/references/content-patterns.md +287 -0
- package/bundled-skills/ai-seo/references/content-types.md +71 -0
- package/bundled-skills/ai-seo/references/format-volatility.md +98 -0
- package/bundled-skills/ai-seo/references/okf.md +104 -0
- package/bundled-skills/ai-seo/references/platform-ranking-factors.md +154 -0
- package/bundled-skills/ai-seo/references/youtube-ai-citations.md +60 -0
- package/bundled-skills/analytics/SKILL.md +310 -0
- package/bundled-skills/analytics/evals/evals.json +90 -0
- package/bundled-skills/analytics/references/event-library.md +260 -0
- package/bundled-skills/analytics/references/ga4-implementation.md +300 -0
- package/bundled-skills/analytics/references/gtm-implementation.md +390 -0
- package/bundled-skills/android-emulator/SKILL.md +32 -0
- package/bundled-skills/anti-AI-design/SKILL.md +189 -0
- package/bundled-skills/anti-pattern-detector/SKILL.md +185 -0
- package/bundled-skills/anti-pattern-detector/references/async-network-patterns.md +581 -0
- package/bundled-skills/anti-pattern-detector/references/build-deploy-patterns.md +554 -0
- package/bundled-skills/anti-pattern-detector/references/cache-patterns.md +446 -0
- package/bundled-skills/anti-pattern-detector/references/cloud-infra-patterns.md +538 -0
- package/bundled-skills/anti-pattern-detector/references/concurrency-patterns.md +558 -0
- package/bundled-skills/anti-pattern-detector/references/language-specific-patterns.md +740 -0
- package/bundled-skills/anti-pattern-detector/references/monitoring-patterns.md +456 -0
- package/bundled-skills/api-design-reviewer/SKILL.md +433 -0
- package/bundled-skills/api-design-reviewer/references/api_antipatterns.md +680 -0
- package/bundled-skills/api-design-reviewer/references/rest_design_rules.md +487 -0
- package/bundled-skills/api-design-reviewer/scripts/api_linter.py +956 -0
- package/bundled-skills/api-design-reviewer/scripts/api_scorecard.py +1661 -0
- package/bundled-skills/api-design-reviewer/scripts/breaking_change_detector.py +1102 -0
- package/bundled-skills/apple-hig-expert/SKILL.md +108 -0
- package/bundled-skills/apple-hig-expert/references/accessibility.md +55 -0
- package/bundled-skills/apple-hig-expert/references/platform-specifics.md +48 -0
- package/bundled-skills/apple-hig-expert/references/visual-design.md +69 -0
- package/bundled-skills/apple-hig-expert/scripts/hig_checker.py +97 -0
- package/bundled-skills/apple-hig-expert/templates/hig-audit-template.md +83 -0
- package/bundled-skills/build-graph/SKILL.md +38 -0
- package/bundled-skills/claude-history-ingest/SKILL.md +463 -0
- package/bundled-skills/claude-history-ingest/references/claude-data-format.md +118 -0
- package/bundled-skills/clean-code/SKILL.md +99 -0
- package/bundled-skills/code-reviewer/README.md +92 -0
- package/bundled-skills/code-reviewer/SKILL.md +183 -0
- package/bundled-skills/code-reviewer/assets/sample_c_clean.c +71 -0
- package/bundled-skills/code-reviewer/assets/sample_c_smells.c +66 -0
- package/bundled-skills/code-reviewer/assets/sample_csharp_clean.cs +100 -0
- package/bundled-skills/code-reviewer/assets/sample_csharp_smells.cs +78 -0
- package/bundled-skills/code-reviewer/assets/sample_java_clean.java +55 -0
- package/bundled-skills/code-reviewer/assets/sample_java_smells.java +56 -0
- package/bundled-skills/code-reviewer/expected_outputs/sample_c_clean_quality.json +65 -0
- package/bundled-skills/code-reviewer/expected_outputs/sample_c_smells_quality.json +155 -0
- package/bundled-skills/code-reviewer/expected_outputs/sample_csharp_clean_quality.json +135 -0
- package/bundled-skills/code-reviewer/expected_outputs/sample_csharp_smells_quality.json +143 -0
- package/bundled-skills/code-reviewer/expected_outputs/sample_java_clean_quality.json +53 -0
- package/bundled-skills/code-reviewer/expected_outputs/sample_java_smells_quality.json +83 -0
- package/bundled-skills/code-reviewer/languages/c.md +103 -0
- package/bundled-skills/code-reviewer/languages/cpp.md +103 -0
- package/bundled-skills/code-reviewer/languages/csharp.md +97 -0
- package/bundled-skills/code-reviewer/languages/dart.md +108 -0
- package/bundled-skills/code-reviewer/languages/go.md +92 -0
- package/bundled-skills/code-reviewer/languages/java.md +103 -0
- package/bundled-skills/code-reviewer/languages/kotlin.md +88 -0
- package/bundled-skills/code-reviewer/languages/php.md +109 -0
- package/bundled-skills/code-reviewer/languages/python.md +94 -0
- package/bundled-skills/code-reviewer/languages/ruby.md +105 -0
- package/bundled-skills/code-reviewer/languages/rust.md +99 -0
- package/bundled-skills/code-reviewer/languages/swift.md +88 -0
- package/bundled-skills/code-reviewer/languages/typescript.md +97 -0
- package/bundled-skills/code-reviewer/rules/universal.md +49 -0
- package/bundled-skills/code-reviewer/scripts/code_quality_checker.py +1020 -0
- package/bundled-skills/code-reviewer/scripts/pr_analyzer.py +535 -0
- package/bundled-skills/code-reviewer/scripts/review_report_generator.py +505 -0
- package/bundled-skills/code-understand/SKILL.md +90 -0
- package/bundled-skills/codex-history-ingest/SKILL.md +248 -0
- package/bundled-skills/codex-history-ingest/references/codex-data-format.md +82 -0
- package/bundled-skills/cold-email/SKILL.md +159 -0
- package/bundled-skills/cold-email/evals/evals.json +94 -0
- package/bundled-skills/cold-email/references/benchmarks.md +83 -0
- package/bundled-skills/cold-email/references/follow-up-sequences.md +81 -0
- package/bundled-skills/cold-email/references/frameworks.md +90 -0
- package/bundled-skills/cold-email/references/personalization.md +79 -0
- package/bundled-skills/cold-email/references/subject-lines.md +53 -0
- package/bundled-skills/competitor-profiling/SKILL.md +415 -0
- package/bundled-skills/competitor-profiling/evals/evals.json +85 -0
- package/bundled-skills/competitor-profiling/references/templates.md +167 -0
- package/bundled-skills/competitor-profiling/references/tool-reference.md +179 -0
- package/bundled-skills/computer-use/SKILL.md +41 -0
- package/bundled-skills/content-strategy/SKILL.md +439 -0
- package/bundled-skills/content-strategy/evals/evals.json +116 -0
- package/bundled-skills/content-strategy/references/content-distribution.md +83 -0
- package/bundled-skills/content-strategy/references/headless-cms.md +194 -0
- package/bundled-skills/copilot-history-ingest/SKILL.md +376 -0
- package/bundled-skills/copilot-history-ingest/references/copilot-data-format.md +321 -0
- package/bundled-skills/copy-editing/SKILL.md +457 -0
- package/bundled-skills/copy-editing/evals/evals.json +89 -0
- package/bundled-skills/copy-editing/references/checklist.md +66 -0
- package/bundled-skills/copy-editing/references/content-refresh.md +38 -0
- package/bundled-skills/copy-editing/references/plain-english-alternatives.md +394 -0
- package/bundled-skills/copywriting/SKILL.md +256 -0
- package/bundled-skills/copywriting/evals/evals.json +126 -0
- package/bundled-skills/copywriting/references/copy-frameworks.md +433 -0
- package/bundled-skills/copywriting/references/natural-transitions.md +272 -0
- package/bundled-skills/cro/SKILL.md +187 -0
- package/bundled-skills/cro/evals/evals.json +111 -0
- package/bundled-skills/cro/references/experiments.md +248 -0
- package/bundled-skills/cro/references/form.md +422 -0
- package/bundled-skills/cross-linker/SKILL.md +309 -0
- package/bundled-skills/customer-research/SKILL.md +305 -0
- package/bundled-skills/customer-research/evals/evals.json +176 -0
- package/bundled-skills/customer-research/references/interviews-and-surveys.md +155 -0
- package/bundled-skills/customer-research/references/source-guides.md +401 -0
- package/bundled-skills/daily-update/SKILL.md +223 -0
- package/bundled-skills/dart-add-unit-test/SKILL.md +122 -0
- package/bundled-skills/dart-build-cli-app/SKILL.md +185 -0
- package/bundled-skills/dart-collect-coverage/SKILL.md +141 -0
- package/bundled-skills/dart-fix-runtime-errors/SKILL.md +166 -0
- package/bundled-skills/dart-generate-test-mocks/SKILL.md +155 -0
- package/bundled-skills/dart-migrate-to-checks-package/SKILL.md +528 -0
- package/bundled-skills/dart-resolve-package-conflicts/SKILL.md +116 -0
- package/bundled-skills/dart-run-static-analysis/SKILL.md +104 -0
- package/bundled-skills/dart-setup-ffi-assets/SKILL.md +419 -0
- package/bundled-skills/dart-use-doc-examples/SKILL.md +96 -0
- package/bundled-skills/dart-use-ffigen/SKILL.md +218 -0
- package/bundled-skills/dart-use-pattern-matching/SKILL.md +146 -0
- package/bundled-skills/dart-use-primary-constructors/SKILL.md +262 -0
- package/bundled-skills/dart-write-documentation/SKILL.md +136 -0
- package/bundled-skills/database-designer/README.md +388 -0
- package/bundled-skills/database-designer/SKILL.md +314 -0
- package/bundled-skills/database-designer/assets/sample_query_patterns.json +375 -0
- package/bundled-skills/database-designer/assets/sample_schema.json +372 -0
- package/bundled-skills/database-designer/assets/sample_schema.sql +207 -0
- package/bundled-skills/database-designer/expected_outputs/index_optimization_sample.txt +60 -0
- package/bundled-skills/database-designer/expected_outputs/migration_sample.txt +124 -0
- package/bundled-skills/database-designer/expected_outputs/schema_analysis_sample.txt +222 -0
- package/bundled-skills/database-designer/index_optimizer.py +926 -0
- package/bundled-skills/database-designer/migration_generator.py +1199 -0
- package/bundled-skills/database-designer/references/database-design-reference.md +476 -0
- package/bundled-skills/database-designer/references/database_selection_decision_tree.md +373 -0
- package/bundled-skills/database-designer/references/index_strategy_patterns.md +424 -0
- package/bundled-skills/database-designer/references/normalization_guide.md +354 -0
- package/bundled-skills/database-designer/schema_analyzer.py +982 -0
- package/bundled-skills/debug-issue/SKILL.md +28 -0
- package/bundled-skills/defuddle/SKILL.md +41 -0
- package/bundled-skills/dependency-auditor/README.md +524 -0
- package/bundled-skills/dependency-auditor/SKILL.md +85 -0
- package/bundled-skills/dependency-auditor/assets/sample_go.mod +53 -0
- package/bundled-skills/dependency-auditor/assets/sample_package.json +72 -0
- package/bundled-skills/dependency-auditor/assets/sample_requirements.txt +71 -0
- package/bundled-skills/dependency-auditor/expected_outputs/sample_license_report.txt +37 -0
- package/bundled-skills/dependency-auditor/expected_outputs/sample_upgrade_plan.txt +59 -0
- package/bundled-skills/dependency-auditor/expected_outputs/sample_vulnerability_report.json +71 -0
- package/bundled-skills/dependency-auditor/references/dependency_management_best_practices.md +643 -0
- package/bundled-skills/dependency-auditor/references/license_compatibility_matrix.md +238 -0
- package/bundled-skills/dependency-auditor/references/vulnerability_assessment_guide.md +461 -0
- package/bundled-skills/dependency-auditor/scripts/dep_scanner.py +794 -0
- package/bundled-skills/dependency-auditor/scripts/license_checker.py +996 -0
- package/bundled-skills/dependency-auditor/scripts/upgrade_planner.py +1029 -0
- package/bundled-skills/dependency-auditor/test-inventory.json +421 -0
- package/bundled-skills/dependency-auditor/test-project/package.json +72 -0
- package/bundled-skills/design-taste-frontend/SKILL.md +247 -0
- package/bundled-skills/dogfood/SKILL.md +27 -0
- package/bundled-skills/eas-app-stores/SKILL.md +176 -0
- package/bundled-skills/eas-app-stores/agents/openai.yaml +4 -0
- package/bundled-skills/eas-app-stores/references/app-store-metadata.md +497 -0
- package/bundled-skills/eas-app-stores/references/ios-app-store.md +376 -0
- package/bundled-skills/eas-app-stores/references/native-ios.md +167 -0
- package/bundled-skills/eas-app-stores/references/play-store.md +244 -0
- package/bundled-skills/eas-app-stores/references/testflight.md +62 -0
- package/bundled-skills/eas-app-stores/references/workflows.md +120 -0
- package/bundled-skills/eas-hosting/SKILL.md +431 -0
- package/bundled-skills/eas-hosting/agents/openai.yaml +4 -0
- package/bundled-skills/eas-observe/SKILL.md +54 -0
- package/bundled-skills/eas-observe/agents/openai.yaml +4 -0
- package/bundled-skills/eas-observe/references/metrics.md +98 -0
- package/bundled-skills/eas-observe/references/queries.md +403 -0
- package/bundled-skills/eas-observe/references/setup.md +476 -0
- package/bundled-skills/eas-observe/references/third-party.md +136 -0
- package/bundled-skills/eas-simulator/SKILL.md +208 -0
- package/bundled-skills/eas-simulator/agents/openai.yaml +4 -0
- package/bundled-skills/eas-simulator/references/controllers.md +106 -0
- package/bundled-skills/eas-simulator/references/run-your-app.md +224 -0
- package/bundled-skills/eas-simulator/references/troubleshooting.md +44 -0
- package/bundled-skills/eas-update/SKILL.md +146 -0
- package/bundled-skills/eas-update/agents/openai.yaml +4 -0
- package/bundled-skills/eas-update-insights/SKILL.md +238 -0
- package/bundled-skills/eas-update-insights/agents/openai.yaml +4 -0
- package/bundled-skills/eas-update-insights/references/channel-insights-schema.md +47 -0
- package/bundled-skills/eas-update-insights/references/update-insights-schema.md +69 -0
- package/bundled-skills/eas-workflows/SKILL.md +99 -0
- package/bundled-skills/eas-workflows/agents/openai.yaml +4 -0
- package/bundled-skills/eas-workflows/scripts/fetch.js +109 -0
- package/bundled-skills/eas-workflows/scripts/package.json +6 -0
- package/bundled-skills/emails/SKILL.md +311 -0
- package/bundled-skills/emails/evals/evals.json +93 -0
- package/bundled-skills/emails/references/copy-guidelines.md +113 -0
- package/bundled-skills/emails/references/email-types.md +515 -0
- package/bundled-skills/emails/references/sequence-templates.md +168 -0
- package/bundled-skills/engineering-standards/SKILL.md +736 -0
- package/bundled-skills/env-secrets-manager/SKILL.md +260 -0
- package/bundled-skills/env-secrets-manager/references/secret-patterns.md +41 -0
- package/bundled-skills/env-secrets-manager/references/validation-detection-rotation.md +358 -0
- package/bundled-skills/env-secrets-manager/scripts/env_auditor.py +145 -0
- package/bundled-skills/explore-codebase/SKILL.md +29 -0
- package/bundled-skills/expo-animation/LICENSE +21 -0
- package/bundled-skills/expo-animation/RECIPES.md +385 -0
- package/bundled-skills/expo-animation/SKILL.md +267 -0
- package/bundled-skills/expo-animation/agents/openai.yaml +4 -0
- package/bundled-skills/expo-app-clip/SKILL.md +290 -0
- package/bundled-skills/expo-app-clip/agents/openai.yaml +4 -0
- package/bundled-skills/expo-app-clip/references/native-module.md +96 -0
- package/bundled-skills/expo-brownfield/SKILL.md +62 -0
- package/bundled-skills/expo-brownfield/agents/openai.yaml +4 -0
- package/bundled-skills/expo-brownfield/references/brownfield-integrated.md +526 -0
- package/bundled-skills/expo-brownfield/references/brownfield-isolated.md +451 -0
- package/bundled-skills/expo-brownfield/references/comparison.md +63 -0
- package/bundled-skills/expo-brownfield/references/troubleshooting.md +88 -0
- package/bundled-skills/expo-data-fetching/SKILL.md +478 -0
- package/bundled-skills/expo-data-fetching/agents/openai.yaml +4 -0
- package/bundled-skills/expo-data-fetching/references/expo-router-loaders.md +341 -0
- package/bundled-skills/expo-data-fetching/references/offline-and-cancellation.md +68 -0
- package/bundled-skills/expo-design-system/SKILL.md +376 -0
- package/bundled-skills/expo-design-system/agents/openai.yaml +4 -0
- package/bundled-skills/expo-design-system/references/audit.md +190 -0
- package/bundled-skills/expo-design-system/references/native-slop.md +74 -0
- package/bundled-skills/expo-dev-client/SKILL.md +182 -0
- package/bundled-skills/expo-dev-client/agents/openai.yaml +4 -0
- package/bundled-skills/expo-dom/SKILL.md +425 -0
- package/bundled-skills/expo-dom/agents/openai.yaml +4 -0
- package/bundled-skills/expo-examples/SKILL.md +106 -0
- package/bundled-skills/expo-examples/agents/openai.yaml +4 -0
- package/bundled-skills/expo-examples/references/catalog.md +105 -0
- package/bundled-skills/expo-migrate-module/SKILL.md +113 -0
- package/bundled-skills/expo-migrate-module/agents/openai.yaml +4 -0
- package/bundled-skills/expo-migrate-module/references/compatibility.md +73 -0
- package/bundled-skills/expo-migrate-module/references/example.md +212 -0
- package/bundled-skills/expo-migrate-module/references/migration-map.md +306 -0
- package/bundled-skills/expo-module/SKILL.md +151 -0
- package/bundled-skills/expo-module/agents/openai.yaml +4 -0
- package/bundled-skills/expo-module/references/config-plugin.md +90 -0
- package/bundled-skills/expo-module/references/create-expo-module.md +206 -0
- package/bundled-skills/expo-module/references/lifecycle.md +127 -0
- package/bundled-skills/expo-module/references/module-config.md +48 -0
- package/bundled-skills/expo-module/references/native-module.md +286 -0
- package/bundled-skills/expo-module/references/native-view.md +171 -0
- package/bundled-skills/expo-native-ui/SKILL.md +201 -0
- package/bundled-skills/expo-native-ui/agents/openai.yaml +4 -0
- package/bundled-skills/expo-native-ui/references/controls.md +231 -0
- package/bundled-skills/expo-native-ui/references/gradients.md +106 -0
- package/bundled-skills/expo-native-ui/references/icons.md +232 -0
- package/bundled-skills/expo-native-ui/references/media.md +193 -0
- package/bundled-skills/expo-native-ui/references/storage.md +121 -0
- package/bundled-skills/expo-native-ui/references/visual-effects.md +198 -0
- package/bundled-skills/expo-native-ui/references/webgpu-three.md +605 -0
- package/bundled-skills/expo-overview/SKILL.md +115 -0
- package/bundled-skills/expo-overview/agents/openai.yaml +4 -0
- package/bundled-skills/expo-project-structure/SKILL.md +114 -0
- package/bundled-skills/expo-project-structure/agents/openai.yaml +4 -0
- package/bundled-skills/expo-router/SKILL.md +240 -0
- package/bundled-skills/expo-router/agents/openai.yaml +4 -0
- package/bundled-skills/expo-router/references/form-sheet.md +253 -0
- package/bundled-skills/expo-router/references/route-structure.md +229 -0
- package/bundled-skills/expo-router/references/search.md +248 -0
- package/bundled-skills/expo-router/references/tabs.md +433 -0
- package/bundled-skills/expo-router/references/toolbar-and-headers.md +284 -0
- package/bundled-skills/expo-router/references/zoom-transitions.md +158 -0
- package/bundled-skills/expo-skill-eval/SKILL.md +314 -0
- package/bundled-skills/expo-skill-eval/agents/visual-grader.md +52 -0
- package/bundled-skills/expo-skill-eval/references/design-rubric.md +45 -0
- package/bundled-skills/expo-skill-eval/references/runtime-matrix.md +34 -0
- package/bundled-skills/expo-skill-eval/scripts/check-static.sh +64 -0
- package/bundled-skills/expo-skill-eval/scripts/clean-fixture.sh +56 -0
- package/bundled-skills/expo-skill-eval/scripts/generate_viewer.py +450 -0
- package/bundled-skills/expo-skill-eval/scripts/latest-sdk.sh +46 -0
- package/bundled-skills/expo-skill-eval/scripts/make-fixture.sh +95 -0
- package/bundled-skills/expo-skill-eval/scripts/make-workspace.sh +31 -0
- package/bundled-skills/expo-skill-eval/scripts/snapshot-android.sh +284 -0
- package/bundled-skills/expo-skill-eval/scripts/snapshot-ios.sh +132 -0
- package/bundled-skills/expo-skill-eval/scripts/snapshot-web.sh +61 -0
- package/bundled-skills/expo-skill-feedback/SKILL.md +87 -0
- package/bundled-skills/expo-skill-feedback/agents/openai.yaml +4 -0
- package/bundled-skills/expo-skill-feedback/scripts/skill-event.cjs +180 -0
- package/bundled-skills/expo-skill-feedback/scripts/telemetry.cjs +65 -0
- package/bundled-skills/expo-skill-feedback/scripts/telemetry_common.cjs +171 -0
- package/bundled-skills/expo-ui/SKILL.md +101 -0
- package/bundled-skills/expo-ui/agents/openai.yaml +4 -0
- package/bundled-skills/expo-ui/references/drop-in-replacements.md +27 -0
- package/bundled-skills/expo-ui/references/jetpack-compose.md +73 -0
- package/bundled-skills/expo-ui/references/swift-ui.md +73 -0
- package/bundled-skills/expo-ui/references/universal.md +104 -0
- package/bundled-skills/expo-ui/scripts/list-components.js +193 -0
- package/bundled-skills/expo-upgrade/SKILL.md +150 -0
- package/bundled-skills/expo-upgrade/agents/openai.yaml +4 -0
- package/bundled-skills/expo-upgrade/references/expo-av-to-audio.md +132 -0
- package/bundled-skills/expo-upgrade/references/expo-av-to-video.md +160 -0
- package/bundled-skills/expo-upgrade/references/native-tabs.md +124 -0
- package/bundled-skills/expo-upgrade/references/new-architecture.md +79 -0
- package/bundled-skills/expo-upgrade/references/react-19.md +79 -0
- package/bundled-skills/expo-upgrade/references/react-compiler.md +59 -0
- package/bundled-skills/expo-upgrade/references/react-navigation-to-expo-router.md +61 -0
- package/bundled-skills/expo-web-to-native/SKILL.md +91 -0
- package/bundled-skills/expo-web-to-native/agents/openai.yaml +4 -0
- package/bundled-skills/expo-web-to-native/references/false-friends.md +119 -0
- package/bundled-skills/expo-web-to-native/references/native-patterns.md +39 -0
- package/bundled-skills/expo-web-to-native/references/run-as-goal.md +48 -0
- package/bundled-skills/expo-web-to-native/references/verify-on-device.md +45 -0
- package/bundled-skills/find-skills/SKILL.md +141 -0
- package/bundled-skills/fix/SKILL.md +113 -0
- package/bundled-skills/fix/flaky-taxonomy.md +134 -0
- package/bundled-skills/flutter-add-integration-test/SKILL.md +163 -0
- package/bundled-skills/flutter-add-widget-preview/SKILL.md +145 -0
- package/bundled-skills/flutter-add-widget-test/SKILL.md +154 -0
- package/bundled-skills/flutter-apply-architecture-best-practices/SKILL.md +162 -0
- package/bundled-skills/flutter-build-responsive-layout/SKILL.md +139 -0
- package/bundled-skills/flutter-fix-layout-issues/SKILL.md +130 -0
- package/bundled-skills/flutter-implement-json-serialization/SKILL.md +153 -0
- package/bundled-skills/flutter-setup-declarative-routing/SKILL.md +255 -0
- package/bundled-skills/flutter-setup-localization/SKILL.md +210 -0
- package/bundled-skills/flutter-use-http-package/SKILL.md +174 -0
- package/bundled-skills/gate/SKILL.md +13 -0
- package/bundled-skills/graph-colorize/SKILL.md +180 -0
- package/bundled-skills/hermes-history-ingest/SKILL.md +239 -0
- package/bundled-skills/hermes-history-ingest/references/hermes-data-format.md +131 -0
- package/bundled-skills/humanizer/SKILL.md +374 -0
- package/bundled-skills/impeccable/SKILL.md +89 -0
- package/bundled-skills/impeccable/reference/adapt.md +312 -0
- package/bundled-skills/impeccable/reference/adapt.native.md +58 -0
- package/bundled-skills/impeccable/reference/android.md +46 -0
- package/bundled-skills/impeccable/reference/animate.md +89 -0
- package/bundled-skills/impeccable/reference/audit.md +136 -0
- package/bundled-skills/impeccable/reference/audit.native.md +139 -0
- package/bundled-skills/impeccable/reference/bolder.md +33 -0
- package/bundled-skills/impeccable/reference/clarify.md +94 -0
- package/bundled-skills/impeccable/reference/colorize.md +86 -0
- package/bundled-skills/impeccable/reference/craft-floor.md +44 -0
- package/bundled-skills/impeccable/reference/craft.md +5 -0
- package/bundled-skills/impeccable/reference/critique.md +806 -0
- package/bundled-skills/impeccable/reference/degraded/asset-producer.md +37 -0
- package/bundled-skills/impeccable/reference/degraded/documenter.md +24 -0
- package/bundled-skills/impeccable/reference/degraded/finish-reviewer.md +38 -0
- package/bundled-skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/bundled-skills/impeccable/reference/delight.md +70 -0
- package/bundled-skills/impeccable/reference/distill.md +111 -0
- package/bundled-skills/impeccable/reference/doctor.md +54 -0
- package/bundled-skills/impeccable/reference/document.md +416 -0
- package/bundled-skills/impeccable/reference/extract.md +69 -0
- package/bundled-skills/impeccable/reference/harden.md +336 -0
- package/bundled-skills/impeccable/reference/hooks.md +111 -0
- package/bundled-skills/impeccable/reference/init.md +131 -0
- package/bundled-skills/impeccable/reference/ios.md +51 -0
- package/bundled-skills/impeccable/reference/layout.md +84 -0
- package/bundled-skills/impeccable/reference/live-setup.md +104 -0
- package/bundled-skills/impeccable/reference/live.md +325 -0
- package/bundled-skills/impeccable/reference/new-work.md +145 -0
- package/bundled-skills/impeccable/reference/onboard.md +234 -0
- package/bundled-skills/impeccable/reference/operate.md +61 -0
- package/bundled-skills/impeccable/reference/optimize.md +258 -0
- package/bundled-skills/impeccable/reference/overdrive.md +127 -0
- package/bundled-skills/impeccable/reference/polish.md +105 -0
- package/bundled-skills/impeccable/reference/quieter.md +99 -0
- package/bundled-skills/impeccable/reference/routing.md +24 -0
- package/bundled-skills/impeccable/reference/shape.md +59 -0
- package/bundled-skills/impeccable/reference/typeset.md +80 -0
- package/bundled-skills/impeccable/reference/visualize.md +46 -0
- package/bundled-skills/impeccable/scripts/VERSION +1 -0
- package/bundled-skills/impeccable/scripts/bin/windows-x64/impeccable.exe +0 -0
- package/bundled-skills/impeccable/scripts/command-metadata.json +94 -0
- package/bundled-skills/impeccable/scripts/data/font-index-failures.json +121 -0
- package/bundled-skills/impeccable/scripts/data/font-index.json +1 -0
- package/bundled-skills/impeccable/scripts/impeccable +206 -0
- package/bundled-skills/impeccable/scripts/impeccable.cmd +214 -0
- package/bundled-skills/impeccable/scripts/live-browser-dom.js +167 -0
- package/bundled-skills/impeccable/scripts/live-browser-ignores.js +242 -0
- package/bundled-skills/impeccable/scripts/live-browser-session.js +144 -0
- package/bundled-skills/impeccable/scripts/live-browser.js +12990 -0
- package/bundled-skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/bundled-skills/impl-validator/SKILL.md +118 -0
- package/bundled-skills/ios-simulator/SKILL.md +32 -0
- package/bundled-skills/json-canvas/SKILL.md +244 -0
- package/bundled-skills/json-canvas/references/EXAMPLES.md +329 -0
- package/bundled-skills/launch/SKILL.md +381 -0
- package/bundled-skills/launch/evals/evals.json +106 -0
- package/bundled-skills/llm-wiki/SKILL.md +661 -0
- package/bundled-skills/llm-wiki/references/WRITING.md +28 -0
- package/bundled-skills/llm-wiki/references/karpathy-pattern.md +45 -0
- package/bundled-skills/marketing-plan/SKILL.md +300 -0
- package/bundled-skills/marketing-plan/evals/evals.json +112 -0
- package/bundled-skills/marketing-plan/references/aarrr-framework.md +180 -0
- package/bundled-skills/marketing-plan/references/budget-planning.md +168 -0
- package/bundled-skills/marketing-plan/references/client-types.md +373 -0
- package/bundled-skills/marketing-plan/references/current-state-rubric.md +255 -0
- package/bundled-skills/marketing-plan/references/example-quietude.md +972 -0
- package/bundled-skills/marketing-plan/references/funding-stage-unlocks.md +230 -0
- package/bundled-skills/marketing-plan/references/growth-patterns.md +190 -0
- package/bundled-skills/marketing-plan/references/idea-cross-reference.md +265 -0
- package/bundled-skills/marketing-plan/references/measurement-framework.md +213 -0
- package/bundled-skills/marketing-plan/references/methodology.md +363 -0
- package/bundled-skills/marketing-plan/references/ops-stack-mapping.md +197 -0
- package/bundled-skills/marketing-plan/references/plan-template.md +494 -0
- package/bundled-skills/marketing-plan/references/team-and-agency-model.md +278 -0
- package/bundled-skills/marketing-psychology/SKILL.md +455 -0
- package/bundled-skills/marketing-psychology/evals/evals.json +88 -0
- package/bundled-skills/mcp-server-builder/README.md +50 -0
- package/bundled-skills/mcp-server-builder/SKILL.md +100 -0
- package/bundled-skills/mcp-server-builder/references/openapi-extraction-guide.md +34 -0
- package/bundled-skills/mcp-server-builder/references/production-hardening-guide.md +78 -0
- package/bundled-skills/mcp-server-builder/references/python-server-template.md +22 -0
- package/bundled-skills/mcp-server-builder/references/typescript-server-template.md +19 -0
- package/bundled-skills/mcp-server-builder/references/validation-checklist.md +30 -0
- package/bundled-skills/mcp-server-builder/scripts/mcp_validator.py +186 -0
- package/bundled-skills/mcp-server-builder/scripts/openapi_to_mcp.py +284 -0
- package/bundled-skills/memory/SKILL.md +10 -0
- package/bundled-skills/memory-bridge/SKILL.md +163 -0
- package/bundled-skills/memory-save/SKILL.md +88 -0
- package/bundled-skills/migrate/SKILL.md +135 -0
- package/bundled-skills/migrate/cypress-mapping.md +79 -0
- package/bundled-skills/migrate/selenium-mapping.md +94 -0
- package/bundled-skills/novahiz-analyse/SKILL.md +74 -0
- package/bundled-skills/novahiz-audit/SKILL.md +75 -0
- package/bundled-skills/novahiz-clarify/SKILL.md +94 -0
- package/bundled-skills/novahiz-converge/SKILL.md +76 -0
- package/bundled-skills/novahiz-gate/SKILL.md +82 -0
- package/bundled-skills/novahiz-implement/SKILL.md +80 -0
- package/bundled-skills/novahiz-memory/SKILL.md +58 -0
- package/bundled-skills/novahiz-plan/SKILL.md +100 -0
- package/bundled-skills/novahiz-planner/SKILL.md +104 -0
- package/bundled-skills/novahiz-task/SKILL.md +105 -0
- package/bundled-skills/obsidian-bases/SKILL.md +499 -0
- package/bundled-skills/obsidian-bases/references/FUNCTIONS_REFERENCE.md +173 -0
- package/bundled-skills/obsidian-cli/SKILL.md +106 -0
- package/bundled-skills/obsidian-layout-adjustment/SKILL.md +226 -0
- package/bundled-skills/obsidian-layout-adjustment/evals/evals.json +53 -0
- package/bundled-skills/obsidian-layout-adjustment/references/workflow-reference.md +212 -0
- package/bundled-skills/obsidian-markdown/SKILL.md +196 -0
- package/bundled-skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/bundled-skills/obsidian-markdown/references/EMBEDS.md +70 -0
- package/bundled-skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/bundled-skills/offers/SKILL.md +155 -0
- package/bundled-skills/offers/evals/evals.json +21 -0
- package/bundled-skills/offers/references/bonus-stacking.md +150 -0
- package/bundled-skills/offers/references/examples.md +215 -0
- package/bundled-skills/offers/references/guarantee-design.md +172 -0
- package/bundled-skills/offers/references/offer-anatomy.md +203 -0
- package/bundled-skills/offers/references/offer-formats.md +240 -0
- package/bundled-skills/offers/references/saas-offers.md +116 -0
- package/bundled-skills/offers/references/scarcity-urgency.md +175 -0
- package/bundled-skills/offers/references/value-equation.md +134 -0
- package/bundled-skills/onboarding/SKILL.md +261 -0
- package/bundled-skills/onboarding/evals/evals.json +108 -0
- package/bundled-skills/onboarding/references/activation-models.md +57 -0
- package/bundled-skills/onboarding/references/experiments.md +258 -0
- package/bundled-skills/onboarding/references/minimum-path-to-value.md +49 -0
- package/bundled-skills/openclaw-history-ingest/SKILL.md +257 -0
- package/bundled-skills/openclaw-history-ingest/references/openclaw-data-format.md +154 -0
- package/bundled-skills/orchestration/SKILL.md +69 -0
- package/bundled-skills/pi-history-ingest/SKILL.md +312 -0
- package/bundled-skills/playwright-agent/SKILL.md +142 -0
- package/bundled-skills/postgresql-optimization/SKILL.md +179 -0
- package/bundled-skills/pricing/SKILL.md +295 -0
- package/bundled-skills/pricing/evals/evals.json +119 -0
- package/bundled-skills/pricing/references/pricing-models.md +66 -0
- package/bundled-skills/pricing/references/pricing-page-teardown.md +85 -0
- package/bundled-skills/pricing/references/research-methods.md +152 -0
- package/bundled-skills/pricing/references/tier-structure.md +232 -0
- package/bundled-skills/product-marketing/SKILL.md +255 -0
- package/bundled-skills/product-marketing/evals/evals.json +98 -0
- package/bundled-skills/prospecting/SKILL.md +262 -0
- package/bundled-skills/prospecting/evals/evals.json +122 -0
- package/bundled-skills/prospecting/references/b2b-prospecting.md +106 -0
- package/bundled-skills/prospecting/references/compliance.md +123 -0
- package/bundled-skills/prospecting/references/data-sources.md +287 -0
- package/bundled-skills/prospecting/references/demand-signals.md +129 -0
- package/bundled-skills/prospecting/references/local-prospecting.md +165 -0
- package/bundled-skills/prospecting/references/saas-prospecting.md +123 -0
- package/bundled-skills/refactor-safely/SKILL.md +29 -0
- package/bundled-skills/review-changes/SKILL.md +30 -0
- package/bundled-skills/review-delta/SKILL.md +46 -0
- package/bundled-skills/review-pr/SKILL.md +66 -0
- package/bundled-skills/sales-enablement/SKILL.md +359 -0
- package/bundled-skills/sales-enablement/evals/evals.json +91 -0
- package/bundled-skills/sales-enablement/references/deck-frameworks.md +263 -0
- package/bundled-skills/sales-enablement/references/demo-scripts.md +355 -0
- package/bundled-skills/sales-enablement/references/objection-library.md +270 -0
- package/bundled-skills/sales-enablement/references/one-pager-templates.md +208 -0
- package/bundled-skills/security-guidance/SKILL.md +161 -0
- package/bundled-skills/security-guidance/references/pretooluse_hook_canon.md +141 -0
- package/bundled-skills/senior-architect/SKILL.md +343 -0
- package/bundled-skills/senior-architect/references/architecture_patterns.md +470 -0
- package/bundled-skills/senior-architect/references/system_design_workflows.md +536 -0
- package/bundled-skills/senior-architect/references/tech_decision_guide.md +412 -0
- package/bundled-skills/senior-architect/scripts/architecture_diagram_generator.py +660 -0
- package/bundled-skills/senior-architect/scripts/dependency_analyzer.py +615 -0
- package/bundled-skills/senior-architect/scripts/project_architect.py +749 -0
- package/bundled-skills/senior-backend/SKILL.md +467 -0
- package/bundled-skills/senior-backend/profiles/django-monolith.json +57 -0
- package/bundled-skills/senior-backend/profiles/fastapi-python.json +58 -0
- package/bundled-skills/senior-backend/profiles/go-or-rust-microservice.json +62 -0
- package/bundled-skills/senior-backend/profiles/node-express.json +57 -0
- package/bundled-skills/senior-backend/references/api_design_patterns.md +530 -0
- package/bundled-skills/senior-backend/references/backend_security_practices.md +1075 -0
- package/bundled-skills/senior-backend/references/composition_map.md +58 -0
- package/bundled-skills/senior-backend/references/database_optimization_guide.md +593 -0
- package/bundled-skills/senior-backend/references/forcing_questions.md +100 -0
- package/bundled-skills/senior-backend/scripts/api_load_tester.py +607 -0
- package/bundled-skills/senior-backend/scripts/api_scaffolder.py +645 -0
- package/bundled-skills/senior-backend/scripts/backend_decision_engine.py +397 -0
- package/bundled-skills/senior-backend/scripts/database_migration_tool.py +858 -0
- package/bundled-skills/seo-audit/SKILL.md +499 -0
- package/bundled-skills/seo-audit/evals/evals.json +136 -0
- package/bundled-skills/seo-audit/references/ai-writing-detection.md +200 -0
- package/bundled-skills/seo-audit/references/international-seo.md +230 -0
- package/bundled-skills/session-brain/SKILL.md +101 -0
- package/bundled-skills/session-search/SKILL.md +97 -0
- package/bundled-skills/skill-creator/LICENSE.txt +202 -0
- package/bundled-skills/skill-creator/SKILL.md +485 -0
- package/bundled-skills/skill-creator/agents/analyzer.md +274 -0
- package/bundled-skills/skill-creator/agents/comparator.md +202 -0
- package/bundled-skills/skill-creator/agents/grader.md +223 -0
- package/bundled-skills/skill-creator/assets/eval_review.html +146 -0
- package/bundled-skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/bundled-skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/bundled-skills/skill-creator/references/schemas.md +430 -0
- package/bundled-skills/skill-creator/scripts/__init__.py +0 -0
- package/bundled-skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/bundled-skills/skill-creator/scripts/generate_report.py +326 -0
- package/bundled-skills/skill-creator/scripts/improve_description.py +247 -0
- package/bundled-skills/skill-creator/scripts/package_skill.py +136 -0
- package/bundled-skills/skill-creator/scripts/quick_validate.py +103 -0
- package/bundled-skills/skill-creator/scripts/run_eval.py +310 -0
- package/bundled-skills/skill-creator/scripts/run_loop.py +328 -0
- package/bundled-skills/skill-creator/scripts/utils.py +47 -0
- package/bundled-skills/social/SKILL.md +413 -0
- package/bundled-skills/social/evals/evals.json +107 -0
- package/bundled-skills/social/references/carousel-frameworks.md +141 -0
- package/bundled-skills/social/references/listening-sources-template.md +123 -0
- package/bundled-skills/social/references/listening.md +283 -0
- package/bundled-skills/social/references/platform-limits.md +110 -0
- package/bundled-skills/social/references/platforms.md +170 -0
- package/bundled-skills/social/references/post-templates.md +179 -0
- package/bundled-skills/social/references/reverse-engineering.md +195 -0
- package/bundled-skills/social/references/short-form-video.md +237 -0
- package/bundled-skills/spec-driven-workflow/SKILL.md +333 -0
- package/bundled-skills/spec-driven-workflow/references/acceptance_criteria_patterns.md +497 -0
- package/bundled-skills/spec-driven-workflow/references/bounded_autonomy_rules.md +273 -0
- package/bundled-skills/spec-driven-workflow/references/spec_format_guide.md +531 -0
- package/bundled-skills/spec-driven-workflow/scripts/spec_generator.py +338 -0
- package/bundled-skills/spec-driven-workflow/scripts/spec_validator.py +461 -0
- package/bundled-skills/spec-driven-workflow/scripts/test_extractor.py +431 -0
- package/bundled-skills/supabase/CHANGELOG.md +78 -0
- package/bundled-skills/supabase/SKILL.md +149 -0
- package/bundled-skills/supabase/assets/feedback-issue-template.md +17 -0
- package/bundled-skills/supabase/references/skill-feedback.md +17 -0
- package/bundled-skills/supabase-postgres-best-practices/CHANGELOG.md +73 -0
- package/bundled-skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/bundled-skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
- package/bundled-skills/supabase-postgres-best-practices/references/_sections.md +39 -0
- package/bundled-skills/supabase-postgres-best-practices/references/_template.md +34 -0
- package/bundled-skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/bundled-skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/bundled-skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/bundled-skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/bundled-skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/bundled-skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/bundled-skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/bundled-skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/bundled-skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/bundled-skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/bundled-skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/bundled-skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/bundled-skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/bundled-skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/bundled-skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/bundled-skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/bundled-skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/bundled-skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/bundled-skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/bundled-skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/bundled-skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/bundled-skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/bundled-skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/bundled-skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/bundled-skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/bundled-skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/bundled-skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/bundled-skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/bundled-skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/bundled-skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/bundled-skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -0
- package/bundled-skills/tag-taxonomy/SKILL.md +218 -0
- package/bundled-skills/typescript-expert/SKILL.md +431 -0
- package/bundled-skills/typescript-expert/references/tsconfig-strict.json +45 -0
- package/bundled-skills/typescript-expert/references/typescript-cheatsheet.md +383 -0
- package/bundled-skills/typescript-expert/references/utility-types.ts +335 -0
- package/bundled-skills/typescript-expert/scripts/ts_diagnostic.py +203 -0
- package/bundled-skills/ui-design-system/SKILL.md +379 -0
- package/bundled-skills/ui-design-system/assets/design_system_doc_template.md +180 -0
- package/bundled-skills/ui-design-system/references/component-architecture.md +396 -0
- package/bundled-skills/ui-design-system/references/developer-handoff.md +509 -0
- package/bundled-skills/ui-design-system/references/responsive-calculations.md +390 -0
- package/bundled-skills/ui-design-system/references/token-generation.md +324 -0
- package/bundled-skills/ui-design-system/scripts/design_token_generator.py +590 -0
- package/bundled-skills/vault-skill-factory/SKILL.md +143 -0
- package/bundled-skills/video/SKILL.md +346 -0
- package/bundled-skills/video/evals/evals.json +103 -0
- package/bundled-skills/video/references/ai-video-prompting.md +175 -0
- package/bundled-skills/video/references/edit-anatomy.md +84 -0
- package/bundled-skills/wiki-agent/SKILL.md +322 -0
- package/bundled-skills/wiki-capture/SKILL.md +337 -0
- package/bundled-skills/wiki-capture/references/RAW-FORMAT.md +115 -0
- package/bundled-skills/wiki-context-pack/SKILL.md +89 -0
- package/bundled-skills/wiki-dashboard/SKILL.md +472 -0
- package/bundled-skills/wiki-dedup/SKILL.md +320 -0
- package/bundled-skills/wiki-digest/SKILL.md +243 -0
- package/bundled-skills/wiki-export/SKILL.md +445 -0
- package/bundled-skills/wiki-history-ingest/SKILL.md +61 -0
- package/bundled-skills/wiki-import/SKILL.md +276 -0
- package/bundled-skills/wiki-ingest/SKILL.md +563 -0
- package/bundled-skills/wiki-ingest/references/ingest-prompts.md +54 -0
- package/bundled-skills/wiki-ingest/references/pageindex.md +72 -0
- package/bundled-skills/wiki-ingest/references/url-sources.md +295 -0
- package/bundled-skills/wiki-lint/SKILL.md +643 -0
- package/bundled-skills/wiki-narrate/SKILL.md +120 -0
- package/bundled-skills/wiki-narrate/references/voices.md +29 -0
- package/bundled-skills/wiki-query/SKILL.md +307 -0
- package/bundled-skills/wiki-rebuild/SKILL.md +212 -0
- package/bundled-skills/wiki-research/SKILL.md +319 -0
- package/bundled-skills/wiki-setup/SKILL.md +359 -0
- package/bundled-skills/wiki-stage-commit/SKILL.md +164 -0
- package/bundled-skills/wiki-status/SKILL.md +556 -0
- package/bundled-skills/wiki-switch/SKILL.md +115 -0
- package/bundled-skills/wiki-synthesize/SKILL.md +212 -0
- package/bundled-skills/wiki-update/SKILL.md +271 -0
- package/bundled-skills/write-a-skill/SKILL.md +141 -0
- package/bundled-skills/write-a-skill/references/companion_tooling.md +67 -0
- package/bundled-skills/write-a-skill/references/description_design_patterns.md +139 -0
- package/bundled-skills/write-a-skill/references/progressive_disclosure_principles.md +89 -0
- package/bundled-skills/write-a-skill/references/quality_gates_for_skills.md +158 -0
- package/bundled-skills/write-a-skill/scripts/skill_description_validator.py +255 -0
- package/bundled-skills/write-a-skill/scripts/skill_review_checklist_runner.py +268 -0
- package/bundled-skills/write-a-skill/scripts/skill_structure_validator.py +277 -0
- package/bundled-skills/zero-hallucination-coder/SKILL.md +271 -0
- package/catalog/categories.json +1030 -0
- package/catalog/overrides.json +525 -0
- package/catalog/providers.json +145 -0
- package/catalog/rules.json +112 -0
- package/docs/ARCHITECTURE.md +82 -0
- package/docs/CONSTITUTION.md +60 -0
- package/docs/EXECUTION.md +79 -0
- package/docs/HARNESSES.md +38 -0
- package/docs/INSTALL.md +123 -0
- package/docs/PROVIDERS.md +128 -0
- package/docs/ROADMAPS.md +74 -0
- package/docs/RULES.md +85 -0
- package/docs/TOKENS.md +48 -0
- package/install/bootstrap.mjs +273 -0
- package/install/install.mjs +466 -0
- package/install/install.ps1 +3 -0
- package/install/install.sh +4 -0
- package/install/lib.mjs +198 -0
- package/install/prompt.mjs +16 -0
- package/install/uninstall.mjs +109 -0
- package/mcp/novahiz-tools/README.md +26 -0
- package/mcp/novahiz-tools/index.mjs +452 -0
- package/novahiz.config.example.json +39 -0
- package/package.json +76 -0
- package/skills/a11y-audit/SKILL.md +211 -0
- package/skills/a11y-audit/assets/sample-component.tsx +51 -0
- package/skills/a11y-audit/expected_outputs/sample-contrast-output.txt +39 -0
- package/skills/a11y-audit/expected_outputs/sample-scan-output.json +34 -0
- package/skills/a11y-audit/expected_outputs/sample-scan-report.md +63 -0
- package/skills/a11y-audit/references/aria-patterns.md +223 -0
- package/skills/a11y-audit/references/audit-report-template.md +46 -0
- package/skills/a11y-audit/references/ci-cd-integration.md +127 -0
- package/skills/a11y-audit/references/color-contrast-guide.md +83 -0
- package/skills/a11y-audit/references/examples-by-framework.md +312 -0
- package/skills/a11y-audit/references/framework-a11y-patterns.md +661 -0
- package/skills/a11y-audit/references/testing-checklist.md +41 -0
- package/skills/a11y-audit/references/wcag-22-new-criteria.md +79 -0
- package/skills/a11y-audit/references/wcag-quick-ref.md +113 -0
- package/skills/a11y-audit/scripts/a11y_scanner.py +684 -0
- package/skills/a11y-audit/scripts/contrast_checker.py +504 -0
- package/skills/adversarial-reviewer/SKILL.md +247 -0
- package/skills/ai-security/SKILL.md +364 -0
- package/skills/ai-security/references/atlas-coverage.md +150 -0
- package/skills/ai-security/scripts/ai_threat_scanner.py +564 -0
- package/skills/anti-AI-design/SKILL.md +189 -0
- package/skills/anti-pattern-detector/SKILL.md +185 -0
- package/skills/anti-pattern-detector/references/async-network-patterns.md +581 -0
- package/skills/anti-pattern-detector/references/build-deploy-patterns.md +554 -0
- package/skills/anti-pattern-detector/references/cache-patterns.md +446 -0
- package/skills/anti-pattern-detector/references/cloud-infra-patterns.md +538 -0
- package/skills/anti-pattern-detector/references/concurrency-patterns.md +558 -0
- package/skills/anti-pattern-detector/references/language-specific-patterns.md +740 -0
- package/skills/anti-pattern-detector/references/monitoring-patterns.md +456 -0
- package/skills/api-design-reviewer/SKILL.md +433 -0
- package/skills/api-design-reviewer/references/api_antipatterns.md +680 -0
- package/skills/api-design-reviewer/references/rest_design_rules.md +487 -0
- package/skills/api-design-reviewer/scripts/api_linter.py +956 -0
- package/skills/api-design-reviewer/scripts/api_scorecard.py +1661 -0
- package/skills/api-design-reviewer/scripts/breaking_change_detector.py +1102 -0
- package/skills/apple-hig-expert/SKILL.md +108 -0
- package/skills/apple-hig-expert/references/accessibility.md +55 -0
- package/skills/apple-hig-expert/references/platform-specifics.md +48 -0
- package/skills/apple-hig-expert/references/visual-design.md +69 -0
- package/skills/apple-hig-expert/scripts/hig_checker.py +97 -0
- package/skills/apple-hig-expert/templates/hig-audit-template.md +83 -0
- package/skills/clean-code/SKILL.md +99 -0
- package/skills/code-reviewer/README.md +92 -0
- package/skills/code-reviewer/SKILL.md +183 -0
- package/skills/code-reviewer/assets/sample_c_clean.c +71 -0
- package/skills/code-reviewer/assets/sample_c_smells.c +66 -0
- package/skills/code-reviewer/assets/sample_csharp_clean.cs +100 -0
- package/skills/code-reviewer/assets/sample_csharp_smells.cs +78 -0
- package/skills/code-reviewer/assets/sample_java_clean.java +55 -0
- package/skills/code-reviewer/assets/sample_java_smells.java +56 -0
- package/skills/code-reviewer/expected_outputs/sample_c_clean_quality.json +65 -0
- package/skills/code-reviewer/expected_outputs/sample_c_smells_quality.json +155 -0
- package/skills/code-reviewer/expected_outputs/sample_csharp_clean_quality.json +135 -0
- package/skills/code-reviewer/expected_outputs/sample_csharp_smells_quality.json +143 -0
- package/skills/code-reviewer/expected_outputs/sample_java_clean_quality.json +53 -0
- package/skills/code-reviewer/expected_outputs/sample_java_smells_quality.json +83 -0
- package/skills/code-reviewer/languages/c.md +103 -0
- package/skills/code-reviewer/languages/cpp.md +103 -0
- package/skills/code-reviewer/languages/csharp.md +97 -0
- package/skills/code-reviewer/languages/dart.md +108 -0
- package/skills/code-reviewer/languages/go.md +92 -0
- package/skills/code-reviewer/languages/java.md +103 -0
- package/skills/code-reviewer/languages/kotlin.md +88 -0
- package/skills/code-reviewer/languages/php.md +109 -0
- package/skills/code-reviewer/languages/python.md +94 -0
- package/skills/code-reviewer/languages/ruby.md +105 -0
- package/skills/code-reviewer/languages/rust.md +99 -0
- package/skills/code-reviewer/languages/swift.md +88 -0
- package/skills/code-reviewer/languages/typescript.md +97 -0
- package/skills/code-reviewer/rules/universal.md +49 -0
- package/skills/code-reviewer/scripts/code_quality_checker.py +1020 -0
- package/skills/code-reviewer/scripts/pr_analyzer.py +535 -0
- package/skills/code-reviewer/scripts/review_report_generator.py +505 -0
- package/skills/code-understand/SKILL.md +90 -0
- package/skills/database-designer/README.md +388 -0
- package/skills/database-designer/SKILL.md +314 -0
- package/skills/database-designer/assets/sample_query_patterns.json +375 -0
- package/skills/database-designer/assets/sample_schema.json +372 -0
- package/skills/database-designer/assets/sample_schema.sql +207 -0
- package/skills/database-designer/expected_outputs/index_optimization_sample.txt +60 -0
- package/skills/database-designer/expected_outputs/migration_sample.txt +124 -0
- package/skills/database-designer/expected_outputs/schema_analysis_sample.txt +222 -0
- package/skills/database-designer/index_optimizer.py +926 -0
- package/skills/database-designer/migration_generator.py +1199 -0
- package/skills/database-designer/references/database-design-reference.md +476 -0
- package/skills/database-designer/references/database_selection_decision_tree.md +373 -0
- package/skills/database-designer/references/index_strategy_patterns.md +424 -0
- package/skills/database-designer/references/normalization_guide.md +354 -0
- package/skills/database-designer/schema_analyzer.py +982 -0
- package/skills/defuddle/SKILL.md +41 -0
- package/skills/dependency-auditor/README.md +524 -0
- package/skills/dependency-auditor/SKILL.md +85 -0
- package/skills/dependency-auditor/assets/sample_go.mod +53 -0
- package/skills/dependency-auditor/assets/sample_package.json +72 -0
- package/skills/dependency-auditor/assets/sample_requirements.txt +71 -0
- package/skills/dependency-auditor/expected_outputs/sample_license_report.txt +37 -0
- package/skills/dependency-auditor/expected_outputs/sample_upgrade_plan.txt +59 -0
- package/skills/dependency-auditor/expected_outputs/sample_vulnerability_report.json +71 -0
- package/skills/dependency-auditor/references/dependency_management_best_practices.md +643 -0
- package/skills/dependency-auditor/references/license_compatibility_matrix.md +238 -0
- package/skills/dependency-auditor/references/vulnerability_assessment_guide.md +461 -0
- package/skills/dependency-auditor/scripts/dep_scanner.py +794 -0
- package/skills/dependency-auditor/scripts/license_checker.py +996 -0
- package/skills/dependency-auditor/scripts/upgrade_planner.py +1029 -0
- package/skills/dependency-auditor/test-inventory.json +421 -0
- package/skills/dependency-auditor/test-project/package.json +72 -0
- package/skills/design-taste-frontend/SKILL.md +247 -0
- package/skills/engineering-standards/SKILL.md +736 -0
- package/skills/env-secrets-manager/SKILL.md +260 -0
- package/skills/env-secrets-manager/references/secret-patterns.md +41 -0
- package/skills/env-secrets-manager/references/validation-detection-rotation.md +358 -0
- package/skills/env-secrets-manager/scripts/env_auditor.py +145 -0
- package/skills/fix/SKILL.md +113 -0
- package/skills/fix/flaky-taxonomy.md +134 -0
- package/skills/gate/SKILL.md +13 -0
- package/skills/humanizer/SKILL.md +374 -0
- package/skills/mcp-server-builder/README.md +50 -0
- package/skills/mcp-server-builder/SKILL.md +100 -0
- package/skills/mcp-server-builder/references/openapi-extraction-guide.md +34 -0
- package/skills/mcp-server-builder/references/production-hardening-guide.md +78 -0
- package/skills/mcp-server-builder/references/python-server-template.md +22 -0
- package/skills/mcp-server-builder/references/typescript-server-template.md +19 -0
- package/skills/mcp-server-builder/references/validation-checklist.md +30 -0
- package/skills/mcp-server-builder/scripts/mcp_validator.py +186 -0
- package/skills/mcp-server-builder/scripts/openapi_to_mcp.py +284 -0
- package/skills/memory/SKILL.md +10 -0
- package/skills/migrate/SKILL.md +135 -0
- package/skills/migrate/cypress-mapping.md +79 -0
- package/skills/migrate/selenium-mapping.md +94 -0
- package/skills/novahiz-analyse/SKILL.md +74 -0
- package/skills/novahiz-audit/SKILL.md +75 -0
- package/skills/novahiz-clarify/SKILL.md +94 -0
- package/skills/novahiz-converge/SKILL.md +76 -0
- package/skills/novahiz-gate/SKILL.md +82 -0
- package/skills/novahiz-implement/SKILL.md +80 -0
- package/skills/novahiz-memory/SKILL.md +58 -0
- package/skills/novahiz-plan/SKILL.md +99 -0
- package/skills/novahiz-planner/SKILL.md +104 -0
- package/skills/novahiz-task/SKILL.md +105 -0
- package/skills/playwright-agent/SKILL.md +142 -0
- package/skills/postgresql-optimization/SKILL.md +179 -0
- package/skills/review-changes/SKILL.md +30 -0
- package/skills/security-guidance/SKILL.md +160 -0
- package/skills/security-guidance/references/pretooluse_hook_canon.md +141 -0
- package/skills/senior-architect/SKILL.md +343 -0
- package/skills/senior-architect/references/architecture_patterns.md +470 -0
- package/skills/senior-architect/references/system_design_workflows.md +536 -0
- package/skills/senior-architect/references/tech_decision_guide.md +412 -0
- package/skills/senior-architect/scripts/architecture_diagram_generator.py +660 -0
- package/skills/senior-architect/scripts/dependency_analyzer.py +615 -0
- package/skills/senior-architect/scripts/project_architect.py +749 -0
- package/skills/senior-backend/SKILL.md +467 -0
- package/skills/senior-backend/profiles/django-monolith.json +57 -0
- package/skills/senior-backend/profiles/fastapi-python.json +58 -0
- package/skills/senior-backend/profiles/go-or-rust-microservice.json +62 -0
- package/skills/senior-backend/profiles/node-express.json +57 -0
- package/skills/senior-backend/references/api_design_patterns.md +530 -0
- package/skills/senior-backend/references/backend_security_practices.md +1075 -0
- package/skills/senior-backend/references/composition_map.md +58 -0
- package/skills/senior-backend/references/database_optimization_guide.md +593 -0
- package/skills/senior-backend/references/forcing_questions.md +100 -0
- package/skills/senior-backend/scripts/api_load_tester.py +607 -0
- package/skills/senior-backend/scripts/api_scaffolder.py +645 -0
- package/skills/senior-backend/scripts/backend_decision_engine.py +397 -0
- package/skills/senior-backend/scripts/database_migration_tool.py +858 -0
- package/skills/senior-secops/SKILL.md +505 -0
- package/skills/senior-secops/references/compliance_requirements.md +792 -0
- package/skills/senior-secops/references/security_standards.md +718 -0
- package/skills/senior-secops/references/vulnerability_management_guide.md +487 -0
- package/skills/senior-secops/scripts/compliance_checker.py +1107 -0
- package/skills/senior-secops/scripts/security_scanner.py +471 -0
- package/skills/senior-secops/scripts/vulnerability_assessor.py +563 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/skills/spec-driven-workflow/SKILL.md +333 -0
- package/skills/spec-driven-workflow/references/acceptance_criteria_patterns.md +497 -0
- package/skills/spec-driven-workflow/references/bounded_autonomy_rules.md +273 -0
- package/skills/spec-driven-workflow/references/spec_format_guide.md +531 -0
- package/skills/spec-driven-workflow/scripts/spec_generator.py +338 -0
- package/skills/spec-driven-workflow/scripts/spec_validator.py +461 -0
- package/skills/spec-driven-workflow/scripts/test_extractor.py +431 -0
- package/skills/supabase/CHANGELOG.md +78 -0
- package/skills/supabase/SKILL.md +149 -0
- package/skills/supabase/assets/feedback-issue-template.md +17 -0
- package/skills/supabase/references/skill-feedback.md +17 -0
- package/skills/supabase-postgres-best-practices/CHANGELOG.md +73 -0
- package/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
- package/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
- package/skills/supabase-postgres-best-practices/references/_template.md +34 -0
- package/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -0
- package/skills/typescript-expert/SKILL.md +431 -0
- package/skills/typescript-expert/references/tsconfig-strict.json +45 -0
- package/skills/typescript-expert/references/typescript-cheatsheet.md +383 -0
- package/skills/typescript-expert/references/utility-types.ts +335 -0
- package/skills/typescript-expert/scripts/ts_diagnostic.py +203 -0
- package/skills/ui-design-system/SKILL.md +379 -0
- package/skills/ui-design-system/assets/design_system_doc_template.md +180 -0
- package/skills/ui-design-system/references/component-architecture.md +396 -0
- package/skills/ui-design-system/references/developer-handoff.md +509 -0
- package/skills/ui-design-system/references/responsive-calculations.md +390 -0
- package/skills/ui-design-system/references/token-generation.md +324 -0
- package/skills/ui-design-system/scripts/design_token_generator.py +590 -0
- package/skills/write-a-skill/SKILL.md +141 -0
- package/skills/write-a-skill/references/companion_tooling.md +67 -0
- package/skills/write-a-skill/references/description_design_patterns.md +139 -0
- package/skills/write-a-skill/references/progressive_disclosure_principles.md +89 -0
- package/skills/write-a-skill/references/quality_gates_for_skills.md +158 -0
- package/skills/write-a-skill/scripts/skill_description_validator.py +255 -0
- package/skills/write-a-skill/scripts/skill_review_checklist_runner.py +268 -0
- package/skills/write-a-skill/scripts/skill_structure_validator.py +277 -0
- package/skills/zero-hallucination-coder/SKILL.md +271 -0
- package/src/catalog.ts +266 -0
- package/src/classify.ts +201 -0
- package/src/cli.ts +115 -0
- package/src/commands/clean.ts +114 -0
- package/src/commands/context.ts +130 -0
- package/src/commands/doctor.ts +191 -0
- package/src/commands/gate.ts +192 -0
- package/src/commands/inspect.ts +296 -0
- package/src/commands/report.ts +85 -0
- package/src/commands/task.ts +310 -0
- package/src/commands/tokens.ts +85 -0
- package/src/content.ts +70 -0
- package/src/db.ts +143 -0
- package/src/deps.ts +36 -0
- package/src/exec.ts +85 -0
- package/src/gate.ts +261 -0
- package/src/ledger.ts +468 -0
- package/src/providers.ts +52 -0
- package/src/relevance.ts +73 -0
- package/src/render.ts +107 -0
- package/src/spec.ts +306 -0
- package/src/targets.ts +296 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,1661 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
API Scorecard - Comprehensive API design quality assessment tool.
|
|
4
|
+
|
|
5
|
+
This script evaluates API designs across multiple dimensions and generates
|
|
6
|
+
a detailed scorecard with letter grades and improvement recommendations.
|
|
7
|
+
|
|
8
|
+
Scoring Dimensions:
|
|
9
|
+
- Consistency (30%): Naming conventions, response patterns, structural consistency
|
|
10
|
+
- Documentation (20%): Completeness and clarity of API documentation
|
|
11
|
+
- Security (20%): Authentication, authorization, and security best practices
|
|
12
|
+
- Usability (15%): Ease of use, discoverability, and developer experience
|
|
13
|
+
- Performance (15%): Caching, pagination, and efficiency patterns
|
|
14
|
+
|
|
15
|
+
Generates letter grades (A-F) with detailed breakdowns and actionable recommendations.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from enum import Enum
|
|
25
|
+
import math
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ScoreCategory(Enum):
|
|
29
|
+
"""Scoring categories."""
|
|
30
|
+
CONSISTENCY = "consistency"
|
|
31
|
+
DOCUMENTATION = "documentation"
|
|
32
|
+
SECURITY = "security"
|
|
33
|
+
USABILITY = "usability"
|
|
34
|
+
PERFORMANCE = "performance"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class CategoryScore:
|
|
39
|
+
"""Score for a specific category."""
|
|
40
|
+
category: ScoreCategory
|
|
41
|
+
score: float # 0-100
|
|
42
|
+
max_score: float # Usually 100
|
|
43
|
+
weight: float # Percentage weight in overall score
|
|
44
|
+
issues: List[str] = field(default_factory=list)
|
|
45
|
+
recommendations: List[str] = field(default_factory=list)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def letter_grade(self) -> str:
|
|
49
|
+
"""Convert score to letter grade."""
|
|
50
|
+
if self.score >= 90:
|
|
51
|
+
return "A"
|
|
52
|
+
elif self.score >= 80:
|
|
53
|
+
return "B"
|
|
54
|
+
elif self.score >= 70:
|
|
55
|
+
return "C"
|
|
56
|
+
elif self.score >= 60:
|
|
57
|
+
return "D"
|
|
58
|
+
else:
|
|
59
|
+
return "F"
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def weighted_score(self) -> float:
|
|
63
|
+
"""Calculate weighted contribution to overall score."""
|
|
64
|
+
return (self.score / 100.0) * self.weight
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class APIScorecard:
|
|
69
|
+
"""Complete API scorecard with all category scores."""
|
|
70
|
+
category_scores: Dict[ScoreCategory, CategoryScore] = field(default_factory=dict)
|
|
71
|
+
overall_score: float = 0.0
|
|
72
|
+
overall_grade: str = "F"
|
|
73
|
+
total_endpoints: int = 0
|
|
74
|
+
api_info: Dict[str, Any] = field(default_factory=dict)
|
|
75
|
+
|
|
76
|
+
def calculate_overall_score(self) -> None:
|
|
77
|
+
"""Calculate overall weighted score and grade."""
|
|
78
|
+
self.overall_score = sum(score.weighted_score for score in self.category_scores.values())
|
|
79
|
+
|
|
80
|
+
if self.overall_score >= 90:
|
|
81
|
+
self.overall_grade = "A"
|
|
82
|
+
elif self.overall_score >= 80:
|
|
83
|
+
self.overall_grade = "B"
|
|
84
|
+
elif self.overall_score >= 70:
|
|
85
|
+
self.overall_grade = "C"
|
|
86
|
+
elif self.overall_score >= 60:
|
|
87
|
+
self.overall_grade = "D"
|
|
88
|
+
else:
|
|
89
|
+
self.overall_grade = "F"
|
|
90
|
+
|
|
91
|
+
def get_top_recommendations(self, limit: int = 5) -> List[str]:
|
|
92
|
+
"""Get top recommendations across all categories."""
|
|
93
|
+
all_recommendations = []
|
|
94
|
+
for category_score in self.category_scores.values():
|
|
95
|
+
for rec in category_score.recommendations:
|
|
96
|
+
all_recommendations.append(f"{category_score.category.value.title()}: {rec}")
|
|
97
|
+
|
|
98
|
+
# Sort by category weight (highest impact first)
|
|
99
|
+
weighted_recs = []
|
|
100
|
+
for category_score in sorted(self.category_scores.values(),
|
|
101
|
+
key=lambda x: x.weight, reverse=True):
|
|
102
|
+
for rec in category_score.recommendations[:2]: # Top 2 per category
|
|
103
|
+
weighted_recs.append(f"{category_score.category.value.title()}: {rec}")
|
|
104
|
+
|
|
105
|
+
return weighted_recs[:limit]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class APIScoringEngine:
|
|
109
|
+
"""Main API scoring engine."""
|
|
110
|
+
|
|
111
|
+
def __init__(self):
|
|
112
|
+
self.scorecard = APIScorecard()
|
|
113
|
+
self.spec: Optional[Dict] = None
|
|
114
|
+
|
|
115
|
+
# Regex patterns for validation
|
|
116
|
+
self.kebab_case_pattern = re.compile(r'^[a-z]+(?:-[a-z0-9]+)*$')
|
|
117
|
+
self.camel_case_pattern = re.compile(r'^[a-z][a-zA-Z0-9]*$')
|
|
118
|
+
self.pascal_case_pattern = re.compile(r'^[A-Z][a-zA-Z0-9]*$')
|
|
119
|
+
|
|
120
|
+
# HTTP methods
|
|
121
|
+
self.http_methods = {'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'}
|
|
122
|
+
|
|
123
|
+
# Category weights (must sum to 100)
|
|
124
|
+
self.category_weights = {
|
|
125
|
+
ScoreCategory.CONSISTENCY: 30.0,
|
|
126
|
+
ScoreCategory.DOCUMENTATION: 20.0,
|
|
127
|
+
ScoreCategory.SECURITY: 20.0,
|
|
128
|
+
ScoreCategory.USABILITY: 15.0,
|
|
129
|
+
ScoreCategory.PERFORMANCE: 15.0
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
def score_api(self, spec: Dict[str, Any]) -> APIScorecard:
|
|
133
|
+
"""Generate comprehensive API scorecard."""
|
|
134
|
+
self.spec = spec
|
|
135
|
+
self.scorecard = APIScorecard()
|
|
136
|
+
|
|
137
|
+
# Extract basic API info
|
|
138
|
+
self._extract_api_info()
|
|
139
|
+
|
|
140
|
+
# Score each category
|
|
141
|
+
self._score_consistency()
|
|
142
|
+
self._score_documentation()
|
|
143
|
+
self._score_security()
|
|
144
|
+
self._score_usability()
|
|
145
|
+
self._score_performance()
|
|
146
|
+
|
|
147
|
+
# Calculate overall score
|
|
148
|
+
self.scorecard.calculate_overall_score()
|
|
149
|
+
|
|
150
|
+
return self.scorecard
|
|
151
|
+
|
|
152
|
+
def _extract_api_info(self) -> None:
|
|
153
|
+
"""Extract basic API information."""
|
|
154
|
+
info = self.spec.get('info', {})
|
|
155
|
+
paths = self.spec.get('paths', {})
|
|
156
|
+
|
|
157
|
+
self.scorecard.api_info = {
|
|
158
|
+
'title': info.get('title', 'Unknown API'),
|
|
159
|
+
'version': info.get('version', ''),
|
|
160
|
+
'description': info.get('description', ''),
|
|
161
|
+
'total_paths': len(paths),
|
|
162
|
+
'openapi_version': self.spec.get('openapi', self.spec.get('swagger', ''))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Count total endpoints
|
|
166
|
+
endpoint_count = 0
|
|
167
|
+
for path_obj in paths.values():
|
|
168
|
+
if isinstance(path_obj, dict):
|
|
169
|
+
endpoint_count += len([m for m in path_obj.keys()
|
|
170
|
+
if m.upper() in self.http_methods])
|
|
171
|
+
|
|
172
|
+
self.scorecard.total_endpoints = endpoint_count
|
|
173
|
+
|
|
174
|
+
def _score_consistency(self) -> None:
|
|
175
|
+
"""Score API consistency (30% weight)."""
|
|
176
|
+
category = ScoreCategory.CONSISTENCY
|
|
177
|
+
score = CategoryScore(
|
|
178
|
+
category=category,
|
|
179
|
+
score=0.0,
|
|
180
|
+
max_score=100.0,
|
|
181
|
+
weight=self.category_weights[category]
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
consistency_checks = [
|
|
185
|
+
self._check_naming_consistency(),
|
|
186
|
+
self._check_response_consistency(),
|
|
187
|
+
self._check_error_format_consistency(),
|
|
188
|
+
self._check_parameter_consistency(),
|
|
189
|
+
self._check_url_structure_consistency(),
|
|
190
|
+
self._check_http_method_consistency(),
|
|
191
|
+
self._check_status_code_consistency()
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
# Average the consistency scores
|
|
195
|
+
valid_scores = [s for s in consistency_checks if s is not None]
|
|
196
|
+
if valid_scores:
|
|
197
|
+
score.score = sum(valid_scores) / len(valid_scores)
|
|
198
|
+
|
|
199
|
+
# Add specific recommendations based on low scores
|
|
200
|
+
if score.score < 70:
|
|
201
|
+
score.recommendations.extend([
|
|
202
|
+
"Review naming conventions across all endpoints and schemas",
|
|
203
|
+
"Standardize response formats and error structures",
|
|
204
|
+
"Ensure consistent HTTP method usage patterns"
|
|
205
|
+
])
|
|
206
|
+
elif score.score < 85:
|
|
207
|
+
score.recommendations.extend([
|
|
208
|
+
"Minor consistency improvements needed in naming or response formats",
|
|
209
|
+
"Consider creating API design guidelines document"
|
|
210
|
+
])
|
|
211
|
+
|
|
212
|
+
self.scorecard.category_scores[category] = score
|
|
213
|
+
|
|
214
|
+
def _check_naming_consistency(self) -> float:
|
|
215
|
+
"""Check naming convention consistency."""
|
|
216
|
+
paths = self.spec.get('paths', {})
|
|
217
|
+
schemas = self.spec.get('components', {}).get('schemas', {})
|
|
218
|
+
|
|
219
|
+
total_checks = 0
|
|
220
|
+
passed_checks = 0
|
|
221
|
+
|
|
222
|
+
# Check path naming (should be kebab-case)
|
|
223
|
+
for path in paths.keys():
|
|
224
|
+
segments = [seg for seg in path.split('/') if seg and not seg.startswith('{')]
|
|
225
|
+
for segment in segments:
|
|
226
|
+
total_checks += 1
|
|
227
|
+
if self.kebab_case_pattern.match(segment) or re.match(r'^v\d+$', segment):
|
|
228
|
+
passed_checks += 1
|
|
229
|
+
|
|
230
|
+
# Check schema naming (should be PascalCase)
|
|
231
|
+
for schema_name in schemas.keys():
|
|
232
|
+
total_checks += 1
|
|
233
|
+
if self.pascal_case_pattern.match(schema_name):
|
|
234
|
+
passed_checks += 1
|
|
235
|
+
|
|
236
|
+
# Check property naming within schemas
|
|
237
|
+
for schema in schemas.values():
|
|
238
|
+
if isinstance(schema, dict) and 'properties' in schema:
|
|
239
|
+
for prop_name in schema['properties'].keys():
|
|
240
|
+
total_checks += 1
|
|
241
|
+
if self.camel_case_pattern.match(prop_name):
|
|
242
|
+
passed_checks += 1
|
|
243
|
+
|
|
244
|
+
return (passed_checks / total_checks * 100) if total_checks > 0 else 100
|
|
245
|
+
|
|
246
|
+
def _check_response_consistency(self) -> float:
|
|
247
|
+
"""Check response format consistency."""
|
|
248
|
+
paths = self.spec.get('paths', {})
|
|
249
|
+
|
|
250
|
+
response_patterns = []
|
|
251
|
+
total_responses = 0
|
|
252
|
+
|
|
253
|
+
for path_obj in paths.values():
|
|
254
|
+
if not isinstance(path_obj, dict):
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
for method, operation in path_obj.items():
|
|
258
|
+
if method.upper() not in self.http_methods or not isinstance(operation, dict):
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
responses = operation.get('responses', {})
|
|
262
|
+
for status_code, response in responses.items():
|
|
263
|
+
if not isinstance(response, dict):
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
total_responses += 1
|
|
267
|
+
content = response.get('content', {})
|
|
268
|
+
|
|
269
|
+
# Analyze response structure
|
|
270
|
+
for media_type, media_obj in content.items():
|
|
271
|
+
schema = media_obj.get('schema', {})
|
|
272
|
+
pattern = self._extract_schema_pattern(schema)
|
|
273
|
+
response_patterns.append(pattern)
|
|
274
|
+
|
|
275
|
+
# Calculate consistency by comparing patterns
|
|
276
|
+
if not response_patterns:
|
|
277
|
+
return 100
|
|
278
|
+
|
|
279
|
+
pattern_counts = {}
|
|
280
|
+
for pattern in response_patterns:
|
|
281
|
+
pattern_key = json.dumps(pattern, sort_keys=True)
|
|
282
|
+
pattern_counts[pattern_key] = pattern_counts.get(pattern_key, 0) + 1
|
|
283
|
+
|
|
284
|
+
# Most common pattern should dominate for good consistency
|
|
285
|
+
max_count = max(pattern_counts.values()) if pattern_counts else 0
|
|
286
|
+
consistency_ratio = max_count / len(response_patterns) if response_patterns else 1
|
|
287
|
+
|
|
288
|
+
return consistency_ratio * 100
|
|
289
|
+
|
|
290
|
+
def _extract_schema_pattern(self, schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
291
|
+
"""Extract a pattern from a schema for consistency checking."""
|
|
292
|
+
if not isinstance(schema, dict):
|
|
293
|
+
return {}
|
|
294
|
+
|
|
295
|
+
pattern = {
|
|
296
|
+
'type': schema.get('type'),
|
|
297
|
+
'has_properties': 'properties' in schema,
|
|
298
|
+
'has_items': 'items' in schema,
|
|
299
|
+
'required_count': len(schema.get('required', [])),
|
|
300
|
+
'property_count': len(schema.get('properties', {}))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return pattern
|
|
304
|
+
|
|
305
|
+
def _check_error_format_consistency(self) -> float:
|
|
306
|
+
"""Check error response format consistency."""
|
|
307
|
+
paths = self.spec.get('paths', {})
|
|
308
|
+
error_responses = []
|
|
309
|
+
|
|
310
|
+
for path_obj in paths.values():
|
|
311
|
+
if not isinstance(path_obj, dict):
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
for method, operation in path_obj.items():
|
|
315
|
+
if method.upper() not in self.http_methods:
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
responses = operation.get('responses', {})
|
|
319
|
+
for status_code, response in responses.items():
|
|
320
|
+
try:
|
|
321
|
+
code_int = int(status_code)
|
|
322
|
+
if code_int >= 400: # Error responses
|
|
323
|
+
content = response.get('content', {})
|
|
324
|
+
for media_type, media_obj in content.items():
|
|
325
|
+
schema = media_obj.get('schema', {})
|
|
326
|
+
error_responses.append(self._extract_schema_pattern(schema))
|
|
327
|
+
except ValueError:
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
if not error_responses:
|
|
331
|
+
return 80 # No error responses defined - somewhat concerning
|
|
332
|
+
|
|
333
|
+
# Check consistency of error response formats
|
|
334
|
+
pattern_counts = {}
|
|
335
|
+
for pattern in error_responses:
|
|
336
|
+
pattern_key = json.dumps(pattern, sort_keys=True)
|
|
337
|
+
pattern_counts[pattern_key] = pattern_counts.get(pattern_key, 0) + 1
|
|
338
|
+
|
|
339
|
+
max_count = max(pattern_counts.values()) if pattern_counts else 0
|
|
340
|
+
consistency_ratio = max_count / len(error_responses) if error_responses else 1
|
|
341
|
+
|
|
342
|
+
return consistency_ratio * 100
|
|
343
|
+
|
|
344
|
+
def _check_parameter_consistency(self) -> float:
|
|
345
|
+
"""Check parameter naming and usage consistency."""
|
|
346
|
+
paths = self.spec.get('paths', {})
|
|
347
|
+
|
|
348
|
+
query_params = []
|
|
349
|
+
path_params = []
|
|
350
|
+
header_params = []
|
|
351
|
+
|
|
352
|
+
for path_obj in paths.values():
|
|
353
|
+
if not isinstance(path_obj, dict):
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
for method, operation in path_obj.items():
|
|
357
|
+
if method.upper() not in self.http_methods:
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
parameters = operation.get('parameters', [])
|
|
361
|
+
for param in parameters:
|
|
362
|
+
if not isinstance(param, dict):
|
|
363
|
+
continue
|
|
364
|
+
|
|
365
|
+
param_name = param.get('name', '')
|
|
366
|
+
param_in = param.get('in', '')
|
|
367
|
+
|
|
368
|
+
if param_in == 'query':
|
|
369
|
+
query_params.append(param_name)
|
|
370
|
+
elif param_in == 'path':
|
|
371
|
+
path_params.append(param_name)
|
|
372
|
+
elif param_in == 'header':
|
|
373
|
+
header_params.append(param_name)
|
|
374
|
+
|
|
375
|
+
# Check naming consistency for each parameter type
|
|
376
|
+
scores = []
|
|
377
|
+
|
|
378
|
+
# Query parameters should be camelCase or kebab-case
|
|
379
|
+
if query_params:
|
|
380
|
+
valid_query = sum(1 for p in query_params
|
|
381
|
+
if self.camel_case_pattern.match(p) or self.kebab_case_pattern.match(p))
|
|
382
|
+
scores.append((valid_query / len(query_params)) * 100)
|
|
383
|
+
|
|
384
|
+
# Path parameters should be camelCase or kebab-case
|
|
385
|
+
if path_params:
|
|
386
|
+
valid_path = sum(1 for p in path_params
|
|
387
|
+
if self.camel_case_pattern.match(p) or self.kebab_case_pattern.match(p))
|
|
388
|
+
scores.append((valid_path / len(path_params)) * 100)
|
|
389
|
+
|
|
390
|
+
return sum(scores) / len(scores) if scores else 100
|
|
391
|
+
|
|
392
|
+
def _check_url_structure_consistency(self) -> float:
|
|
393
|
+
"""Check URL structure and pattern consistency."""
|
|
394
|
+
paths = self.spec.get('paths', {})
|
|
395
|
+
|
|
396
|
+
total_paths = len(paths)
|
|
397
|
+
if total_paths == 0:
|
|
398
|
+
return 0
|
|
399
|
+
|
|
400
|
+
structure_score = 0
|
|
401
|
+
|
|
402
|
+
# Check for consistent versioning
|
|
403
|
+
versioned_paths = 0
|
|
404
|
+
for path in paths.keys():
|
|
405
|
+
if re.search(r'/v\d+/', path):
|
|
406
|
+
versioned_paths += 1
|
|
407
|
+
|
|
408
|
+
# Either all or none should be versioned for consistency
|
|
409
|
+
if versioned_paths == 0 or versioned_paths == total_paths:
|
|
410
|
+
structure_score += 25
|
|
411
|
+
elif versioned_paths > total_paths * 0.8:
|
|
412
|
+
structure_score += 20
|
|
413
|
+
|
|
414
|
+
# Check for reasonable path depth
|
|
415
|
+
reasonable_depth = 0
|
|
416
|
+
for path in paths.keys():
|
|
417
|
+
segments = [seg for seg in path.split('/') if seg]
|
|
418
|
+
if 2 <= len(segments) <= 5: # Reasonable depth
|
|
419
|
+
reasonable_depth += 1
|
|
420
|
+
|
|
421
|
+
structure_score += (reasonable_depth / total_paths) * 25
|
|
422
|
+
|
|
423
|
+
# Check for RESTful resource patterns
|
|
424
|
+
restful_patterns = 0
|
|
425
|
+
for path in paths.keys():
|
|
426
|
+
# Look for patterns like /resources/{id} or /resources
|
|
427
|
+
if re.match(r'^/[a-z-]+(/\{[^}]+\})?(/[a-z-]+)*$', path):
|
|
428
|
+
restful_patterns += 1
|
|
429
|
+
|
|
430
|
+
structure_score += (restful_patterns / total_paths) * 30
|
|
431
|
+
|
|
432
|
+
# Check for consistent trailing slash usage
|
|
433
|
+
with_slash = sum(1 for path in paths.keys() if path.endswith('/'))
|
|
434
|
+
without_slash = total_paths - with_slash
|
|
435
|
+
|
|
436
|
+
# Either all or none should have trailing slashes
|
|
437
|
+
if with_slash == 0 or without_slash == 0:
|
|
438
|
+
structure_score += 20
|
|
439
|
+
elif min(with_slash, without_slash) < total_paths * 0.1:
|
|
440
|
+
structure_score += 15
|
|
441
|
+
|
|
442
|
+
return min(structure_score, 100)
|
|
443
|
+
|
|
444
|
+
def _check_http_method_consistency(self) -> float:
|
|
445
|
+
"""Check HTTP method usage consistency."""
|
|
446
|
+
paths = self.spec.get('paths', {})
|
|
447
|
+
|
|
448
|
+
method_usage = {}
|
|
449
|
+
total_operations = 0
|
|
450
|
+
|
|
451
|
+
for path, path_obj in paths.items():
|
|
452
|
+
if not isinstance(path_obj, dict):
|
|
453
|
+
continue
|
|
454
|
+
|
|
455
|
+
for method in path_obj.keys():
|
|
456
|
+
if method.upper() in self.http_methods:
|
|
457
|
+
method_upper = method.upper()
|
|
458
|
+
total_operations += 1
|
|
459
|
+
|
|
460
|
+
# Analyze method usage patterns
|
|
461
|
+
if method_upper not in method_usage:
|
|
462
|
+
method_usage[method_upper] = {'count': 0, 'appropriate': 0}
|
|
463
|
+
|
|
464
|
+
method_usage[method_upper]['count'] += 1
|
|
465
|
+
|
|
466
|
+
# Check if method usage seems appropriate
|
|
467
|
+
if self._is_method_usage_appropriate(path, method_upper, path_obj[method]):
|
|
468
|
+
method_usage[method_upper]['appropriate'] += 1
|
|
469
|
+
|
|
470
|
+
if total_operations == 0:
|
|
471
|
+
return 0
|
|
472
|
+
|
|
473
|
+
# Calculate appropriateness score
|
|
474
|
+
total_appropriate = sum(data['appropriate'] for data in method_usage.values())
|
|
475
|
+
return (total_appropriate / total_operations) * 100
|
|
476
|
+
|
|
477
|
+
def _is_method_usage_appropriate(self, path: str, method: str, operation: Dict) -> bool:
|
|
478
|
+
"""Check if HTTP method usage is appropriate for the endpoint."""
|
|
479
|
+
# Simple heuristics for method appropriateness
|
|
480
|
+
has_request_body = 'requestBody' in operation
|
|
481
|
+
path_has_id = '{' in path and '}' in path
|
|
482
|
+
|
|
483
|
+
if method == 'GET':
|
|
484
|
+
return not has_request_body # GET should not have body
|
|
485
|
+
elif method == 'POST':
|
|
486
|
+
return not path_has_id # POST typically for collections
|
|
487
|
+
elif method == 'PUT':
|
|
488
|
+
return path_has_id and has_request_body # PUT for specific resources
|
|
489
|
+
elif method == 'PATCH':
|
|
490
|
+
return path_has_id # PATCH for specific resources
|
|
491
|
+
elif method == 'DELETE':
|
|
492
|
+
return path_has_id # DELETE for specific resources
|
|
493
|
+
|
|
494
|
+
return True # Default to appropriate for other methods
|
|
495
|
+
|
|
496
|
+
def _check_status_code_consistency(self) -> float:
|
|
497
|
+
"""Check HTTP status code usage consistency."""
|
|
498
|
+
paths = self.spec.get('paths', {})
|
|
499
|
+
|
|
500
|
+
method_status_patterns = {}
|
|
501
|
+
total_operations = 0
|
|
502
|
+
|
|
503
|
+
for path_obj in paths.values():
|
|
504
|
+
if not isinstance(path_obj, dict):
|
|
505
|
+
continue
|
|
506
|
+
|
|
507
|
+
for method, operation in path_obj.items():
|
|
508
|
+
if method.upper() not in self.http_methods:
|
|
509
|
+
continue
|
|
510
|
+
|
|
511
|
+
total_operations += 1
|
|
512
|
+
responses = operation.get('responses', {})
|
|
513
|
+
status_codes = set(responses.keys())
|
|
514
|
+
|
|
515
|
+
if method.upper() not in method_status_patterns:
|
|
516
|
+
method_status_patterns[method.upper()] = []
|
|
517
|
+
|
|
518
|
+
method_status_patterns[method.upper()].append(status_codes)
|
|
519
|
+
|
|
520
|
+
if total_operations == 0:
|
|
521
|
+
return 0
|
|
522
|
+
|
|
523
|
+
# Check consistency within each method type
|
|
524
|
+
consistency_scores = []
|
|
525
|
+
|
|
526
|
+
for method, status_patterns in method_status_patterns.items():
|
|
527
|
+
if not status_patterns:
|
|
528
|
+
continue
|
|
529
|
+
|
|
530
|
+
# Find common status codes for this method
|
|
531
|
+
all_codes = set()
|
|
532
|
+
for pattern in status_patterns:
|
|
533
|
+
all_codes.update(pattern)
|
|
534
|
+
|
|
535
|
+
# Calculate how many operations use the most common codes
|
|
536
|
+
code_usage = {}
|
|
537
|
+
for code in all_codes:
|
|
538
|
+
code_usage[code] = sum(1 for pattern in status_patterns if code in pattern)
|
|
539
|
+
|
|
540
|
+
# Score based on consistency of common status codes
|
|
541
|
+
if status_patterns:
|
|
542
|
+
avg_consistency = sum(
|
|
543
|
+
len([code for code in pattern if code_usage.get(code, 0) > len(status_patterns) * 0.5])
|
|
544
|
+
for pattern in status_patterns
|
|
545
|
+
) / len(status_patterns)
|
|
546
|
+
|
|
547
|
+
method_consistency = min(avg_consistency / 3.0 * 100, 100) # Expect ~3 common codes
|
|
548
|
+
consistency_scores.append(method_consistency)
|
|
549
|
+
|
|
550
|
+
return sum(consistency_scores) / len(consistency_scores) if consistency_scores else 100
|
|
551
|
+
|
|
552
|
+
def _score_documentation(self) -> None:
|
|
553
|
+
"""Score API documentation quality (20% weight)."""
|
|
554
|
+
category = ScoreCategory.DOCUMENTATION
|
|
555
|
+
score = CategoryScore(
|
|
556
|
+
category=category,
|
|
557
|
+
score=0.0,
|
|
558
|
+
max_score=100.0,
|
|
559
|
+
weight=self.category_weights[category]
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
documentation_checks = [
|
|
563
|
+
self._check_api_level_documentation(),
|
|
564
|
+
self._check_endpoint_documentation(),
|
|
565
|
+
self._check_schema_documentation(),
|
|
566
|
+
self._check_parameter_documentation(),
|
|
567
|
+
self._check_response_documentation(),
|
|
568
|
+
self._check_example_coverage()
|
|
569
|
+
]
|
|
570
|
+
|
|
571
|
+
valid_scores = [s for s in documentation_checks if s is not None]
|
|
572
|
+
if valid_scores:
|
|
573
|
+
score.score = sum(valid_scores) / len(valid_scores)
|
|
574
|
+
|
|
575
|
+
# Add recommendations based on score
|
|
576
|
+
if score.score < 60:
|
|
577
|
+
score.recommendations.extend([
|
|
578
|
+
"Add comprehensive descriptions to all API components",
|
|
579
|
+
"Include examples for complex operations and schemas",
|
|
580
|
+
"Document all parameters and response fields"
|
|
581
|
+
])
|
|
582
|
+
elif score.score < 80:
|
|
583
|
+
score.recommendations.extend([
|
|
584
|
+
"Improve documentation completeness for some endpoints",
|
|
585
|
+
"Add more examples to enhance developer experience"
|
|
586
|
+
])
|
|
587
|
+
|
|
588
|
+
self.scorecard.category_scores[category] = score
|
|
589
|
+
|
|
590
|
+
def _check_api_level_documentation(self) -> float:
|
|
591
|
+
"""Check API-level documentation completeness."""
|
|
592
|
+
info = self.spec.get('info', {})
|
|
593
|
+
score = 0
|
|
594
|
+
|
|
595
|
+
# Required fields
|
|
596
|
+
if info.get('title'):
|
|
597
|
+
score += 20
|
|
598
|
+
if info.get('version'):
|
|
599
|
+
score += 20
|
|
600
|
+
if info.get('description') and len(info['description']) > 20:
|
|
601
|
+
score += 30
|
|
602
|
+
|
|
603
|
+
# Optional but recommended fields
|
|
604
|
+
if info.get('contact'):
|
|
605
|
+
score += 15
|
|
606
|
+
if info.get('license'):
|
|
607
|
+
score += 15
|
|
608
|
+
|
|
609
|
+
return score
|
|
610
|
+
|
|
611
|
+
def _check_endpoint_documentation(self) -> float:
|
|
612
|
+
"""Check endpoint-level documentation completeness."""
|
|
613
|
+
paths = self.spec.get('paths', {})
|
|
614
|
+
|
|
615
|
+
total_operations = 0
|
|
616
|
+
documented_operations = 0
|
|
617
|
+
|
|
618
|
+
for path_obj in paths.values():
|
|
619
|
+
if not isinstance(path_obj, dict):
|
|
620
|
+
continue
|
|
621
|
+
|
|
622
|
+
for method, operation in path_obj.items():
|
|
623
|
+
if method.upper() not in self.http_methods:
|
|
624
|
+
continue
|
|
625
|
+
|
|
626
|
+
total_operations += 1
|
|
627
|
+
doc_score = 0
|
|
628
|
+
|
|
629
|
+
if operation.get('summary'):
|
|
630
|
+
doc_score += 1
|
|
631
|
+
if operation.get('description') and len(operation['description']) > 20:
|
|
632
|
+
doc_score += 1
|
|
633
|
+
if operation.get('operationId'):
|
|
634
|
+
doc_score += 1
|
|
635
|
+
|
|
636
|
+
# Consider it documented if it has at least 2/3 elements
|
|
637
|
+
if doc_score >= 2:
|
|
638
|
+
documented_operations += 1
|
|
639
|
+
|
|
640
|
+
return (documented_operations / total_operations * 100) if total_operations > 0 else 100
|
|
641
|
+
|
|
642
|
+
def _check_schema_documentation(self) -> float:
|
|
643
|
+
"""Check schema documentation completeness."""
|
|
644
|
+
schemas = self.spec.get('components', {}).get('schemas', {})
|
|
645
|
+
|
|
646
|
+
if not schemas:
|
|
647
|
+
return 80 # No schemas to document
|
|
648
|
+
|
|
649
|
+
total_schemas = len(schemas)
|
|
650
|
+
documented_schemas = 0
|
|
651
|
+
|
|
652
|
+
for schema_name, schema in schemas.items():
|
|
653
|
+
if not isinstance(schema, dict):
|
|
654
|
+
continue
|
|
655
|
+
|
|
656
|
+
doc_elements = 0
|
|
657
|
+
|
|
658
|
+
# Schema-level description
|
|
659
|
+
if schema.get('description'):
|
|
660
|
+
doc_elements += 1
|
|
661
|
+
|
|
662
|
+
# Property descriptions
|
|
663
|
+
properties = schema.get('properties', {})
|
|
664
|
+
if properties:
|
|
665
|
+
described_props = sum(1 for prop in properties.values()
|
|
666
|
+
if isinstance(prop, dict) and prop.get('description'))
|
|
667
|
+
if described_props > len(properties) * 0.5: # At least 50% documented
|
|
668
|
+
doc_elements += 1
|
|
669
|
+
|
|
670
|
+
# Examples
|
|
671
|
+
if schema.get('example') or any(
|
|
672
|
+
isinstance(prop, dict) and prop.get('example')
|
|
673
|
+
for prop in properties.values()
|
|
674
|
+
):
|
|
675
|
+
doc_elements += 1
|
|
676
|
+
|
|
677
|
+
if doc_elements >= 2:
|
|
678
|
+
documented_schemas += 1
|
|
679
|
+
|
|
680
|
+
return (documented_schemas / total_schemas * 100) if total_schemas > 0 else 100
|
|
681
|
+
|
|
682
|
+
def _check_parameter_documentation(self) -> float:
|
|
683
|
+
"""Check parameter documentation completeness."""
|
|
684
|
+
paths = self.spec.get('paths', {})
|
|
685
|
+
|
|
686
|
+
total_params = 0
|
|
687
|
+
documented_params = 0
|
|
688
|
+
|
|
689
|
+
for path_obj in paths.values():
|
|
690
|
+
if not isinstance(path_obj, dict):
|
|
691
|
+
continue
|
|
692
|
+
|
|
693
|
+
for method, operation in path_obj.items():
|
|
694
|
+
if method.upper() not in self.http_methods:
|
|
695
|
+
continue
|
|
696
|
+
|
|
697
|
+
parameters = operation.get('parameters', [])
|
|
698
|
+
for param in parameters:
|
|
699
|
+
if not isinstance(param, dict):
|
|
700
|
+
continue
|
|
701
|
+
|
|
702
|
+
total_params += 1
|
|
703
|
+
|
|
704
|
+
doc_score = 0
|
|
705
|
+
if param.get('description'):
|
|
706
|
+
doc_score += 1
|
|
707
|
+
if param.get('example') or (param.get('schema', {}).get('example')):
|
|
708
|
+
doc_score += 1
|
|
709
|
+
|
|
710
|
+
if doc_score >= 1: # At least description
|
|
711
|
+
documented_params += 1
|
|
712
|
+
|
|
713
|
+
return (documented_params / total_params * 100) if total_params > 0 else 100
|
|
714
|
+
|
|
715
|
+
def _check_response_documentation(self) -> float:
|
|
716
|
+
"""Check response documentation completeness."""
|
|
717
|
+
paths = self.spec.get('paths', {})
|
|
718
|
+
|
|
719
|
+
total_responses = 0
|
|
720
|
+
documented_responses = 0
|
|
721
|
+
|
|
722
|
+
for path_obj in paths.values():
|
|
723
|
+
if not isinstance(path_obj, dict):
|
|
724
|
+
continue
|
|
725
|
+
|
|
726
|
+
for method, operation in path_obj.items():
|
|
727
|
+
if method.upper() not in self.http_methods:
|
|
728
|
+
continue
|
|
729
|
+
|
|
730
|
+
responses = operation.get('responses', {})
|
|
731
|
+
for status_code, response in responses.items():
|
|
732
|
+
if not isinstance(response, dict):
|
|
733
|
+
continue
|
|
734
|
+
|
|
735
|
+
total_responses += 1
|
|
736
|
+
|
|
737
|
+
if response.get('description'):
|
|
738
|
+
documented_responses += 1
|
|
739
|
+
|
|
740
|
+
return (documented_responses / total_responses * 100) if total_responses > 0 else 100
|
|
741
|
+
|
|
742
|
+
def _check_example_coverage(self) -> float:
|
|
743
|
+
"""Check example coverage across the API."""
|
|
744
|
+
paths = self.spec.get('paths', {})
|
|
745
|
+
schemas = self.spec.get('components', {}).get('schemas', {})
|
|
746
|
+
|
|
747
|
+
# Check examples in operations
|
|
748
|
+
total_operations = 0
|
|
749
|
+
operations_with_examples = 0
|
|
750
|
+
|
|
751
|
+
for path_obj in paths.values():
|
|
752
|
+
if not isinstance(path_obj, dict):
|
|
753
|
+
continue
|
|
754
|
+
|
|
755
|
+
for method, operation in path_obj.items():
|
|
756
|
+
if method.upper() not in self.http_methods:
|
|
757
|
+
continue
|
|
758
|
+
|
|
759
|
+
total_operations += 1
|
|
760
|
+
|
|
761
|
+
has_example = False
|
|
762
|
+
|
|
763
|
+
# Check request body examples
|
|
764
|
+
request_body = operation.get('requestBody', {})
|
|
765
|
+
if self._has_examples(request_body.get('content', {})):
|
|
766
|
+
has_example = True
|
|
767
|
+
|
|
768
|
+
# Check response examples
|
|
769
|
+
responses = operation.get('responses', {})
|
|
770
|
+
for response in responses.values():
|
|
771
|
+
if isinstance(response, dict) and self._has_examples(response.get('content', {})):
|
|
772
|
+
has_example = True
|
|
773
|
+
break
|
|
774
|
+
|
|
775
|
+
if has_example:
|
|
776
|
+
operations_with_examples += 1
|
|
777
|
+
|
|
778
|
+
# Check examples in schemas
|
|
779
|
+
total_schemas = len(schemas)
|
|
780
|
+
schemas_with_examples = 0
|
|
781
|
+
|
|
782
|
+
for schema in schemas.values():
|
|
783
|
+
if isinstance(schema, dict) and self._schema_has_examples(schema):
|
|
784
|
+
schemas_with_examples += 1
|
|
785
|
+
|
|
786
|
+
# Combine scores
|
|
787
|
+
operation_score = (operations_with_examples / total_operations * 100) if total_operations > 0 else 100
|
|
788
|
+
schema_score = (schemas_with_examples / total_schemas * 100) if total_schemas > 0 else 100
|
|
789
|
+
|
|
790
|
+
return (operation_score + schema_score) / 2
|
|
791
|
+
|
|
792
|
+
def _has_examples(self, content: Dict[str, Any]) -> bool:
|
|
793
|
+
"""Check if content has examples."""
|
|
794
|
+
for media_type, media_obj in content.items():
|
|
795
|
+
if isinstance(media_obj, dict):
|
|
796
|
+
if media_obj.get('example') or media_obj.get('examples'):
|
|
797
|
+
return True
|
|
798
|
+
return False
|
|
799
|
+
|
|
800
|
+
def _schema_has_examples(self, schema: Dict[str, Any]) -> bool:
|
|
801
|
+
"""Check if schema has examples."""
|
|
802
|
+
if schema.get('example'):
|
|
803
|
+
return True
|
|
804
|
+
|
|
805
|
+
properties = schema.get('properties', {})
|
|
806
|
+
for prop in properties.values():
|
|
807
|
+
if isinstance(prop, dict) and prop.get('example'):
|
|
808
|
+
return True
|
|
809
|
+
|
|
810
|
+
return False
|
|
811
|
+
|
|
812
|
+
def _score_security(self) -> None:
|
|
813
|
+
"""Score API security implementation (20% weight)."""
|
|
814
|
+
category = ScoreCategory.SECURITY
|
|
815
|
+
score = CategoryScore(
|
|
816
|
+
category=category,
|
|
817
|
+
score=0.0,
|
|
818
|
+
max_score=100.0,
|
|
819
|
+
weight=self.category_weights[category]
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
security_checks = [
|
|
823
|
+
self._check_security_schemes(),
|
|
824
|
+
self._check_security_requirements(),
|
|
825
|
+
self._check_https_usage(),
|
|
826
|
+
self._check_authentication_patterns(),
|
|
827
|
+
self._check_sensitive_data_handling()
|
|
828
|
+
]
|
|
829
|
+
|
|
830
|
+
valid_scores = [s for s in security_checks if s is not None]
|
|
831
|
+
if valid_scores:
|
|
832
|
+
score.score = sum(valid_scores) / len(valid_scores)
|
|
833
|
+
|
|
834
|
+
# Add recommendations
|
|
835
|
+
if score.score < 50:
|
|
836
|
+
score.recommendations.extend([
|
|
837
|
+
"Implement comprehensive security schemes (OAuth2, API keys, etc.)",
|
|
838
|
+
"Ensure all endpoints have appropriate security requirements",
|
|
839
|
+
"Add input validation and rate limiting patterns"
|
|
840
|
+
])
|
|
841
|
+
elif score.score < 80:
|
|
842
|
+
score.recommendations.extend([
|
|
843
|
+
"Review security coverage for all endpoints",
|
|
844
|
+
"Consider additional security measures for sensitive operations"
|
|
845
|
+
])
|
|
846
|
+
|
|
847
|
+
self.scorecard.category_scores[category] = score
|
|
848
|
+
|
|
849
|
+
def _check_security_schemes(self) -> float:
|
|
850
|
+
"""Check security scheme definitions."""
|
|
851
|
+
security_schemes = self.spec.get('components', {}).get('securitySchemes', {})
|
|
852
|
+
|
|
853
|
+
if not security_schemes:
|
|
854
|
+
return 20 # Very low score for no security
|
|
855
|
+
|
|
856
|
+
score = 40 # Base score for having security schemes
|
|
857
|
+
|
|
858
|
+
scheme_types = set()
|
|
859
|
+
for scheme in security_schemes.values():
|
|
860
|
+
if isinstance(scheme, dict):
|
|
861
|
+
scheme_type = scheme.get('type')
|
|
862
|
+
scheme_types.add(scheme_type)
|
|
863
|
+
|
|
864
|
+
# Bonus for modern security schemes
|
|
865
|
+
if 'oauth2' in scheme_types:
|
|
866
|
+
score += 30
|
|
867
|
+
if 'apiKey' in scheme_types:
|
|
868
|
+
score += 15
|
|
869
|
+
if 'http' in scheme_types:
|
|
870
|
+
score += 15
|
|
871
|
+
|
|
872
|
+
return min(score, 100)
|
|
873
|
+
|
|
874
|
+
def _check_security_requirements(self) -> float:
|
|
875
|
+
"""Check security requirement coverage."""
|
|
876
|
+
paths = self.spec.get('paths', {})
|
|
877
|
+
global_security = self.spec.get('security', [])
|
|
878
|
+
|
|
879
|
+
total_operations = 0
|
|
880
|
+
secured_operations = 0
|
|
881
|
+
|
|
882
|
+
for path_obj in paths.values():
|
|
883
|
+
if not isinstance(path_obj, dict):
|
|
884
|
+
continue
|
|
885
|
+
|
|
886
|
+
for method, operation in path_obj.items():
|
|
887
|
+
if method.upper() not in self.http_methods:
|
|
888
|
+
continue
|
|
889
|
+
|
|
890
|
+
total_operations += 1
|
|
891
|
+
|
|
892
|
+
# Check if operation has security requirements
|
|
893
|
+
operation_security = operation.get('security')
|
|
894
|
+
|
|
895
|
+
if operation_security is not None:
|
|
896
|
+
secured_operations += 1
|
|
897
|
+
elif global_security:
|
|
898
|
+
secured_operations += 1
|
|
899
|
+
|
|
900
|
+
return (secured_operations / total_operations * 100) if total_operations > 0 else 0
|
|
901
|
+
|
|
902
|
+
def _check_https_usage(self) -> float:
|
|
903
|
+
"""Check HTTPS enforcement."""
|
|
904
|
+
servers = self.spec.get('servers', [])
|
|
905
|
+
|
|
906
|
+
if not servers:
|
|
907
|
+
return 60 # No servers defined - assume HTTPS
|
|
908
|
+
|
|
909
|
+
https_servers = 0
|
|
910
|
+
for server in servers:
|
|
911
|
+
if isinstance(server, dict):
|
|
912
|
+
url = server.get('url', '')
|
|
913
|
+
if url.startswith('https://') or not url.startswith('http://'):
|
|
914
|
+
https_servers += 1
|
|
915
|
+
|
|
916
|
+
return (https_servers / len(servers) * 100) if servers else 100
|
|
917
|
+
|
|
918
|
+
def _check_authentication_patterns(self) -> float:
|
|
919
|
+
"""Check authentication pattern quality."""
|
|
920
|
+
security_schemes = self.spec.get('components', {}).get('securitySchemes', {})
|
|
921
|
+
|
|
922
|
+
if not security_schemes:
|
|
923
|
+
return 0
|
|
924
|
+
|
|
925
|
+
pattern_scores = []
|
|
926
|
+
|
|
927
|
+
for scheme in security_schemes.values():
|
|
928
|
+
if not isinstance(scheme, dict):
|
|
929
|
+
continue
|
|
930
|
+
|
|
931
|
+
scheme_type = scheme.get('type', '').lower()
|
|
932
|
+
|
|
933
|
+
if scheme_type == 'oauth2':
|
|
934
|
+
# OAuth2 is highly recommended
|
|
935
|
+
flows = scheme.get('flows', {})
|
|
936
|
+
if flows:
|
|
937
|
+
pattern_scores.append(95)
|
|
938
|
+
else:
|
|
939
|
+
pattern_scores.append(80)
|
|
940
|
+
elif scheme_type == 'http':
|
|
941
|
+
scheme_scheme = scheme.get('scheme', '').lower()
|
|
942
|
+
if scheme_scheme == 'bearer':
|
|
943
|
+
pattern_scores.append(85)
|
|
944
|
+
elif scheme_scheme == 'basic':
|
|
945
|
+
pattern_scores.append(60) # Less secure
|
|
946
|
+
else:
|
|
947
|
+
pattern_scores.append(70)
|
|
948
|
+
elif scheme_type == 'apikey':
|
|
949
|
+
location = scheme.get('in', '').lower()
|
|
950
|
+
if location == 'header':
|
|
951
|
+
pattern_scores.append(75)
|
|
952
|
+
else:
|
|
953
|
+
pattern_scores.append(60) # Query/cookie less secure
|
|
954
|
+
else:
|
|
955
|
+
pattern_scores.append(50) # Unknown scheme
|
|
956
|
+
|
|
957
|
+
return sum(pattern_scores) / len(pattern_scores) if pattern_scores else 0
|
|
958
|
+
|
|
959
|
+
def _check_sensitive_data_handling(self) -> float:
|
|
960
|
+
"""Check sensitive data handling patterns."""
|
|
961
|
+
# This is a simplified check - in reality would need more sophisticated analysis
|
|
962
|
+
schemas = self.spec.get('components', {}).get('schemas', {})
|
|
963
|
+
|
|
964
|
+
score = 80 # Default good score
|
|
965
|
+
|
|
966
|
+
# Look for potential sensitive fields without proper handling
|
|
967
|
+
sensitive_field_names = {'password', 'secret', 'token', 'key', 'ssn', 'credit_card'}
|
|
968
|
+
|
|
969
|
+
for schema in schemas.values():
|
|
970
|
+
if not isinstance(schema, dict):
|
|
971
|
+
continue
|
|
972
|
+
|
|
973
|
+
properties = schema.get('properties', {})
|
|
974
|
+
for prop_name, prop_def in properties.items():
|
|
975
|
+
if not isinstance(prop_def, dict):
|
|
976
|
+
continue
|
|
977
|
+
|
|
978
|
+
# Check for sensitive field names
|
|
979
|
+
if any(sensitive in prop_name.lower() for sensitive in sensitive_field_names):
|
|
980
|
+
# Check if it's marked as sensitive (writeOnly, format: password, etc.)
|
|
981
|
+
if not (prop_def.get('writeOnly') or
|
|
982
|
+
prop_def.get('format') == 'password' or
|
|
983
|
+
'password' in prop_def.get('description', '').lower()):
|
|
984
|
+
score -= 10 # Penalty for exposed sensitive field
|
|
985
|
+
|
|
986
|
+
return max(score, 0)
|
|
987
|
+
|
|
988
|
+
def _score_usability(self) -> None:
|
|
989
|
+
"""Score API usability and developer experience (15% weight)."""
|
|
990
|
+
category = ScoreCategory.USABILITY
|
|
991
|
+
score = CategoryScore(
|
|
992
|
+
category=category,
|
|
993
|
+
score=0.0,
|
|
994
|
+
max_score=100.0,
|
|
995
|
+
weight=self.category_weights[category]
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
usability_checks = [
|
|
999
|
+
self._check_discoverability(),
|
|
1000
|
+
self._check_error_handling(),
|
|
1001
|
+
self._check_filtering_and_searching(),
|
|
1002
|
+
self._check_resource_relationships(),
|
|
1003
|
+
self._check_developer_experience()
|
|
1004
|
+
]
|
|
1005
|
+
|
|
1006
|
+
valid_scores = [s for s in usability_checks if s is not None]
|
|
1007
|
+
if valid_scores:
|
|
1008
|
+
score.score = sum(valid_scores) / len(valid_scores)
|
|
1009
|
+
|
|
1010
|
+
# Add recommendations
|
|
1011
|
+
if score.score < 60:
|
|
1012
|
+
score.recommendations.extend([
|
|
1013
|
+
"Improve error messages with actionable guidance",
|
|
1014
|
+
"Add filtering and search capabilities to list endpoints",
|
|
1015
|
+
"Enhance resource discoverability with better linking"
|
|
1016
|
+
])
|
|
1017
|
+
elif score.score < 80:
|
|
1018
|
+
score.recommendations.extend([
|
|
1019
|
+
"Consider adding HATEOAS links for better discoverability",
|
|
1020
|
+
"Enhance developer experience with better examples"
|
|
1021
|
+
])
|
|
1022
|
+
|
|
1023
|
+
self.scorecard.category_scores[category] = score
|
|
1024
|
+
|
|
1025
|
+
def _check_discoverability(self) -> float:
|
|
1026
|
+
"""Check API discoverability features."""
|
|
1027
|
+
paths = self.spec.get('paths', {})
|
|
1028
|
+
|
|
1029
|
+
# Look for root/discovery endpoints
|
|
1030
|
+
has_root = '/' in paths or any(path == '/api' or path.startswith('/api/') for path in paths)
|
|
1031
|
+
|
|
1032
|
+
# Look for HATEOAS patterns in responses
|
|
1033
|
+
hateoas_score = 0
|
|
1034
|
+
total_responses = 0
|
|
1035
|
+
|
|
1036
|
+
for path_obj in paths.values():
|
|
1037
|
+
if not isinstance(path_obj, dict):
|
|
1038
|
+
continue
|
|
1039
|
+
|
|
1040
|
+
for method, operation in path_obj.items():
|
|
1041
|
+
if method.upper() not in self.http_methods:
|
|
1042
|
+
continue
|
|
1043
|
+
|
|
1044
|
+
responses = operation.get('responses', {})
|
|
1045
|
+
for response in responses.values():
|
|
1046
|
+
if not isinstance(response, dict):
|
|
1047
|
+
continue
|
|
1048
|
+
|
|
1049
|
+
total_responses += 1
|
|
1050
|
+
|
|
1051
|
+
# Look for link-like properties in response schemas
|
|
1052
|
+
content = response.get('content', {})
|
|
1053
|
+
for media_obj in content.values():
|
|
1054
|
+
schema = media_obj.get('schema', {})
|
|
1055
|
+
if self._has_link_properties(schema):
|
|
1056
|
+
hateoas_score += 1
|
|
1057
|
+
break
|
|
1058
|
+
|
|
1059
|
+
discovery_score = 50 if has_root else 30
|
|
1060
|
+
if total_responses > 0:
|
|
1061
|
+
hateoas_ratio = hateoas_score / total_responses
|
|
1062
|
+
discovery_score += hateoas_ratio * 50
|
|
1063
|
+
|
|
1064
|
+
return min(discovery_score, 100)
|
|
1065
|
+
|
|
1066
|
+
def _has_link_properties(self, schema: Dict[str, Any]) -> bool:
|
|
1067
|
+
"""Check if schema has link-like properties."""
|
|
1068
|
+
if not isinstance(schema, dict):
|
|
1069
|
+
return False
|
|
1070
|
+
|
|
1071
|
+
properties = schema.get('properties', {})
|
|
1072
|
+
link_indicators = {'links', '_links', 'href', 'url', 'self', 'next', 'prev'}
|
|
1073
|
+
|
|
1074
|
+
return any(prop_name.lower() in link_indicators for prop_name in properties.keys())
|
|
1075
|
+
|
|
1076
|
+
def _check_error_handling(self) -> float:
|
|
1077
|
+
"""Check error handling quality."""
|
|
1078
|
+
paths = self.spec.get('paths', {})
|
|
1079
|
+
|
|
1080
|
+
total_operations = 0
|
|
1081
|
+
operations_with_errors = 0
|
|
1082
|
+
detailed_error_responses = 0
|
|
1083
|
+
|
|
1084
|
+
for path_obj in paths.values():
|
|
1085
|
+
if not isinstance(path_obj, dict):
|
|
1086
|
+
continue
|
|
1087
|
+
|
|
1088
|
+
for method, operation in path_obj.items():
|
|
1089
|
+
if method.upper() not in self.http_methods:
|
|
1090
|
+
continue
|
|
1091
|
+
|
|
1092
|
+
total_operations += 1
|
|
1093
|
+
responses = operation.get('responses', {})
|
|
1094
|
+
|
|
1095
|
+
# Check for error responses
|
|
1096
|
+
has_error_responses = any(
|
|
1097
|
+
status_code.startswith('4') or status_code.startswith('5')
|
|
1098
|
+
for status_code in responses.keys()
|
|
1099
|
+
)
|
|
1100
|
+
|
|
1101
|
+
if has_error_responses:
|
|
1102
|
+
operations_with_errors += 1
|
|
1103
|
+
|
|
1104
|
+
# Check for detailed error schemas
|
|
1105
|
+
for status_code, response in responses.items():
|
|
1106
|
+
if (status_code.startswith('4') or status_code.startswith('5')) and isinstance(response, dict):
|
|
1107
|
+
content = response.get('content', {})
|
|
1108
|
+
for media_obj in content.values():
|
|
1109
|
+
schema = media_obj.get('schema', {})
|
|
1110
|
+
if self._has_detailed_error_schema(schema):
|
|
1111
|
+
detailed_error_responses += 1
|
|
1112
|
+
break
|
|
1113
|
+
break
|
|
1114
|
+
|
|
1115
|
+
if total_operations == 0:
|
|
1116
|
+
return 0
|
|
1117
|
+
|
|
1118
|
+
error_coverage = (operations_with_errors / total_operations) * 60
|
|
1119
|
+
error_detail = (detailed_error_responses / operations_with_errors * 40) if operations_with_errors > 0 else 0
|
|
1120
|
+
|
|
1121
|
+
return error_coverage + error_detail
|
|
1122
|
+
|
|
1123
|
+
def _has_detailed_error_schema(self, schema: Dict[str, Any]) -> bool:
|
|
1124
|
+
"""Check if error schema has detailed information."""
|
|
1125
|
+
if not isinstance(schema, dict):
|
|
1126
|
+
return False
|
|
1127
|
+
|
|
1128
|
+
properties = schema.get('properties', {})
|
|
1129
|
+
error_fields = {'error', 'message', 'details', 'code', 'timestamp'}
|
|
1130
|
+
|
|
1131
|
+
matching_fields = sum(1 for field in error_fields if field in properties)
|
|
1132
|
+
return matching_fields >= 2 # At least 2 standard error fields
|
|
1133
|
+
|
|
1134
|
+
def _check_filtering_and_searching(self) -> float:
|
|
1135
|
+
"""Check filtering and search capabilities."""
|
|
1136
|
+
paths = self.spec.get('paths', {})
|
|
1137
|
+
|
|
1138
|
+
collection_endpoints = 0
|
|
1139
|
+
endpoints_with_filtering = 0
|
|
1140
|
+
|
|
1141
|
+
for path, path_obj in paths.items():
|
|
1142
|
+
if not isinstance(path_obj, dict):
|
|
1143
|
+
continue
|
|
1144
|
+
|
|
1145
|
+
# Identify collection endpoints (no path parameters)
|
|
1146
|
+
if '{' not in path:
|
|
1147
|
+
get_operation = path_obj.get('get')
|
|
1148
|
+
if get_operation:
|
|
1149
|
+
collection_endpoints += 1
|
|
1150
|
+
|
|
1151
|
+
# Check for filtering/search parameters
|
|
1152
|
+
parameters = get_operation.get('parameters', [])
|
|
1153
|
+
filter_params = {'filter', 'search', 'q', 'query', 'limit', 'page', 'offset'}
|
|
1154
|
+
|
|
1155
|
+
has_filtering = any(
|
|
1156
|
+
isinstance(param, dict) and param.get('name', '').lower() in filter_params
|
|
1157
|
+
for param in parameters
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
if has_filtering:
|
|
1161
|
+
endpoints_with_filtering += 1
|
|
1162
|
+
|
|
1163
|
+
return (endpoints_with_filtering / collection_endpoints * 100) if collection_endpoints > 0 else 100
|
|
1164
|
+
|
|
1165
|
+
def _check_resource_relationships(self) -> float:
|
|
1166
|
+
"""Check resource relationship handling."""
|
|
1167
|
+
paths = self.spec.get('paths', {})
|
|
1168
|
+
schemas = self.spec.get('components', {}).get('schemas', {})
|
|
1169
|
+
|
|
1170
|
+
# Look for nested resource patterns
|
|
1171
|
+
nested_resources = 0
|
|
1172
|
+
total_resource_paths = 0
|
|
1173
|
+
|
|
1174
|
+
for path in paths.keys():
|
|
1175
|
+
# Skip root paths
|
|
1176
|
+
if path.count('/') >= 3: # e.g., /api/users/123/orders
|
|
1177
|
+
total_resource_paths += 1
|
|
1178
|
+
if '{' in path:
|
|
1179
|
+
nested_resources += 1
|
|
1180
|
+
|
|
1181
|
+
# Look for relationship fields in schemas
|
|
1182
|
+
schemas_with_relations = 0
|
|
1183
|
+
for schema in schemas.values():
|
|
1184
|
+
if not isinstance(schema, dict):
|
|
1185
|
+
continue
|
|
1186
|
+
|
|
1187
|
+
properties = schema.get('properties', {})
|
|
1188
|
+
relation_indicators = {'id', '_id', 'ref', 'link', 'relationship'}
|
|
1189
|
+
|
|
1190
|
+
has_relations = any(
|
|
1191
|
+
any(indicator in prop_name.lower() for indicator in relation_indicators)
|
|
1192
|
+
for prop_name in properties.keys()
|
|
1193
|
+
)
|
|
1194
|
+
|
|
1195
|
+
if has_relations:
|
|
1196
|
+
schemas_with_relations += 1
|
|
1197
|
+
|
|
1198
|
+
nested_score = (nested_resources / total_resource_paths * 50) if total_resource_paths > 0 else 25
|
|
1199
|
+
schema_score = (schemas_with_relations / len(schemas) * 50) if schemas else 25
|
|
1200
|
+
|
|
1201
|
+
return nested_score + schema_score
|
|
1202
|
+
|
|
1203
|
+
def _check_developer_experience(self) -> float:
|
|
1204
|
+
"""Check overall developer experience factors."""
|
|
1205
|
+
# This is a composite score based on various DX factors
|
|
1206
|
+
factors = []
|
|
1207
|
+
|
|
1208
|
+
# Factor 1: Consistent response structure
|
|
1209
|
+
factors.append(self._check_response_consistency())
|
|
1210
|
+
|
|
1211
|
+
# Factor 2: Clear operation IDs
|
|
1212
|
+
paths = self.spec.get('paths', {})
|
|
1213
|
+
total_operations = 0
|
|
1214
|
+
operations_with_ids = 0
|
|
1215
|
+
|
|
1216
|
+
for path_obj in paths.values():
|
|
1217
|
+
if not isinstance(path_obj, dict):
|
|
1218
|
+
continue
|
|
1219
|
+
|
|
1220
|
+
for method, operation in path_obj.items():
|
|
1221
|
+
if method.upper() not in self.http_methods:
|
|
1222
|
+
continue
|
|
1223
|
+
|
|
1224
|
+
total_operations += 1
|
|
1225
|
+
if isinstance(operation, dict) and operation.get('operationId'):
|
|
1226
|
+
operations_with_ids += 1
|
|
1227
|
+
|
|
1228
|
+
operation_id_score = (operations_with_ids / total_operations * 100) if total_operations > 0 else 100
|
|
1229
|
+
factors.append(operation_id_score)
|
|
1230
|
+
|
|
1231
|
+
# Factor 3: Reasonable path complexity
|
|
1232
|
+
avg_path_complexity = 0
|
|
1233
|
+
if paths:
|
|
1234
|
+
complexities = []
|
|
1235
|
+
for path in paths.keys():
|
|
1236
|
+
segments = [seg for seg in path.split('/') if seg]
|
|
1237
|
+
complexities.append(len(segments))
|
|
1238
|
+
|
|
1239
|
+
avg_complexity = sum(complexities) / len(complexities)
|
|
1240
|
+
# Optimal complexity is 3-4 segments
|
|
1241
|
+
if 3 <= avg_complexity <= 4:
|
|
1242
|
+
avg_path_complexity = 100
|
|
1243
|
+
elif 2 <= avg_complexity <= 5:
|
|
1244
|
+
avg_path_complexity = 80
|
|
1245
|
+
else:
|
|
1246
|
+
avg_path_complexity = 60
|
|
1247
|
+
|
|
1248
|
+
factors.append(avg_path_complexity)
|
|
1249
|
+
|
|
1250
|
+
return sum(factors) / len(factors) if factors else 0
|
|
1251
|
+
|
|
1252
|
+
def _score_performance(self) -> None:
|
|
1253
|
+
"""Score API performance patterns (15% weight)."""
|
|
1254
|
+
category = ScoreCategory.PERFORMANCE
|
|
1255
|
+
score = CategoryScore(
|
|
1256
|
+
category=category,
|
|
1257
|
+
score=0.0,
|
|
1258
|
+
max_score=100.0,
|
|
1259
|
+
weight=self.category_weights[category]
|
|
1260
|
+
)
|
|
1261
|
+
|
|
1262
|
+
performance_checks = [
|
|
1263
|
+
self._check_caching_headers(),
|
|
1264
|
+
self._check_pagination_patterns(),
|
|
1265
|
+
self._check_compression_support(),
|
|
1266
|
+
self._check_efficiency_patterns(),
|
|
1267
|
+
self._check_batch_operations()
|
|
1268
|
+
]
|
|
1269
|
+
|
|
1270
|
+
valid_scores = [s for s in performance_checks if s is not None]
|
|
1271
|
+
if valid_scores:
|
|
1272
|
+
score.score = sum(valid_scores) / len(valid_scores)
|
|
1273
|
+
|
|
1274
|
+
# Add recommendations
|
|
1275
|
+
if score.score < 60:
|
|
1276
|
+
score.recommendations.extend([
|
|
1277
|
+
"Implement pagination for list endpoints",
|
|
1278
|
+
"Add caching headers for cacheable responses",
|
|
1279
|
+
"Consider batch operations for bulk updates"
|
|
1280
|
+
])
|
|
1281
|
+
elif score.score < 80:
|
|
1282
|
+
score.recommendations.extend([
|
|
1283
|
+
"Review caching strategies for better performance",
|
|
1284
|
+
"Consider field selection parameters for large responses"
|
|
1285
|
+
])
|
|
1286
|
+
|
|
1287
|
+
self.scorecard.category_scores[category] = score
|
|
1288
|
+
|
|
1289
|
+
def _check_caching_headers(self) -> float:
|
|
1290
|
+
"""Check caching header implementation."""
|
|
1291
|
+
paths = self.spec.get('paths', {})
|
|
1292
|
+
|
|
1293
|
+
get_operations = 0
|
|
1294
|
+
cacheable_operations = 0
|
|
1295
|
+
|
|
1296
|
+
for path_obj in paths.values():
|
|
1297
|
+
if not isinstance(path_obj, dict):
|
|
1298
|
+
continue
|
|
1299
|
+
|
|
1300
|
+
get_operation = path_obj.get('get')
|
|
1301
|
+
if get_operation and isinstance(get_operation, dict):
|
|
1302
|
+
get_operations += 1
|
|
1303
|
+
|
|
1304
|
+
# Check for caching-related headers in responses
|
|
1305
|
+
responses = get_operation.get('responses', {})
|
|
1306
|
+
for response in responses.values():
|
|
1307
|
+
if not isinstance(response, dict):
|
|
1308
|
+
continue
|
|
1309
|
+
|
|
1310
|
+
headers = response.get('headers', {})
|
|
1311
|
+
cache_headers = {'cache-control', 'etag', 'last-modified', 'expires'}
|
|
1312
|
+
|
|
1313
|
+
if any(header.lower() in cache_headers for header in headers.keys()):
|
|
1314
|
+
cacheable_operations += 1
|
|
1315
|
+
break
|
|
1316
|
+
|
|
1317
|
+
return (cacheable_operations / get_operations * 100) if get_operations > 0 else 50
|
|
1318
|
+
|
|
1319
|
+
def _check_pagination_patterns(self) -> float:
|
|
1320
|
+
"""Check pagination implementation."""
|
|
1321
|
+
paths = self.spec.get('paths', {})
|
|
1322
|
+
|
|
1323
|
+
collection_endpoints = 0
|
|
1324
|
+
paginated_endpoints = 0
|
|
1325
|
+
|
|
1326
|
+
for path, path_obj in paths.items():
|
|
1327
|
+
if not isinstance(path_obj, dict):
|
|
1328
|
+
continue
|
|
1329
|
+
|
|
1330
|
+
# Identify collection endpoints
|
|
1331
|
+
if '{' not in path: # No path parameters = collection
|
|
1332
|
+
get_operation = path_obj.get('get')
|
|
1333
|
+
if get_operation and isinstance(get_operation, dict):
|
|
1334
|
+
collection_endpoints += 1
|
|
1335
|
+
|
|
1336
|
+
# Check for pagination parameters
|
|
1337
|
+
parameters = get_operation.get('parameters', [])
|
|
1338
|
+
pagination_params = {'limit', 'offset', 'page', 'pagesize', 'per_page', 'cursor'}
|
|
1339
|
+
|
|
1340
|
+
has_pagination = any(
|
|
1341
|
+
isinstance(param, dict) and param.get('name', '').lower() in pagination_params
|
|
1342
|
+
for param in parameters
|
|
1343
|
+
)
|
|
1344
|
+
|
|
1345
|
+
if has_pagination:
|
|
1346
|
+
paginated_endpoints += 1
|
|
1347
|
+
|
|
1348
|
+
return (paginated_endpoints / collection_endpoints * 100) if collection_endpoints > 0 else 100
|
|
1349
|
+
|
|
1350
|
+
def _check_compression_support(self) -> float:
|
|
1351
|
+
"""Check compression support indicators."""
|
|
1352
|
+
# This is speculative - OpenAPI doesn't directly specify compression
|
|
1353
|
+
# Look for indicators that compression is considered
|
|
1354
|
+
|
|
1355
|
+
servers = self.spec.get('servers', [])
|
|
1356
|
+
|
|
1357
|
+
# Check if any server descriptions mention compression
|
|
1358
|
+
compression_mentions = 0
|
|
1359
|
+
for server in servers:
|
|
1360
|
+
if isinstance(server, dict):
|
|
1361
|
+
description = server.get('description', '').lower()
|
|
1362
|
+
if any(term in description for term in ['gzip', 'compress', 'deflate']):
|
|
1363
|
+
compression_mentions += 1
|
|
1364
|
+
|
|
1365
|
+
# Base score - assume compression is handled at server level
|
|
1366
|
+
base_score = 70
|
|
1367
|
+
|
|
1368
|
+
if compression_mentions > 0:
|
|
1369
|
+
return min(base_score + (compression_mentions * 10), 100)
|
|
1370
|
+
|
|
1371
|
+
return base_score
|
|
1372
|
+
|
|
1373
|
+
def _check_efficiency_patterns(self) -> float:
|
|
1374
|
+
"""Check efficiency patterns like field selection."""
|
|
1375
|
+
paths = self.spec.get('paths', {})
|
|
1376
|
+
|
|
1377
|
+
total_get_operations = 0
|
|
1378
|
+
operations_with_selection = 0
|
|
1379
|
+
|
|
1380
|
+
for path_obj in paths.values():
|
|
1381
|
+
if not isinstance(path_obj, dict):
|
|
1382
|
+
continue
|
|
1383
|
+
|
|
1384
|
+
get_operation = path_obj.get('get')
|
|
1385
|
+
if get_operation and isinstance(get_operation, dict):
|
|
1386
|
+
total_get_operations += 1
|
|
1387
|
+
|
|
1388
|
+
# Check for field selection parameters
|
|
1389
|
+
parameters = get_operation.get('parameters', [])
|
|
1390
|
+
selection_params = {'fields', 'select', 'include', 'exclude'}
|
|
1391
|
+
|
|
1392
|
+
has_selection = any(
|
|
1393
|
+
isinstance(param, dict) and param.get('name', '').lower() in selection_params
|
|
1394
|
+
for param in parameters
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
if has_selection:
|
|
1398
|
+
operations_with_selection += 1
|
|
1399
|
+
|
|
1400
|
+
return (operations_with_selection / total_get_operations * 100) if total_get_operations > 0 else 60
|
|
1401
|
+
|
|
1402
|
+
def _check_batch_operations(self) -> float:
|
|
1403
|
+
"""Check for batch operation support."""
|
|
1404
|
+
paths = self.spec.get('paths', {})
|
|
1405
|
+
|
|
1406
|
+
# Look for batch endpoints
|
|
1407
|
+
batch_indicators = ['batch', 'bulk', 'multi']
|
|
1408
|
+
batch_endpoints = 0
|
|
1409
|
+
|
|
1410
|
+
for path in paths.keys():
|
|
1411
|
+
if any(indicator in path.lower() for indicator in batch_indicators):
|
|
1412
|
+
batch_endpoints += 1
|
|
1413
|
+
|
|
1414
|
+
# Look for array-based request bodies (indicating batch operations)
|
|
1415
|
+
array_operations = 0
|
|
1416
|
+
total_post_put_operations = 0
|
|
1417
|
+
|
|
1418
|
+
for path_obj in paths.values():
|
|
1419
|
+
if not isinstance(path_obj, dict):
|
|
1420
|
+
continue
|
|
1421
|
+
|
|
1422
|
+
for method in ['post', 'put', 'patch']:
|
|
1423
|
+
operation = path_obj.get(method)
|
|
1424
|
+
if operation and isinstance(operation, dict):
|
|
1425
|
+
total_post_put_operations += 1
|
|
1426
|
+
|
|
1427
|
+
request_body = operation.get('requestBody', {})
|
|
1428
|
+
content = request_body.get('content', {})
|
|
1429
|
+
|
|
1430
|
+
for media_obj in content.values():
|
|
1431
|
+
schema = media_obj.get('schema', {})
|
|
1432
|
+
if schema.get('type') == 'array':
|
|
1433
|
+
array_operations += 1
|
|
1434
|
+
break
|
|
1435
|
+
|
|
1436
|
+
# Score based on presence of batch patterns
|
|
1437
|
+
batch_score = min(batch_endpoints * 20, 60) # Up to 60 points for explicit batch endpoints
|
|
1438
|
+
|
|
1439
|
+
if total_post_put_operations > 0:
|
|
1440
|
+
array_score = (array_operations / total_post_put_operations) * 40
|
|
1441
|
+
batch_score += array_score
|
|
1442
|
+
|
|
1443
|
+
return min(batch_score, 100)
|
|
1444
|
+
|
|
1445
|
+
def generate_json_report(self) -> str:
|
|
1446
|
+
"""Generate JSON format scorecard."""
|
|
1447
|
+
report_data = {
|
|
1448
|
+
"overall": {
|
|
1449
|
+
"score": round(self.scorecard.overall_score, 2),
|
|
1450
|
+
"grade": self.scorecard.overall_grade,
|
|
1451
|
+
"totalEndpoints": self.scorecard.total_endpoints
|
|
1452
|
+
},
|
|
1453
|
+
"api_info": self.scorecard.api_info,
|
|
1454
|
+
"categories": {},
|
|
1455
|
+
"topRecommendations": self.scorecard.get_top_recommendations()
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
for category, score in self.scorecard.category_scores.items():
|
|
1459
|
+
report_data["categories"][category.value] = {
|
|
1460
|
+
"score": round(score.score, 2),
|
|
1461
|
+
"grade": score.letter_grade,
|
|
1462
|
+
"weight": score.weight,
|
|
1463
|
+
"weightedScore": round(score.weighted_score, 2),
|
|
1464
|
+
"issues": score.issues,
|
|
1465
|
+
"recommendations": score.recommendations
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
return json.dumps(report_data, indent=2)
|
|
1469
|
+
|
|
1470
|
+
def generate_text_report(self) -> str:
|
|
1471
|
+
"""Generate human-readable scorecard report."""
|
|
1472
|
+
lines = [
|
|
1473
|
+
"═══════════════════════════════════════════════════════════════",
|
|
1474
|
+
" API DESIGN SCORECARD",
|
|
1475
|
+
"═══════════════════════════════════════════════════════════════",
|
|
1476
|
+
f"API: {self.scorecard.api_info.get('title', 'Unknown')}",
|
|
1477
|
+
f"Version: {self.scorecard.api_info.get('version', 'Unknown')}",
|
|
1478
|
+
f"Total Endpoints: {self.scorecard.total_endpoints}",
|
|
1479
|
+
"",
|
|
1480
|
+
f"🏆 OVERALL GRADE: {self.scorecard.overall_grade} ({self.scorecard.overall_score:.1f}/100.0)",
|
|
1481
|
+
"",
|
|
1482
|
+
"═══════════════════════════════════════════════════════════════",
|
|
1483
|
+
"DETAILED BREAKDOWN:",
|
|
1484
|
+
"═══════════════════════════════════════════════════════════════"
|
|
1485
|
+
]
|
|
1486
|
+
|
|
1487
|
+
# Sort categories by weight (most important first)
|
|
1488
|
+
sorted_categories = sorted(
|
|
1489
|
+
self.scorecard.category_scores.items(),
|
|
1490
|
+
key=lambda x: x[1].weight,
|
|
1491
|
+
reverse=True
|
|
1492
|
+
)
|
|
1493
|
+
|
|
1494
|
+
for category, score in sorted_categories:
|
|
1495
|
+
category_name = category.value.title().replace('_', ' ')
|
|
1496
|
+
|
|
1497
|
+
lines.extend([
|
|
1498
|
+
"",
|
|
1499
|
+
f"📊 {category_name.upper()} - Grade: {score.letter_grade} ({score.score:.1f}/100)",
|
|
1500
|
+
f" Weight: {score.weight}% | Contribution: {score.weighted_score:.1f} points",
|
|
1501
|
+
" " + "─" * 50
|
|
1502
|
+
])
|
|
1503
|
+
|
|
1504
|
+
if score.recommendations:
|
|
1505
|
+
lines.append(" 💡 Recommendations:")
|
|
1506
|
+
for rec in score.recommendations[:3]: # Top 3 recommendations
|
|
1507
|
+
lines.append(f" • {rec}")
|
|
1508
|
+
else:
|
|
1509
|
+
lines.append(" ✅ No specific recommendations - performing well!")
|
|
1510
|
+
|
|
1511
|
+
# Overall assessment
|
|
1512
|
+
lines.extend([
|
|
1513
|
+
"",
|
|
1514
|
+
"═══════════════════════════════════════════════════════════════",
|
|
1515
|
+
"OVERALL ASSESSMENT:",
|
|
1516
|
+
"═══════════════════════════════════════════════════════════════"
|
|
1517
|
+
])
|
|
1518
|
+
|
|
1519
|
+
if self.scorecard.overall_grade == "A":
|
|
1520
|
+
lines.extend([
|
|
1521
|
+
"🏆 EXCELLENT! Your API demonstrates outstanding design quality.",
|
|
1522
|
+
" Continue following these best practices and consider sharing",
|
|
1523
|
+
" your approach as a reference for other teams."
|
|
1524
|
+
])
|
|
1525
|
+
elif self.scorecard.overall_grade == "B":
|
|
1526
|
+
lines.extend([
|
|
1527
|
+
"✅ GOOD! Your API follows most best practices with room for",
|
|
1528
|
+
" minor improvements. Focus on the recommendations above",
|
|
1529
|
+
" to achieve excellence."
|
|
1530
|
+
])
|
|
1531
|
+
elif self.scorecard.overall_grade == "C":
|
|
1532
|
+
lines.extend([
|
|
1533
|
+
"⚠️ FAIR! Your API has a solid foundation but several areas",
|
|
1534
|
+
" need improvement. Prioritize the high-weight categories",
|
|
1535
|
+
" for maximum impact."
|
|
1536
|
+
])
|
|
1537
|
+
elif self.scorecard.overall_grade == "D":
|
|
1538
|
+
lines.extend([
|
|
1539
|
+
"❌ NEEDS IMPROVEMENT! Your API has significant issues that",
|
|
1540
|
+
" may impact developer experience and maintainability.",
|
|
1541
|
+
" Focus on consistency and documentation first."
|
|
1542
|
+
])
|
|
1543
|
+
else: # Grade F
|
|
1544
|
+
lines.extend([
|
|
1545
|
+
"🚨 CRITICAL ISSUES! Your API requires major redesign to meet",
|
|
1546
|
+
" basic quality standards. Consider comprehensive review",
|
|
1547
|
+
" of design principles and best practices."
|
|
1548
|
+
])
|
|
1549
|
+
|
|
1550
|
+
# Top recommendations
|
|
1551
|
+
top_recs = self.scorecard.get_top_recommendations(3)
|
|
1552
|
+
if top_recs:
|
|
1553
|
+
lines.extend([
|
|
1554
|
+
"",
|
|
1555
|
+
"🎯 TOP PRIORITY RECOMMENDATIONS:",
|
|
1556
|
+
""
|
|
1557
|
+
])
|
|
1558
|
+
for i, rec in enumerate(top_recs, 1):
|
|
1559
|
+
lines.append(f" {i}. {rec}")
|
|
1560
|
+
|
|
1561
|
+
lines.extend([
|
|
1562
|
+
"",
|
|
1563
|
+
"═══════════════════════════════════════════════════════════════",
|
|
1564
|
+
f"Generated by API Scorecard Tool | Score: {self.scorecard.overall_grade} ({self.scorecard.overall_score:.1f}%)",
|
|
1565
|
+
"═══════════════════════════════════════════════════════════════"
|
|
1566
|
+
])
|
|
1567
|
+
|
|
1568
|
+
return "\n".join(lines)
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
def main():
|
|
1572
|
+
"""Main CLI entry point."""
|
|
1573
|
+
parser = argparse.ArgumentParser(
|
|
1574
|
+
description="Generate comprehensive API design quality scorecard",
|
|
1575
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1576
|
+
epilog="""
|
|
1577
|
+
Examples:
|
|
1578
|
+
python api_scorecard.py openapi.json
|
|
1579
|
+
python api_scorecard.py --format json openapi.json > scorecard.json
|
|
1580
|
+
python api_scorecard.py --output scorecard.txt openapi.json
|
|
1581
|
+
"""
|
|
1582
|
+
)
|
|
1583
|
+
|
|
1584
|
+
parser.add_argument(
|
|
1585
|
+
'spec_file',
|
|
1586
|
+
help='OpenAPI/Swagger specification file (JSON format)'
|
|
1587
|
+
)
|
|
1588
|
+
|
|
1589
|
+
parser.add_argument(
|
|
1590
|
+
'--format',
|
|
1591
|
+
choices=['text', 'json'],
|
|
1592
|
+
default='text',
|
|
1593
|
+
help='Output format (default: text)'
|
|
1594
|
+
)
|
|
1595
|
+
|
|
1596
|
+
parser.add_argument(
|
|
1597
|
+
'--output',
|
|
1598
|
+
help='Output file (default: stdout)'
|
|
1599
|
+
)
|
|
1600
|
+
|
|
1601
|
+
parser.add_argument(
|
|
1602
|
+
'--min-grade',
|
|
1603
|
+
choices=['A', 'B', 'C', 'D', 'F'],
|
|
1604
|
+
help='Exit with code 1 if grade is below minimum'
|
|
1605
|
+
)
|
|
1606
|
+
|
|
1607
|
+
args = parser.parse_args()
|
|
1608
|
+
|
|
1609
|
+
# Load specification file
|
|
1610
|
+
try:
|
|
1611
|
+
with open(args.spec_file, 'r') as f:
|
|
1612
|
+
spec = json.load(f)
|
|
1613
|
+
except FileNotFoundError:
|
|
1614
|
+
print(f"Error: Specification file '{args.spec_file}' not found.", file=sys.stderr)
|
|
1615
|
+
return 1
|
|
1616
|
+
except json.JSONDecodeError as e:
|
|
1617
|
+
print(f"Error: Invalid JSON in '{args.spec_file}': {e}", file=sys.stderr)
|
|
1618
|
+
return 1
|
|
1619
|
+
|
|
1620
|
+
# Initialize scoring engine and generate scorecard
|
|
1621
|
+
engine = APIScoringEngine()
|
|
1622
|
+
|
|
1623
|
+
try:
|
|
1624
|
+
scorecard = engine.score_api(spec)
|
|
1625
|
+
except Exception as e:
|
|
1626
|
+
print(f"Error during scoring: {e}", file=sys.stderr)
|
|
1627
|
+
return 1
|
|
1628
|
+
|
|
1629
|
+
# Generate report
|
|
1630
|
+
if args.format == 'json':
|
|
1631
|
+
output = engine.generate_json_report()
|
|
1632
|
+
else:
|
|
1633
|
+
output = engine.generate_text_report()
|
|
1634
|
+
|
|
1635
|
+
# Write output
|
|
1636
|
+
if args.output:
|
|
1637
|
+
try:
|
|
1638
|
+
with open(args.output, 'w') as f:
|
|
1639
|
+
f.write(output)
|
|
1640
|
+
print(f"Scorecard written to {args.output}")
|
|
1641
|
+
except IOError as e:
|
|
1642
|
+
print(f"Error writing to '{args.output}': {e}", file=sys.stderr)
|
|
1643
|
+
return 1
|
|
1644
|
+
else:
|
|
1645
|
+
print(output)
|
|
1646
|
+
|
|
1647
|
+
# Check minimum grade requirement
|
|
1648
|
+
if args.min_grade:
|
|
1649
|
+
grade_order = ['F', 'D', 'C', 'B', 'A']
|
|
1650
|
+
current_grade_index = grade_order.index(scorecard.overall_grade)
|
|
1651
|
+
min_grade_index = grade_order.index(args.min_grade)
|
|
1652
|
+
|
|
1653
|
+
if current_grade_index < min_grade_index:
|
|
1654
|
+
print(f"Grade {scorecard.overall_grade} is below minimum required grade {args.min_grade}", file=sys.stderr)
|
|
1655
|
+
return 1
|
|
1656
|
+
|
|
1657
|
+
return 0
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
if __name__ == '__main__':
|
|
1661
|
+
sys.exit(main())
|