@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,107 @@
1
+ import type { GraphData } from "./types";
2
+
3
+ // Seed-file search over the file-level graph — the "find files about X" primitive
4
+ // keryx lacked (agents kept mis-reaching for `gdgraph query "<nl>"`, which only
5
+ // does cycles/orphans). Deterministic and offline: rank file nodes by how many
6
+ // query terms match their path, with a basename boost, tie-broken by fan-in
7
+ // (dependents) as an importance proxy. The result is a short seed list to feed
8
+ // into `gdgraph affected <file>`.
9
+
10
+ export interface FindResult {
11
+ path: string;
12
+ score: number;
13
+ matched: string[];
14
+ dependents: number;
15
+ }
16
+
17
+ export interface SymbolFindResult {
18
+ id: string;
19
+ name: string;
20
+ kind: string;
21
+ path: string;
22
+ startLine: number;
23
+ score: number;
24
+ matched: string[];
25
+ }
26
+
27
+ const STOP = new Set([
28
+ "and", "the", "of", "to", "a", "an", "in", "for", "or", "with", "on", "is", "at", "by",
29
+ ]);
30
+
31
+ // Split a free-text query into distinct, meaningful search terms.
32
+ export function tokenize(query: string): string[] {
33
+ return [
34
+ ...new Set(
35
+ query
36
+ .toLowerCase()
37
+ .split(/[^a-z0-9]+/)
38
+ .filter((t) => t.length >= 2 && !STOP.has(t)),
39
+ ),
40
+ ];
41
+ }
42
+
43
+ // Rank symbol nodes by name match — the precise half of `find` when the symbol
44
+ // layer is active. Exact name match is boosted so `find "clonePipeline"` returns
45
+ // the definition, not just path hits.
46
+ export function findSymbols(graph: GraphData, query: string, limit = 15): SymbolFindResult[] {
47
+ const terms = tokenize(query);
48
+ const symbols = graph.symbols ?? [];
49
+ if (terms.length === 0 || symbols.length === 0) {
50
+ return [];
51
+ }
52
+
53
+ const results: SymbolFindResult[] = [];
54
+ for (const symbol of symbols) {
55
+ const nameLower = symbol.name.toLowerCase();
56
+ const matched = terms.filter((t) => nameLower.includes(t));
57
+ if (matched.length === 0) {
58
+ continue;
59
+ }
60
+ const exactBonus = terms.some((t) => nameLower === t) ? 20 : 0;
61
+ results.push({
62
+ id: symbol.id,
63
+ name: symbol.name,
64
+ kind: symbol.kind,
65
+ path: symbol.path,
66
+ startLine: symbol.startLine,
67
+ score: matched.length * 10 + exactBonus,
68
+ matched,
69
+ });
70
+ }
71
+ results.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path) || a.startLine - b.startLine);
72
+ return results.slice(0, limit);
73
+ }
74
+
75
+ export function findNodes(graph: GraphData, query: string, limit = 20): FindResult[] {
76
+ const terms = tokenize(query);
77
+ if (terms.length === 0) {
78
+ return [];
79
+ }
80
+
81
+ // fan-in (dependents) per node id: how many edges point at it.
82
+ const fanIn = new Map<string, number>();
83
+ for (const edge of graph.edges) {
84
+ fanIn.set(edge.to, (fanIn.get(edge.to) ?? 0) + 1);
85
+ }
86
+
87
+ const results: FindResult[] = [];
88
+ for (const node of graph.nodes) {
89
+ if (node.kind !== "file") {
90
+ continue;
91
+ }
92
+ const lowerPath = node.path.toLowerCase();
93
+ const base = lowerPath.split("/").pop() ?? lowerPath;
94
+ const matched = terms.filter((t) => lowerPath.includes(t));
95
+ if (matched.length === 0) {
96
+ continue;
97
+ }
98
+ const baseHits = matched.filter((t) => base.includes(t)).length;
99
+ const score = matched.length * 10 + baseHits * 5;
100
+ results.push({ path: node.path, score, matched, dependents: fanIn.get(node.id) ?? 0 });
101
+ }
102
+
103
+ results.sort(
104
+ (a, b) => b.score - a.score || b.dependents - a.dependents || a.path.localeCompare(b.path),
105
+ );
106
+ return results.slice(0, limit);
107
+ }
@@ -0,0 +1,129 @@
1
+ // Pure personalized PageRank (specification.md §8.3; T-B6, B-4/B-5/B-7).
2
+ //
3
+ // Deterministic power-iteration with FIXED params (damping / iterations /
4
+ // tolerance) and a total-order tie-break, so a re-run is byte-identical. No dep,
5
+ // no network, no vectors/embeddings — a plain weighted graph centrality.
6
+ //
7
+ // Rank flows ALONG edges `from → to`, so a file imported/called by many
8
+ // accumulates rank from its dependents — surfacing the central, widely-depended
9
+ // files/symbols first. Dangling nodes (no out-edges) redistribute their mass via
10
+ // the personalization vector, conserving total mass every iteration.
11
+
12
+ export interface RankEdge {
13
+ from: string;
14
+ to: string;
15
+ weight: number;
16
+ }
17
+
18
+ export interface PageRankOptions {
19
+ damping: number;
20
+ iterations: number;
21
+ tolerance: number;
22
+ // Restart distribution: node id → mass. Missing/empty ⇒ uniform over nodes.
23
+ personalization?: Map<string, number>;
24
+ }
25
+
26
+ export interface RankedNode {
27
+ id: string;
28
+ score: number;
29
+ }
30
+
31
+ // Compute personalized PageRank over `nodes` with weighted `edges`. Returns a
32
+ // total-ordered ranking (score desc, then id asc) — reproducible for identical
33
+ // input + params.
34
+ export function personalizedPageRank(
35
+ nodes: string[],
36
+ edges: RankEdge[],
37
+ options: PageRankOptions,
38
+ ): RankedNode[] {
39
+ const uniqueNodes = [...new Set(nodes)].sort();
40
+ const n = uniqueNodes.length;
41
+ if (n === 0) {
42
+ return [];
43
+ }
44
+
45
+ const index = new Map<string, number>();
46
+ uniqueNodes.forEach((id, i) => index.set(id, i));
47
+
48
+ // Out-weight totals + adjacency (only edges whose both endpoints are nodes).
49
+ const outWeight = new Array<number>(n).fill(0);
50
+ const incoming: Array<Array<{ src: number; weight: number }>> = Array.from(
51
+ { length: n },
52
+ () => [],
53
+ );
54
+ for (const edge of edges) {
55
+ const from = index.get(edge.from);
56
+ const to = index.get(edge.to);
57
+ if (from === undefined || to === undefined) {
58
+ continue;
59
+ }
60
+ const weight = edge.weight > 0 ? edge.weight : 0;
61
+ if (weight === 0) {
62
+ continue;
63
+ }
64
+ outWeight[from] = (outWeight[from] ?? 0) + weight;
65
+ (incoming[to] ??= []).push({ src: from, weight });
66
+ }
67
+
68
+ // Personalization / restart vector, normalized. Empty ⇒ uniform.
69
+ const restart = new Array<number>(n).fill(0);
70
+ const personalization = options.personalization;
71
+ let restartTotal = 0;
72
+ if (personalization && personalization.size > 0) {
73
+ for (const [id, mass] of personalization) {
74
+ const i = index.get(id);
75
+ if (i !== undefined && mass > 0) {
76
+ restart[i] = (restart[i] ?? 0) + mass;
77
+ restartTotal += mass;
78
+ }
79
+ }
80
+ }
81
+ if (restartTotal <= 0) {
82
+ for (let i = 0; i < n; i += 1) {
83
+ restart[i] = 1 / n;
84
+ }
85
+ } else {
86
+ for (let i = 0; i < n; i += 1) {
87
+ restart[i] = (restart[i] ?? 0) / restartTotal;
88
+ }
89
+ }
90
+
91
+ const damping = options.damping;
92
+ let rank = restart.slice();
93
+
94
+ for (let iter = 0; iter < options.iterations; iter += 1) {
95
+ // Mass held by dangling nodes (no out-weight) is redistributed via restart.
96
+ let danglingMass = 0;
97
+ for (let i = 0; i < n; i += 1) {
98
+ if ((outWeight[i] ?? 0) === 0) {
99
+ danglingMass += rank[i] ?? 0;
100
+ }
101
+ }
102
+
103
+ const next = new Array<number>(n).fill(0);
104
+ for (let i = 0; i < n; i += 1) {
105
+ let inbound = 0;
106
+ for (const { src, weight } of incoming[i] ?? []) {
107
+ const srcOut = outWeight[src] ?? 0;
108
+ if (srcOut > 0) {
109
+ inbound += ((rank[src] ?? 0) * weight) / srcOut;
110
+ }
111
+ }
112
+ const restartI = restart[i] ?? 0;
113
+ next[i] = (1 - damping) * restartI + damping * (inbound + danglingMass * restartI);
114
+ }
115
+
116
+ let delta = 0;
117
+ for (let i = 0; i < n; i += 1) {
118
+ delta += Math.abs((next[i] ?? 0) - (rank[i] ?? 0));
119
+ }
120
+ rank = next;
121
+ if (delta < options.tolerance) {
122
+ break;
123
+ }
124
+ }
125
+
126
+ return uniqueNodes
127
+ .map((id, i) => ({ id, score: rank[i] ?? 0 }))
128
+ .sort((a, b) => b.score - a.score || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
129
+ }
@@ -0,0 +1,56 @@
1
+ import { expect, test } from "bun:test";
2
+ import { findPath, labelNode } from "./path";
3
+ import type { GraphData, SymbolNode } from "./types";
4
+
5
+ function fileNode(path: string) {
6
+ return { id: path, kind: "file" as const, path, language: "typescript" as const };
7
+ }
8
+ function importEdge(from: string, to: string) {
9
+ return { id: `${from}->${to}`, from, to, kind: "imports" as const, specifier: to };
10
+ }
11
+ function sym(id: string, name: string, path: string, line: number): SymbolNode {
12
+ return { id, kind: "function", path, name, container: null, startLine: line, endLine: line + 3, language: "typescript" };
13
+ }
14
+ function call(from: string, to: string) {
15
+ return { id: `${from}=>${to}`, from, to, kind: "calls" as const, resolved: true };
16
+ }
17
+
18
+ // a.ts imports b.ts imports c.ts; symbols foo(a) -> bar(b)
19
+ const GRAPH: GraphData = {
20
+ nodes: [fileNode("src/a.ts"), fileNode("src/b.ts"), fileNode("src/c.ts")],
21
+ edges: [importEdge("src/a.ts", "src/b.ts"), importEdge("src/b.ts", "src/c.ts")],
22
+ symbols: [sym("src/a.ts#foo", "foo", "src/a.ts", 1), sym("src/b.ts#bar", "bar", "src/b.ts", 2)],
23
+ calls: [call("src/a.ts#foo", "src/b.ts#bar")],
24
+ };
25
+
26
+ test("finds a file-to-file import path", () => {
27
+ const r = findPath(GRAPH, "src/a.ts", "src/c.ts");
28
+ expect(r.nodes).toEqual(["src/a.ts", "src/b.ts", "src/c.ts"]);
29
+ });
30
+
31
+ test("connects two symbols across the file+call layers", () => {
32
+ const r = findPath(GRAPH, "foo", "bar");
33
+ // foo -> bar via the resolved call edge (or foo -> a.ts -> b.ts -> bar)
34
+ expect(r.nodes[0]).toBe("src/a.ts#foo");
35
+ expect(r.nodes.at(-1)).toBe("src/b.ts#bar");
36
+ expect(r.nodes.length).toBeGreaterThanOrEqual(2);
37
+ });
38
+
39
+ test("resolves endpoints by symbol name and by file substring", () => {
40
+ const r = findPath(GRAPH, "foo", "c.ts");
41
+ expect(r.fromResolved).toContain("src/a.ts#foo");
42
+ expect(r.toResolved).toContain("src/c.ts");
43
+ expect(r.nodes.length).toBeGreaterThan(0);
44
+ });
45
+
46
+ test("returns empty nodes when an endpoint cannot be resolved", () => {
47
+ const r = findPath(GRAPH, "foo", "does-not-exist-xyz");
48
+ expect(r.toResolved).toEqual([]);
49
+ expect(r.nodes).toEqual([]);
50
+ });
51
+
52
+ test("labelNode renders symbols as name (path:line) and files verbatim", () => {
53
+ const byId = new Map(GRAPH.symbols!.map((s) => [s.id, s]));
54
+ expect(labelNode("src/a.ts#foo", byId)).toBe("foo (src/a.ts:1)");
55
+ expect(labelNode("src/c.ts", byId)).toBe("src/c.ts");
56
+ });
@@ -0,0 +1,112 @@
1
+ import type { GraphData, SymbolNode } from "./types";
2
+ import { resolveSymbols } from "./symbol";
3
+
4
+ // "How are A and B connected?" — shortest path over a UNIFIED graph that spans
5
+ // both layers: file import edges, symbol call edges, and file→symbol "defines"
6
+ // bridges (a symbol's owning file). Endpoints may be file paths or symbol names.
7
+ // Undirected BFS so a connection is found regardless of edge direction; the
8
+ // rendered chain still shows the concrete hops. Pure over the in-memory graph.
9
+
10
+ export interface PathResult {
11
+ fromToken: string;
12
+ toToken: string;
13
+ fromResolved: string[];
14
+ toResolved: string[];
15
+ // Node-id chain from an A-endpoint to a B-endpoint, or [] when unconnected.
16
+ nodes: string[];
17
+ }
18
+
19
+ // Resolve an endpoint token to candidate node ids: exact file path, then symbol
20
+ // name matches, then file-path substring.
21
+ function resolveEndpoint(graph: GraphData, token: string): string[] {
22
+ const filePaths = new Set(
23
+ graph.nodes.filter((n) => n.kind === "file").map((n) => n.path),
24
+ );
25
+ if (filePaths.has(token)) {
26
+ return [token];
27
+ }
28
+ const bySymbol = resolveSymbols(graph.symbols ?? [], token, 10).map((s) => s.id);
29
+ if (bySymbol.length > 0) {
30
+ return bySymbol;
31
+ }
32
+ return [...filePaths].filter((p) => p.includes(token));
33
+ }
34
+
35
+ function buildAdjacency(graph: GraphData): Map<string, Set<string>> {
36
+ const adj = new Map<string, Set<string>>();
37
+ const link = (a: string, b: string): void => {
38
+ if (!adj.has(a)) adj.set(a, new Set());
39
+ if (!adj.has(b)) adj.set(b, new Set());
40
+ adj.get(a)!.add(b);
41
+ adj.get(b)!.add(a);
42
+ };
43
+
44
+ for (const edge of graph.edges) {
45
+ link(edge.from, edge.to); // file → file imports
46
+ }
47
+ const symbolIds = new Set((graph.symbols ?? []).map((s) => s.id));
48
+ for (const symbol of graph.symbols ?? []) {
49
+ link(symbol.path, symbol.id); // file defines symbol (layer bridge)
50
+ }
51
+ for (const call of graph.calls ?? []) {
52
+ if (call.kind === "calls" && symbolIds.has(call.from) && symbolIds.has(call.to)) {
53
+ link(call.from, call.to); // resolved symbol → symbol call
54
+ }
55
+ }
56
+ return adj;
57
+ }
58
+
59
+ function bfs(adj: Map<string, Set<string>>, starts: string[], goals: Set<string>): string[] {
60
+ const prev = new Map<string, string | null>();
61
+ const queue: string[] = [];
62
+ for (const start of starts) {
63
+ if (!prev.has(start)) {
64
+ prev.set(start, null);
65
+ queue.push(start);
66
+ }
67
+ }
68
+
69
+ let found: string | null = null;
70
+ while (queue.length > 0) {
71
+ const current = queue.shift()!;
72
+ if (goals.has(current)) {
73
+ found = current;
74
+ break;
75
+ }
76
+ for (const next of adj.get(current) ?? []) {
77
+ if (!prev.has(next)) {
78
+ prev.set(next, current);
79
+ queue.push(next);
80
+ }
81
+ }
82
+ }
83
+
84
+ if (found === null) {
85
+ return [];
86
+ }
87
+ const chain: string[] = [];
88
+ let node: string | null = found;
89
+ while (node !== null) {
90
+ chain.unshift(node);
91
+ node = prev.get(node) ?? null;
92
+ }
93
+ return chain;
94
+ }
95
+
96
+ export function findPath(graph: GraphData, fromToken: string, toToken: string): PathResult {
97
+ const fromResolved = resolveEndpoint(graph, fromToken);
98
+ const toResolved = resolveEndpoint(graph, toToken);
99
+ if (fromResolved.length === 0 || toResolved.length === 0) {
100
+ return { fromToken, toToken, fromResolved, toResolved, nodes: [] };
101
+ }
102
+ const adj = buildAdjacency(graph);
103
+ const nodes = bfs(adj, fromResolved, new Set(toResolved));
104
+ return { fromToken, toToken, fromResolved, toResolved, nodes };
105
+ }
106
+
107
+ // Render a node id as a human label: "name (path:line)" for a symbol, else the
108
+ // file path verbatim.
109
+ export function labelNode(nodeId: string, symbolsById: Map<string, SymbolNode>): string {
110
+ const symbol = symbolsById.get(nodeId);
111
+ return symbol ? `${symbol.name} (${symbol.path}:${symbol.startLine})` : nodeId;
112
+ }
@@ -0,0 +1,133 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import type { CallEdge, GraphData, GraphEdge, GraphNode, SymbolNode } from "./types";
4
+ import { resolveGraphTarget } from "./target";
5
+
6
+ export async function loadGraph(projectRoot: string): Promise<GraphData> {
7
+ const storageDir = path.join(projectRoot, ".metaproject", "data", "gdgraph", "storage");
8
+ const nodes = await readJsonl<GraphNode>(path.join(storageDir, "nodes.jsonl"));
9
+ const edges = await readJsonl<GraphEdge>(path.join(storageDir, "edges.jsonl"));
10
+ // B1 symbol layer: loaded ONLY if present. Missing files ⇒ empty/omitted layer
11
+ // (never an error — mirrors `readJsonl` tolerance). File-level graph unchanged.
12
+ const symbols = await readJsonl<SymbolNode>(path.join(storageDir, "symbols.jsonl"));
13
+ const calls = await readJsonl<CallEdge>(path.join(storageDir, "calls.jsonl"));
14
+ const graph: GraphData = { nodes, edges };
15
+ if (symbols.length > 0) {
16
+ graph.symbols = symbols;
17
+ }
18
+ if (calls.length > 0) {
19
+ graph.calls = calls;
20
+ }
21
+ return graph;
22
+ }
23
+
24
+ export function getOrphans(graph: GraphData): string[] {
25
+ const inbound = new Set(
26
+ graph.edges.filter((edge) => edge.kind !== "unresolved").map((edge) => edge.to),
27
+ );
28
+ const outbound = new Set(
29
+ graph.edges.filter((edge) => edge.kind !== "unresolved").map((edge) => edge.from),
30
+ );
31
+ return graph.nodes
32
+ .map((node) => node.path)
33
+ .filter((file) => !inbound.has(file) && !outbound.has(file))
34
+ .sort();
35
+ }
36
+
37
+ export function getAffected(
38
+ graph: GraphData,
39
+ target: string,
40
+ ): { target: string; dependencies: string[]; dependents: string[] } {
41
+ // Exact-then-suffix, refusing an ambiguous suffix (see ./target.ts). Shared
42
+ // with `computeAffected` so the two entry points cannot drift apart.
43
+ const resolvedTarget = resolveGraphTarget(graph, target);
44
+
45
+ return {
46
+ target: resolvedTarget,
47
+ dependencies: graph.edges
48
+ .filter((edge) => edge.kind !== "unresolved" && edge.from === resolvedTarget)
49
+ .map((edge) => edge.to)
50
+ .sort(),
51
+ dependents: graph.edges
52
+ .filter((edge) => edge.kind !== "unresolved" && edge.to === resolvedTarget)
53
+ .map((edge) => edge.from)
54
+ .sort(),
55
+ };
56
+ }
57
+
58
+ export function getCycles(graph: GraphData): string[][] {
59
+ const adjacency = new Map<string, string[]>();
60
+ for (const node of graph.nodes) {
61
+ adjacency.set(node.path, []);
62
+ }
63
+ for (const edge of graph.edges) {
64
+ if (edge.kind !== "imports") {
65
+ continue;
66
+ }
67
+ adjacency.get(edge.from)?.push(edge.to);
68
+ }
69
+
70
+ const cycles = new Map<string, string[]>();
71
+ const visited = new Set<string>();
72
+ const stack = new Set<string>();
73
+ const pathStack: string[] = [];
74
+
75
+ function visit(node: string): void {
76
+ visited.add(node);
77
+ stack.add(node);
78
+ pathStack.push(node);
79
+
80
+ for (const next of adjacency.get(node) ?? []) {
81
+ if (!visited.has(next)) {
82
+ visit(next);
83
+ continue;
84
+ }
85
+
86
+ if (stack.has(next)) {
87
+ const start = pathStack.indexOf(next);
88
+ const cycle = [...pathStack.slice(start), next];
89
+ cycles.set(canonicalCycle(cycle), cycle);
90
+ }
91
+ }
92
+
93
+ stack.delete(node);
94
+ pathStack.pop();
95
+ }
96
+
97
+ for (const node of graph.nodes.map((item) => item.path)) {
98
+ if (!visited.has(node)) {
99
+ visit(node);
100
+ }
101
+ }
102
+
103
+ return [...cycles.values()].sort((a, b) => a.join("").localeCompare(b.join("")));
104
+ }
105
+
106
+ async function readJsonl<T>(filePath: string): Promise<T[]> {
107
+ let content: string;
108
+ try {
109
+ content = await readFile(filePath, "utf8");
110
+ } catch {
111
+ // Absent file ⇒ empty layer (the symbol layer is optional, and a missing
112
+ // core file should degrade rather than crash graph navigation).
113
+ return [];
114
+ }
115
+ const items: T[] = [];
116
+ for (const line of content.split("\n").map((entry) => entry.trim()).filter(Boolean)) {
117
+ try {
118
+ items.push(JSON.parse(line) as T);
119
+ } catch {
120
+ // Ignore malformed JSONL records so one bad line does not break graph navigation.
121
+ }
122
+ }
123
+ return items;
124
+ }
125
+
126
+ function canonicalCycle(cycle: string[]): string {
127
+ const withoutLast = cycle.slice(0, -1);
128
+ const variants = withoutLast.map((_, index) => [
129
+ ...withoutLast.slice(index),
130
+ ...withoutLast.slice(0, index),
131
+ ].join("->"));
132
+ return variants.sort()[0] ?? cycle.join("->");
133
+ }
@@ -0,0 +1,110 @@
1
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { expect, test } from "bun:test";
6
+ import { DEFAULT_GDGRAPH_CONFIG, mergeGdgraphConfig } from "./config";
7
+ import { personalizedPageRank } from "./pagerank";
8
+ import { buildRankEdges, computeRepomap, estimateTokens, writeRepomap } from "./repomap";
9
+ import type { GraphData } from "./types";
10
+
11
+ const FIXTURE_DIR = fileURLToPath(new URL("../../fixtures/repomap/", import.meta.url));
12
+
13
+ async function loadFixtureGraph(): Promise<GraphData> {
14
+ const raw = JSON.parse(await readFile(path.join(FIXTURE_DIR, "graph.json"), "utf8"));
15
+ return { nodes: raw.nodes, edges: raw.edges };
16
+ }
17
+
18
+ async function loadExpected(): Promise<any> {
19
+ return JSON.parse(await readFile(path.join(FIXTURE_DIR, "expected.json"), "utf8"));
20
+ }
21
+
22
+ test("AC3.3 — top-ranked entries match expected centrality ordering", async () => {
23
+ const graph = await loadFixtureGraph();
24
+ const expected = await loadExpected();
25
+ const nodes = graph.nodes.filter((node) => node.kind === "file").map((node) => node.path);
26
+ const edges = buildRankEdges(graph, DEFAULT_GDGRAPH_CONFIG);
27
+ const ranked = personalizedPageRank(nodes, edges, {
28
+ damping: DEFAULT_GDGRAPH_CONFIG.repomap.damping,
29
+ iterations: DEFAULT_GDGRAPH_CONFIG.repomap.iterations,
30
+ tolerance: DEFAULT_GDGRAPH_CONFIG.repomap.tolerance,
31
+ });
32
+ expect(ranked.map((entry) => entry.id)).toEqual(expected.centralityOrder);
33
+ });
34
+
35
+ test("AC3.2 — repomap fits the token budget with a stable omission marker", async () => {
36
+ const graph = await loadFixtureGraph();
37
+ const expected = await loadExpected();
38
+ const config = mergeGdgraphConfig({ repomap: { tokenBudget: expected.tokenBudget } });
39
+ const result = computeRepomap(graph, config, {});
40
+
41
+ expect(result.tokens).toBeLessThanOrEqual(expected.tokenBudget);
42
+ expect(estimateTokens(result.content)).toBeLessThanOrEqual(expected.tokenBudget);
43
+ expect(result.entries.map((entry) => entry.path)).toEqual(expected.topWithinBudget);
44
+ expect(result.omitted).toBe(expected.omitted);
45
+ expect(result.content).toContain(`… ${expected.omitted} entries omitted …`);
46
+ });
47
+
48
+ test("AC3.2 — --budget override is honored and bounds the output", async () => {
49
+ const graph = await loadFixtureGraph();
50
+ const result = computeRepomap(graph, DEFAULT_GDGRAPH_CONFIG, { budget: 30 });
51
+ expect(result.tokens).toBeLessThanOrEqual(30);
52
+ expect(result.omitted).toBeGreaterThan(0);
53
+ });
54
+
55
+ test("AC3.4 — re-running repomap yields a byte-identical file", async () => {
56
+ const graph = await loadFixtureGraph();
57
+ const root = await mkdtemp(path.join(tmpdir(), "keryx-repomap-"));
58
+ try {
59
+ const config = mergeGdgraphConfig({ repomap: { tokenBudget: 40 } });
60
+ const first = await writeRepomap(root, graph, config, {});
61
+ const firstBytes = await readFile(first.path, "utf8");
62
+ const second = await writeRepomap(root, graph, config, {});
63
+ const secondBytes = await readFile(second.path, "utf8");
64
+ expect(secondBytes).toBe(firstBytes);
65
+ } finally {
66
+ await rm(root, { recursive: true, force: true });
67
+ }
68
+ });
69
+
70
+ test("AC3.6 — --seed biases personalization; seeded/unseeded each reproducible", async () => {
71
+ const graph = await loadFixtureGraph();
72
+ const unseeded = computeRepomap(graph, DEFAULT_GDGRAPH_CONFIG, {});
73
+ const seeded = computeRepomap(graph, DEFAULT_GDGRAPH_CONFIG, { seed: ["src/a.ts"] });
74
+ const seededAgain = computeRepomap(graph, DEFAULT_GDGRAPH_CONFIG, { seed: ["src/a.ts"] });
75
+
76
+ // Each run reproducible.
77
+ expect(seeded.content).toBe(seededAgain.content);
78
+ // Seeding on a.ts lifts it above its unseeded rank position.
79
+ const unseededRank = unseeded.entries.findIndex((entry) => entry.path === "src/a.ts");
80
+ const seededRank = seeded.entries.findIndex((entry) => entry.path === "src/a.ts");
81
+ expect(seededRank).toBeGreaterThanOrEqual(0);
82
+ expect(seededRank).toBeLessThan(unseededRank === -1 ? Number.MAX_SAFE_INTEGER : unseededRank + 1);
83
+ });
84
+
85
+ test("AC3.5 — repomap ranks symbol-aware weights when the layer is present", async () => {
86
+ const graph = await loadFixtureGraph();
87
+ // Add a tiny symbol layer so buildRankEdges exercises calls/defines weights.
88
+ const withSymbols: GraphData = {
89
+ ...graph,
90
+ symbols: [
91
+ {
92
+ id: "src/core.ts#run",
93
+ kind: "function",
94
+ path: "src/core.ts",
95
+ name: "run",
96
+ container: null,
97
+ startLine: 1,
98
+ endLine: 2,
99
+ language: "typescript",
100
+ signature: "run()",
101
+ },
102
+ ],
103
+ calls: [
104
+ { id: "d", from: "src/core.ts", to: "src/core.ts#run", kind: "defines", resolved: true },
105
+ ],
106
+ };
107
+ const result = computeRepomap(withSymbols, DEFAULT_GDGRAPH_CONFIG, { budget: 400 });
108
+ const core = result.entries.find((entry) => entry.path === "src/core.ts");
109
+ expect(core?.symbols).toEqual(["run()"]);
110
+ });