@humanbased/crosscheck 1.2.0-beta.80 โ†’ 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 (301) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +388 -149
  3. package/README.zh.md +1 -1
  4. package/crosscheck.config.example.yml +2 -63
  5. package/dist/__tests__/codex.test.js +27 -14
  6. package/dist/__tests__/codex.test.js.map +1 -1
  7. package/dist/__tests__/comment-bodies.test.js +1 -49
  8. package/dist/__tests__/comment-bodies.test.js.map +1 -1
  9. package/dist/__tests__/conflict-resolve.test.js +1 -44
  10. package/dist/__tests__/conflict-resolve.test.js.map +1 -1
  11. package/dist/__tests__/fix.test.js +0 -33
  12. package/dist/__tests__/fix.test.js.map +1 -1
  13. package/dist/__tests__/onboard-preservation.test.js +0 -5
  14. package/dist/__tests__/onboard-preservation.test.js.map +1 -1
  15. package/dist/__tests__/optimize.test.js +0 -2
  16. package/dist/__tests__/optimize.test.js.map +1 -1
  17. package/dist/__tests__/repo-picker.test.js +1 -7
  18. package/dist/__tests__/repo-picker.test.js.map +1 -1
  19. package/dist/__tests__/review-comment-body.test.js +0 -35
  20. package/dist/__tests__/review-comment-body.test.js.map +1 -1
  21. package/dist/__tests__/review-models.test.js +3 -19
  22. package/dist/__tests__/review-models.test.js.map +1 -1
  23. package/dist/__tests__/runner.test.js +1 -29
  24. package/dist/__tests__/runner.test.js.map +1 -1
  25. package/dist/cli.js +0 -29
  26. package/dist/cli.js.map +1 -1
  27. package/dist/commands/onboard.d.ts +0 -11
  28. package/dist/commands/onboard.d.ts.map +1 -1
  29. package/dist/commands/onboard.js +2 -88
  30. package/dist/commands/onboard.js.map +1 -1
  31. package/dist/commands/review.d.ts.map +1 -1
  32. package/dist/commands/review.js +6 -65
  33. package/dist/commands/review.js.map +1 -1
  34. package/dist/commands/run.d.ts.map +1 -1
  35. package/dist/commands/run.js +3 -23
  36. package/dist/commands/run.js.map +1 -1
  37. package/dist/commands/status.d.ts.map +1 -1
  38. package/dist/commands/status.js +1 -53
  39. package/dist/commands/status.js.map +1 -1
  40. package/dist/commands/watch.d.ts.map +1 -1
  41. package/dist/commands/watch.js +6 -52
  42. package/dist/commands/watch.js.map +1 -1
  43. package/dist/config/loader.d.ts +1 -3
  44. package/dist/config/loader.d.ts.map +1 -1
  45. package/dist/config/loader.js +0 -13
  46. package/dist/config/loader.js.map +1 -1
  47. package/dist/config/review-model-tiers.json +3 -3
  48. package/dist/config/schema.d.ts +10 -254
  49. package/dist/config/schema.d.ts.map +1 -1
  50. package/dist/config/schema.js +2 -62
  51. package/dist/config/schema.js.map +1 -1
  52. package/dist/github/client.d.ts +1 -6
  53. package/dist/github/client.d.ts.map +1 -1
  54. package/dist/github/client.js +7 -15
  55. package/dist/github/client.js.map +1 -1
  56. package/dist/issues/ticket-ref.d.ts.map +1 -1
  57. package/dist/issues/ticket-ref.js +5 -6
  58. package/dist/issues/ticket-ref.js.map +1 -1
  59. package/dist/lib/clone.d.ts +0 -1
  60. package/dist/lib/clone.d.ts.map +1 -1
  61. package/dist/lib/clone.js +10 -32
  62. package/dist/lib/clone.js.map +1 -1
  63. package/dist/lib/comment-bodies.d.ts +0 -35
  64. package/dist/lib/comment-bodies.d.ts.map +1 -1
  65. package/dist/lib/comment-bodies.js +9 -43
  66. package/dist/lib/comment-bodies.js.map +1 -1
  67. package/dist/lib/repo-picker.d.ts +0 -3
  68. package/dist/lib/repo-picker.d.ts.map +1 -1
  69. package/dist/lib/repo-picker.js +9 -45
  70. package/dist/lib/repo-picker.js.map +1 -1
  71. package/dist/lib/review-models.d.ts +2 -15
  72. package/dist/lib/review-models.d.ts.map +1 -1
  73. package/dist/lib/review-models.js +6 -26
  74. package/dist/lib/review-models.js.map +1 -1
  75. package/dist/lib/runner.d.ts +0 -12
  76. package/dist/lib/runner.d.ts.map +1 -1
  77. package/dist/lib/runner.js +42 -186
  78. package/dist/lib/runner.js.map +1 -1
  79. package/dist/lib/workflow.d.ts +0 -9
  80. package/dist/lib/workflow.d.ts.map +1 -1
  81. package/dist/lib/workflow.js +0 -20
  82. package/dist/lib/workflow.js.map +1 -1
  83. package/dist/reviewers/claude.d.ts +1 -4
  84. package/dist/reviewers/claude.d.ts.map +1 -1
  85. package/dist/reviewers/claude.js +7 -39
  86. package/dist/reviewers/claude.js.map +1 -1
  87. package/dist/reviewers/codex.d.ts +1 -3
  88. package/dist/reviewers/codex.d.ts.map +1 -1
  89. package/dist/reviewers/codex.js +70 -76
  90. package/dist/reviewers/codex.js.map +1 -1
  91. package/dist/reviewers/conflict-resolve.d.ts +1 -3
  92. package/dist/reviewers/conflict-resolve.d.ts.map +1 -1
  93. package/dist/reviewers/conflict-resolve.js +6 -21
  94. package/dist/reviewers/conflict-resolve.js.map +1 -1
  95. package/dist/reviewers/fix.d.ts +2 -5
  96. package/dist/reviewers/fix.d.ts.map +1 -1
  97. package/dist/reviewers/fix.js +10 -26
  98. package/dist/reviewers/fix.js.map +1 -1
  99. package/get-started.md +4 -139
  100. package/package.json +3 -4
  101. package/assets/icon-256.png +0 -0
  102. package/assets/linear-comment.svg +0 -18
  103. package/assets/linear-onboard.svg +0 -30
  104. package/assets/linear-status.svg +0 -23
  105. package/assets/linear-test.svg +0 -34
  106. package/assets/skills/code-review/.crosscheck-skill.json +0 -9
  107. package/assets/skills/code-review/LICENSE +0 -21
  108. package/assets/skills/code-review/SKILL.md +0 -89
  109. package/assets/skills/code-review/agents/openai.yaml +0 -3
  110. package/assets/skills/code-review-skill/.crosscheck-skill.json +0 -9
  111. package/assets/skills/code-review-skill/LICENSE +0 -21
  112. package/assets/skills/code-review-skill/SKILL.md +0 -231
  113. package/assets/skills/code-review-skill/assets/pr-review-template.md +0 -137
  114. package/assets/skills/code-review-skill/assets/review-checklist.md +0 -123
  115. package/assets/skills/code-review-skill/reference/angular.md +0 -768
  116. package/assets/skills/code-review-skill/reference/architecture-review-guide.md +0 -472
  117. package/assets/skills/code-review-skill/reference/c.md +0 -890
  118. package/assets/skills/code-review-skill/reference/code-quality-universal.md +0 -488
  119. package/assets/skills/code-review-skill/reference/code-review-best-practices.md +0 -136
  120. package/assets/skills/code-review-skill/reference/common-bugs-checklist.md +0 -286
  121. package/assets/skills/code-review-skill/reference/cpp.md +0 -893
  122. package/assets/skills/code-review-skill/reference/cross-cutting/async-concurrency-patterns.md +0 -515
  123. package/assets/skills/code-review-skill/reference/cross-cutting/error-handling-principles.md +0 -492
  124. package/assets/skills/code-review-skill/reference/cross-cutting/n-plus-one-queries.md +0 -309
  125. package/assets/skills/code-review-skill/reference/cross-cutting/sql-injection-prevention.md +0 -308
  126. package/assets/skills/code-review-skill/reference/cross-cutting/xss-prevention.md +0 -264
  127. package/assets/skills/code-review-skill/reference/csharp.md +0 -525
  128. package/assets/skills/code-review-skill/reference/css-less-sass.md +0 -661
  129. package/assets/skills/code-review-skill/reference/django.md +0 -985
  130. package/assets/skills/code-review-skill/reference/fastapi.md +0 -580
  131. package/assets/skills/code-review-skill/reference/go.md +0 -993
  132. package/assets/skills/code-review-skill/reference/java.md +0 -409
  133. package/assets/skills/code-review-skill/reference/java8.md +0 -586
  134. package/assets/skills/code-review-skill/reference/kotlin.md +0 -1018
  135. package/assets/skills/code-review-skill/reference/nestjs.md +0 -593
  136. package/assets/skills/code-review-skill/reference/performance-review-guide.md +0 -816
  137. package/assets/skills/code-review-skill/reference/php.md +0 -684
  138. package/assets/skills/code-review-skill/reference/python.md +0 -1073
  139. package/assets/skills/code-review-skill/reference/qt.md +0 -757
  140. package/assets/skills/code-review-skill/reference/react.md +0 -871
  141. package/assets/skills/code-review-skill/reference/ruby.md +0 -964
  142. package/assets/skills/code-review-skill/reference/rust.md +0 -846
  143. package/assets/skills/code-review-skill/reference/security-review-guide.md +0 -494
  144. package/assets/skills/code-review-skill/reference/svelte.md +0 -1064
  145. package/assets/skills/code-review-skill/reference/swift.md +0 -936
  146. package/assets/skills/code-review-skill/reference/typescript.md +0 -1016
  147. package/assets/skills/code-review-skill/reference/vue.md +0 -924
  148. package/assets/skills/code-review-skill/reference/zig.md +0 -440
  149. package/assets/skills/code-review-skill/scripts/pr-analyzer.py +0 -435
  150. package/assets/skills/code-review-skill/scripts/test_pr_analyzer.py +0 -380
  151. package/assets/skills/codebase-design/.crosscheck-skill.json +0 -9
  152. package/assets/skills/codebase-design/DEEPENING.md +0 -37
  153. package/assets/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  154. package/assets/skills/codebase-design/LICENSE +0 -21
  155. package/assets/skills/codebase-design/SKILL.md +0 -114
  156. package/assets/skills/codebase-design/agents/openai.yaml +0 -3
  157. package/assets/skills/diagnosing-bugs/.crosscheck-skill.json +0 -9
  158. package/assets/skills/diagnosing-bugs/LICENSE +0 -21
  159. package/assets/skills/diagnosing-bugs/SKILL.md +0 -134
  160. package/assets/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  161. package/assets/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -41
  162. package/dist/__tests__/can-write-verdict.test.d.ts +0 -2
  163. package/dist/__tests__/can-write-verdict.test.d.ts.map +0 -1
  164. package/dist/__tests__/can-write-verdict.test.js +0 -31
  165. package/dist/__tests__/can-write-verdict.test.js.map +0 -1
  166. package/dist/__tests__/linear-branding.test.d.ts +0 -2
  167. package/dist/__tests__/linear-branding.test.d.ts.map +0 -1
  168. package/dist/__tests__/linear-branding.test.js +0 -156
  169. package/dist/__tests__/linear-branding.test.js.map +0 -1
  170. package/dist/__tests__/linear-client.test.d.ts +0 -2
  171. package/dist/__tests__/linear-client.test.d.ts.map +0 -1
  172. package/dist/__tests__/linear-client.test.js +0 -120
  173. package/dist/__tests__/linear-client.test.js.map +0 -1
  174. package/dist/__tests__/linear-comment.test.d.ts +0 -2
  175. package/dist/__tests__/linear-comment.test.d.ts.map +0 -1
  176. package/dist/__tests__/linear-comment.test.js +0 -151
  177. package/dist/__tests__/linear-comment.test.js.map +0 -1
  178. package/dist/__tests__/linear-identity.test.d.ts +0 -2
  179. package/dist/__tests__/linear-identity.test.d.ts.map +0 -1
  180. package/dist/__tests__/linear-identity.test.js +0 -253
  181. package/dist/__tests__/linear-identity.test.js.map +0 -1
  182. package/dist/__tests__/linear-notify.test.d.ts +0 -2
  183. package/dist/__tests__/linear-notify.test.d.ts.map +0 -1
  184. package/dist/__tests__/linear-notify.test.js +0 -144
  185. package/dist/__tests__/linear-notify.test.js.map +0 -1
  186. package/dist/__tests__/linear-ref.test.d.ts +0 -2
  187. package/dist/__tests__/linear-ref.test.d.ts.map +0 -1
  188. package/dist/__tests__/linear-ref.test.js +0 -261
  189. package/dist/__tests__/linear-ref.test.js.map +0 -1
  190. package/dist/__tests__/linear-test-ref.test.d.ts +0 -2
  191. package/dist/__tests__/linear-test-ref.test.d.ts.map +0 -1
  192. package/dist/__tests__/linear-test-ref.test.js +0 -81
  193. package/dist/__tests__/linear-test-ref.test.js.map +0 -1
  194. package/dist/__tests__/linear-verify.test.d.ts +0 -2
  195. package/dist/__tests__/linear-verify.test.d.ts.map +0 -1
  196. package/dist/__tests__/linear-verify.test.js +0 -132
  197. package/dist/__tests__/linear-verify.test.js.map +0 -1
  198. package/dist/__tests__/linear-worker.test.d.ts +0 -2
  199. package/dist/__tests__/linear-worker.test.d.ts.map +0 -1
  200. package/dist/__tests__/linear-worker.test.js +0 -83
  201. package/dist/__tests__/linear-worker.test.js.map +0 -1
  202. package/dist/__tests__/linear-write-possible.test.d.ts +0 -2
  203. package/dist/__tests__/linear-write-possible.test.d.ts.map +0 -1
  204. package/dist/__tests__/linear-write-possible.test.js +0 -30
  205. package/dist/__tests__/linear-write-possible.test.js.map +0 -1
  206. package/dist/__tests__/repository-guidance.test.d.ts +0 -2
  207. package/dist/__tests__/repository-guidance.test.d.ts.map +0 -1
  208. package/dist/__tests__/repository-guidance.test.js +0 -107
  209. package/dist/__tests__/repository-guidance.test.js.map +0 -1
  210. package/dist/__tests__/review-strategy.test.d.ts +0 -2
  211. package/dist/__tests__/review-strategy.test.d.ts.map +0 -1
  212. package/dist/__tests__/review-strategy.test.js +0 -195
  213. package/dist/__tests__/review-strategy.test.js.map +0 -1
  214. package/dist/__tests__/skill-attribution.test.d.ts +0 -2
  215. package/dist/__tests__/skill-attribution.test.d.ts.map +0 -1
  216. package/dist/__tests__/skill-attribution.test.js +0 -53
  217. package/dist/__tests__/skill-attribution.test.js.map +0 -1
  218. package/dist/__tests__/skill-broker.test.d.ts +0 -2
  219. package/dist/__tests__/skill-broker.test.d.ts.map +0 -1
  220. package/dist/__tests__/skill-broker.test.js +0 -144
  221. package/dist/__tests__/skill-broker.test.js.map +0 -1
  222. package/dist/__tests__/skill-catalog.test.d.ts +0 -2
  223. package/dist/__tests__/skill-catalog.test.d.ts.map +0 -1
  224. package/dist/__tests__/skill-catalog.test.js +0 -40
  225. package/dist/__tests__/skill-catalog.test.js.map +0 -1
  226. package/dist/__tests__/skill-installer.test.d.ts +0 -2
  227. package/dist/__tests__/skill-installer.test.d.ts.map +0 -1
  228. package/dist/__tests__/skill-installer.test.js +0 -96
  229. package/dist/__tests__/skill-installer.test.js.map +0 -1
  230. package/dist/__tests__/skills-config.test.d.ts +0 -2
  231. package/dist/__tests__/skills-config.test.d.ts.map +0 -1
  232. package/dist/__tests__/skills-config.test.js +0 -12
  233. package/dist/__tests__/skills-config.test.js.map +0 -1
  234. package/dist/commands/linear-test.d.ts +0 -18
  235. package/dist/commands/linear-test.d.ts.map +0 -1
  236. package/dist/commands/linear-test.js +0 -130
  237. package/dist/commands/linear-test.js.map +0 -1
  238. package/dist/commands/skill.d.ts +0 -2
  239. package/dist/commands/skill.d.ts.map +0 -1
  240. package/dist/commands/skill.js +0 -16
  241. package/dist/commands/skill.js.map +0 -1
  242. package/dist/config/review-strategy.json +0 -202
  243. package/dist/lib/repository-guidance.d.ts +0 -2
  244. package/dist/lib/repository-guidance.d.ts.map +0 -1
  245. package/dist/lib/repository-guidance.js +0 -55
  246. package/dist/lib/repository-guidance.js.map +0 -1
  247. package/dist/lib/review-strategy.d.ts +0 -62
  248. package/dist/lib/review-strategy.d.ts.map +0 -1
  249. package/dist/lib/review-strategy.js +0 -235
  250. package/dist/lib/review-strategy.js.map +0 -1
  251. package/dist/linear/client.d.ts +0 -18
  252. package/dist/linear/client.d.ts.map +0 -1
  253. package/dist/linear/client.js +0 -67
  254. package/dist/linear/client.js.map +0 -1
  255. package/dist/linear/comment.d.ts +0 -20
  256. package/dist/linear/comment.d.ts.map +0 -1
  257. package/dist/linear/comment.js +0 -57
  258. package/dist/linear/comment.js.map +0 -1
  259. package/dist/linear/identity.d.ts +0 -59
  260. package/dist/linear/identity.d.ts.map +0 -1
  261. package/dist/linear/identity.js +0 -187
  262. package/dist/linear/identity.js.map +0 -1
  263. package/dist/linear/notify.d.ts +0 -35
  264. package/dist/linear/notify.d.ts.map +0 -1
  265. package/dist/linear/notify.js +0 -76
  266. package/dist/linear/notify.js.map +0 -1
  267. package/dist/linear/ref.d.ts +0 -13
  268. package/dist/linear/ref.d.ts.map +0 -1
  269. package/dist/linear/ref.js +0 -90
  270. package/dist/linear/ref.js.map +0 -1
  271. package/dist/linear/verify.d.ts +0 -26
  272. package/dist/linear/verify.d.ts.map +0 -1
  273. package/dist/linear/verify.js +0 -67
  274. package/dist/linear/verify.js.map +0 -1
  275. package/dist/skills/attribution.d.ts +0 -4
  276. package/dist/skills/attribution.d.ts.map +0 -1
  277. package/dist/skills/attribution.js +0 -14
  278. package/dist/skills/attribution.js.map +0 -1
  279. package/dist/skills/broker-server.d.ts +0 -2
  280. package/dist/skills/broker-server.d.ts.map +0 -1
  281. package/dist/skills/broker-server.js +0 -17
  282. package/dist/skills/broker-server.js.map +0 -1
  283. package/dist/skills/broker.d.ts +0 -42
  284. package/dist/skills/broker.d.ts.map +0 -1
  285. package/dist/skills/broker.js +0 -285
  286. package/dist/skills/broker.js.map +0 -1
  287. package/dist/skills/catalog.d.ts +0 -28
  288. package/dist/skills/catalog.d.ts.map +0 -1
  289. package/dist/skills/catalog.js +0 -104
  290. package/dist/skills/catalog.js.map +0 -1
  291. package/dist/skills/installer.d.ts +0 -10
  292. package/dist/skills/installer.d.ts.map +0 -1
  293. package/dist/skills/installer.js +0 -138
  294. package/dist/skills/installer.js.map +0 -1
  295. package/dist/skills/integrity.d.ts +0 -4
  296. package/dist/skills/integrity.d.ts.map +0 -1
  297. package/dist/skills/integrity.js +0 -36
  298. package/dist/skills/integrity.js.map +0 -1
  299. package/docs/dynamic-thoroughness.md +0 -286
  300. package/docs/linear-identity-contract.md +0 -139
  301. 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()