@humanbased/crosscheck 1.2.0-beta.81 โ†’ 1.2.0

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 (314) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +375 -182
  3. package/README.zh.md +1 -1
  4. package/crosscheck.config.example.yml +9 -101
  5. package/dist/__tests__/board.test.js +0 -11
  6. package/dist/__tests__/board.test.js.map +1 -1
  7. package/dist/__tests__/codex.test.js +27 -14
  8. package/dist/__tests__/codex.test.js.map +1 -1
  9. package/dist/__tests__/comment-bodies.test.js +1 -49
  10. package/dist/__tests__/comment-bodies.test.js.map +1 -1
  11. package/dist/__tests__/conflict-resolve.test.js +1 -44
  12. package/dist/__tests__/conflict-resolve.test.js.map +1 -1
  13. package/dist/__tests__/fix.test.js +0 -33
  14. package/dist/__tests__/fix.test.js.map +1 -1
  15. package/dist/__tests__/onboard-preservation.test.js +3 -59
  16. package/dist/__tests__/onboard-preservation.test.js.map +1 -1
  17. package/dist/__tests__/optimize.test.js +0 -2
  18. package/dist/__tests__/optimize.test.js.map +1 -1
  19. package/dist/__tests__/repo-picker.test.js +1 -7
  20. package/dist/__tests__/repo-picker.test.js.map +1 -1
  21. package/dist/__tests__/review-comment-body.test.js +0 -35
  22. package/dist/__tests__/review-comment-body.test.js.map +1 -1
  23. package/dist/__tests__/review-models.test.js +3 -19
  24. package/dist/__tests__/review-models.test.js.map +1 -1
  25. package/dist/__tests__/runner.test.js +1 -29
  26. package/dist/__tests__/runner.test.js.map +1 -1
  27. package/dist/cli.js +0 -29
  28. package/dist/cli.js.map +1 -1
  29. package/dist/commands/onboard.d.ts +3 -36
  30. package/dist/commands/onboard.d.ts.map +1 -1
  31. package/dist/commands/onboard.js +42 -233
  32. package/dist/commands/onboard.js.map +1 -1
  33. package/dist/commands/review.d.ts.map +1 -1
  34. package/dist/commands/review.js +6 -65
  35. package/dist/commands/review.js.map +1 -1
  36. package/dist/commands/run.d.ts.map +1 -1
  37. package/dist/commands/run.js +7 -45
  38. package/dist/commands/run.js.map +1 -1
  39. package/dist/commands/status.d.ts.map +1 -1
  40. package/dist/commands/status.js +1 -53
  41. package/dist/commands/status.js.map +1 -1
  42. package/dist/commands/watch.d.ts.map +1 -1
  43. package/dist/commands/watch.js +12 -90
  44. package/dist/commands/watch.js.map +1 -1
  45. package/dist/config/loader.d.ts +1 -3
  46. package/dist/config/loader.d.ts.map +1 -1
  47. package/dist/config/loader.js +0 -13
  48. package/dist/config/loader.js.map +1 -1
  49. package/dist/config/review-model-tiers.json +3 -3
  50. package/dist/config/schema.d.ts +15 -261
  51. package/dist/config/schema.d.ts.map +1 -1
  52. package/dist/config/schema.js +8 -90
  53. package/dist/config/schema.js.map +1 -1
  54. package/dist/github/client.d.ts +1 -21
  55. package/dist/github/client.d.ts.map +1 -1
  56. package/dist/github/client.js +7 -46
  57. package/dist/github/client.js.map +1 -1
  58. package/dist/github/webhook.d.ts +0 -4
  59. package/dist/github/webhook.d.ts.map +1 -1
  60. package/dist/github/webhook.js.map +1 -1
  61. package/dist/issues/ticket-ref.d.ts.map +1 -1
  62. package/dist/issues/ticket-ref.js +5 -6
  63. package/dist/issues/ticket-ref.js.map +1 -1
  64. package/dist/lib/annotation.d.ts +0 -7
  65. package/dist/lib/annotation.d.ts.map +1 -1
  66. package/dist/lib/annotation.js +1 -11
  67. package/dist/lib/annotation.js.map +1 -1
  68. package/dist/lib/board.d.ts +0 -3
  69. package/dist/lib/board.d.ts.map +1 -1
  70. package/dist/lib/board.js +2 -4
  71. package/dist/lib/board.js.map +1 -1
  72. package/dist/lib/clone.d.ts +0 -1
  73. package/dist/lib/clone.d.ts.map +1 -1
  74. package/dist/lib/clone.js +10 -32
  75. package/dist/lib/clone.js.map +1 -1
  76. package/dist/lib/comment-bodies.d.ts +0 -37
  77. package/dist/lib/comment-bodies.d.ts.map +1 -1
  78. package/dist/lib/comment-bodies.js +9 -47
  79. package/dist/lib/comment-bodies.js.map +1 -1
  80. package/dist/lib/repo-picker.d.ts +0 -3
  81. package/dist/lib/repo-picker.d.ts.map +1 -1
  82. package/dist/lib/repo-picker.js +9 -45
  83. package/dist/lib/repo-picker.js.map +1 -1
  84. package/dist/lib/review-models.d.ts +2 -15
  85. package/dist/lib/review-models.d.ts.map +1 -1
  86. package/dist/lib/review-models.js +6 -26
  87. package/dist/lib/review-models.js.map +1 -1
  88. package/dist/lib/runner.d.ts +0 -92
  89. package/dist/lib/runner.d.ts.map +1 -1
  90. package/dist/lib/runner.js +54 -470
  91. package/dist/lib/runner.js.map +1 -1
  92. package/dist/lib/workflow.d.ts +0 -9
  93. package/dist/lib/workflow.d.ts.map +1 -1
  94. package/dist/lib/workflow.js +0 -20
  95. package/dist/lib/workflow.js.map +1 -1
  96. package/dist/reviewers/claude.d.ts +1 -4
  97. package/dist/reviewers/claude.d.ts.map +1 -1
  98. package/dist/reviewers/claude.js +7 -39
  99. package/dist/reviewers/claude.js.map +1 -1
  100. package/dist/reviewers/codex.d.ts +1 -3
  101. package/dist/reviewers/codex.d.ts.map +1 -1
  102. package/dist/reviewers/codex.js +70 -76
  103. package/dist/reviewers/codex.js.map +1 -1
  104. package/dist/reviewers/conflict-resolve.d.ts +1 -3
  105. package/dist/reviewers/conflict-resolve.d.ts.map +1 -1
  106. package/dist/reviewers/conflict-resolve.js +6 -21
  107. package/dist/reviewers/conflict-resolve.js.map +1 -1
  108. package/dist/reviewers/fix.d.ts +2 -5
  109. package/dist/reviewers/fix.d.ts.map +1 -1
  110. package/dist/reviewers/fix.js +10 -26
  111. package/dist/reviewers/fix.js.map +1 -1
  112. package/get-started.md +11 -207
  113. package/package.json +3 -4
  114. package/assets/icon-256.png +0 -0
  115. package/assets/linear-comment.svg +0 -18
  116. package/assets/linear-onboard.svg +0 -30
  117. package/assets/linear-status.svg +0 -23
  118. package/assets/linear-test.svg +0 -34
  119. package/assets/skills/code-review/.crosscheck-skill.json +0 -9
  120. package/assets/skills/code-review/LICENSE +0 -21
  121. package/assets/skills/code-review/SKILL.md +0 -89
  122. package/assets/skills/code-review/agents/openai.yaml +0 -3
  123. package/assets/skills/code-review-skill/.crosscheck-skill.json +0 -9
  124. package/assets/skills/code-review-skill/LICENSE +0 -21
  125. package/assets/skills/code-review-skill/SKILL.md +0 -231
  126. package/assets/skills/code-review-skill/assets/pr-review-template.md +0 -137
  127. package/assets/skills/code-review-skill/assets/review-checklist.md +0 -123
  128. package/assets/skills/code-review-skill/reference/angular.md +0 -768
  129. package/assets/skills/code-review-skill/reference/architecture-review-guide.md +0 -472
  130. package/assets/skills/code-review-skill/reference/c.md +0 -890
  131. package/assets/skills/code-review-skill/reference/code-quality-universal.md +0 -488
  132. package/assets/skills/code-review-skill/reference/code-review-best-practices.md +0 -136
  133. package/assets/skills/code-review-skill/reference/common-bugs-checklist.md +0 -286
  134. package/assets/skills/code-review-skill/reference/cpp.md +0 -893
  135. package/assets/skills/code-review-skill/reference/cross-cutting/async-concurrency-patterns.md +0 -515
  136. package/assets/skills/code-review-skill/reference/cross-cutting/error-handling-principles.md +0 -492
  137. package/assets/skills/code-review-skill/reference/cross-cutting/n-plus-one-queries.md +0 -309
  138. package/assets/skills/code-review-skill/reference/cross-cutting/sql-injection-prevention.md +0 -308
  139. package/assets/skills/code-review-skill/reference/cross-cutting/xss-prevention.md +0 -264
  140. package/assets/skills/code-review-skill/reference/csharp.md +0 -525
  141. package/assets/skills/code-review-skill/reference/css-less-sass.md +0 -661
  142. package/assets/skills/code-review-skill/reference/django.md +0 -985
  143. package/assets/skills/code-review-skill/reference/fastapi.md +0 -580
  144. package/assets/skills/code-review-skill/reference/go.md +0 -993
  145. package/assets/skills/code-review-skill/reference/java.md +0 -409
  146. package/assets/skills/code-review-skill/reference/java8.md +0 -586
  147. package/assets/skills/code-review-skill/reference/kotlin.md +0 -1018
  148. package/assets/skills/code-review-skill/reference/nestjs.md +0 -593
  149. package/assets/skills/code-review-skill/reference/performance-review-guide.md +0 -816
  150. package/assets/skills/code-review-skill/reference/php.md +0 -684
  151. package/assets/skills/code-review-skill/reference/python.md +0 -1073
  152. package/assets/skills/code-review-skill/reference/qt.md +0 -757
  153. package/assets/skills/code-review-skill/reference/react.md +0 -871
  154. package/assets/skills/code-review-skill/reference/ruby.md +0 -964
  155. package/assets/skills/code-review-skill/reference/rust.md +0 -846
  156. package/assets/skills/code-review-skill/reference/security-review-guide.md +0 -494
  157. package/assets/skills/code-review-skill/reference/svelte.md +0 -1064
  158. package/assets/skills/code-review-skill/reference/swift.md +0 -936
  159. package/assets/skills/code-review-skill/reference/typescript.md +0 -1016
  160. package/assets/skills/code-review-skill/reference/vue.md +0 -924
  161. package/assets/skills/code-review-skill/reference/zig.md +0 -440
  162. package/assets/skills/code-review-skill/scripts/pr-analyzer.py +0 -435
  163. package/assets/skills/code-review-skill/scripts/test_pr_analyzer.py +0 -380
  164. package/assets/skills/codebase-design/.crosscheck-skill.json +0 -9
  165. package/assets/skills/codebase-design/DEEPENING.md +0 -37
  166. package/assets/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  167. package/assets/skills/codebase-design/LICENSE +0 -21
  168. package/assets/skills/codebase-design/SKILL.md +0 -114
  169. package/assets/skills/codebase-design/agents/openai.yaml +0 -3
  170. package/assets/skills/diagnosing-bugs/.crosscheck-skill.json +0 -9
  171. package/assets/skills/diagnosing-bugs/LICENSE +0 -21
  172. package/assets/skills/diagnosing-bugs/SKILL.md +0 -134
  173. package/assets/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  174. package/assets/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -41
  175. package/dist/__tests__/can-write-verdict.test.d.ts +0 -2
  176. package/dist/__tests__/can-write-verdict.test.d.ts.map +0 -1
  177. package/dist/__tests__/can-write-verdict.test.js +0 -31
  178. package/dist/__tests__/can-write-verdict.test.js.map +0 -1
  179. package/dist/__tests__/linear-branding.test.d.ts +0 -2
  180. package/dist/__tests__/linear-branding.test.d.ts.map +0 -1
  181. package/dist/__tests__/linear-branding.test.js +0 -156
  182. package/dist/__tests__/linear-branding.test.js.map +0 -1
  183. package/dist/__tests__/linear-client.test.d.ts +0 -2
  184. package/dist/__tests__/linear-client.test.d.ts.map +0 -1
  185. package/dist/__tests__/linear-client.test.js +0 -120
  186. package/dist/__tests__/linear-client.test.js.map +0 -1
  187. package/dist/__tests__/linear-comment.test.d.ts +0 -2
  188. package/dist/__tests__/linear-comment.test.d.ts.map +0 -1
  189. package/dist/__tests__/linear-comment.test.js +0 -151
  190. package/dist/__tests__/linear-comment.test.js.map +0 -1
  191. package/dist/__tests__/linear-identity.test.d.ts +0 -2
  192. package/dist/__tests__/linear-identity.test.d.ts.map +0 -1
  193. package/dist/__tests__/linear-identity.test.js +0 -253
  194. package/dist/__tests__/linear-identity.test.js.map +0 -1
  195. package/dist/__tests__/linear-notify.test.d.ts +0 -2
  196. package/dist/__tests__/linear-notify.test.d.ts.map +0 -1
  197. package/dist/__tests__/linear-notify.test.js +0 -144
  198. package/dist/__tests__/linear-notify.test.js.map +0 -1
  199. package/dist/__tests__/linear-ref.test.d.ts +0 -2
  200. package/dist/__tests__/linear-ref.test.d.ts.map +0 -1
  201. package/dist/__tests__/linear-ref.test.js +0 -261
  202. package/dist/__tests__/linear-ref.test.js.map +0 -1
  203. package/dist/__tests__/linear-test-ref.test.d.ts +0 -2
  204. package/dist/__tests__/linear-test-ref.test.d.ts.map +0 -1
  205. package/dist/__tests__/linear-test-ref.test.js +0 -81
  206. package/dist/__tests__/linear-test-ref.test.js.map +0 -1
  207. package/dist/__tests__/linear-verify.test.d.ts +0 -2
  208. package/dist/__tests__/linear-verify.test.d.ts.map +0 -1
  209. package/dist/__tests__/linear-verify.test.js +0 -132
  210. package/dist/__tests__/linear-verify.test.js.map +0 -1
  211. package/dist/__tests__/linear-worker.test.d.ts +0 -2
  212. package/dist/__tests__/linear-worker.test.d.ts.map +0 -1
  213. package/dist/__tests__/linear-worker.test.js +0 -83
  214. package/dist/__tests__/linear-worker.test.js.map +0 -1
  215. package/dist/__tests__/linear-write-possible.test.d.ts +0 -2
  216. package/dist/__tests__/linear-write-possible.test.d.ts.map +0 -1
  217. package/dist/__tests__/linear-write-possible.test.js +0 -30
  218. package/dist/__tests__/linear-write-possible.test.js.map +0 -1
  219. package/dist/__tests__/repository-guidance.test.d.ts +0 -2
  220. package/dist/__tests__/repository-guidance.test.d.ts.map +0 -1
  221. package/dist/__tests__/repository-guidance.test.js +0 -107
  222. package/dist/__tests__/repository-guidance.test.js.map +0 -1
  223. package/dist/__tests__/review-strategy.test.d.ts +0 -2
  224. package/dist/__tests__/review-strategy.test.d.ts.map +0 -1
  225. package/dist/__tests__/review-strategy.test.js +0 -397
  226. package/dist/__tests__/review-strategy.test.js.map +0 -1
  227. package/dist/__tests__/skill-attribution.test.d.ts +0 -2
  228. package/dist/__tests__/skill-attribution.test.d.ts.map +0 -1
  229. package/dist/__tests__/skill-attribution.test.js +0 -53
  230. package/dist/__tests__/skill-attribution.test.js.map +0 -1
  231. package/dist/__tests__/skill-broker.test.d.ts +0 -2
  232. package/dist/__tests__/skill-broker.test.d.ts.map +0 -1
  233. package/dist/__tests__/skill-broker.test.js +0 -144
  234. package/dist/__tests__/skill-broker.test.js.map +0 -1
  235. package/dist/__tests__/skill-catalog.test.d.ts +0 -2
  236. package/dist/__tests__/skill-catalog.test.d.ts.map +0 -1
  237. package/dist/__tests__/skill-catalog.test.js +0 -40
  238. package/dist/__tests__/skill-catalog.test.js.map +0 -1
  239. package/dist/__tests__/skill-installer.test.d.ts +0 -2
  240. package/dist/__tests__/skill-installer.test.d.ts.map +0 -1
  241. package/dist/__tests__/skill-installer.test.js +0 -96
  242. package/dist/__tests__/skill-installer.test.js.map +0 -1
  243. package/dist/__tests__/skills-config.test.d.ts +0 -2
  244. package/dist/__tests__/skills-config.test.d.ts.map +0 -1
  245. package/dist/__tests__/skills-config.test.js +0 -12
  246. package/dist/__tests__/skills-config.test.js.map +0 -1
  247. package/dist/commands/linear-test.d.ts +0 -18
  248. package/dist/commands/linear-test.d.ts.map +0 -1
  249. package/dist/commands/linear-test.js +0 -130
  250. package/dist/commands/linear-test.js.map +0 -1
  251. package/dist/commands/skill.d.ts +0 -2
  252. package/dist/commands/skill.d.ts.map +0 -1
  253. package/dist/commands/skill.js +0 -16
  254. package/dist/commands/skill.js.map +0 -1
  255. package/dist/config/review-strategy.json +0 -204
  256. package/dist/lib/repository-guidance.d.ts +0 -2
  257. package/dist/lib/repository-guidance.d.ts.map +0 -1
  258. package/dist/lib/repository-guidance.js +0 -55
  259. package/dist/lib/repository-guidance.js.map +0 -1
  260. package/dist/lib/review-strategy.d.ts +0 -92
  261. package/dist/lib/review-strategy.d.ts.map +0 -1
  262. package/dist/lib/review-strategy.js +0 -282
  263. package/dist/lib/review-strategy.js.map +0 -1
  264. package/dist/linear/client.d.ts +0 -18
  265. package/dist/linear/client.d.ts.map +0 -1
  266. package/dist/linear/client.js +0 -67
  267. package/dist/linear/client.js.map +0 -1
  268. package/dist/linear/comment.d.ts +0 -20
  269. package/dist/linear/comment.d.ts.map +0 -1
  270. package/dist/linear/comment.js +0 -57
  271. package/dist/linear/comment.js.map +0 -1
  272. package/dist/linear/identity.d.ts +0 -59
  273. package/dist/linear/identity.d.ts.map +0 -1
  274. package/dist/linear/identity.js +0 -187
  275. package/dist/linear/identity.js.map +0 -1
  276. package/dist/linear/notify.d.ts +0 -35
  277. package/dist/linear/notify.d.ts.map +0 -1
  278. package/dist/linear/notify.js +0 -76
  279. package/dist/linear/notify.js.map +0 -1
  280. package/dist/linear/ref.d.ts +0 -13
  281. package/dist/linear/ref.d.ts.map +0 -1
  282. package/dist/linear/ref.js +0 -90
  283. package/dist/linear/ref.js.map +0 -1
  284. package/dist/linear/verify.d.ts +0 -26
  285. package/dist/linear/verify.d.ts.map +0 -1
  286. package/dist/linear/verify.js +0 -67
  287. package/dist/linear/verify.js.map +0 -1
  288. package/dist/skills/attribution.d.ts +0 -4
  289. package/dist/skills/attribution.d.ts.map +0 -1
  290. package/dist/skills/attribution.js +0 -14
  291. package/dist/skills/attribution.js.map +0 -1
  292. package/dist/skills/broker-server.d.ts +0 -2
  293. package/dist/skills/broker-server.d.ts.map +0 -1
  294. package/dist/skills/broker-server.js +0 -17
  295. package/dist/skills/broker-server.js.map +0 -1
  296. package/dist/skills/broker.d.ts +0 -42
  297. package/dist/skills/broker.d.ts.map +0 -1
  298. package/dist/skills/broker.js +0 -285
  299. package/dist/skills/broker.js.map +0 -1
  300. package/dist/skills/catalog.d.ts +0 -28
  301. package/dist/skills/catalog.d.ts.map +0 -1
  302. package/dist/skills/catalog.js +0 -104
  303. package/dist/skills/catalog.js.map +0 -1
  304. package/dist/skills/installer.d.ts +0 -10
  305. package/dist/skills/installer.d.ts.map +0 -1
  306. package/dist/skills/installer.js +0 -138
  307. package/dist/skills/installer.js.map +0 -1
  308. package/dist/skills/integrity.d.ts +0 -4
  309. package/dist/skills/integrity.d.ts.map +0 -1
  310. package/dist/skills/integrity.js +0 -36
  311. package/dist/skills/integrity.js.map +0 -1
  312. package/docs/dynamic-thoroughness.md +0 -738
  313. package/docs/linear-identity-contract.md +0 -139
  314. package/docs/linear-identity.md +0 -293
