@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,193 @@
|
|
|
1
|
+
// Ranked, token-budgeted repo map (specification.md §8.3; T-B7, B-4/C0-8).
|
|
2
|
+
//
|
|
3
|
+
// Ranks files (or symbols, when the layer is present) via personalized PageRank
|
|
4
|
+
// and renders `path + top symbols + signatures` into
|
|
5
|
+
// `.metaproject/data/gdgraph/artifacts/repomap.md`, enforcing a hard token
|
|
6
|
+
// budget. Overflow entries are dropped in rank order with a stable
|
|
7
|
+
// "… N entries omitted …" marker. The token estimator mirrors gdctx's byte
|
|
8
|
+
// budget idiom (default `chars-div-4`). Deterministic: a re-run diff is empty.
|
|
9
|
+
|
|
10
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import type { GdgraphConfig } from "./config";
|
|
13
|
+
import { personalizedPageRank, type RankEdge } from "./pagerank";
|
|
14
|
+
import type { GraphData, SymbolNode } from "./types";
|
|
15
|
+
|
|
16
|
+
export interface RepomapEntry {
|
|
17
|
+
path: string;
|
|
18
|
+
score: number;
|
|
19
|
+
symbols: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RepomapOptions {
|
|
23
|
+
budget?: number;
|
|
24
|
+
seed?: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RepomapResult {
|
|
28
|
+
path: string;
|
|
29
|
+
content: string;
|
|
30
|
+
entries: RepomapEntry[];
|
|
31
|
+
tokens: number;
|
|
32
|
+
omitted: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Local token estimator — the documented `chars-div-4` default (AC3.2). Kept
|
|
36
|
+
// tiny + local since there is no shared gdctx estimator in this repo.
|
|
37
|
+
export function estimateTokens(text: string): number {
|
|
38
|
+
return Math.ceil(text.length / 4);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The stable omission marker (AC3.2). `count` varies; the shape is fixed.
|
|
42
|
+
function omissionMarker(count: number): string {
|
|
43
|
+
return `\n… ${count} entries omitted …\n`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function repomapArtifactPath(cwd: string): string {
|
|
47
|
+
return path.join(cwd, ".metaproject", "data", "gdgraph", "artifacts", "repomap.md");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Build the weighted rank edges from the graph: import edges w=1.0, and — when
|
|
51
|
+
// the symbol layer is present — CALL edges w=callWeight and `defines` w=0.5.
|
|
52
|
+
export function buildRankEdges(graph: GraphData, config: GdgraphConfig): RankEdge[] {
|
|
53
|
+
const edges: RankEdge[] = [];
|
|
54
|
+
for (const edge of graph.edges) {
|
|
55
|
+
if (edge.kind === "imports") {
|
|
56
|
+
edges.push({ from: edge.from, to: edge.to, weight: 1.0 });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const call of graph.calls ?? []) {
|
|
60
|
+
if (call.kind === "calls") {
|
|
61
|
+
edges.push({ from: call.from, to: call.to, weight: config.repomap.callWeight });
|
|
62
|
+
} else if (call.kind === "defines") {
|
|
63
|
+
edges.push({ from: call.from, to: call.to, weight: 0.5 });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return edges;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function renderSymbol(symbol: SymbolNode): string {
|
|
70
|
+
if (symbol.signature && symbol.signature.length > 0) {
|
|
71
|
+
return symbol.signature;
|
|
72
|
+
}
|
|
73
|
+
const container = symbol.container ? `${symbol.container}.` : "";
|
|
74
|
+
return `${symbol.kind} ${container}${symbol.name}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function topSymbols(graph: GraphData, filePath: string, max: number): string[] {
|
|
78
|
+
const symbols = (graph.symbols ?? [])
|
|
79
|
+
.filter((symbol) => symbol.path === filePath)
|
|
80
|
+
.sort((a, b) =>
|
|
81
|
+
a.startLine !== b.startLine
|
|
82
|
+
? a.startLine - b.startLine
|
|
83
|
+
: a.name < b.name
|
|
84
|
+
? -1
|
|
85
|
+
: a.name > b.name
|
|
86
|
+
? 1
|
|
87
|
+
: 0,
|
|
88
|
+
)
|
|
89
|
+
.slice(0, max);
|
|
90
|
+
return symbols.map(renderSymbol);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function renderEntryBlock(entry: RepomapEntry): string {
|
|
94
|
+
const lines = [`## ${entry.path}`];
|
|
95
|
+
for (const symbol of entry.symbols) {
|
|
96
|
+
lines.push(`- ${symbol}`);
|
|
97
|
+
}
|
|
98
|
+
return `${lines.join("\n")}\n\n`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const HEADER = [
|
|
102
|
+
"# gdgraph Repomap",
|
|
103
|
+
"",
|
|
104
|
+
"Ranked by personalized PageRank over the import/call graph.",
|
|
105
|
+
"",
|
|
106
|
+
"",
|
|
107
|
+
].join("\n");
|
|
108
|
+
|
|
109
|
+
// Compute the ranked, budget-fitted repomap. Pure over the in-memory graph +
|
|
110
|
+
// config; no I/O. `writeRepomap` persists the result.
|
|
111
|
+
export function computeRepomap(
|
|
112
|
+
graph: GraphData,
|
|
113
|
+
config: GdgraphConfig,
|
|
114
|
+
options: RepomapOptions = {},
|
|
115
|
+
): RepomapResult {
|
|
116
|
+
const budget = options.budget ?? config.repomap.tokenBudget;
|
|
117
|
+
|
|
118
|
+
// Rank file nodes (the stable, always-present layer).
|
|
119
|
+
const fileNodes = graph.nodes.filter((node) => node.kind === "file").map((node) => node.path);
|
|
120
|
+
const rankEdges = buildRankEdges(graph, config);
|
|
121
|
+
|
|
122
|
+
const personalization = new Map<string, number>();
|
|
123
|
+
for (const seed of options.seed ?? []) {
|
|
124
|
+
const normalized = seed.replace(/^\.\//, "");
|
|
125
|
+
const match = fileNodes.find((file) => file === normalized || file.endsWith(normalized));
|
|
126
|
+
if (match) {
|
|
127
|
+
personalization.set(match, (personalization.get(match) ?? 0) + 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const ranked = personalizedPageRank(fileNodes, rankEdges, {
|
|
132
|
+
damping: config.repomap.damping,
|
|
133
|
+
iterations: config.repomap.iterations,
|
|
134
|
+
tolerance: config.repomap.tolerance,
|
|
135
|
+
...(personalization.size > 0 ? { personalization } : {}),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const allEntries: RepomapEntry[] = ranked.map((node) => ({
|
|
139
|
+
path: node.id,
|
|
140
|
+
score: node.score,
|
|
141
|
+
symbols: topSymbols(graph, node.id, config.repomap.maxSymbolsPerFile),
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
// Greedily fill within the budget, always leaving room for the marker.
|
|
145
|
+
let out = HEADER;
|
|
146
|
+
const rendered: RepomapEntry[] = [];
|
|
147
|
+
for (const entry of allEntries) {
|
|
148
|
+
const trial = out + renderEntryBlock(entry);
|
|
149
|
+
if (estimateTokens(trial) <= budget) {
|
|
150
|
+
out = trial;
|
|
151
|
+
rendered.push(entry);
|
|
152
|
+
} else {
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let omitted = allEntries.length - rendered.length;
|
|
158
|
+
if (omitted > 0) {
|
|
159
|
+
// Ensure the final content (incl. marker) stays within budget; pop entries
|
|
160
|
+
// until the marker fits (AC3.2 hard bound).
|
|
161
|
+
while (rendered.length > 0 && estimateTokens(out + omissionMarker(omitted)) > budget) {
|
|
162
|
+
const popped = rendered.pop();
|
|
163
|
+
if (popped) {
|
|
164
|
+
out = out.slice(0, out.length - renderEntryBlock(popped).length);
|
|
165
|
+
omitted += 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
out += omissionMarker(omitted);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
path: repomapArtifactPath("."),
|
|
173
|
+
content: out,
|
|
174
|
+
entries: rendered,
|
|
175
|
+
tokens: estimateTokens(out),
|
|
176
|
+
omitted,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Compute + persist `artifacts/repomap.md`. Re-running yields a byte-identical
|
|
181
|
+
// file (deterministic ranking + rendering).
|
|
182
|
+
export async function writeRepomap(
|
|
183
|
+
cwd: string,
|
|
184
|
+
graph: GraphData,
|
|
185
|
+
config: GdgraphConfig,
|
|
186
|
+
options: RepomapOptions = {},
|
|
187
|
+
): Promise<RepomapResult> {
|
|
188
|
+
const result = computeRepomap(graph, config, options);
|
|
189
|
+
const artifactPath = repomapArtifactPath(cwd);
|
|
190
|
+
await mkdir(path.dirname(artifactPath), { recursive: true });
|
|
191
|
+
await writeFile(artifactPath, result.content, "utf8");
|
|
192
|
+
return { ...result, path: artifactPath };
|
|
193
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { expect, test } from "bun:test";
|
|
5
|
+
import { createGdgraphService } from "./service";
|
|
6
|
+
|
|
7
|
+
async function makeProject(): Promise<string> {
|
|
8
|
+
const root = await mkdtemp(path.join(tmpdir(), "keryx-svc-"));
|
|
9
|
+
await mkdir(path.join(root, "src"), { recursive: true });
|
|
10
|
+
await writeFile(path.join(root, "src", "a.ts"), "export const a = 1;\n");
|
|
11
|
+
await writeFile(path.join(root, "src", "b.ts"), "import { a } from './a';\nexport const b = a;\n");
|
|
12
|
+
await writeFile(path.join(root, "src", "c.ts"), "import { b } from './b';\nexport const c = b;\n");
|
|
13
|
+
return root;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("AC5.3/T-1 — service.affected is a pure in-process method (transport-independent)", async () => {
|
|
17
|
+
const root = await makeProject();
|
|
18
|
+
try {
|
|
19
|
+
const service = createGdgraphService();
|
|
20
|
+
await service.build(root);
|
|
21
|
+
// depth 2 closure of a: b (hop1) + c (hop2).
|
|
22
|
+
const affected = await service.affected(root, "src/a.ts", { depth: 2 });
|
|
23
|
+
expect(affected.dependents).toEqual(["src/b.ts", "src/c.ts"]);
|
|
24
|
+
// default depth (config) = 1 ⇒ only direct dependents.
|
|
25
|
+
const shallow = await service.affected(root, "src/a.ts");
|
|
26
|
+
expect(shallow.dependents).toEqual(["src/b.ts"]);
|
|
27
|
+
} finally {
|
|
28
|
+
await rm(root, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("AC5.3/T-1 — service.repomap writes artifacts/repomap.md deterministically", async () => {
|
|
33
|
+
const root = await makeProject();
|
|
34
|
+
try {
|
|
35
|
+
const service = createGdgraphService();
|
|
36
|
+
await service.build(root);
|
|
37
|
+
const first = await service.repomap(root, { budget: 2000 });
|
|
38
|
+
expect(first.path).toContain(path.join("artifacts", "repomap.md"));
|
|
39
|
+
expect(first.tokens).toBeLessThanOrEqual(2000);
|
|
40
|
+
const second = await service.repomap(root, { budget: 2000 });
|
|
41
|
+
expect(second.content).toBe(first.content);
|
|
42
|
+
} finally {
|
|
43
|
+
await rm(root, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("service.query returns cycles + orphans over the built graph", async () => {
|
|
48
|
+
const root = await makeProject();
|
|
49
|
+
try {
|
|
50
|
+
const service = createGdgraphService();
|
|
51
|
+
await service.build(root);
|
|
52
|
+
const cycles = await service.query(root, "cycles");
|
|
53
|
+
expect(Array.isArray(cycles)).toBe(true);
|
|
54
|
+
const orphans = await service.query(root, "orphans");
|
|
55
|
+
expect(Array.isArray(orphans)).toBe(true);
|
|
56
|
+
} finally {
|
|
57
|
+
await rm(root, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Canonical in-process gdgraph service facade (specification.md §7; T-B1, M-2).
|
|
2
|
+
//
|
|
3
|
+
// `createGdgraphService()` is the transport-independent contract Block A's MCP
|
|
4
|
+
// Tools wrap (`gdgraph.affected`, etc.) — no new logic lives in `mcp/`. Every
|
|
5
|
+
// method is pure over storage + config (no network, no optional dep on the
|
|
6
|
+
// default path) and unit-testable without any transport (T-1).
|
|
7
|
+
|
|
8
|
+
import { buildGraph } from "./build";
|
|
9
|
+
import { computeAffected, type AffectedOptions, type AffectedResult } from "./affected";
|
|
10
|
+
import { loadGdgraphConfig } from "./config";
|
|
11
|
+
import { getCycles, getOrphans, loadGraph } from "./query";
|
|
12
|
+
import { writeRepomap, type RepomapOptions, type RepomapResult } from "./repomap";
|
|
13
|
+
import type { GraphData } from "./types";
|
|
14
|
+
|
|
15
|
+
export interface GdgraphService {
|
|
16
|
+
build(cwd: string): Promise<{ nodes: number; edges: number; summaryPath: string }>;
|
|
17
|
+
loadGraph(cwd: string): Promise<GraphData>;
|
|
18
|
+
affected(cwd: string, target: string, options?: AffectedOptions): Promise<AffectedResult>;
|
|
19
|
+
repomap(cwd: string, options?: RepomapOptions): Promise<RepomapResult>;
|
|
20
|
+
query(cwd: string, q: "cycles" | "orphans"): Promise<string[] | string[][]>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createGdgraphService(): GdgraphService {
|
|
24
|
+
return {
|
|
25
|
+
async build(cwd) {
|
|
26
|
+
return buildGraph(cwd);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async loadGraph(cwd) {
|
|
30
|
+
return loadGraph(cwd);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async affected(cwd, target, options = {}) {
|
|
34
|
+
const config = await loadGdgraphConfig(cwd);
|
|
35
|
+
const graph = await loadGraph(cwd);
|
|
36
|
+
const depth = options.depth ?? config.affected.defaultDepth;
|
|
37
|
+
return computeAffected(graph, target, { ...options, depth });
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async repomap(cwd, options = {}) {
|
|
41
|
+
const config = await loadGdgraphConfig(cwd);
|
|
42
|
+
const graph = await loadGraph(cwd);
|
|
43
|
+
return writeRepomap(cwd, graph, config, options);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async query(cwd, q) {
|
|
47
|
+
const graph = await loadGraph(cwd);
|
|
48
|
+
return q === "cycles" ? getCycles(graph) : getOrphans(graph);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
// Cheap, low-noise graph-staleness signal: the graph is "maybe stale" when the
|
|
5
|
+
// repo's `.git/HEAD` (which changes on commit / branch switch) is newer than the
|
|
6
|
+
// built graph storage. Unlike "uncommitted changes", this does NOT fire on every
|
|
7
|
+
// working-tree edit during active development — only after the repo state moved
|
|
8
|
+
// since the last build. Best-effort: any error ⇒ not stale (never warns wrongly).
|
|
9
|
+
export async function graphMaybeStale(cwd: string): Promise<boolean> {
|
|
10
|
+
const nodes = path.join(cwd, ".metaproject", "data", "gdgraph", "storage", "nodes.jsonl");
|
|
11
|
+
const head = path.join(cwd, ".git", "HEAD");
|
|
12
|
+
try {
|
|
13
|
+
const [nodesStat, headStat] = await Promise.all([stat(nodes), stat(head)]);
|
|
14
|
+
return headStat.mtimeMs > nodesStat.mtimeMs;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const STALE_NOTE = "note: repo moved since the last graph build — `keryx gdgraph build` to refresh.";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { querySymbol, resolveSymbols, transitiveCallers } from "./symbol";
|
|
3
|
+
import type { GraphData, SymbolNode, CallEdge } from "./types";
|
|
4
|
+
|
|
5
|
+
function sym(id: string, name: string, path: string, startLine: number): SymbolNode {
|
|
6
|
+
return { id, kind: "function", path, name, container: null, startLine, endLine: startLine + 5, language: "typescript" };
|
|
7
|
+
}
|
|
8
|
+
function call(from: string, to: string, resolved: boolean): CallEdge {
|
|
9
|
+
return { id: `${from}->${to}`, from, to, kind: resolved ? "calls" : "unresolved-call", resolved };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const A = sym("src/a.ts#foo", "foo", "src/a.ts", 10);
|
|
13
|
+
const B = sym("src/b.ts#bar", "bar", "src/b.ts", 20);
|
|
14
|
+
const C = sym("src/c.ts#Foo", "Foo", "src/c.ts", 5);
|
|
15
|
+
|
|
16
|
+
const GRAPH: GraphData = {
|
|
17
|
+
nodes: [],
|
|
18
|
+
edges: [],
|
|
19
|
+
symbols: [A, B, C],
|
|
20
|
+
calls: [
|
|
21
|
+
call("src/b.ts#bar", "src/a.ts#foo", true), // bar calls foo
|
|
22
|
+
call("src/a.ts#foo", "src/c.ts#Foo", true), // foo calls Foo
|
|
23
|
+
call("src/a.ts#foo", "someUnknown.method", false), // foo calls unresolved
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
test("resolveSymbols prefers exact name, then case-insensitive, then substring", () => {
|
|
28
|
+
expect(resolveSymbols(GRAPH.symbols!, "foo").map((s) => s.id)).toEqual(["src/a.ts#foo"]);
|
|
29
|
+
// case-insensitive tier only when no exact: "FOO" -> foo (exact 'foo'? no; ci matches foo AND Foo)
|
|
30
|
+
const ci = resolveSymbols(GRAPH.symbols!, "FOO").map((s) => s.name).sort();
|
|
31
|
+
expect(ci).toEqual(["Foo", "foo"]);
|
|
32
|
+
// substring tier
|
|
33
|
+
expect(resolveSymbols(GRAPH.symbols!, "ba").map((s) => s.name)).toEqual(["bar"]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("querySymbol returns definition, callers, and callees", () => {
|
|
37
|
+
const r = querySymbol(GRAPH, "foo");
|
|
38
|
+
expect(r.definitions.map((d) => d.id)).toEqual(["src/a.ts#foo"]);
|
|
39
|
+
// bar calls foo -> caller
|
|
40
|
+
expect(r.callers.map((c) => c.label)).toEqual(["bar (src/b.ts:20)"]);
|
|
41
|
+
// foo calls Foo (resolved) + someUnknown.method (unresolved)
|
|
42
|
+
const labels = r.callees.map((c) => c.label);
|
|
43
|
+
expect(labels).toContain("Foo (src/c.ts:5)");
|
|
44
|
+
expect(labels).toContain("someUnknown.method");
|
|
45
|
+
const unresolved = r.callees.find((c) => c.label === "someUnknown.method");
|
|
46
|
+
expect(unresolved?.resolved).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("querySymbol on no match returns empty definitions", () => {
|
|
50
|
+
expect(querySymbol(GRAPH, "nonexistent").definitions).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("querySymbol tolerates a graph with no symbol layer", () => {
|
|
54
|
+
const r = querySymbol({ nodes: [], edges: [] }, "foo");
|
|
55
|
+
expect(r.definitions).toEqual([]);
|
|
56
|
+
expect(r.callers).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("transitiveCallers walks the reverse call graph with hop distances", () => {
|
|
60
|
+
// chain: baz -> bar -> foo (bar calls foo, baz calls bar)
|
|
61
|
+
const graph: GraphData = {
|
|
62
|
+
nodes: [],
|
|
63
|
+
edges: [],
|
|
64
|
+
symbols: [A, B, sym("src/d.ts#baz", "baz", "src/d.ts", 40)],
|
|
65
|
+
calls: [
|
|
66
|
+
call("src/b.ts#bar", "src/a.ts#foo", true),
|
|
67
|
+
call("src/d.ts#baz", "src/b.ts#bar", true),
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
const impact = transitiveCallers(graph, ["src/a.ts#foo"], 5);
|
|
71
|
+
const byName = Object.fromEntries(impact.map((n) => [n.label.split(" ")[0], n.hop]));
|
|
72
|
+
expect(byName["bar"]).toBe(1);
|
|
73
|
+
expect(byName["baz"]).toBe(2);
|
|
74
|
+
expect(impact.some((n) => n.label.startsWith("foo"))).toBe(false); // seed excluded
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("transitiveCallers respects maxDepth", () => {
|
|
78
|
+
const graph: GraphData = {
|
|
79
|
+
nodes: [],
|
|
80
|
+
edges: [],
|
|
81
|
+
symbols: [A, B, sym("src/d.ts#baz", "baz", "src/d.ts", 40)],
|
|
82
|
+
calls: [
|
|
83
|
+
call("src/b.ts#bar", "src/a.ts#foo", true),
|
|
84
|
+
call("src/d.ts#baz", "src/b.ts#bar", true),
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
const impact = transitiveCallers(graph, ["src/a.ts#foo"], 1);
|
|
88
|
+
expect(impact.map((n) => n.label.split(" ")[0])).toEqual(["bar"]); // baz is hop 2, excluded
|
|
89
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { CallEdge, GraphData, SymbolNode } from "./types";
|
|
2
|
+
|
|
3
|
+
// Symbol-level query over the (optional) tree-sitter layer: "where is X defined /
|
|
4
|
+
// who calls X / what does X call". Pure over the in-memory graph; the CLI layer
|
|
5
|
+
// handles the "symbol layer not active" case when `graph.symbols` is absent.
|
|
6
|
+
|
|
7
|
+
export interface SymbolRef {
|
|
8
|
+
// Display label: "name (path:line)" for a resolved symbol, else the raw token.
|
|
9
|
+
label: string;
|
|
10
|
+
resolved: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SymbolQueryResult {
|
|
14
|
+
query: string;
|
|
15
|
+
definitions: SymbolNode[];
|
|
16
|
+
callers: SymbolRef[];
|
|
17
|
+
callees: SymbolRef[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Resolve a name to matching symbols: exact name, then case-insensitive, then
|
|
21
|
+
// substring — stopping at the first tier that yields hits so precise names win.
|
|
22
|
+
export function resolveSymbols(symbols: SymbolNode[], name: string, limit = 25): SymbolNode[] {
|
|
23
|
+
const q = name.trim();
|
|
24
|
+
if (!q) return [];
|
|
25
|
+
const lower = q.toLowerCase();
|
|
26
|
+
|
|
27
|
+
const exact = symbols.filter((s) => s.name === q);
|
|
28
|
+
if (exact.length > 0) return exact.slice(0, limit);
|
|
29
|
+
|
|
30
|
+
const ci = symbols.filter((s) => s.name.toLowerCase() === lower);
|
|
31
|
+
if (ci.length > 0) return ci.slice(0, limit);
|
|
32
|
+
|
|
33
|
+
return symbols.filter((s) => s.name.toLowerCase().includes(lower)).slice(0, limit);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function labelFor(token: string, byId: Map<string, SymbolNode>): SymbolRef {
|
|
37
|
+
const symbol = byId.get(token);
|
|
38
|
+
if (symbol) {
|
|
39
|
+
return { label: `${symbol.name} (${symbol.path}:${symbol.startLine})`, resolved: true };
|
|
40
|
+
}
|
|
41
|
+
return { label: token, resolved: false };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function dedupeRefs(refs: SymbolRef[]): SymbolRef[] {
|
|
45
|
+
const seen = new Set<string>();
|
|
46
|
+
const out: SymbolRef[] = [];
|
|
47
|
+
for (const ref of refs) {
|
|
48
|
+
if (seen.has(ref.label)) continue;
|
|
49
|
+
seen.add(ref.label);
|
|
50
|
+
out.push(ref);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ImpactNode {
|
|
56
|
+
label: string;
|
|
57
|
+
hop: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Transitive caller blast radius over the call graph: "if I change these
|
|
61
|
+
// symbols, which symbols (transitively) call them" — the symbol-level impact
|
|
62
|
+
// that file-level `affected` cannot express. Reverse BFS over resolved call
|
|
63
|
+
// edges up to `maxDepth`, nearest hop wins, seeds excluded.
|
|
64
|
+
export function transitiveCallers(
|
|
65
|
+
graph: GraphData,
|
|
66
|
+
seedIds: string[],
|
|
67
|
+
maxDepth: number,
|
|
68
|
+
): ImpactNode[] {
|
|
69
|
+
const calls = (graph.calls ?? []).filter((c) => c.kind === "calls");
|
|
70
|
+
const byId = new Map((graph.symbols ?? []).map((s) => [s.id, s]));
|
|
71
|
+
// callee id -> set of caller ids
|
|
72
|
+
const callersOf = new Map<string, Set<string>>();
|
|
73
|
+
for (const call of calls) {
|
|
74
|
+
if (!callersOf.has(call.to)) callersOf.set(call.to, new Set());
|
|
75
|
+
callersOf.get(call.to)!.add(call.from);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const hop = new Map<string, number>();
|
|
79
|
+
let frontier = new Set(seedIds);
|
|
80
|
+
for (const seed of seedIds) hop.set(seed, 0);
|
|
81
|
+
|
|
82
|
+
for (let depth = 1; depth <= maxDepth; depth += 1) {
|
|
83
|
+
const next = new Set<string>();
|
|
84
|
+
for (const id of frontier) {
|
|
85
|
+
for (const caller of callersOf.get(id) ?? []) {
|
|
86
|
+
if (!hop.has(caller)) {
|
|
87
|
+
hop.set(caller, depth);
|
|
88
|
+
next.add(caller);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (next.size === 0) break;
|
|
93
|
+
frontier = next;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const out: ImpactNode[] = [];
|
|
97
|
+
for (const [id, h] of hop) {
|
|
98
|
+
if (h === 0) continue; // exclude the seeds themselves
|
|
99
|
+
const symbol = byId.get(id);
|
|
100
|
+
if (symbol) {
|
|
101
|
+
out.push({ label: `${symbol.name} (${symbol.path}:${symbol.startLine})`, hop: h });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return out.sort((a, b) => a.hop - b.hop || a.label.localeCompare(b.label));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function querySymbol(graph: GraphData, name: string): SymbolQueryResult {
|
|
108
|
+
const symbols = graph.symbols ?? [];
|
|
109
|
+
const calls = graph.calls ?? [];
|
|
110
|
+
const definitions = resolveSymbols(symbols, name);
|
|
111
|
+
const ids = new Set(definitions.map((s) => s.id));
|
|
112
|
+
const byId = new Map(symbols.map((s) => [s.id, s]));
|
|
113
|
+
|
|
114
|
+
const callEdges = calls.filter((c: CallEdge) => c.kind === "calls" || c.kind === "unresolved-call");
|
|
115
|
+
const callers = dedupeRefs(
|
|
116
|
+
callEdges.filter((c) => ids.has(c.to)).map((c) => labelFor(c.from, byId)),
|
|
117
|
+
).sort((a, b) => a.label.localeCompare(b.label));
|
|
118
|
+
const callees = dedupeRefs(
|
|
119
|
+
callEdges.filter((c) => ids.has(c.from)).map((c) => labelFor(c.to, byId)),
|
|
120
|
+
).sort((a, b) => a.label.localeCompare(b.label));
|
|
121
|
+
|
|
122
|
+
return { query: name, definitions, callers, callees };
|
|
123
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { isTreesitterEnabled, setTreesitterEnabled, TREESITTER_CAPABILITY } from "./symbols-capability";
|
|
3
|
+
|
|
4
|
+
test("enable adds the capability entry to an empty manifest", () => {
|
|
5
|
+
const next = setTreesitterEnabled({}, true);
|
|
6
|
+
expect(isTreesitterEnabled(next)).toBe(true);
|
|
7
|
+
const caps = (next.modules as any).gdgraph.capabilities;
|
|
8
|
+
expect(caps).toHaveLength(1);
|
|
9
|
+
expect(caps[0]).toMatchObject({ id: TREESITTER_CAPABILITY, enabled: true, kind: "ceiling" });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("disable flips enabled to false", () => {
|
|
13
|
+
const enabled = setTreesitterEnabled({}, true);
|
|
14
|
+
const disabled = setTreesitterEnabled(enabled, false);
|
|
15
|
+
expect(isTreesitterEnabled(disabled)).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("is idempotent — never duplicates the capability entry", () => {
|
|
19
|
+
let m: Record<string, unknown> = {};
|
|
20
|
+
m = setTreesitterEnabled(m, true);
|
|
21
|
+
m = setTreesitterEnabled(m, true);
|
|
22
|
+
const caps = (m.modules as any).gdgraph.capabilities.filter(
|
|
23
|
+
(c: any) => c.id === TREESITTER_CAPABILITY,
|
|
24
|
+
);
|
|
25
|
+
expect(caps).toHaveLength(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("preserves other gdgraph capabilities and other modules", () => {
|
|
29
|
+
const manifest = {
|
|
30
|
+
modules: {
|
|
31
|
+
gdgraph: { enabled: true, capabilities: [{ id: "gdgraph.other", enabled: true }] },
|
|
32
|
+
security: { enabled: true },
|
|
33
|
+
},
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
};
|
|
36
|
+
const next = setTreesitterEnabled(manifest, true) as any;
|
|
37
|
+
const ids = next.modules.gdgraph.capabilities.map((c: any) => c.id).sort();
|
|
38
|
+
expect(ids).toEqual(["gdgraph.other", "gdgraph.treesitter"]);
|
|
39
|
+
expect(next.modules.security).toEqual({ enabled: true });
|
|
40
|
+
expect(next.schemaVersion).toBe(1);
|
|
41
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Enable/disable the `gdgraph.treesitter` capability in a parsed
|
|
2
|
+
// `metaproject.json` — the opt-in switch for the symbol layer. Pure manifest
|
|
3
|
+
// transforms (the command layer owns file IO), merge-safe: preserves any other
|
|
4
|
+
// gdgraph capabilities and never rewrites unrelated modules.
|
|
5
|
+
|
|
6
|
+
export const TREESITTER_CAPABILITY = "gdgraph.treesitter";
|
|
7
|
+
|
|
8
|
+
type Json = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
function treesitterEntry(enabled: boolean): Json {
|
|
11
|
+
return {
|
|
12
|
+
id: TREESITTER_CAPABILITY,
|
|
13
|
+
enabled,
|
|
14
|
+
kind: "ceiling",
|
|
15
|
+
optionalDependency: "web-tree-sitter",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Return a new manifest with the gdgraph.treesitter capability set to `enabled`,
|
|
20
|
+
// adding the gdgraph module capabilities array if absent. Other capabilities and
|
|
21
|
+
// modules are preserved untouched.
|
|
22
|
+
export function setTreesitterEnabled(manifest: Json, enabled: boolean): Json {
|
|
23
|
+
const modules =
|
|
24
|
+
typeof manifest.modules === "object" && manifest.modules !== null
|
|
25
|
+
? { ...(manifest.modules as Json) }
|
|
26
|
+
: {};
|
|
27
|
+
const gdgraph =
|
|
28
|
+
typeof modules.gdgraph === "object" && modules.gdgraph !== null
|
|
29
|
+
? { ...(modules.gdgraph as Json) }
|
|
30
|
+
: {};
|
|
31
|
+
|
|
32
|
+
const existing = Array.isArray(gdgraph.capabilities) ? (gdgraph.capabilities as unknown[]) : [];
|
|
33
|
+
const others = existing.filter(
|
|
34
|
+
(c) => !(c && typeof c === "object" && (c as Json).id === TREESITTER_CAPABILITY),
|
|
35
|
+
);
|
|
36
|
+
gdgraph.capabilities = [...others, treesitterEntry(enabled)];
|
|
37
|
+
modules.gdgraph = gdgraph;
|
|
38
|
+
return { ...manifest, modules };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Whether the manifest has gdgraph.treesitter enabled.
|
|
42
|
+
export function isTreesitterEnabled(manifest: Json): boolean {
|
|
43
|
+
const modules = manifest.modules as Json | undefined;
|
|
44
|
+
const gdgraph = modules?.gdgraph as Json | undefined;
|
|
45
|
+
const caps = Array.isArray(gdgraph?.capabilities) ? (gdgraph?.capabilities as unknown[]) : [];
|
|
46
|
+
return caps.some(
|
|
47
|
+
(c) => c && typeof c === "object" && (c as Json).id === TREESITTER_CAPABILITY && (c as Json).enabled === true,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Shared graph-target resolution for `affected` / `getAffected`.
|
|
2
|
+
//
|
|
3
|
+
// Both entry points used to resolve a user-supplied path with ONE predicate:
|
|
4
|
+
//
|
|
5
|
+
// nodes.find(n => n.path === normalized || n.path.endsWith(normalized))
|
|
6
|
+
//
|
|
7
|
+
// which has two defects. `find` returns the FIRST hit in node order, so a suffix
|
|
8
|
+
// match that sorts earlier beats a later exact match — with a nested checkout in
|
|
9
|
+
// the graph, `src/lib/fs.ts` resolved to `.claude/worktrees/<wt>/src/lib/fs.ts`.
|
|
10
|
+
// And `endsWith` is not path-segment aware, so `fs.ts` also matches `myfs.ts`.
|
|
11
|
+
// Worst of all, an ambiguous target was silently resolved to one arbitrary
|
|
12
|
+
// candidate and the answer was reported as if it were exact.
|
|
13
|
+
//
|
|
14
|
+
// Resolution is now two-pass — exact match first, then a suffix match anchored to
|
|
15
|
+
// a `/` boundary — and an ambiguous suffix REFUSES rather than guessing, the way
|
|
16
|
+
// `resolveFlowDir` does (`src/flow/store.ts:86`). Acting on a guessed node is how
|
|
17
|
+
// a blast-radius answer ends up describing a different file than the caller asked
|
|
18
|
+
// about.
|
|
19
|
+
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import type { GraphData } from "./types";
|
|
22
|
+
|
|
23
|
+
export function normalizeTarget(target: string): string {
|
|
24
|
+
return target.replace(/^\.\//, "").split(path.sep).join("/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Resolve `target` to a node path. Returns the normalized target unchanged when
|
|
28
|
+
// nothing matches (callers then report an empty blast radius, as before).
|
|
29
|
+
// Throws when the target is an ambiguous suffix of several distinct nodes.
|
|
30
|
+
export function resolveGraphTarget(graph: GraphData, target: string): string {
|
|
31
|
+
const normalized = normalizeTarget(target);
|
|
32
|
+
|
|
33
|
+
// Pass 1 — exact path. An exact match is never ambiguous and always wins.
|
|
34
|
+
if (graph.nodes.some((item) => item.path === normalized)) {
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Pass 2 — suffix match on a `/` boundary, so `lib/fs.ts` matches
|
|
39
|
+
// `src/lib/fs.ts` but `fs.ts` does not match `src/lib/myfs.ts`.
|
|
40
|
+
const suffix = `/${normalized}`;
|
|
41
|
+
const candidates = [
|
|
42
|
+
...new Set(
|
|
43
|
+
graph.nodes.filter((item) => item.path.endsWith(suffix)).map((item) => item.path),
|
|
44
|
+
),
|
|
45
|
+
].sort();
|
|
46
|
+
|
|
47
|
+
if (candidates.length > 1) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Graph target "${target}" is ambiguous — ${candidates.length} files match it:\n` +
|
|
50
|
+
candidates.map((candidate) => ` - ${candidate}`).join("\n") +
|
|
51
|
+
"\nRe-run with the full repository-relative path.",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return candidates[0] ?? normalized;
|
|
56
|
+
}
|