@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.
Files changed (297) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +351 -0
  3. package/dist/cli.js +48427 -0
  4. package/dist/proxy-worker.js +381 -0
  5. package/package.json +63 -0
  6. package/src/gdgraph/affected.test.ts +100 -0
  7. package/src/gdgraph/affected.ts +135 -0
  8. package/src/gdgraph/build-integrity.test.ts +193 -0
  9. package/src/gdgraph/build-lang.test.ts +399 -0
  10. package/src/gdgraph/build.test.ts +120 -0
  11. package/src/gdgraph/build.ts +804 -0
  12. package/src/gdgraph/config.test.ts +47 -0
  13. package/src/gdgraph/config.ts +113 -0
  14. package/src/gdgraph/core-sources.test.ts +99 -0
  15. package/src/gdgraph/core-sources.ts +20 -0
  16. package/src/gdgraph/enrich.ts +79 -0
  17. package/src/gdgraph/fallback.test.ts +153 -0
  18. package/src/gdgraph/find.test.ts +78 -0
  19. package/src/gdgraph/find.ts +107 -0
  20. package/src/gdgraph/pagerank.ts +129 -0
  21. package/src/gdgraph/path.test.ts +56 -0
  22. package/src/gdgraph/path.ts +112 -0
  23. package/src/gdgraph/query.ts +133 -0
  24. package/src/gdgraph/repomap.test.ts +110 -0
  25. package/src/gdgraph/repomap.ts +193 -0
  26. package/src/gdgraph/service.test.ts +59 -0
  27. package/src/gdgraph/service.ts +51 -0
  28. package/src/gdgraph/staleness.ts +20 -0
  29. package/src/gdgraph/symbol.test.ts +89 -0
  30. package/src/gdgraph/symbol.ts +123 -0
  31. package/src/gdgraph/symbols-capability.test.ts +41 -0
  32. package/src/gdgraph/symbols-capability.ts +49 -0
  33. package/src/gdgraph/target.ts +56 -0
  34. package/src/gdgraph/treesitter/adapter.test.ts +249 -0
  35. package/src/gdgraph/treesitter/adapter.ts +213 -0
  36. package/src/gdgraph/treesitter/extract.test.ts +278 -0
  37. package/src/gdgraph/treesitter/extract.ts +389 -0
  38. package/src/gdgraph/treesitter/grammars.ts +110 -0
  39. package/src/gdgraph/treesitter/no-treesitter-import.test.ts +51 -0
  40. package/src/gdgraph/treesitter/resolve-calls.test.ts +38 -0
  41. package/src/gdgraph/types.ts +63 -0
  42. package/src/gdskills/bundled/rules/core/api-contracts.mdc +148 -0
  43. package/src/gdskills/bundled/rules/core/async-patterns.mdc +185 -0
  44. package/src/gdskills/bundled/rules/core/clean-architecture.mdc +163 -0
  45. package/src/gdskills/bundled/rules/core/code-review-ai-assistant.mdc +49 -0
  46. package/src/gdskills/bundled/rules/core/code-review-b091-profile.mdc +48 -0
  47. package/src/gdskills/bundled/rules/core/code-style-patterns.mdc +54 -0
  48. package/src/gdskills/bundled/rules/core/commit-message-formatting.mdc +25 -0
  49. package/src/gdskills/bundled/rules/core/database-patterns.mdc +162 -0
  50. package/src/gdskills/bundled/rules/core/documentation-management.mdc +106 -0
  51. package/src/gdskills/bundled/rules/core/error-handling.mdc +163 -0
  52. package/src/gdskills/bundled/rules/core/execution-metrics.md +99 -0
  53. package/src/gdskills/bundled/rules/core/frontend-assistant.mdc +40 -0
  54. package/src/gdskills/bundled/rules/core/git-rules.mdc +37 -0
  55. package/src/gdskills/bundled/rules/core/gproject-contracts.mdc +198 -0
  56. package/src/gdskills/bundled/rules/core/implementation-doc-mandate.mdc +149 -0
  57. package/src/gdskills/bundled/rules/core/implementation-plans.mdc +58 -0
  58. package/src/gdskills/bundled/rules/core/jobs-documentation.mdc +221 -0
  59. package/src/gdskills/bundled/rules/core/mobx-store-template.mdc +561 -0
  60. package/src/gdskills/bundled/rules/core/model-selection.mdc +53 -0
  61. package/src/gdskills/bundled/rules/core/nestjs-dto.mdc +50 -0
  62. package/src/gdskills/bundled/rules/core/playwright-testing.mdc +321 -0
  63. package/src/gdskills/bundled/rules/core/requirements-management.mdc +35 -0
  64. package/src/gdskills/bundled/rules/core/requirements-package-standard.mdc +103 -0
  65. package/src/gdskills/bundled/rules/core/review-agent-profile.mdc +49 -0
  66. package/src/gdskills/bundled/rules/core/review-strict-profile.mdc +44 -0
  67. package/src/gdskills/bundled/rules/core/rule-management-workflow.mdc +62 -0
  68. package/src/gdskills/bundled/rules/core/security-baseline.mdc +155 -0
  69. package/src/gdskills/bundled/rules/core/shared-definitions.mdc +79 -0
  70. package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +55 -0
  71. package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +88 -0
  72. package/src/gdskills/bundled/rules/core/solid-principles.mdc +149 -0
  73. package/src/gdskills/bundled/rules/core/storybook-guidelines.mdc +36 -0
  74. package/src/gdskills/bundled/rules/core/subagent-context-construction.md +107 -0
  75. package/src/gdskills/bundled/rules/core/subagent-status-protocol.md +169 -0
  76. package/src/gdskills/bundled/rules/core/tdd-workflow.mdc +96 -0
  77. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.codex.md +47 -0
  78. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.cursor.md +55 -0
  79. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +352 -0
  80. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.opencode.md +39 -0
  81. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.zed.md +29 -0
  82. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.codex.md +567 -0
  83. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.cursor.md +567 -0
  84. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.md +655 -0
  85. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.opencode.md +567 -0
  86. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.zed.md +567 -0
  87. package/src/gdskills/bundled/skills/orchestration/context-collector/input-contract.schema.json +134 -0
  88. package/src/gdskills/bundled/skills/orchestration/context-collector/orchestrator-prompt.md +183 -0
  89. package/src/gdskills/bundled/skills/orchestration/context-collector/output-contract.schema.json +208 -0
  90. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.codex.md +1412 -0
  91. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.cursor.md +1412 -0
  92. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.detail.md +735 -0
  93. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.md +468 -0
  94. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.opencode.md +1412 -0
  95. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.zed.md +1412 -0
  96. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/analysis-request.md +62 -0
  97. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/analysis-request.template.md +79 -0
  98. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/input-contract.schema.json +247 -0
  99. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/orchestrator-prompt.md +362 -0
  100. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/output-contract.schema.json +87 -0
  101. package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.codex.md +105 -0
  102. package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.cursor.md +105 -0
  103. package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.md +165 -0
  104. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +379 -0
  105. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/input-contract.schema.json +33 -0
  106. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/output-contract.schema.json +41 -0
  107. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.codex.md +307 -0
  108. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.cursor.md +307 -0
  109. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.md +373 -0
  110. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.opencode.md +307 -0
  111. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.zed.md +307 -0
  112. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/input-contract.schema.json +130 -0
  113. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/issue-request.template.md +50 -0
  114. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/orchestrator-prompt.md +155 -0
  115. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/output-contract.schema.json +61 -0
  116. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.codex.md +365 -0
  117. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.cursor.md +365 -0
  118. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.md +373 -0
  119. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.opencode.md +365 -0
  120. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.zed.md +365 -0
  121. package/src/gdskills/bundled/skills/orchestration/job-documenter/input-contract.schema.json +55 -0
  122. package/src/gdskills/bundled/skills/orchestration/job-documenter/output-contract.schema.json +58 -0
  123. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.codex.md +1701 -0
  124. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.cursor.md +1701 -0
  125. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1734 -0
  126. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.opencode.md +1701 -0
  127. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.zed.md +1701 -0
  128. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/input-contract.schema.json +188 -0
  129. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/orchestrator-prompt.md +179 -0
  130. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/output-contract.schema.json +103 -0
  131. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/state.schema.json +164 -0
  132. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.codex.md +395 -0
  133. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.cursor.md +395 -0
  134. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +556 -0
  135. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.opencode.md +395 -0
  136. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.zed.md +395 -0
  137. package/src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json +204 -0
  138. package/src/gdskills/bundled/skills/orchestration/task-implementer/orchestrator-prompt.md +206 -0
  139. package/src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json +51 -0
  140. package/src/gdskills/bundled/skills/orchestration/task-implementer/task-request.template.md +111 -0
  141. package/src/gdskills/bundled/skills/planning/autodoc-analyst/SKILL.md +163 -0
  142. package/src/gdskills/bundled/skills/planning/autodoc-architect/SKILL.md +161 -0
  143. package/src/gdskills/bundled/skills/planning/autodoc-assembler/SKILL.md +128 -0
  144. package/src/gdskills/bundled/skills/planning/autodoc-orchestrator/SKILL.md +321 -0
  145. package/src/gdskills/bundled/skills/planning/autodoc-scanner/SKILL.md +166 -0
  146. package/src/gdskills/bundled/skills/planning/autodoc-writer/SKILL.md +253 -0
  147. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.claude.md +46 -0
  148. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.codex.md +90 -0
  149. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.cursor.md +90 -0
  150. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +90 -0
  151. package/src/gdskills/bundled/skills/planning/consistency-checker/SKILL.codex.md +33 -0
  152. package/src/gdskills/bundled/skills/planning/consistency-checker/SKILL.cursor.md +33 -0
  153. package/src/gdskills/bundled/skills/planning/consistency-checker/SKILL.md +190 -0
  154. package/src/gdskills/bundled/skills/planning/docpack-orchestrator/SKILL.md +142 -0
  155. package/src/gdskills/bundled/skills/planning/docpack-orchestrator/input-contract.schema.json +31 -0
  156. package/src/gdskills/bundled/skills/planning/docpack-orchestrator/output-contract.schema.json +31 -0
  157. package/src/gdskills/bundled/skills/planning/docpack-review/SKILL.md +80 -0
  158. package/src/gdskills/bundled/skills/planning/docpack-review/input-contract.schema.json +20 -0
  159. package/src/gdskills/bundled/skills/planning/docpack-review/output-contract.schema.json +36 -0
  160. package/src/gdskills/bundled/skills/planning/interview/SKILL.codex.md +181 -0
  161. package/src/gdskills/bundled/skills/planning/interview/SKILL.cursor.md +181 -0
  162. package/src/gdskills/bundled/skills/planning/interview/SKILL.md +187 -0
  163. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.claude.md +94 -0
  164. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.codex.md +99 -0
  165. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.cursor.md +99 -0
  166. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +105 -0
  167. package/src/gdskills/bundled/skills/planning/patterns-researcher/SKILL.codex.md +33 -0
  168. package/src/gdskills/bundled/skills/planning/patterns-researcher/SKILL.cursor.md +33 -0
  169. package/src/gdskills/bundled/skills/planning/patterns-researcher/SKILL.md +237 -0
  170. package/src/gdskills/bundled/skills/planning/planner/SKILL.codex.md +33 -0
  171. package/src/gdskills/bundled/skills/planning/planner/SKILL.cursor.md +33 -0
  172. package/src/gdskills/bundled/skills/planning/planner/SKILL.md +190 -0
  173. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.codex.md +193 -0
  174. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.cursor.md +193 -0
  175. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.md +193 -0
  176. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.opencode.md +193 -0
  177. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.zed.md +193 -0
  178. package/src/gdskills/bundled/skills/planning/prd-creator/input-contract.schema.json +63 -0
  179. package/src/gdskills/bundled/skills/planning/prd-creator/output-contract.schema.json +175 -0
  180. package/src/gdskills/bundled/skills/planning/problem-definer/SKILL.codex.md +31 -0
  181. package/src/gdskills/bundled/skills/planning/problem-definer/SKILL.cursor.md +31 -0
  182. package/src/gdskills/bundled/skills/planning/problem-definer/SKILL.md +153 -0
  183. package/src/gdskills/bundled/skills/planning/project-discovery/SKILL.codex.md +32 -0
  184. package/src/gdskills/bundled/skills/planning/project-discovery/SKILL.cursor.md +32 -0
  185. package/src/gdskills/bundled/skills/planning/project-discovery/SKILL.md +166 -0
  186. package/src/gdskills/bundled/skills/planning/spec-writer/SKILL.codex.md +44 -0
  187. package/src/gdskills/bundled/skills/planning/spec-writer/SKILL.cursor.md +44 -0
  188. package/src/gdskills/bundled/skills/planning/spec-writer/SKILL.md +243 -0
  189. package/src/gdskills/bundled/skills/planning/stack-advisor/SKILL.codex.md +43 -0
  190. package/src/gdskills/bundled/skills/planning/stack-advisor/SKILL.cursor.md +43 -0
  191. package/src/gdskills/bundled/skills/planning/stack-advisor/SKILL.md +193 -0
  192. package/src/gdskills/bundled/skills/platform/agent-entrypoint-distiller/SKILL.md +47 -0
  193. package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.codex.md +87 -0
  194. package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.cursor.md +87 -0
  195. package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.md +87 -0
  196. package/src/gdskills/bundled/skills/platform/hookify/SKILL.codex.md +100 -0
  197. package/src/gdskills/bundled/skills/platform/hookify/SKILL.cursor.md +100 -0
  198. package/src/gdskills/bundled/skills/platform/hookify/SKILL.md +100 -0
  199. package/src/gdskills/bundled/skills/quality/changelog/SKILL.claude.md +45 -0
  200. package/src/gdskills/bundled/skills/quality/changelog/SKILL.codex.md +84 -0
  201. package/src/gdskills/bundled/skills/quality/changelog/SKILL.cursor.md +84 -0
  202. package/src/gdskills/bundled/skills/quality/changelog/SKILL.md +84 -0
  203. package/src/gdskills/bundled/skills/quality/commit/SKILL.codex.md +66 -0
  204. package/src/gdskills/bundled/skills/quality/commit/SKILL.cursor.md +66 -0
  205. package/src/gdskills/bundled/skills/quality/commit/SKILL.md +66 -0
  206. package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.claude.md +40 -0
  207. package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.codex.md +66 -0
  208. package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.cursor.md +66 -0
  209. package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.md +66 -0
  210. package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.claude.md +45 -0
  211. package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.codex.md +81 -0
  212. package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.cursor.md +81 -0
  213. package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.md +81 -0
  214. package/src/gdskills/bundled/skills/quality/deploy/SKILL.claude.md +42 -0
  215. package/src/gdskills/bundled/skills/quality/deploy/SKILL.codex.md +70 -0
  216. package/src/gdskills/bundled/skills/quality/deploy/SKILL.cursor.md +70 -0
  217. package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +70 -0
  218. package/src/gdskills/bundled/skills/quality/metaproject-security/SKILL.md +103 -0
  219. package/src/gdskills/bundled/skills/quality/perf-check/SKILL.claude.md +48 -0
  220. package/src/gdskills/bundled/skills/quality/perf-check/SKILL.codex.md +83 -0
  221. package/src/gdskills/bundled/skills/quality/perf-check/SKILL.cursor.md +83 -0
  222. package/src/gdskills/bundled/skills/quality/perf-check/SKILL.md +83 -0
  223. package/src/gdskills/bundled/skills/quality/pr/SKILL.codex.md +75 -0
  224. package/src/gdskills/bundled/skills/quality/pr/SKILL.cursor.md +75 -0
  225. package/src/gdskills/bundled/skills/quality/pr/SKILL.md +75 -0
  226. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.codex.md +364 -0
  227. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.cursor.md +295 -0
  228. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.md +378 -0
  229. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.opencode.md +364 -0
  230. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.zed.md +38 -0
  231. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/input-contract.schema.json +53 -0
  232. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/output-contract.schema.json +46 -0
  233. package/src/gdskills/bundled/skills/quality/push/SKILL.codex.md +52 -0
  234. package/src/gdskills/bundled/skills/quality/push/SKILL.cursor.md +52 -0
  235. package/src/gdskills/bundled/skills/quality/push/SKILL.md +52 -0
  236. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.claude.md +40 -0
  237. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.codex.md +61 -0
  238. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.cursor.md +61 -0
  239. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.md +61 -0
  240. package/src/gdskills/bundled/skills/quality/test-gen/SKILL.claude.md +30 -0
  241. package/src/gdskills/bundled/skills/quality/test-gen/SKILL.codex.md +75 -0
  242. package/src/gdskills/bundled/skills/quality/test-gen/SKILL.cursor.md +75 -0
  243. package/src/gdskills/bundled/skills/quality/test-gen/SKILL.md +75 -0
  244. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.codex.md +64 -0
  245. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.cursor.md +74 -0
  246. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.md +339 -0
  247. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.opencode.md +48 -0
  248. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.zed.md +44 -0
  249. package/src/gdskills/bundled/skills/quality/tests-creator/input-contract.schema.json +121 -0
  250. package/src/gdskills/bundled/skills/quality/tests-creator/output-contract.schema.json +127 -0
  251. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.codex.md +204 -0
  252. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.cursor.md +204 -0
  253. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.md +203 -0
  254. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.opencode.md +204 -0
  255. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.zed.md +204 -0
  256. package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.codex.md +209 -0
  257. package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.cursor.md +209 -0
  258. package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.md +208 -0
  259. package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.opencode.md +209 -0
  260. package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.zed.md +209 -0
  261. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.codex.md +263 -0
  262. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.cursor.md +263 -0
  263. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.md +258 -0
  264. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.opencode.md +263 -0
  265. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.zed.md +263 -0
  266. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.codex.md +175 -0
  267. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.cursor.md +175 -0
  268. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +168 -0
  269. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.opencode.md +175 -0
  270. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.zed.md +175 -0
  271. package/src/gdskills/bundled/skills/review/review-architecture/SKILL.md +360 -0
  272. package/src/gdskills/bundled/skills/review/review-backend/SKILL.md +337 -0
  273. package/src/gdskills/bundled/skills/review/review-clean-code/SKILL.md +477 -0
  274. package/src/gdskills/bundled/skills/review/review-core-boundaries/SKILL.md +93 -0
  275. package/src/gdskills/bundled/skills/review/review-flow-graph/SKILL.md +119 -0
  276. package/src/gdskills/bundled/skills/review/review-frontend/SKILL.md +595 -0
  277. package/src/gdskills/bundled/skills/review/review-frontend-conventions/SKILL.md +144 -0
  278. package/src/gdskills/bundled/skills/review/review-highload/SKILL.md +519 -0
  279. package/src/gdskills/bundled/skills/review/review-logic/SKILL.md +323 -0
  280. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +927 -0
  281. package/src/gdskills/bundled/skills/review/review-orchestrator/input-contract.schema.json +153 -0
  282. package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +77 -0
  283. package/src/gdskills/bundled/skills/review/review-orchestrator/review-context.schema.json +389 -0
  284. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +189 -0
  285. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +241 -0
  286. package/src/gdskills/bundled/skills/review/review-performance/SKILL.md +344 -0
  287. package/src/gdskills/bundled/skills/review/review-pr-feedback/SKILL.md +327 -0
  288. package/src/gdskills/bundled/skills/review/review-security-code/SKILL.md +348 -0
  289. package/src/gdskills/bundled/skills/review/review-strict/SKILL.md +328 -0
  290. package/src/gdskills/bundled/skills/review/review-style/SKILL.md +343 -0
  291. package/src/gdskills/bundled/skills/review/review-testing-practices/SKILL.md +134 -0
  292. package/src/gdskills/bundled/skills/shared/git-merge-base.md +33 -0
  293. package/src/gdskills/contracts/agent-event.schema.json +32 -0
  294. package/src/gdskills/contracts/orchestrator-state.schema.json +58 -0
  295. package/src/gdskills/contracts/review-finding.schema.json +59 -0
  296. package/src/gdskills/contracts/subagent-dispatch.schema.json +123 -0
  297. 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
+ }