@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,804 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { GraphData, GraphEdge, GraphNode } from "./types";
|
|
5
|
+
|
|
6
|
+
const SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".java", ".py"];
|
|
7
|
+
const SOURCE_RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".d.ts", ".java", ".py"];
|
|
8
|
+
const ASSET_EXTENSIONS = [
|
|
9
|
+
".css",
|
|
10
|
+
".scss",
|
|
11
|
+
".sass",
|
|
12
|
+
".less",
|
|
13
|
+
".json",
|
|
14
|
+
".svg",
|
|
15
|
+
".png",
|
|
16
|
+
".jpg",
|
|
17
|
+
".jpeg",
|
|
18
|
+
".gif",
|
|
19
|
+
".webp",
|
|
20
|
+
".avif",
|
|
21
|
+
".ico",
|
|
22
|
+
".hbs",
|
|
23
|
+
".html",
|
|
24
|
+
".glsl",
|
|
25
|
+
".md",
|
|
26
|
+
".wasm",
|
|
27
|
+
".woff",
|
|
28
|
+
".woff2",
|
|
29
|
+
".ttf",
|
|
30
|
+
".otf",
|
|
31
|
+
];
|
|
32
|
+
const IGNORE_DIRS = new Set([
|
|
33
|
+
".git",
|
|
34
|
+
// Agent-harness scratch space. `.claude/worktrees/**` holds full git worktrees
|
|
35
|
+
// — checked-out copies of THIS repository. Walking into them indexes every
|
|
36
|
+
// source file once per worktree, so a single module shows up N+1 times, the
|
|
37
|
+
// one real cycle is reported N+1 times, and `affected` can resolve a target to
|
|
38
|
+
// a stale copy. Never source-index a nested checkout of ourselves.
|
|
39
|
+
".claude",
|
|
40
|
+
".metaproject",
|
|
41
|
+
"node_modules",
|
|
42
|
+
".cache",
|
|
43
|
+
".docusaurus",
|
|
44
|
+
".next",
|
|
45
|
+
".turbo",
|
|
46
|
+
"dist",
|
|
47
|
+
"build",
|
|
48
|
+
"coverage",
|
|
49
|
+
"generated",
|
|
50
|
+
"out",
|
|
51
|
+
"public",
|
|
52
|
+
"storybook-static",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
type BuildResult = {
|
|
56
|
+
nodes: number;
|
|
57
|
+
edges: number;
|
|
58
|
+
summaryPath: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type SourceCollection = {
|
|
62
|
+
files: string[];
|
|
63
|
+
skippedDirectories: string[];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type PathMapping = {
|
|
67
|
+
pattern: string;
|
|
68
|
+
targets: string[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// A per-language import resolver, selected by the importing file's language.
|
|
72
|
+
// TS/JS keeps the tsconfig/relative logic; Java/Python get source-layout aware
|
|
73
|
+
// resolvers. `candidateBases` receives `fromFile` so language-specific resolvers
|
|
74
|
+
// (e.g. Python relative imports) can resolve against the importing file.
|
|
75
|
+
type ImportResolver = {
|
|
76
|
+
matchesAlias(specifier: string): boolean;
|
|
77
|
+
candidateBases(specifier: string, fromFile: string): string[];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// All resolvers are constructed ONCE per build (source roots parsed once, cached)
|
|
81
|
+
// and selected per file by `pickResolver`.
|
|
82
|
+
type ResolverSet = {
|
|
83
|
+
tsconfig: ImportResolver;
|
|
84
|
+
java: ImportResolver;
|
|
85
|
+
python: ImportResolver;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export async function buildGraph(projectRoot: string): Promise<BuildResult> {
|
|
89
|
+
const collection = await collectSourceFiles(projectRoot);
|
|
90
|
+
const files = collection.files;
|
|
91
|
+
const fileSet = new Set(files);
|
|
92
|
+
const resolvers = await loadResolvers(projectRoot);
|
|
93
|
+
const nodes: GraphNode[] = files.map((file) => ({
|
|
94
|
+
id: file,
|
|
95
|
+
kind: "file",
|
|
96
|
+
path: file,
|
|
97
|
+
language: getLanguage(file),
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
const edges: GraphEdge[] = [];
|
|
101
|
+
const assetNodes = new Map<string, GraphNode>();
|
|
102
|
+
const fileRecords: Array<{ path: string; content: string }> = [];
|
|
103
|
+
for (const file of files) {
|
|
104
|
+
const absolutePath = path.join(projectRoot, file);
|
|
105
|
+
const content = await readFile(absolutePath, "utf8");
|
|
106
|
+
fileRecords.push({ path: file, content });
|
|
107
|
+
const language = getLanguage(file);
|
|
108
|
+
const resolver = pickResolver(resolvers, language);
|
|
109
|
+
// Java/Python imports are always real module references — a non-relative one
|
|
110
|
+
// that fails to resolve must be recorded as `unresolved` (never silently
|
|
111
|
+
// dropped), so the resolution metric is honest. TS/JS keeps the exact
|
|
112
|
+
// original guard (relative + tsconfig alias only) ⇒ byte-identical output.
|
|
113
|
+
const isLanguageAware = language === "java" || language === "python";
|
|
114
|
+
const specifiers = extractImportSpecifiers(content, language);
|
|
115
|
+
|
|
116
|
+
for (const specifier of specifiers) {
|
|
117
|
+
const resolved = resolveImport(projectRoot, file, specifier, fileSet, resolver);
|
|
118
|
+
const asset = resolved ? null : resolveAssetImport(projectRoot, file, specifier, resolver);
|
|
119
|
+
const shouldTrackUnresolved =
|
|
120
|
+
specifier.startsWith(".") || resolver.matchesAlias(specifier) || isLanguageAware;
|
|
121
|
+
if (!resolved && !asset && !shouldTrackUnresolved) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (asset && !assetNodes.has(asset)) {
|
|
125
|
+
assetNodes.set(asset, {
|
|
126
|
+
id: asset,
|
|
127
|
+
kind: "asset",
|
|
128
|
+
path: asset,
|
|
129
|
+
language: "asset",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
edges.push({
|
|
134
|
+
id: `edge:${edges.length + 1}`,
|
|
135
|
+
from: file,
|
|
136
|
+
to: resolved ?? asset ?? specifier,
|
|
137
|
+
kind: resolved ? "imports" : asset ? "asset" : "unresolved",
|
|
138
|
+
specifier,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const graph = { nodes: [...nodes, ...assetNodes.values()].sort((a, b) => a.path.localeCompare(b.path)), edges };
|
|
144
|
+
await writeGraph(projectRoot, graph);
|
|
145
|
+
const summaryPath = await writeSummary(projectRoot, graph, collection);
|
|
146
|
+
|
|
147
|
+
// B1: after the UNCHANGED file-level build, optionally enrich with the
|
|
148
|
+
// tree-sitter symbol layer behind the Block 0 capability seam. Loaded
|
|
149
|
+
// DYNAMICALLY + defensively so an environment lacking the seam (e.g. the
|
|
150
|
+
// copied core runner) degrades cleanly to file-level output. When the
|
|
151
|
+
// capability is disabled/unavailable this is a no-op and the four legacy
|
|
152
|
+
// artifacts stay byte-identical (the golden rule, B-1/C0-7/F-3).
|
|
153
|
+
try {
|
|
154
|
+
const { enrichBuildWithSymbols } = await import("./enrich");
|
|
155
|
+
await enrichBuildWithSymbols(projectRoot, fileRecords);
|
|
156
|
+
} catch {
|
|
157
|
+
// Enrichment module or seam unavailable ⇒ file-level graph only.
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
nodes: nodes.length,
|
|
162
|
+
edges: edges.length,
|
|
163
|
+
summaryPath,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function collectSourceFiles(projectRoot: string): Promise<SourceCollection> {
|
|
168
|
+
const result: string[] = [];
|
|
169
|
+
const skippedDirectories: string[] = [];
|
|
170
|
+
|
|
171
|
+
async function walk(relativeDir: string): Promise<void> {
|
|
172
|
+
const absoluteDir = path.join(projectRoot, relativeDir);
|
|
173
|
+
const entries = await readdir(absoluteDir, { withFileTypes: true });
|
|
174
|
+
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
if (entry.isDirectory()) {
|
|
177
|
+
if (IGNORE_DIRS.has(entry.name)) {
|
|
178
|
+
skippedDirectories.push(normalizePath(path.posix.join(relativeDir, entry.name)).replace(/^\.\//, ""));
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
await walk(normalizePath(path.posix.join(relativeDir, entry.name)));
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!entry.isFile()) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!SOURCE_EXTENSIONS.includes(path.extname(entry.name))) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
result.push(
|
|
194
|
+
normalizePath(path.posix.join(relativeDir, entry.name)).replace(/^\.\//, ""),
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await walk(".");
|
|
200
|
+
return { files: result.sort(), skippedDirectories: skippedDirectories.sort() };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function extractImportSpecifiers(content: string, language: string): string[] {
|
|
204
|
+
// Java/Python are not TS/JS syntax — the tsx transpiler cannot scan them
|
|
205
|
+
// (it throws today, which is why they already reach the fallback). Route them
|
|
206
|
+
// explicitly to the regex fallback that carries the java/python patterns,
|
|
207
|
+
// rather than depending on the transpiler always throwing. TS/JS keep the
|
|
208
|
+
// exact original transpiler-then-fallback path ⇒ byte-identical output.
|
|
209
|
+
if (language === "java" || language === "python") {
|
|
210
|
+
return extractImportSpecifiersFallback(content);
|
|
211
|
+
}
|
|
212
|
+
// `Bun.Transpiler#scanImports` ERASES type-only imports: `import type {X} from
|
|
213
|
+
// "./m"` and `export type {X} from "./m"` are compiled away, so the transpiler
|
|
214
|
+
// never reports them. Using the regex fallback only as a catch handler meant
|
|
215
|
+
// every type-only edge was silently missing from the graph — a module imported
|
|
216
|
+
// exclusively for its types (e.g. `src/security/types.ts`) reported zero
|
|
217
|
+
// dependents and showed up as a false orphan. UNION the two extractors: the
|
|
218
|
+
// transpiler contributes the specifiers a regex cannot see (dynamic/`require`,
|
|
219
|
+
// odd formatting), the fallback contributes the type-only ones.
|
|
220
|
+
const scanned = scanImportsOrEmpty(content);
|
|
221
|
+
const fallback = extractImportSpecifiersFallback(content);
|
|
222
|
+
return [...new Set([...scanned, ...fallback])].sort();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function scanImportsOrEmpty(content: string): string[] {
|
|
226
|
+
try {
|
|
227
|
+
const scanner = new Bun.Transpiler({ loader: "tsx" });
|
|
228
|
+
return scanner
|
|
229
|
+
.scanImports(content)
|
|
230
|
+
.map((entry) => entry.path)
|
|
231
|
+
.filter((specifier): specifier is string => typeof specifier === "string" && specifier.length > 0);
|
|
232
|
+
} catch {
|
|
233
|
+
// Unparseable source ⇒ the regex fallback alone still yields the imports.
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function extractImportSpecifiersFallback(content: string): string[] {
|
|
239
|
+
const specifiers = new Set<string>();
|
|
240
|
+
|
|
241
|
+
// Remove comments (JavaScript/TypeScript/Java style)
|
|
242
|
+
const withoutLineComments = content.replace(/\/\/.*$/gm, "");
|
|
243
|
+
const withoutBlockComments = withoutLineComments.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
244
|
+
// Python-style comments
|
|
245
|
+
const cleaned = withoutBlockComments.replace(/#.*$/gm, "");
|
|
246
|
+
|
|
247
|
+
// JavaScript/TypeScript patterns
|
|
248
|
+
const jsPatterns = [
|
|
249
|
+
/\bimport\s+(?:type\s+)?(?:[^'"]*?\s+from\s+)?["']([^"']+)["']/g,
|
|
250
|
+
/\bexport\s+(?:type\s+)?[^'"]*?\s+from\s+["']([^"']+)["']/g,
|
|
251
|
+
/\bimport\s*\(\s*["']([^"']+)["']\s*\)/g,
|
|
252
|
+
/\brequire\s*\(\s*["']([^"']+)["']\s*\)/g,
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
// Java patterns (import com.example.Class;)
|
|
256
|
+
const javaPatterns = [
|
|
257
|
+
/\bimport\s+(?:static\s+)?([a-zA-Z_][a-zA-Z0-9_\.]*(?:\.\*)?)\s*;/g,
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
// Python patterns (import module, from module import name).
|
|
261
|
+
// - `import` is anchored to statement start so the `import` in
|
|
262
|
+
// `from . import mod` is not mis-read as importing a module named `mod`.
|
|
263
|
+
// - The relative form (`from . import x`, `from ..a.b import c`) was dropped
|
|
264
|
+
// before because the module regex required a leading letter; the third
|
|
265
|
+
// pattern captures the leading-dot forms.
|
|
266
|
+
const pythonPatterns = [
|
|
267
|
+
/^[ \t]*import\s+([a-zA-Z_][a-zA-Z0-9_\.]*)/gm,
|
|
268
|
+
/\bfrom\s+([a-zA-Z_][a-zA-Z0-9_\.]*)\s+import/g,
|
|
269
|
+
/\bfrom\s+(\.+[a-zA-Z0-9_\.]*)\s+import/g,
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
for (const pattern of [...jsPatterns, ...javaPatterns, ...pythonPatterns]) {
|
|
273
|
+
for (const match of cleaned.matchAll(pattern)) {
|
|
274
|
+
const specifier = match[1];
|
|
275
|
+
if (specifier) {
|
|
276
|
+
specifiers.add(specifier);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return [...specifiers].sort();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function resolveImport(
|
|
285
|
+
projectRoot: string,
|
|
286
|
+
fromFile: string,
|
|
287
|
+
specifier: string,
|
|
288
|
+
fileSet: Set<string>,
|
|
289
|
+
resolver: ImportResolver,
|
|
290
|
+
): string | null {
|
|
291
|
+
for (const base of importCandidateBases(projectRoot, fromFile, specifier, resolver)) {
|
|
292
|
+
const resolved = resolveSourceCandidate(base, fileSet);
|
|
293
|
+
if (resolved) {
|
|
294
|
+
return resolved;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function resolveAssetImport(
|
|
301
|
+
projectRoot: string,
|
|
302
|
+
fromFile: string,
|
|
303
|
+
specifier: string,
|
|
304
|
+
resolver: ImportResolver,
|
|
305
|
+
): string | null {
|
|
306
|
+
for (const candidate of importCandidateBases(projectRoot, fromFile, specifier, resolver)) {
|
|
307
|
+
const extension = path.posix.extname(candidate);
|
|
308
|
+
if (ASSET_EXTENSIONS.includes(extension) && existsSync(path.join(projectRoot, candidate))) {
|
|
309
|
+
return candidate;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function importCandidateBases(
|
|
316
|
+
projectRoot: string,
|
|
317
|
+
fromFile: string,
|
|
318
|
+
specifier: string,
|
|
319
|
+
resolver: ImportResolver,
|
|
320
|
+
): string[] {
|
|
321
|
+
const normalizedSpecifier = stripImportSuffix(specifier);
|
|
322
|
+
// Relative specifiers are filesystem-relative for TS/JS, but Python uses
|
|
323
|
+
// dotted-level semantics (`.x`, `..a.b`) — hand those to the Python resolver.
|
|
324
|
+
if (normalizedSpecifier.startsWith(".") && getLanguage(fromFile) !== "python") {
|
|
325
|
+
const fromDir = path.posix.dirname(fromFile);
|
|
326
|
+
return [normalizePath(path.posix.normalize(path.posix.join(fromDir, normalizedSpecifier)))];
|
|
327
|
+
}
|
|
328
|
+
return resolver.candidateBases(normalizedSpecifier, fromFile)
|
|
329
|
+
.map((candidate) => normalizePath(path.relative(projectRoot, path.join(projectRoot, candidate))));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function resolveSourceCandidate(base: string, fileSet: Set<string>): string | null {
|
|
333
|
+
const candidates = [
|
|
334
|
+
base,
|
|
335
|
+
...SOURCE_RESOLUTION_EXTENSIONS.map((extension) => `${base}${extension}`),
|
|
336
|
+
...SOURCE_RESOLUTION_EXTENSIONS.map((extension) => path.posix.join(base, `index${extension}`)),
|
|
337
|
+
// Python package: `pkg` → `pkg/__init__.py`. Harmless for other languages
|
|
338
|
+
// (never present in their file sets), so TS/JS resolution is unchanged.
|
|
339
|
+
path.posix.join(base, "__init__.py"),
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
return candidates.find((candidate) => fileSet.has(candidate)) ?? null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function stripImportSuffix(specifier: string): string {
|
|
346
|
+
const queryIndex = specifier.indexOf("?");
|
|
347
|
+
const hashIndex = specifier.indexOf("#", 1);
|
|
348
|
+
const cutIndex = [queryIndex, hashIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
349
|
+
return cutIndex === undefined ? specifier : specifier.slice(0, cutIndex);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Build every per-language resolver once. Source roots for Java are parsed a
|
|
353
|
+
// single time here and captured in the resolver closure (no per-file re-parse).
|
|
354
|
+
async function loadResolvers(projectRoot: string): Promise<ResolverSet> {
|
|
355
|
+
const tsconfig = await loadTsconfigResolver(projectRoot);
|
|
356
|
+
const java = await loadJavaResolver(projectRoot);
|
|
357
|
+
const python = loadPythonResolver(projectRoot);
|
|
358
|
+
return { tsconfig, java, python };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function pickResolver(resolvers: ResolverSet, language: string): ImportResolver {
|
|
362
|
+
if (language === "java") {
|
|
363
|
+
return resolvers.java;
|
|
364
|
+
}
|
|
365
|
+
if (language === "python") {
|
|
366
|
+
return resolvers.python;
|
|
367
|
+
}
|
|
368
|
+
return resolvers.tsconfig;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Java resolver — maps a fully-qualified name `a.b.C` to `<sourceRoot>/a/b/C`
|
|
372
|
+
// (dots→slashes) for each discovered source root; `resolveSourceCandidate`
|
|
373
|
+
// appends `.java`. Source roots come from `pom.xml` (Gradle: T5), defaulting to
|
|
374
|
+
// the Maven conventions when build config is absent/unparseable.
|
|
375
|
+
async function loadJavaResolver(projectRoot: string): Promise<ImportResolver> {
|
|
376
|
+
const roots = await discoverJavaSourceRoots(projectRoot);
|
|
377
|
+
return createJavaResolver(roots);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function discoverJavaSourceRoots(projectRoot: string): Promise<string[]> {
|
|
381
|
+
const pomPath = path.join(projectRoot, "pom.xml");
|
|
382
|
+
if (existsSync(pomPath)) {
|
|
383
|
+
try {
|
|
384
|
+
const roots = parseMavenSourceRoots(await readFile(pomPath, "utf8"));
|
|
385
|
+
if (roots.length > 0) {
|
|
386
|
+
return roots;
|
|
387
|
+
}
|
|
388
|
+
} catch {
|
|
389
|
+
// Unparseable pom ⇒ fall back to conventions.
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (const gradleName of ["build.gradle", "build.gradle.kts"]) {
|
|
393
|
+
const gradlePath = path.join(projectRoot, gradleName);
|
|
394
|
+
if (existsSync(gradlePath)) {
|
|
395
|
+
try {
|
|
396
|
+
const roots = parseGradleSourceRoots(await readFile(gradlePath, "utf8"));
|
|
397
|
+
if (roots.length > 0) {
|
|
398
|
+
return roots;
|
|
399
|
+
}
|
|
400
|
+
} catch {
|
|
401
|
+
// Unparseable build script ⇒ fall back to conventions.
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return ["src/main/java", "src/test/java"];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function createJavaResolver(sourceRoots: string[]): ImportResolver {
|
|
409
|
+
return {
|
|
410
|
+
matchesAlias() {
|
|
411
|
+
return false;
|
|
412
|
+
},
|
|
413
|
+
candidateBases(specifier) {
|
|
414
|
+
// Wildcard `import a.b.*;` is a package reference, not a file — never
|
|
415
|
+
// fabricate a file edge for it (it is tracked as `unresolved` instead).
|
|
416
|
+
if (specifier.endsWith(".*")) {
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
const relative = specifier.split(".").join("/");
|
|
420
|
+
return sourceRoots.map((root) => normalizePath(path.posix.join(root, relative)));
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Python resolver — resolves dotted modules against the project root (and `src/`
|
|
426
|
+
// when present), plus relative imports against the importing file's package.
|
|
427
|
+
// `resolveSourceCandidate` appends `.py` / `__init__.py`.
|
|
428
|
+
function loadPythonResolver(projectRoot: string): ImportResolver {
|
|
429
|
+
const hasSrc = existsSync(path.join(projectRoot, "src"));
|
|
430
|
+
return createPythonResolver(hasSrc);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function createPythonResolver(hasSrc: boolean): ImportResolver {
|
|
434
|
+
const roots = hasSrc ? ["", "src"] : [""];
|
|
435
|
+
return {
|
|
436
|
+
matchesAlias() {
|
|
437
|
+
return false;
|
|
438
|
+
},
|
|
439
|
+
candidateBases(specifier, fromFile) {
|
|
440
|
+
if (specifier.startsWith(".")) {
|
|
441
|
+
// Relative import: leading dots = level. 1 dot = the importing file's
|
|
442
|
+
// package; each extra dot walks one package up. The remaining dotted
|
|
443
|
+
// path (if any) is appended as sub-packages.
|
|
444
|
+
const dots = /^\.+/.exec(specifier)?.[0].length ?? 0;
|
|
445
|
+
const rest = specifier.slice(dots);
|
|
446
|
+
let baseDir = path.posix.dirname(fromFile);
|
|
447
|
+
for (let level = 1; level < dots; level += 1) {
|
|
448
|
+
baseDir = path.posix.dirname(baseDir);
|
|
449
|
+
}
|
|
450
|
+
const suffix = rest ? rest.split(".").join("/") : "";
|
|
451
|
+
const base = suffix ? path.posix.join(baseDir, suffix) : baseDir;
|
|
452
|
+
return [normalizePath(base)];
|
|
453
|
+
}
|
|
454
|
+
const relative = specifier.split(".").join("/");
|
|
455
|
+
return roots.map((root) =>
|
|
456
|
+
normalizePath(root ? path.posix.join(root, relative) : relative),
|
|
457
|
+
);
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Parse Maven source roots from a `pom.xml` string (repo-relative, POSIX).
|
|
463
|
+
// - single module: honors `<build><sourceDirectory>` / `<testSourceDirectory>`,
|
|
464
|
+
// defaulting to `src/main/java` / `src/test/java`.
|
|
465
|
+
// - multi-module: unions each `<module>`'s conventional roots.
|
|
466
|
+
// Lightweight string/regex scan — no XML dependency, graceful on malformed input.
|
|
467
|
+
export function parseMavenSourceRoots(pomXml: string): string[] {
|
|
468
|
+
// Always try the Maven conventions — real poms often only reference the source
|
|
469
|
+
// dir via a `${project.build.sourceDirectory}` property inside a plugin config
|
|
470
|
+
// (which resolves to `src/main/java`), so the conventions must be present even
|
|
471
|
+
// when an explicit-looking `<sourceDirectory>` tag exists.
|
|
472
|
+
const roots = new Set<string>(["src/main/java", "src/test/java"]);
|
|
473
|
+
|
|
474
|
+
for (const match of pomXml.matchAll(/<module>\s*([^<]+?)\s*<\/module>/g)) {
|
|
475
|
+
const moduleDir = match[1]?.trim();
|
|
476
|
+
if (moduleDir && !moduleDir.includes("${")) {
|
|
477
|
+
roots.add(normalizePath(path.posix.join(moduleDir, "src/main/java")));
|
|
478
|
+
roots.add(normalizePath(path.posix.join(moduleDir, "src/test/java")));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Union any explicit, literal source-dir overrides. Skip Maven property
|
|
483
|
+
// placeholders (`${...}`) — they cannot be statically resolved to a real path.
|
|
484
|
+
for (const tag of ["sourceDirectory", "testSourceDirectory"]) {
|
|
485
|
+
const pattern = new RegExp(`<${tag}>\\s*([^<]+?)\\s*</${tag}>`, "g");
|
|
486
|
+
for (const match of pomXml.matchAll(pattern)) {
|
|
487
|
+
const dir = match[1]?.trim();
|
|
488
|
+
if (dir && !dir.includes("${")) {
|
|
489
|
+
roots.add(normalizePath(dir));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return [...roots];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Parse Gradle source roots from a `build.gradle`/`build.gradle.kts` string.
|
|
498
|
+
// Reads `srcDirs` entries from `sourceSets { … { java { srcDirs … } } }` in both
|
|
499
|
+
// the Groovy DSL (`srcDirs 'a', 'b'`) and the Kotlin DSL (`srcDirs("a", "b")`).
|
|
500
|
+
// Falls back to the Maven-layout conventions when no `srcDirs` are declared.
|
|
501
|
+
export function parseGradleSourceRoots(buildGradle: string): string[] {
|
|
502
|
+
const roots = new Set<string>();
|
|
503
|
+
for (const match of buildGradle.matchAll(/srcDirs\s*\(?\s*([^)\n]+)/g)) {
|
|
504
|
+
for (const quoted of (match[1] ?? "").matchAll(/['"]([^'"]+)['"]/g)) {
|
|
505
|
+
const dir = quoted[1]?.trim();
|
|
506
|
+
if (dir) {
|
|
507
|
+
roots.add(normalizePath(dir));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (roots.size === 0) {
|
|
512
|
+
roots.add("src/main/java");
|
|
513
|
+
roots.add("src/test/java");
|
|
514
|
+
}
|
|
515
|
+
return [...roots];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function loadTsconfigResolver(projectRoot: string): Promise<ImportResolver> {
|
|
519
|
+
const empty = createTsconfigResolver(null, []);
|
|
520
|
+
const tsconfigPath = path.join(projectRoot, "tsconfig.json");
|
|
521
|
+
if (!existsSync(tsconfigPath)) {
|
|
522
|
+
return empty;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
const raw = await readFile(tsconfigPath, "utf8");
|
|
527
|
+
const parsed = JSON.parse(stripJsonComments(raw)) as {
|
|
528
|
+
compilerOptions?: {
|
|
529
|
+
baseUrl?: unknown;
|
|
530
|
+
paths?: unknown;
|
|
531
|
+
};
|
|
532
|
+
};
|
|
533
|
+
const options = parsed.compilerOptions ?? {};
|
|
534
|
+
const baseUrl = typeof options.baseUrl === "string"
|
|
535
|
+
? normalizePath(path.posix.normalize(options.baseUrl)).replace(/^\.\//, "")
|
|
536
|
+
: null;
|
|
537
|
+
const paths = isRecord(options.paths)
|
|
538
|
+
? Object.entries(options.paths)
|
|
539
|
+
.filter((entry): entry is [string, string[]] => Array.isArray(entry[1]))
|
|
540
|
+
.map(([pattern, targets]) => ({
|
|
541
|
+
pattern,
|
|
542
|
+
targets: targets.filter((target): target is string => typeof target === "string"),
|
|
543
|
+
}))
|
|
544
|
+
: [];
|
|
545
|
+
return createTsconfigResolver(baseUrl, paths);
|
|
546
|
+
} catch {
|
|
547
|
+
return empty;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function createTsconfigResolver(baseUrl: string | null, mappings: PathMapping[]): ImportResolver {
|
|
552
|
+
return {
|
|
553
|
+
matchesAlias(specifier) {
|
|
554
|
+
const normalizedSpecifier = stripImportSuffix(specifier);
|
|
555
|
+
return mappings.some((mapping) => matchPathPattern(mapping.pattern, normalizedSpecifier) !== null);
|
|
556
|
+
},
|
|
557
|
+
candidateBases(specifier) {
|
|
558
|
+
const normalizedSpecifier = stripImportSuffix(specifier);
|
|
559
|
+
const candidates: string[] = [];
|
|
560
|
+
for (const mapping of mappings) {
|
|
561
|
+
const match = matchPathPattern(mapping.pattern, normalizedSpecifier);
|
|
562
|
+
if (match === null) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
for (const target of mapping.targets) {
|
|
566
|
+
candidates.push(applyPathTarget(baseUrl, target, match));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (baseUrl !== null) {
|
|
570
|
+
candidates.push(normalizePath(path.posix.join(baseUrl, normalizedSpecifier)));
|
|
571
|
+
}
|
|
572
|
+
return [...new Set(candidates.map((candidate) => candidate.replace(/^\.\//, "")))];
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function matchPathPattern(pattern: string, specifier: string): string | null {
|
|
578
|
+
const starIndex = pattern.indexOf("*");
|
|
579
|
+
if (starIndex < 0) {
|
|
580
|
+
return pattern === specifier ? "" : null;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const prefix = pattern.slice(0, starIndex);
|
|
584
|
+
const suffix = pattern.slice(starIndex + 1);
|
|
585
|
+
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
return specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function applyPathTarget(baseUrl: string | null, target: string, wildcard: string): string {
|
|
592
|
+
const replaced = target.includes("*") ? target.replace("*", wildcard) : target;
|
|
593
|
+
return normalizePath(path.posix.normalize(path.posix.join(baseUrl ?? "", replaced)));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function stripJsonComments(source: string): string {
|
|
597
|
+
let out = "";
|
|
598
|
+
let i = 0;
|
|
599
|
+
let quote: '"' | "'" | null = null;
|
|
600
|
+
|
|
601
|
+
while (i < source.length) {
|
|
602
|
+
const char = source[i];
|
|
603
|
+
const next = source[i + 1];
|
|
604
|
+
|
|
605
|
+
if (quote) {
|
|
606
|
+
out += char;
|
|
607
|
+
if (char === "\\") {
|
|
608
|
+
out += next ?? "";
|
|
609
|
+
i += 2;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (char === quote) {
|
|
613
|
+
quote = null;
|
|
614
|
+
}
|
|
615
|
+
i += 1;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (char === '"' || char === "'") {
|
|
620
|
+
quote = char;
|
|
621
|
+
out += char;
|
|
622
|
+
i += 1;
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (char === "/" && next === "/") {
|
|
626
|
+
while (i < source.length && source[i] !== "\n") i += 1;
|
|
627
|
+
out += "\n";
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (char === "/" && next === "*") {
|
|
631
|
+
i += 2;
|
|
632
|
+
while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i += 1;
|
|
633
|
+
i += 2;
|
|
634
|
+
out += " ";
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
out += char;
|
|
639
|
+
i += 1;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return out;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
646
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async function writeGraph(projectRoot: string, graph: GraphData): Promise<void> {
|
|
650
|
+
const storageDir = path.join(projectRoot, ".metaproject", "data", "gdgraph", "storage");
|
|
651
|
+
const artifactsDir = path.join(projectRoot, ".metaproject", "data", "gdgraph", "artifacts");
|
|
652
|
+
await mkdir(storageDir, { recursive: true });
|
|
653
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
654
|
+
|
|
655
|
+
await writeFile(
|
|
656
|
+
path.join(storageDir, "nodes.jsonl"),
|
|
657
|
+
graph.nodes.map((node) => JSON.stringify(node)).join("\n") + "\n",
|
|
658
|
+
"utf8",
|
|
659
|
+
);
|
|
660
|
+
await writeFile(
|
|
661
|
+
path.join(storageDir, "edges.jsonl"),
|
|
662
|
+
graph.edges.map((edge) => JSON.stringify(edge)).join("\n") + "\n",
|
|
663
|
+
"utf8",
|
|
664
|
+
);
|
|
665
|
+
await writeFile(
|
|
666
|
+
path.join(artifactsDir, "module-map.json"),
|
|
667
|
+
JSON.stringify(buildModuleMap(graph), null, 2) + "\n",
|
|
668
|
+
"utf8",
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function writeSummary(
|
|
673
|
+
projectRoot: string,
|
|
674
|
+
graph: GraphData,
|
|
675
|
+
collection: SourceCollection,
|
|
676
|
+
): Promise<string> {
|
|
677
|
+
const artifactsDir = path.join(projectRoot, ".metaproject", "data", "gdgraph", "artifacts");
|
|
678
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
679
|
+
|
|
680
|
+
const unresolved = graph.edges.filter((edge) => edge.kind === "unresolved");
|
|
681
|
+
const imports = graph.edges.filter((edge) => edge.kind === "imports");
|
|
682
|
+
const assets = graph.edges.filter((edge) => edge.kind === "asset");
|
|
683
|
+
const codeNodes = graph.nodes.filter((node) => node.kind === "file");
|
|
684
|
+
const assetNodes = graph.nodes.filter((node) => node.kind === "asset");
|
|
685
|
+
// Resolution is measured over ALL extracted specifiers (resolved + unresolved).
|
|
686
|
+
// Non-relative Java/Python imports that fail to resolve are now recorded as
|
|
687
|
+
// `unresolved` edges (not dropped), so this denominator is honest. When zero
|
|
688
|
+
// imports were extracted the rate is `n/a` — never a false `100%` from `0/0`.
|
|
689
|
+
const importTotal = imports.length + unresolved.length;
|
|
690
|
+
const resolutionDisplay = importTotal > 0
|
|
691
|
+
? `${Math.round((imports.length / importTotal) * 1000) / 10}%`
|
|
692
|
+
: "n/a";
|
|
693
|
+
const moduleRows = Object.entries(buildModuleMap({ nodes: codeNodes, edges: graph.edges }))
|
|
694
|
+
.map(([moduleName, files]) => ({ moduleName, files: files.length }))
|
|
695
|
+
.sort((a, b) => b.files - a.files)
|
|
696
|
+
.slice(0, 20)
|
|
697
|
+
.map((item) => `| ${item.moduleName} | ${item.files} |`)
|
|
698
|
+
.join("\n");
|
|
699
|
+
const unresolvedRows = Object.entries(groupBy(unresolved, (edge) => unresolvedType(edge.specifier)))
|
|
700
|
+
.sort((a, b) => b[1].length - a[1].length)
|
|
701
|
+
.map(([type, items]) => `| ${type} | ${items.length} |`)
|
|
702
|
+
.join("\n");
|
|
703
|
+
const skippedList = collection.skippedDirectories.length > 0
|
|
704
|
+
? collection.skippedDirectories.slice(0, 30).map((dir) => `- \`${dir}\``).join("\n")
|
|
705
|
+
: "- none";
|
|
706
|
+
const summaryPath = path.join(artifactsDir, "summary.md");
|
|
707
|
+
const content = `# gdgraph Summary
|
|
708
|
+
|
|
709
|
+
## Stats
|
|
710
|
+
|
|
711
|
+
- Source files indexed: ${codeNodes.length}
|
|
712
|
+
- Imported asset files indexed: ${assetNodes.length}
|
|
713
|
+
- Total nodes: ${graph.nodes.length}
|
|
714
|
+
- Edges: ${graph.edges.length}
|
|
715
|
+
- Import edges: ${imports.length}
|
|
716
|
+
- Asset edges: ${assets.length}
|
|
717
|
+
- Unresolved imports: ${unresolved.length}
|
|
718
|
+
- Import resolution: ${resolutionDisplay}
|
|
719
|
+
- Skipped generated/static directories: ${collection.skippedDirectories.length}
|
|
720
|
+
|
|
721
|
+
## Top Modules
|
|
722
|
+
|
|
723
|
+
| Module | Source Files |
|
|
724
|
+
|---|---:|
|
|
725
|
+
${moduleRows || "| _none_ | 0 |"}
|
|
726
|
+
|
|
727
|
+
## Unresolved By Type
|
|
728
|
+
|
|
729
|
+
| Type | Count |
|
|
730
|
+
|---|---:|
|
|
731
|
+
${unresolvedRows || "| _none_ | 0 |"}
|
|
732
|
+
|
|
733
|
+
## Skipped Directories
|
|
734
|
+
|
|
735
|
+
${skippedList}
|
|
736
|
+
|
|
737
|
+
## Generated Files
|
|
738
|
+
|
|
739
|
+
- \`.metaproject/data/gdgraph/storage/nodes.jsonl\`
|
|
740
|
+
- \`.metaproject/data/gdgraph/storage/edges.jsonl\`
|
|
741
|
+
- \`.metaproject/data/gdgraph/artifacts/module-map.json\`
|
|
742
|
+
|
|
743
|
+
## Next Commands
|
|
744
|
+
|
|
745
|
+
\`\`\`bash
|
|
746
|
+
keryx gdgraph query cycles
|
|
747
|
+
keryx gdgraph query orphans
|
|
748
|
+
keryx gdgraph affected <file>
|
|
749
|
+
\`\`\`
|
|
750
|
+
`;
|
|
751
|
+
|
|
752
|
+
await writeFile(summaryPath, content, "utf8");
|
|
753
|
+
return summaryPath;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function buildModuleMap(graph: GraphData): Record<string, string[]> {
|
|
757
|
+
const modules: Record<string, string[]> = {};
|
|
758
|
+
for (const node of graph.nodes.filter((item) => item.kind === "file")) {
|
|
759
|
+
const [first, second] = node.path.split("/");
|
|
760
|
+
const moduleName = first === "src" && second ? second : first ?? "root";
|
|
761
|
+
modules[moduleName] ??= [];
|
|
762
|
+
modules[moduleName].push(node.path);
|
|
763
|
+
}
|
|
764
|
+
return modules;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function getLanguage(file: string): "typescript" | "javascript" | "java" | "python" {
|
|
768
|
+
if (file.endsWith(".ts") || file.endsWith(".tsx")) {
|
|
769
|
+
return "typescript";
|
|
770
|
+
}
|
|
771
|
+
if (file.endsWith(".java")) {
|
|
772
|
+
return "java";
|
|
773
|
+
}
|
|
774
|
+
if (file.endsWith(".py")) {
|
|
775
|
+
return "python";
|
|
776
|
+
}
|
|
777
|
+
return "javascript";
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function groupBy<T>(items: T[], getKey: (item: T) => string): Record<string, T[]> {
|
|
781
|
+
const grouped: Record<string, T[]> = {};
|
|
782
|
+
for (const item of items) {
|
|
783
|
+
const key = getKey(item);
|
|
784
|
+
grouped[key] ??= [];
|
|
785
|
+
grouped[key].push(item);
|
|
786
|
+
}
|
|
787
|
+
return grouped;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function unresolvedType(specifier: string): string {
|
|
791
|
+
const stripped = stripImportSuffix(specifier);
|
|
792
|
+
const extension = path.posix.extname(stripped);
|
|
793
|
+
if (extension) {
|
|
794
|
+
return extension;
|
|
795
|
+
}
|
|
796
|
+
if (specifier.startsWith(".")) {
|
|
797
|
+
return "relative-code";
|
|
798
|
+
}
|
|
799
|
+
return "package";
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function normalizePath(value: string): string {
|
|
803
|
+
return value.split(path.sep).join("/");
|
|
804
|
+
}
|