@@ -1,435 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- PR Analyzer - Analyze PR complexity and suggest review approach.
4
-
5
- Usage:
6
- python pr-analyzer.py [--diff-file FILE] [--stats]
7
-
8
- Or pipe diff directly:
9
- git diff main...HEAD | python pr-analyzer.py
10
- """
11
-
12
- import os
13
- import sys
14
- import re
15
- import argparse
16
- from collections import defaultdict
17
- from dataclasses import dataclass
18
- from typing import List, Dict, Optional
19
-
20
- RISK_NO_TESTS = "NO_TEST_CHANGES"
21
-
22
-
23
- @dataclass
24
- class FileStats:
25
- """Statistics for a single file."""
26
- filename: str
27
- additions: int = 0
28
- deletions: int = 0
29
- is_test: bool = False
30
- is_config: bool = False
31
- language: str = "unknown"
32
-
33
-
34
- @dataclass
35
- class PRAnalysis:
36
- """Complete PR analysis results."""
37
- total_files: int
38
- total_additions: int
39
- total_deletions: int
40
- files: List[FileStats]
41
- complexity_score: float
42
- size_category: str
43
- estimated_review_time: int
44
- risk_factors: List[str]
45
- suggestions: List[str]
46
-
47
-
48
- def detect_language(filename: str) -> str:
49
- """Detect programming language from filename."""
50
- _, ext = os.path.splitext(filename)
51
- extensions = {
52
- '.py': 'Python',
53
- '.js': 'JavaScript',
54
- '.ts': 'TypeScript',
55
- '.tsx': 'TypeScript/React',
56
- '.jsx': 'JavaScript/React',
57
- '.rs': 'Rust',
58
- '.go': 'Go',
59
- '.c': 'C',
60
- '.h': 'C/C++',
61
- '.cpp': 'C++',
62
- '.hpp': 'C++',
63
- '.cc': 'C++',
64
- '.cxx': 'C++',
65
- '.hh': 'C++',
66
- '.hxx': 'C++',
67
- '.java': 'Java',
68
- '.kt': 'Kotlin',
69
- '.swift': 'Swift',
70
- '.rb': 'Ruby',
71
- '.php': 'PHP',
72
- '.cs': 'C#',
73
- '.vue': 'Vue',
74
- '.svelte': 'Svelte',
75
- '.sql': 'SQL',
76
- '.md': 'Markdown',
77
- '.json': 'JSON',
78
- '.yaml': 'YAML',
79
- '.yml': 'YAML',
80
- '.toml': 'TOML',
81
- '.css': 'CSS',
82
- '.scss': 'SCSS',
83
- '.less': 'Less',
84
- '.html': 'HTML',
85
- '.zig': 'Zig',
86
- '.ex': 'Elixir',
87
- '.exs': 'Elixir',
88
- '.erl': 'Erlang',
89
- '.scala': 'Scala',
90
- '.lua': 'Lua',
91
- }
92
- return extensions.get(ext.lower(), 'unknown')
93
-
94
-
95
- def is_test_file(filename: str) -> bool:
96
- """Check if file is a test file."""
97
- test_patterns = [
98
- r'(?:^|/)test_[^/]+\.py$', # Python: test_handler.py (anchored to path segment)
99
- r'[^/]+_test\.', # *_test.<ext> โ€” Rust, Go, etc.
100
- r'[^/]+\.test\.(js|ts|tsx|jsx)$', # JS/TS: handler.test.ts
101
- r'[^/]+\.spec\.(js|ts|tsx|jsx)$', # JS/TS: handler.spec.ts
102
- r'(?:^|/)tests?/', # tests/ or test/ directory
103
- r'(?:^|/)__tests__/', # __tests__/ directory
104
- r'(?:^|/)spec/', # spec/ directory (Ruby, etc.)
105
- ]
106
- return any(re.search(p, filename) for p in test_patterns)
107
-
108
-
109
- def is_config_file(filename: str) -> bool:
110
- """Check if file is a configuration file.
111
-
112
- Uses explicit known-name matching to avoid flagging data files
113
- like data.json, openapi.yaml, or swagger.json as config.
114
- """
115
- basename = os.path.basename(filename)
116
-
117
- # Env files (.env, .env.local, .env.production, โ€ฆ)
118
- if basename.startswith('.env'):
119
- return True
120
-
121
- # Known config filenames (exact match)
122
- known_config_names = {
123
- 'package.json', 'package-lock.json',
124
- 'tsconfig.json', 'jsconfig.json',
125
- 'babel.config.json', 'babel.config.js',
126
- 'webpack.config.js', 'webpack.config.ts',
127
- 'rollup.config.js', 'vite.config.ts', 'vite.config.js',
128
- '.eslintrc.json', '.eslintrc.js', '.eslintrc.yml',
129
- '.prettierrc.json', '.prettierrc.yml', '.prettierrc.js',
130
- 'jest.config.js', 'jest.config.ts',
131
- 'vitest.config.ts', 'vitest.config.js',
132
- 'tailwind.config.js', 'tailwind.config.ts',
133
- 'postcss.config.js',
134
- 'docker-compose.yml', 'docker-compose.yaml',
135
- 'Dockerfile',
136
- 'Makefile', 'CMakeLists.txt',
137
- 'pyproject.toml', 'poetry.toml', 'Pipfile',
138
- 'setup.cfg', 'setup.py', 'tox.ini',
139
- 'Cargo.toml', 'Cargo.lock',
140
- 'go.mod', 'go.sum',
141
- 'Gemfile', 'Gemfile.lock',
142
- 'composer.json', 'composer.lock',
143
- 'Podfile', 'Package.swift',
144
- '.gitignore', '.gitattributes',
145
- 'gradle.properties', 'build.gradle', 'build.gradle.kts',
146
- 'settings.gradle', 'settings.gradle.kts',
147
- }
148
- if basename in known_config_names:
149
- return True
150
-
151
- # Known config path patterns
152
- config_path_patterns = [
153
- r'\.github/workflows/[^/]+\.ya?ml$',
154
- r'\.vscode/',
155
- r'\.idea/',
156
- ]
157
- if any(re.search(p, filename) for p in config_path_patterns):
158
- return True
159
-
160
- # Files with "config" in the name (e.g. app.config.ts, database_config.yml)
161
- if re.search(r'config\.', basename):
162
- return True
163
-
164
- # Files under a config/ directory
165
- if re.search(r'(?:^|/)config/', filename):
166
- return True
167
-
168
- return False
169
-
170
-
171
- def parse_diff(diff_content: str) -> List[FileStats]:
172
- """Parse git diff output and extract file statistics."""
173
- files = []
174
- current_file = None
175
-
176
- for line in diff_content.split('\n'):
177
- # New file header
178
- if line.startswith('diff --git'):
179
- if current_file:
180
- files.append(current_file)
181
- # "diff --git a/<path> b/<path>" โ€” match the b/ side via a
182
- # backreference so a literal "b/" inside paths like lib/, web/ or
183
- # db/ can't be mistaken for the prefix. Renames have differing
184
- # paths, so fall back to the b/ side after the separating space.
185
- match = re.match(r'diff --git a/(.+?) b/\1', line)
186
- if not match:
187
- match = re.search(r' b/(.+)$', line)
188
- if match:
189
- filename = match.group(1)
190
- current_file = FileStats(
191
- filename=filename,
192
- language=detect_language(filename),
193
- is_test=is_test_file(filename),
194
- is_config=is_config_file(filename),
195
- )
196
- else:
197
- current_file = None
198
- elif current_file:
199
- if line.startswith('+') and not line.startswith('+++'):
200
- current_file.additions += 1
201
- elif line.startswith('-') and not line.startswith('---'):
202
- current_file.deletions += 1
203
-
204
- if current_file:
205
- files.append(current_file)
206
-
207
- return files
208
-
209
-
210
- def calculate_complexity(files: List[FileStats]) -> float:
211
- """Calculate complexity score (0-1 scale)."""
212
- if not files:
213
- return 0.0
214
-
215
- total_changes = sum(f.additions + f.deletions for f in files)
216
-
217
- # Base complexity from size
218
- size_factor = min(total_changes / 1000, 1.0)
219
-
220
- # Factor for number of files
221
- file_factor = min(len(files) / 20, 1.0)
222
-
223
- # Factor for non-test code ratio
224
- test_lines = sum(f.additions + f.deletions for f in files if f.is_test)
225
- non_test_ratio = 1 - (test_lines / max(total_changes, 1))
226
-
227
- # Factor for language diversity
228
- languages = set(f.language for f in files if f.language != 'unknown')
229
- lang_factor = min(len(languages) / 5, 1.0)
230
-
231
- complexity = (
232
- size_factor * 0.4 +
233
- file_factor * 0.2 +
234
- non_test_ratio * 0.2 +
235
- lang_factor * 0.2
236
- )
237
-
238
- return round(complexity, 2)
239
-
240
-
241
- def categorize_size(total_changes: int) -> str:
242
- """Categorize PR size."""
243
- if total_changes < 50:
244
- return "XS (Extra Small)"
245
- elif total_changes < 200:
246
- return "S (Small)"
247
- elif total_changes < 400:
248
- return "M (Medium)"
249
- elif total_changes < 800:
250
- return "L (Large)"
251
- else:
252
- return "XL (Extra Large) - Consider splitting"
253
-
254
-
255
- def estimate_review_time(files: List[FileStats], complexity: float) -> int:
256
- """Estimate review time in minutes."""
257
- total_changes = sum(f.additions + f.deletions for f in files)
258
-
259
- # Base time: ~1 minute per 20 lines
260
- base_time = total_changes / 20
261
-
262
- # Adjust for complexity
263
- adjusted_time = base_time * (1 + complexity)
264
-
265
- # Minimum 5 minutes, maximum 120 minutes
266
- return max(5, min(120, int(adjusted_time)))
267
-
268
-
269
- def identify_risk_factors(files: List[FileStats]) -> List[str]:
270
- """Identify potential risk factors in the PR."""
271
- risks = []
272
-
273
- total_changes = sum(f.additions + f.deletions for f in files)
274
- test_changes = sum(f.additions + f.deletions for f in files if f.is_test)
275
-
276
- if total_changes > 400:
277
- risks.append("Large PR (>400 lines) - harder to review thoroughly")
278
-
279
- if test_changes == 0 and total_changes > 50:
280
- risks.append(f"{RISK_NO_TESTS}: No test changes - verify test coverage")
281
-
282
- if total_changes > 100 and test_changes / max(total_changes, 1) < 0.2:
283
- risks.append("Low test ratio (<20%) - consider adding more tests")
284
-
285
- # Security-sensitive files
286
- security_patterns = ['.env', 'auth', 'security', 'password', 'token', 'secret']
287
- for f in files:
288
- if any(p in f.filename.lower() for p in security_patterns):
289
- risks.append(f"Security-sensitive file: {f.filename}")
290
- break
291
-
292
- # Database changes
293
- for f in files:
294
- if 'migration' in f.filename.lower() or f.language == 'SQL':
295
- risks.append("Database changes detected - review carefully")
296
- break
297
-
298
- # Config changes
299
- config_files = [f for f in files if f.is_config]
300
- if config_files:
301
- risks.append(f"Configuration changes in {len(config_files)} file(s)")
302
-
303
- return risks
304
-
305
-
306
- def generate_suggestions(files: List[FileStats], complexity: float, risks: List[str]) -> List[str]:
307
- """Generate review suggestions."""
308
- suggestions = []
309
-
310
- total_changes = sum(f.additions + f.deletions for f in files)
311
-
312
- if total_changes > 800:
313
- suggestions.append("Consider splitting this PR into smaller, focused changes")
314
-
315
- if complexity > 0.7:
316
- suggestions.append("High complexity - allocate extra review time")
317
- suggestions.append("Consider pair reviewing for critical sections")
318
-
319
- if any(RISK_NO_TESTS in r for r in risks):
320
- suggestions.append("Request test additions before approval")
321
-
322
- # Language-specific suggestions
323
- languages = set(f.language for f in files)
324
- if 'TypeScript' in languages or 'TypeScript/React' in languages:
325
- suggestions.append("Check for proper type usage (avoid 'any')")
326
- if 'Rust' in languages:
327
- suggestions.append("Check for unwrap() usage and error handling")
328
- if 'C' in languages or 'C++' in languages or 'C/C++' in languages:
329
- suggestions.append("Check for memory safety, bounds checks, and UB risks")
330
- if 'SQL' in languages:
331
- suggestions.append("Review for SQL injection and query performance")
332
-
333
- if not suggestions:
334
- suggestions.append("Standard review process should suffice")
335
-
336
- return suggestions
337
-
338
-
339
- def analyze_pr(diff_content: str) -> PRAnalysis:
340
- """Perform complete PR analysis."""
341
- files = parse_diff(diff_content)
342
-
343
- total_additions = sum(f.additions for f in files)
344
- total_deletions = sum(f.deletions for f in files)
345
- total_changes = total_additions + total_deletions
346
-
347
- complexity = calculate_complexity(files)
348
- risks = identify_risk_factors(files)
349
- suggestions = generate_suggestions(files, complexity, risks)
350
-
351
- return PRAnalysis(
352
- total_files=len(files),
353
- total_additions=total_additions,
354
- total_deletions=total_deletions,
355
- files=files,
356
- complexity_score=complexity,
357
- size_category=categorize_size(total_changes),
358
- estimated_review_time=estimate_review_time(files, complexity),
359
- risk_factors=risks,
360
- suggestions=suggestions,
361
- )
362
-
363
-
364
- def print_analysis(analysis: PRAnalysis, show_files: bool = False):
365
- """Print analysis results."""
366
- print("\n" + "=" * 60)
367
- print("PR ANALYSIS REPORT")
368
- print("=" * 60)
369
-
370
- print(f"\n๐Ÿ“Š SUMMARY")
371
- print(f" Files changed: {analysis.total_files}")
372
- print(f" Additions: +{analysis.total_additions}")
373
- print(f" Deletions: -{analysis.total_deletions}")
374
- print(f" Total changes: {analysis.total_additions + analysis.total_deletions}")
375
-
376
- print(f"\n๐Ÿ“ SIZE: {analysis.size_category}")
377
- print(f" Complexity score: {analysis.complexity_score}/1.0")
378
- print(f" Estimated review time: ~{analysis.estimated_review_time} minutes")
379
-
380
- if analysis.risk_factors:
381
- print(f"\nโš ๏ธ RISK FACTORS:")
382
- for risk in analysis.risk_factors:
383
- print(f" โ€ข {risk}")
384
-
385
- print(f"\n๐Ÿ’ก SUGGESTIONS:")
386
- for suggestion in analysis.suggestions:
387
- print(f" โ€ข {suggestion}")
388
-
389
- if show_files:
390
- print(f"\n๐Ÿ“ FILES:")
391
- # Group by language
392
- by_lang: Dict[str, List[FileStats]] = defaultdict(list)
393
- for f in analysis.files:
394
- by_lang[f.language].append(f)
395
-
396
- for lang, lang_files in sorted(by_lang.items()):
397
- print(f"\n [{lang}]")
398
- for f in lang_files:
399
- prefix = "๐Ÿงช" if f.is_test else "โš™๏ธ" if f.is_config else "๐Ÿ“„"
400
- print(f" {prefix} {f.filename} (+{f.additions}/-{f.deletions})")
401
-
402
- print("\n" + "=" * 60)
403
-
404
-
405
- def main():
406
- parser = argparse.ArgumentParser(description='Analyze PR complexity')
407
- parser.add_argument('--diff-file', '-f', help='Path to diff file')
408
- parser.add_argument('--stats', '-s', action='store_true', help='Show file details')
409
- args = parser.parse_args()
410
-
411
- # Read diff from file or stdin
412
- try:
413
- if args.diff_file:
414
- with open(args.diff_file, 'r', encoding='utf-8', errors='replace') as f:
415
- diff_content = f.read()
416
- elif not sys.stdin.isatty():
417
- diff_content = sys.stdin.buffer.read().decode('utf-8', errors='replace')
418
- else:
419
- print("Usage: git diff main...HEAD | python pr-analyzer.py")
420
- print(" python pr-analyzer.py -f diff.txt")
421
- sys.exit(1)
422
- except OSError as e:
423
- print(f"Error reading diff input: {e}", file=sys.stderr)
424
- sys.exit(1)
425
-
426
- if not diff_content.strip():
427
- print("No diff content provided")
428
- sys.exit(1)
429
-
430
- analysis = analyze_pr(diff_content)
431
- print_analysis(analysis, show_files=args.stats)
432
-
433
-
434
- if __name__ == '__main__':
435
- main()