@mrciphersmith/keryx 0.2.9
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 +21 -0
- package/README.md +351 -0
- package/dist/cli.js +48427 -0
- package/dist/proxy-worker.js +381 -0
- package/package.json +63 -0
- package/src/gdgraph/affected.test.ts +100 -0
- package/src/gdgraph/affected.ts +135 -0
- package/src/gdgraph/build-integrity.test.ts +193 -0
- package/src/gdgraph/build-lang.test.ts +399 -0
- package/src/gdgraph/build.test.ts +120 -0
- package/src/gdgraph/build.ts +804 -0
- package/src/gdgraph/config.test.ts +47 -0
- package/src/gdgraph/config.ts +113 -0
- package/src/gdgraph/core-sources.test.ts +99 -0
- package/src/gdgraph/core-sources.ts +20 -0
- package/src/gdgraph/enrich.ts +79 -0
- package/src/gdgraph/fallback.test.ts +153 -0
- package/src/gdgraph/find.test.ts +78 -0
- package/src/gdgraph/find.ts +107 -0
- package/src/gdgraph/pagerank.ts +129 -0
- package/src/gdgraph/path.test.ts +56 -0
- package/src/gdgraph/path.ts +112 -0
- package/src/gdgraph/query.ts +133 -0
- package/src/gdgraph/repomap.test.ts +110 -0
- package/src/gdgraph/repomap.ts +193 -0
- package/src/gdgraph/service.test.ts +59 -0
- package/src/gdgraph/service.ts +51 -0
- package/src/gdgraph/staleness.ts +20 -0
- package/src/gdgraph/symbol.test.ts +89 -0
- package/src/gdgraph/symbol.ts +123 -0
- package/src/gdgraph/symbols-capability.test.ts +41 -0
- package/src/gdgraph/symbols-capability.ts +49 -0
- package/src/gdgraph/target.ts +56 -0
- package/src/gdgraph/treesitter/adapter.test.ts +249 -0
- package/src/gdgraph/treesitter/adapter.ts +213 -0
- package/src/gdgraph/treesitter/extract.test.ts +278 -0
- package/src/gdgraph/treesitter/extract.ts +389 -0
- package/src/gdgraph/treesitter/grammars.ts +110 -0
- package/src/gdgraph/treesitter/no-treesitter-import.test.ts +51 -0
- package/src/gdgraph/treesitter/resolve-calls.test.ts +38 -0
- package/src/gdgraph/types.ts +63 -0
- package/src/gdskills/bundled/rules/core/api-contracts.mdc +148 -0
- package/src/gdskills/bundled/rules/core/async-patterns.mdc +185 -0
- package/src/gdskills/bundled/rules/core/clean-architecture.mdc +163 -0
- package/src/gdskills/bundled/rules/core/code-review-ai-assistant.mdc +49 -0
- package/src/gdskills/bundled/rules/core/code-review-b091-profile.mdc +48 -0
- package/src/gdskills/bundled/rules/core/code-style-patterns.mdc +54 -0
- package/src/gdskills/bundled/rules/core/commit-message-formatting.mdc +25 -0
- package/src/gdskills/bundled/rules/core/database-patterns.mdc +162 -0
- package/src/gdskills/bundled/rules/core/documentation-management.mdc +106 -0
- package/src/gdskills/bundled/rules/core/error-handling.mdc +163 -0
- package/src/gdskills/bundled/rules/core/execution-metrics.md +99 -0
- package/src/gdskills/bundled/rules/core/frontend-assistant.mdc +40 -0
- package/src/gdskills/bundled/rules/core/git-rules.mdc +37 -0
- package/src/gdskills/bundled/rules/core/gproject-contracts.mdc +198 -0
- package/src/gdskills/bundled/rules/core/implementation-doc-mandate.mdc +149 -0
- package/src/gdskills/bundled/rules/core/implementation-plans.mdc +58 -0
- package/src/gdskills/bundled/rules/core/jobs-documentation.mdc +221 -0
- package/src/gdskills/bundled/rules/core/mobx-store-template.mdc +561 -0
- package/src/gdskills/bundled/rules/core/model-selection.mdc +53 -0
- package/src/gdskills/bundled/rules/core/nestjs-dto.mdc +50 -0
- package/src/gdskills/bundled/rules/core/playwright-testing.mdc +321 -0
- package/src/gdskills/bundled/rules/core/requirements-management.mdc +35 -0
- package/src/gdskills/bundled/rules/core/requirements-package-standard.mdc +103 -0
- package/src/gdskills/bundled/rules/core/review-agent-profile.mdc +49 -0
- package/src/gdskills/bundled/rules/core/review-strict-profile.mdc +44 -0
- package/src/gdskills/bundled/rules/core/rule-management-workflow.mdc +62 -0
- package/src/gdskills/bundled/rules/core/security-baseline.mdc +155 -0
- package/src/gdskills/bundled/rules/core/shared-definitions.mdc +79 -0
- package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +55 -0
- package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +88 -0
- package/src/gdskills/bundled/rules/core/solid-principles.mdc +149 -0
- package/src/gdskills/bundled/rules/core/storybook-guidelines.mdc +36 -0
- package/src/gdskills/bundled/rules/core/subagent-context-construction.md +107 -0
- package/src/gdskills/bundled/rules/core/subagent-status-protocol.md +169 -0
- package/src/gdskills/bundled/rules/core/tdd-workflow.mdc +96 -0
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.codex.md +47 -0
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.cursor.md +55 -0
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +352 -0
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.opencode.md +39 -0
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.zed.md +29 -0
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.codex.md +567 -0
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.cursor.md +567 -0
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.md +655 -0
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.opencode.md +567 -0
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.zed.md +567 -0
- package/src/gdskills/bundled/skills/orchestration/context-collector/input-contract.schema.json +134 -0
- package/src/gdskills/bundled/skills/orchestration/context-collector/orchestrator-prompt.md +183 -0
- package/src/gdskills/bundled/skills/orchestration/context-collector/output-contract.schema.json +208 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.codex.md +1412 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.cursor.md +1412 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.detail.md +735 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.md +468 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.opencode.md +1412 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.zed.md +1412 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/analysis-request.md +62 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/analysis-request.template.md +79 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/input-contract.schema.json +247 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/orchestrator-prompt.md +362 -0
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/output-contract.schema.json +87 -0
- package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.codex.md +105 -0
- package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.cursor.md +105 -0
- package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.md +165 -0
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +379 -0
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/input-contract.schema.json +33 -0
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/output-contract.schema.json +41 -0
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.codex.md +307 -0
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.cursor.md +307 -0
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.md +373 -0
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.opencode.md +307 -0
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.zed.md +307 -0
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/input-contract.schema.json +130 -0
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/issue-request.template.md +50 -0
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/orchestrator-prompt.md +155 -0
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/output-contract.schema.json +61 -0
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.codex.md +365 -0
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.cursor.md +365 -0
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.md +373 -0
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.opencode.md +365 -0
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.zed.md +365 -0
- package/src/gdskills/bundled/skills/orchestration/job-documenter/input-contract.schema.json +55 -0
- package/src/gdskills/bundled/skills/orchestration/job-documenter/output-contract.schema.json +58 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.codex.md +1701 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.cursor.md +1701 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1734 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.opencode.md +1701 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.zed.md +1701 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/input-contract.schema.json +188 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/orchestrator-prompt.md +179 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/output-contract.schema.json +103 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/state.schema.json +164 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.codex.md +395 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.cursor.md +395 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +556 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.opencode.md +395 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.zed.md +395 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json +204 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/orchestrator-prompt.md +206 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json +51 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/task-request.template.md +111 -0
- package/src/gdskills/bundled/skills/planning/autodoc-analyst/SKILL.md +163 -0
- package/src/gdskills/bundled/skills/planning/autodoc-architect/SKILL.md +161 -0
- package/src/gdskills/bundled/skills/planning/autodoc-assembler/SKILL.md +128 -0
- package/src/gdskills/bundled/skills/planning/autodoc-orchestrator/SKILL.md +321 -0
- package/src/gdskills/bundled/skills/planning/autodoc-scanner/SKILL.md +166 -0
- package/src/gdskills/bundled/skills/planning/autodoc-writer/SKILL.md +253 -0
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.claude.md +46 -0
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.codex.md +90 -0
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.cursor.md +90 -0
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +90 -0
- package/src/gdskills/bundled/skills/planning/consistency-checker/SKILL.codex.md +33 -0
- package/src/gdskills/bundled/skills/planning/consistency-checker/SKILL.cursor.md +33 -0
- package/src/gdskills/bundled/skills/planning/consistency-checker/SKILL.md +190 -0
- package/src/gdskills/bundled/skills/planning/docpack-orchestrator/SKILL.md +142 -0
- package/src/gdskills/bundled/skills/planning/docpack-orchestrator/input-contract.schema.json +31 -0
- package/src/gdskills/bundled/skills/planning/docpack-orchestrator/output-contract.schema.json +31 -0
- package/src/gdskills/bundled/skills/planning/docpack-review/SKILL.md +80 -0
- package/src/gdskills/bundled/skills/planning/docpack-review/input-contract.schema.json +20 -0
- package/src/gdskills/bundled/skills/planning/docpack-review/output-contract.schema.json +36 -0
- package/src/gdskills/bundled/skills/planning/interview/SKILL.codex.md +181 -0
- package/src/gdskills/bundled/skills/planning/interview/SKILL.cursor.md +181 -0
- package/src/gdskills/bundled/skills/planning/interview/SKILL.md +187 -0
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.claude.md +94 -0
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.codex.md +99 -0
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.cursor.md +99 -0
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +105 -0
- package/src/gdskills/bundled/skills/planning/patterns-researcher/SKILL.codex.md +33 -0
- package/src/gdskills/bundled/skills/planning/patterns-researcher/SKILL.cursor.md +33 -0
- package/src/gdskills/bundled/skills/planning/patterns-researcher/SKILL.md +237 -0
- package/src/gdskills/bundled/skills/planning/planner/SKILL.codex.md +33 -0
- package/src/gdskills/bundled/skills/planning/planner/SKILL.cursor.md +33 -0
- package/src/gdskills/bundled/skills/planning/planner/SKILL.md +190 -0
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.codex.md +193 -0
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.cursor.md +193 -0
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.md +193 -0
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.opencode.md +193 -0
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.zed.md +193 -0
- package/src/gdskills/bundled/skills/planning/prd-creator/input-contract.schema.json +63 -0
- package/src/gdskills/bundled/skills/planning/prd-creator/output-contract.schema.json +175 -0
- package/src/gdskills/bundled/skills/planning/problem-definer/SKILL.codex.md +31 -0
- package/src/gdskills/bundled/skills/planning/problem-definer/SKILL.cursor.md +31 -0
- package/src/gdskills/bundled/skills/planning/problem-definer/SKILL.md +153 -0
- package/src/gdskills/bundled/skills/planning/project-discovery/SKILL.codex.md +32 -0
- package/src/gdskills/bundled/skills/planning/project-discovery/SKILL.cursor.md +32 -0
- package/src/gdskills/bundled/skills/planning/project-discovery/SKILL.md +166 -0
- package/src/gdskills/bundled/skills/planning/spec-writer/SKILL.codex.md +44 -0
- package/src/gdskills/bundled/skills/planning/spec-writer/SKILL.cursor.md +44 -0
- package/src/gdskills/bundled/skills/planning/spec-writer/SKILL.md +243 -0
- package/src/gdskills/bundled/skills/planning/stack-advisor/SKILL.codex.md +43 -0
- package/src/gdskills/bundled/skills/planning/stack-advisor/SKILL.cursor.md +43 -0
- package/src/gdskills/bundled/skills/planning/stack-advisor/SKILL.md +193 -0
- package/src/gdskills/bundled/skills/platform/agent-entrypoint-distiller/SKILL.md +47 -0
- package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.codex.md +87 -0
- package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.cursor.md +87 -0
- package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.md +87 -0
- package/src/gdskills/bundled/skills/platform/hookify/SKILL.codex.md +100 -0
- package/src/gdskills/bundled/skills/platform/hookify/SKILL.cursor.md +100 -0
- package/src/gdskills/bundled/skills/platform/hookify/SKILL.md +100 -0
- package/src/gdskills/bundled/skills/quality/changelog/SKILL.claude.md +45 -0
- package/src/gdskills/bundled/skills/quality/changelog/SKILL.codex.md +84 -0
- package/src/gdskills/bundled/skills/quality/changelog/SKILL.cursor.md +84 -0
- package/src/gdskills/bundled/skills/quality/changelog/SKILL.md +84 -0
- package/src/gdskills/bundled/skills/quality/commit/SKILL.codex.md +66 -0
- package/src/gdskills/bundled/skills/quality/commit/SKILL.cursor.md +66 -0
- package/src/gdskills/bundled/skills/quality/commit/SKILL.md +66 -0
- package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.claude.md +40 -0
- package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.codex.md +66 -0
- package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.cursor.md +66 -0
- package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.md +66 -0
- package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.claude.md +45 -0
- package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.codex.md +81 -0
- package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.cursor.md +81 -0
- package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.md +81 -0
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.claude.md +42 -0
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.codex.md +70 -0
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.cursor.md +70 -0
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +70 -0
- package/src/gdskills/bundled/skills/quality/metaproject-security/SKILL.md +103 -0
- package/src/gdskills/bundled/skills/quality/perf-check/SKILL.claude.md +48 -0
- package/src/gdskills/bundled/skills/quality/perf-check/SKILL.codex.md +83 -0
- package/src/gdskills/bundled/skills/quality/perf-check/SKILL.cursor.md +83 -0
- package/src/gdskills/bundled/skills/quality/perf-check/SKILL.md +83 -0
- package/src/gdskills/bundled/skills/quality/pr/SKILL.codex.md +75 -0
- package/src/gdskills/bundled/skills/quality/pr/SKILL.cursor.md +75 -0
- package/src/gdskills/bundled/skills/quality/pr/SKILL.md +75 -0
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.codex.md +364 -0
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.cursor.md +295 -0
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.md +378 -0
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.opencode.md +364 -0
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.zed.md +38 -0
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/input-contract.schema.json +53 -0
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/output-contract.schema.json +46 -0
- package/src/gdskills/bundled/skills/quality/push/SKILL.codex.md +52 -0
- package/src/gdskills/bundled/skills/quality/push/SKILL.cursor.md +52 -0
- package/src/gdskills/bundled/skills/quality/push/SKILL.md +52 -0
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.claude.md +40 -0
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.codex.md +61 -0
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.cursor.md +61 -0
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.md +61 -0
- package/src/gdskills/bundled/skills/quality/test-gen/SKILL.claude.md +30 -0
- package/src/gdskills/bundled/skills/quality/test-gen/SKILL.codex.md +75 -0
- package/src/gdskills/bundled/skills/quality/test-gen/SKILL.cursor.md +75 -0
- package/src/gdskills/bundled/skills/quality/test-gen/SKILL.md +75 -0
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.codex.md +64 -0
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.cursor.md +74 -0
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.md +339 -0
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.opencode.md +48 -0
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.zed.md +44 -0
- package/src/gdskills/bundled/skills/quality/tests-creator/input-contract.schema.json +121 -0
- package/src/gdskills/bundled/skills/quality/tests-creator/output-contract.schema.json +127 -0
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.codex.md +204 -0
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.cursor.md +204 -0
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.md +203 -0
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.opencode.md +204 -0
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.zed.md +204 -0
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.codex.md +209 -0
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.cursor.md +209 -0
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.md +208 -0
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.opencode.md +209 -0
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.zed.md +209 -0
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.codex.md +263 -0
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.cursor.md +263 -0
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.md +258 -0
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.opencode.md +263 -0
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.zed.md +263 -0
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.codex.md +175 -0
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.cursor.md +175 -0
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +168 -0
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.opencode.md +175 -0
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.zed.md +175 -0
- package/src/gdskills/bundled/skills/review/review-architecture/SKILL.md +360 -0
- package/src/gdskills/bundled/skills/review/review-backend/SKILL.md +337 -0
- package/src/gdskills/bundled/skills/review/review-clean-code/SKILL.md +477 -0
- package/src/gdskills/bundled/skills/review/review-core-boundaries/SKILL.md +93 -0
- package/src/gdskills/bundled/skills/review/review-flow-graph/SKILL.md +119 -0
- package/src/gdskills/bundled/skills/review/review-frontend/SKILL.md +595 -0
- package/src/gdskills/bundled/skills/review/review-frontend-conventions/SKILL.md +144 -0
- package/src/gdskills/bundled/skills/review/review-highload/SKILL.md +519 -0
- package/src/gdskills/bundled/skills/review/review-logic/SKILL.md +323 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +927 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/input-contract.schema.json +153 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +77 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/review-context.schema.json +389 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +189 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +241 -0
- package/src/gdskills/bundled/skills/review/review-performance/SKILL.md +344 -0
- package/src/gdskills/bundled/skills/review/review-pr-feedback/SKILL.md +327 -0
- package/src/gdskills/bundled/skills/review/review-security-code/SKILL.md +348 -0
- package/src/gdskills/bundled/skills/review/review-strict/SKILL.md +328 -0
- package/src/gdskills/bundled/skills/review/review-style/SKILL.md +343 -0
- package/src/gdskills/bundled/skills/review/review-testing-practices/SKILL.md +134 -0
- package/src/gdskills/bundled/skills/shared/git-merge-base.md +33 -0
- package/src/gdskills/contracts/agent-event.schema.json +32 -0
- package/src/gdskills/contracts/orchestrator-state.schema.json +58 -0
- package/src/gdskills/contracts/review-finding.schema.json +59 -0
- package/src/gdskills/contracts/subagent-dispatch.schema.json +123 -0
- package/src/gdskills/contracts/subagent-result.schema.json +136 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Grammar WASM resolution via the Block 0 Asset Resolver (specification.md §8.1;
|
|
2
|
+
// T-B11, A-1..A-7). Grammar assets are pinned in `.metaproject/assets.lock.json`
|
|
3
|
+
// and resolved (sha256-verified on EVERY load) through `resolveAsset` — a
|
|
4
|
+
// tampered/missing grammar ⇒ `null` ⇒ the capability degrades to the regex
|
|
5
|
+
// fallback (AC1.6). This module NEVER touches the network and imports no optional
|
|
6
|
+
// dependency.
|
|
7
|
+
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { loadAssetsLock, registryFromLock } from "../../assets/lock";
|
|
10
|
+
import { resolveAsset } from "../../assets/resolver";
|
|
11
|
+
|
|
12
|
+
export type GrammarLanguage = "typescript" | "tsx" | "javascript" | "java" | "python";
|
|
13
|
+
|
|
14
|
+
// The asset id for a language's grammar in `assets.lock.json`.
|
|
15
|
+
export function grammarAssetId(language: GrammarLanguage): string {
|
|
16
|
+
return `tree-sitter-${language}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Map an extractor language to its parsed-symbol language tag.
|
|
20
|
+
export function symbolLanguage(language: GrammarLanguage): "typescript" | "javascript" | "java" | "python" {
|
|
21
|
+
if (language === "javascript") return "javascript";
|
|
22
|
+
if (language === "python") return "python";
|
|
23
|
+
if (language === "java") return "java";
|
|
24
|
+
return "typescript";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ResolvedGrammar {
|
|
28
|
+
language: GrammarLanguage;
|
|
29
|
+
path: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Resolve a single grammar to a verified on-disk wasm path, or `null` when the
|
|
33
|
+
// asset is absent / fails checksum. `grammarsPath` (config T1) supplies an
|
|
34
|
+
// optional per-id user override directory of `tree-sitter-<lang>.wasm` files.
|
|
35
|
+
export async function resolveGrammar(
|
|
36
|
+
cwd: string,
|
|
37
|
+
language: GrammarLanguage,
|
|
38
|
+
grammarsPath?: string | null,
|
|
39
|
+
): Promise<ResolvedGrammar | null> {
|
|
40
|
+
const id = grammarAssetId(language);
|
|
41
|
+
const lock = await loadAssetsLock(cwd);
|
|
42
|
+
const overrides = grammarsPath
|
|
43
|
+
? { [id]: { path: path.join(grammarsPath, `${id}.wasm`) } }
|
|
44
|
+
: undefined;
|
|
45
|
+
const registry = registryFromLock(lock, overrides);
|
|
46
|
+
const resolved = await resolveAsset(registry, id);
|
|
47
|
+
if (!resolved) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return { language, path: resolved.path };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Resolve every requested grammar that is available. Unavailable ones are
|
|
54
|
+
// skipped (not an error) — the adapter is available when at least one resolves.
|
|
55
|
+
export async function resolveGrammars(
|
|
56
|
+
cwd: string,
|
|
57
|
+
languages: GrammarLanguage[],
|
|
58
|
+
grammarsPath?: string | null,
|
|
59
|
+
): Promise<ResolvedGrammar[]> {
|
|
60
|
+
const resolved: ResolvedGrammar[] = [];
|
|
61
|
+
for (const language of languages) {
|
|
62
|
+
const grammar = await resolveGrammar(cwd, language, grammarsPath);
|
|
63
|
+
if (grammar) {
|
|
64
|
+
resolved.push(grammar);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return resolved;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Normalize config language strings to supported grammar languages.
|
|
71
|
+
export function toGrammarLanguages(languages: string[]): GrammarLanguage[] {
|
|
72
|
+
const out: GrammarLanguage[] = [];
|
|
73
|
+
for (const language of languages) {
|
|
74
|
+
if (
|
|
75
|
+
language === "typescript" ||
|
|
76
|
+
language === "tsx" ||
|
|
77
|
+
language === "javascript" ||
|
|
78
|
+
language === "java" ||
|
|
79
|
+
language === "python"
|
|
80
|
+
) {
|
|
81
|
+
if (!out.includes(language as GrammarLanguage)) {
|
|
82
|
+
out.push(language as GrammarLanguage);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Choose the grammar for a source file path.
|
|
90
|
+
export function grammarForFile(file: string, available: GrammarLanguage[]): GrammarLanguage | null {
|
|
91
|
+
const ext = path.extname(file);
|
|
92
|
+
const preference: GrammarLanguage[] =
|
|
93
|
+
ext === ".tsx"
|
|
94
|
+
? ["tsx", "typescript"]
|
|
95
|
+
: ext === ".ts"
|
|
96
|
+
? ["typescript"]
|
|
97
|
+
: ext === ".jsx" || ext === ".js" || ext === ".mjs" || ext === ".cjs"
|
|
98
|
+
? ["javascript"]
|
|
99
|
+
: ext === ".java"
|
|
100
|
+
? ["java"]
|
|
101
|
+
: ext === ".py"
|
|
102
|
+
? ["python"]
|
|
103
|
+
: [];
|
|
104
|
+
for (const language of preference) {
|
|
105
|
+
if (available.includes(language)) {
|
|
106
|
+
return language;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { expect, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
// AC1.5 (C0-2): `web-tree-sitter` is imported ONLY via `await import()` inside
|
|
7
|
+
// `src/gdgraph/treesitter/adapter.ts`. This is an ADDITIVE, gdgraph-specific
|
|
8
|
+
// assertion layered on the generic `src/capability/no-optional-imports.test.ts`
|
|
9
|
+
// (which already scans every optionalDependencies package across `src/`).
|
|
10
|
+
|
|
11
|
+
const SRC_ROOT = fileURLToPath(new URL("../../", import.meta.url));
|
|
12
|
+
const ADAPTER = path.join(SRC_ROOT, "gdgraph", "treesitter", "adapter.ts");
|
|
13
|
+
|
|
14
|
+
async function tsFiles(dir: string): Promise<string[]> {
|
|
15
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
16
|
+
const files: string[] = [];
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const full = path.join(dir, entry.name);
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
files.push(...(await tsFiles(full)));
|
|
21
|
+
} else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
22
|
+
files.push(full);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return files;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test("no static import of web-tree-sitter anywhere in src/", async () => {
|
|
29
|
+
const files = await tsFiles(SRC_ROOT);
|
|
30
|
+
const staticImport = /\b(?:import|export)\b[^()]*?\bfrom\s*['"]web-tree-sitter['"]|\bimport\s*['"]web-tree-sitter['"]|\brequire\s*\(\s*['"]web-tree-sitter['"]\s*\)/;
|
|
31
|
+
const violations: string[] = [];
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
const content = await readFile(file, "utf8");
|
|
34
|
+
if (staticImport.test(content)) {
|
|
35
|
+
violations.push(path.relative(SRC_ROOT, file));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
expect(violations).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("adapter declares web-tree-sitter as a lazy optionalDependency, never a static import", async () => {
|
|
42
|
+
const content = await readFile(ADAPTER, "utf8");
|
|
43
|
+
// The dep is loaded lazily by the seam (`await import(spec.optionalDependency)`
|
|
44
|
+
// in capability/seam.ts) using the id the adapter declares here.
|
|
45
|
+
expect(content).toContain('optionalDependency: "web-tree-sitter"');
|
|
46
|
+
// The adapter never statically imports the dep (types are structural).
|
|
47
|
+
expect(/from\s*['"]web-tree-sitter['"]/.test(content)).toBe(false);
|
|
48
|
+
|
|
49
|
+
const seam = await readFile(path.join(SRC_ROOT, "capability", "seam.ts"), "utf8");
|
|
50
|
+
expect(seam).toContain("await import(spec.optionalDependency)");
|
|
51
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { resolveCrossFileCalls } from "./extract";
|
|
3
|
+
import type { CallEdge, SymbolNode } from "../types";
|
|
4
|
+
|
|
5
|
+
function sym(id: string, name: string, path: string): SymbolNode {
|
|
6
|
+
return { id, kind: "function", path, name, container: null, startLine: 1, endLine: 2, language: "typescript" };
|
|
7
|
+
}
|
|
8
|
+
function unresolved(from: string, to: string): CallEdge {
|
|
9
|
+
return { id: `u:${from}=>${to}`, from, to, kind: "unresolved-call", resolved: false };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
test("resolves a cross-file member call to a globally-unique symbol", () => {
|
|
13
|
+
const symbols = [sym("src/a.ts#runPipeline", "runPipeline", "src/a.ts"), sym("src/b.ts#caller", "caller", "src/b.ts")];
|
|
14
|
+
// caller (in b.ts) calls `TasksApi.runPipeline` — cross-file, unresolved by per-file extraction
|
|
15
|
+
const calls = [unresolved("src/b.ts#caller", "TasksApi.runPipeline")];
|
|
16
|
+
const out = resolveCrossFileCalls(symbols, calls);
|
|
17
|
+
expect(out).toHaveLength(1);
|
|
18
|
+
expect(out[0]).toMatchObject({ from: "src/b.ts#caller", to: "src/a.ts#runPipeline", kind: "calls", resolved: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("leaves an ambiguous name (multiple defs) unresolved — never guesses", () => {
|
|
22
|
+
const symbols = [
|
|
23
|
+
sym("src/a.ts#clonePipeline", "clonePipeline", "src/a.ts"),
|
|
24
|
+
sym("src/b.ts#clonePipeline", "clonePipeline", "src/b.ts"),
|
|
25
|
+
sym("src/c.ts#caller", "caller", "src/c.ts"),
|
|
26
|
+
];
|
|
27
|
+
const out = resolveCrossFileCalls(symbols, [unresolved("src/c.ts#caller", "x.clonePipeline")]);
|
|
28
|
+
expect(out[0]?.kind).toBe("unresolved-call");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("drops self-edges and passes resolved calls through unchanged", () => {
|
|
32
|
+
const symbols = [sym("src/a.ts#f", "f", "src/a.ts")];
|
|
33
|
+
const resolved: CallEdge = { id: "c", from: "src/a.ts#g", to: "src/a.ts#f", kind: "calls", resolved: true };
|
|
34
|
+
const out = resolveCrossFileCalls(symbols, [unresolved("src/a.ts#f", "f"), resolved]);
|
|
35
|
+
// the self-call f->f is not added as a resolved edge; the pre-resolved call stays
|
|
36
|
+
expect(out.some((c) => c.kind === "calls" && c.from === "src/a.ts#f" && c.to === "src/a.ts#f")).toBe(false);
|
|
37
|
+
expect(out).toContainEqual(resolved);
|
|
38
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type GraphNode = {
|
|
2
|
+
id: string;
|
|
3
|
+
kind: "file" | "asset";
|
|
4
|
+
path: string;
|
|
5
|
+
language: "typescript" | "javascript" | "java" | "python" | "asset";
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type GraphEdge = {
|
|
9
|
+
id: string;
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
kind: "imports" | "asset" | "unresolved";
|
|
13
|
+
specifier: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type GraphData = {
|
|
17
|
+
nodes: GraphNode[];
|
|
18
|
+
edges: GraphEdge[];
|
|
19
|
+
// B1 symbol layer — present only when tree-sitter enrichment ran and wrote
|
|
20
|
+
// `storage/symbols.jsonl` / `storage/calls.jsonl`. Missing ⇒ omitted (never an
|
|
21
|
+
// error; `loadGraph` loads them only if present). File-level consumers ignore
|
|
22
|
+
// these fields, keeping the legacy graph shape backward-compatible.
|
|
23
|
+
symbols?: SymbolNode[];
|
|
24
|
+
calls?: CallEdge[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// --- B1 symbol layer types (additive; only materialized in symbols.jsonl /
|
|
28
|
+
// calls.jsonl when the `gdgraph.treesitter` capability is active). ---
|
|
29
|
+
|
|
30
|
+
export type SymbolKind = "function" | "class" | "method" | "interface";
|
|
31
|
+
|
|
32
|
+
export type SymbolNode = {
|
|
33
|
+
// "<path>#<Container>.<name>" (+ "@<startLine>" on name collision).
|
|
34
|
+
id: string;
|
|
35
|
+
kind: SymbolKind;
|
|
36
|
+
// Owning file (matches a file GraphNode.path).
|
|
37
|
+
path: string;
|
|
38
|
+
name: string;
|
|
39
|
+
// Enclosing class/namespace, or null.
|
|
40
|
+
container: string | null;
|
|
41
|
+
// 1-based; positional, for stable disambiguation.
|
|
42
|
+
startLine: number;
|
|
43
|
+
endLine: number;
|
|
44
|
+
language: "typescript" | "javascript" | "java" | "python";
|
|
45
|
+
// Rendered for repomap.
|
|
46
|
+
signature?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type CallEdge = {
|
|
50
|
+
id: string;
|
|
51
|
+
// SymbolNode.id of caller (or file path when caller unknown).
|
|
52
|
+
from: string;
|
|
53
|
+
// SymbolNode.id of callee, or raw callee text when unresolved.
|
|
54
|
+
to: string;
|
|
55
|
+
// "defines": file → symbol containment.
|
|
56
|
+
kind: "calls" | "defines" | "unresolved-call";
|
|
57
|
+
resolved: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type SymbolLayer = {
|
|
61
|
+
symbols: SymbolNode[];
|
|
62
|
+
calls: CallEdge[];
|
|
63
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "API contract rules: OpenAPI-first design, semantic versioning, no breaking changes without major version bump, contract testing. Use when designing, modifying, or consuming HTTP APIs."
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# API Contracts
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
Prevent silent breaking changes, version drift, and undocumented API behaviour. Enforce OpenAPI-first design and strict versioning discipline.
|
|
10
|
+
|
|
11
|
+
## When To Apply
|
|
12
|
+
Apply when creating new API endpoints, modifying existing endpoints, changing DTOs/schemas, or writing API client code.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principle: Contract Before Code
|
|
17
|
+
|
|
18
|
+
The OpenAPI spec (or equivalent schema) is the source of truth. Code is generated from or validated against the spec — not the other way around.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
1. Design the endpoint in OpenAPI/schema first
|
|
22
|
+
2. Review the contract for breaking changes
|
|
23
|
+
3. Generate or update types/validators from the spec
|
|
24
|
+
4. Implement the handler
|
|
25
|
+
5. Write contract tests that validate the spec is honoured
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Rules
|
|
31
|
+
|
|
32
|
+
### Rule 1: OpenAPI-First for New Endpoints
|
|
33
|
+
Every new HTTP endpoint MUST have an OpenAPI 3.x definition before the handler is implemented:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
# Required fields for every endpoint definition
|
|
37
|
+
paths:
|
|
38
|
+
/users/{id}:
|
|
39
|
+
get:
|
|
40
|
+
operationId: getUser # unique, stable identifier
|
|
41
|
+
summary: Get user by ID
|
|
42
|
+
parameters:
|
|
43
|
+
- name: id
|
|
44
|
+
in: path
|
|
45
|
+
required: true
|
|
46
|
+
schema: { type: string, format: uuid }
|
|
47
|
+
responses:
|
|
48
|
+
'200':
|
|
49
|
+
description: User found
|
|
50
|
+
content:
|
|
51
|
+
application/json:
|
|
52
|
+
schema: { $ref: '#/components/schemas/User' }
|
|
53
|
+
'404':
|
|
54
|
+
$ref: '#/components/responses/NotFound'
|
|
55
|
+
'401':
|
|
56
|
+
$ref: '#/components/responses/Unauthorized'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Required for every endpoint:**
|
|
60
|
+
- `operationId` (stable, unique)
|
|
61
|
+
- All path/query/header parameters documented
|
|
62
|
+
- All response codes documented (including 4xx, 5xx)
|
|
63
|
+
- Request body schema referenced (not inlined) for reuse
|
|
64
|
+
|
|
65
|
+
### Rule 2: Semantic Versioning for APIs
|
|
66
|
+
|
|
67
|
+
| Change Type | Version Impact | Action Required |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Add optional field to response | None / patch | Safe — backwards compatible |
|
|
70
|
+
| Add optional field to request | None / patch | Safe — old clients omit it |
|
|
71
|
+
| Add new endpoint | Minor bump | Document in changelog |
|
|
72
|
+
| Make optional field required | **Major bump** | Breaking change |
|
|
73
|
+
| Remove field from response | **Major bump** | Breaking change |
|
|
74
|
+
| Rename field | **Major bump** | Breaking change |
|
|
75
|
+
| Change field type | **Major bump** | Breaking change |
|
|
76
|
+
| Remove endpoint | **Major bump** | Deprecate first, remove in next major |
|
|
77
|
+
|
|
78
|
+
**Breaking change protocol:**
|
|
79
|
+
1. Add deprecation notice to old version (`deprecated: true` in OpenAPI)
|
|
80
|
+
2. Implement new version at `/v{N+1}/...` or with new operationId
|
|
81
|
+
3. Run old version in parallel for ≥1 release cycle
|
|
82
|
+
4. Remove old version only after clients have migrated
|
|
83
|
+
|
|
84
|
+
### Rule 3: Contract Testing
|
|
85
|
+
Every public API endpoint MUST have at least one contract test that:
|
|
86
|
+
- Validates the response shape matches the OpenAPI schema
|
|
87
|
+
- Tests the error responses (at minimum: 400, 404, 401 where applicable)
|
|
88
|
+
- Uses schema validation, not string matching
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Example: contract test using a schema validator
|
|
92
|
+
describe('GET /users/:id contract', () => {
|
|
93
|
+
it('returns User schema on 200', async () => {
|
|
94
|
+
const res = await request(app).get('/users/valid-id');
|
|
95
|
+
expect(res.status).toBe(200);
|
|
96
|
+
expect(res.body).toMatchSchema(UserSchema); // validated against OpenAPI
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('returns 404 with NotFound schema on unknown id', async () => {
|
|
100
|
+
const res = await request(app).get('/users/nonexistent');
|
|
101
|
+
expect(res.status).toBe(404);
|
|
102
|
+
expect(res.body).toMatchSchema(NotFoundSchema);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Rule 4: No Undocumented Fields
|
|
108
|
+
Every field in a request or response body MUST appear in the schema. Fields not in the schema must be stripped from responses (`additionalProperties: false` in JSON Schema).
|
|
109
|
+
|
|
110
|
+
### Rule 5: Consistent Error Schema
|
|
111
|
+
All error responses must use the same envelope schema:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
interface ApiError {
|
|
115
|
+
code: string; // machine-readable, e.g. "USER_NOT_FOUND"
|
|
116
|
+
message: string; // human-readable
|
|
117
|
+
details?: unknown; // optional structured context
|
|
118
|
+
requestId: string; // for traceability
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Never return raw Error objects, stack traces, or ORM error messages to API consumers.
|
|
123
|
+
|
|
124
|
+
### Rule 6: Idempotency Keys for Mutations
|
|
125
|
+
POST/PUT/PATCH endpoints that create or modify state MUST support idempotency via `Idempotency-Key` header or a stable `clientId` in the body. This prevents duplicate operations on network retry.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Iron Laws
|
|
130
|
+
|
|
131
|
+
**IRON LAW 1: NO breaking change may be deployed without a major version bump and a deprecation period.**
|
|
132
|
+
**IRON LAW 2: The OpenAPI spec is updated BEFORE the implementation — not after as documentation.**
|
|
133
|
+
**IRON LAW 3: Error responses MUST use the standard ApiError envelope — no raw exceptions, no ad-hoc shapes.**
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Red Flags — Stop and re-read this rule if you are thinking:
|
|
138
|
+
|
|
139
|
+
| Rationalization | Why it's wrong |
|
|
140
|
+
|---|---|
|
|
141
|
+
| "It's just adding a field, that's not breaking" | Removing a field later will be breaking — document it now to establish the contract |
|
|
142
|
+
| "The spec can wait, I'll document it later" | Specs written after code document what the code does, not what it should do — bugs get documented too |
|
|
143
|
+
| "Renaming a field is internal, consumers will adapt" | Consumers have no way to know a field was renamed — this silently breaks every client |
|
|
144
|
+
| "I'll use v2 later if needed, for now v1 is fine" | Delaying versioning means v1 can never be changed without breaking consumers |
|
|
145
|
+
| "Contract tests slow down development" | Contract tests catch breaking changes before they reach consumers — not after |
|
|
146
|
+
| "The error format doesn't matter as long as the status code is right" | Status codes alone are insufficient for automated error handling — codes and schemas matter |
|
|
147
|
+
|
|
148
|
+
**IRON LAW: A BREAKING CHANGE WITHOUT A VERSION BUMP IS A PRODUCTION INCIDENT WAITING TO HAPPEN.**
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Async patterns: Promise.all for parallelism, no unhandled rejections, mandatory timeouts for external calls, no blocking the event loop. Use when writing async/await code, parallel operations, or queue consumers."
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Async Patterns
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
Prevent common async pitfalls: sequential execution where parallel is safe, unhandled promise rejections, missing timeouts, and event loop blockage.
|
|
10
|
+
|
|
11
|
+
## When To Apply
|
|
12
|
+
Apply when writing any `async`/`await` code, calling external services, processing queues, or coordinating multiple async operations.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
### Rule 1: Use Promise.all for Independent Operations
|
|
19
|
+
|
|
20
|
+
Sequential await for independent operations wastes time. Run them in parallel.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// BAD — sequential: takes 300ms (100 + 100 + 100)
|
|
24
|
+
const user = await fetchUser(id);
|
|
25
|
+
const orders = await fetchOrders(id);
|
|
26
|
+
const prefs = await fetchPreferences(id);
|
|
27
|
+
|
|
28
|
+
// GOOD — parallel: takes 100ms (max of 100, 100, 100)
|
|
29
|
+
const [user, orders, prefs] = await Promise.all([
|
|
30
|
+
fetchUser(id),
|
|
31
|
+
fetchOrders(id),
|
|
32
|
+
fetchPreferences(id),
|
|
33
|
+
]);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Rule:** If operation B does not depend on the result of operation A, they MUST run in parallel.
|
|
37
|
+
|
|
38
|
+
### Rule 2: Use Promise.allSettled When Partial Failure is Acceptable
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// BAD — one failure cancels all
|
|
42
|
+
const results = await Promise.all([criticalOp(), optionalOp()]); // if optionalOp rejects, criticalOp result is lost
|
|
43
|
+
|
|
44
|
+
// GOOD — each result handled independently
|
|
45
|
+
const [critical, optional] = await Promise.allSettled([criticalOp(), optionalOp()]);
|
|
46
|
+
if (critical.status === 'rejected') throw critical.reason;
|
|
47
|
+
if (optional.status === 'rejected') logger.warn('optional failed', optional.reason);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `Promise.all` when ALL operations are required. Use `Promise.allSettled` when SOME can fail gracefully.
|
|
51
|
+
|
|
52
|
+
### Rule 3: Every External Call Has a Timeout
|
|
53
|
+
|
|
54
|
+
Network calls without timeouts hang indefinitely, blocking resources.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// BAD — hangs forever if service is down
|
|
58
|
+
const response = await fetch('https://api.external.com/data');
|
|
59
|
+
|
|
60
|
+
// GOOD — fails fast after 5 seconds
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const timeout = setTimeout(() => controller.abort(), 5_000);
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch('https://api.external.com/data', { signal: controller.signal });
|
|
65
|
+
// ...
|
|
66
|
+
} finally {
|
|
67
|
+
clearTimeout(timeout);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// GOOD — with a utility wrapper
|
|
71
|
+
const response = await fetchWithTimeout('https://api.external.com/data', { timeout: 5_000 });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Default timeouts:**
|
|
75
|
+
- Internal service calls: 3s
|
|
76
|
+
- External API calls: 10s
|
|
77
|
+
- Long-running operations (file processing, ML): 60s with progress events
|
|
78
|
+
- Database queries: configure at connection pool level
|
|
79
|
+
|
|
80
|
+
### Rule 4: No Unhandled Promise Rejections
|
|
81
|
+
|
|
82
|
+
Every promise must have a `.catch()` or be `await`-ed inside a `try/catch`.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// BAD — fire-and-forget without error handling
|
|
86
|
+
sendNotification(userId, message); // rejection is swallowed silently
|
|
87
|
+
|
|
88
|
+
// BAD — floating promise in Express handler
|
|
89
|
+
app.get('/data', (req, res) => {
|
|
90
|
+
loadData(); // not awaited, errors don't propagate to response
|
|
91
|
+
res.json({ ok: true });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// GOOD — fire-and-forget with explicit error handling
|
|
95
|
+
sendNotification(userId, message).catch(err => {
|
|
96
|
+
logger.error('notification failed', { userId, err });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// GOOD — awaited in async handler
|
|
100
|
+
app.get('/data', async (req, res) => {
|
|
101
|
+
const data = await loadData();
|
|
102
|
+
res.json(data);
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Rule 5: No CPU-Blocking Operations on the Event Loop
|
|
107
|
+
|
|
108
|
+
Operations that block the event loop starve all other concurrent requests:
|
|
109
|
+
- `JSON.parse` / `JSON.stringify` on payloads > 1MB → use streaming
|
|
110
|
+
- Synchronous file reads in hot paths → use `fs.promises.*`
|
|
111
|
+
- CPU-intensive loops → offload to worker threads
|
|
112
|
+
- Synchronous crypto operations → use `crypto.subtle` or async equivalents
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// BAD — blocks event loop for large payload
|
|
116
|
+
app.post('/import', (req, res) => {
|
|
117
|
+
const data = JSON.parse(req.body.payload); // synchronous, large payload blocks
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// GOOD — streaming parse
|
|
121
|
+
import { pipeline } from 'stream/promises';
|
|
122
|
+
app.post('/import', async (req, res) => {
|
|
123
|
+
await pipeline(req, new JSONStreamParser(), new DataProcessor());
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Rule 6: Batch Async Operations with Controlled Concurrency
|
|
128
|
+
|
|
129
|
+
Running N async operations at once can overwhelm downstream services. Use a concurrency limiter:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// BAD — 1000 parallel requests to an API
|
|
133
|
+
await Promise.all(items.map(item => processItem(item)));
|
|
134
|
+
|
|
135
|
+
// GOOD — max 10 concurrent operations
|
|
136
|
+
import PLimit from 'p-limit';
|
|
137
|
+
const limit = PLimit(10);
|
|
138
|
+
await Promise.all(items.map(item => limit(() => processItem(item))));
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Rule 7: Async Initialization Pattern
|
|
142
|
+
|
|
143
|
+
Classes with async setup must not use the constructor for initialization:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// BAD — constructor cannot be async
|
|
147
|
+
class DatabasePool {
|
|
148
|
+
constructor() {
|
|
149
|
+
this.pool = await createPool(); // syntax error
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// GOOD — factory method pattern
|
|
154
|
+
class DatabasePool {
|
|
155
|
+
private constructor(private readonly pool: Pool) {}
|
|
156
|
+
|
|
157
|
+
static async create(): Promise<DatabasePool> {
|
|
158
|
+
const pool = await createPool();
|
|
159
|
+
return new DatabasePool(pool);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const db = await DatabasePool.create();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Iron Laws
|
|
168
|
+
|
|
169
|
+
**IRON LAW 1: Sequential await for independent operations is a performance bug. Use Promise.all.**
|
|
170
|
+
**IRON LAW 2: Every external HTTP call must have an explicit timeout configured.**
|
|
171
|
+
**IRON LAW 3: A floating promise (not awaited, no .catch) is an unhandled rejection waiting to crash the process.**
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Red Flags — Stop and re-read this rule if you are thinking:
|
|
176
|
+
|
|
177
|
+
| Rationalization | Why it's wrong |
|
|
178
|
+
|---|---|
|
|
179
|
+
| "I'll add a timeout later if it's needed" | Missing timeouts are invisible until a downstream service hangs — add them when the call is written |
|
|
180
|
+
| "The operation is fast, sequential await is fine" | Sequential operations that are "fast" become bottlenecks under load |
|
|
181
|
+
| "I don't need to await this, it's just a side effect" | Fire-and-forget without `.catch` silently loses errors that may indicate data corruption |
|
|
182
|
+
| "Promise.allSettled is verbose, I'll use Promise.all and handle errors in each function" | Promise.all cancels all operations on first rejection — allSettled gives you full control |
|
|
183
|
+
| "Concurrency limiting adds complexity" | Uncontrolled concurrency causes rate limit errors, OOM, and cascading failures downstream |
|
|
184
|
+
|
|
185
|
+
**IRON LAW: AN ASYNC FUNCTION THAT CAN HANG INDEFINITELY IS A DENIAL-OF-SERVICE VULNERABILITY.**
|