@claude-flow/cli 3.0.0-alpha.2 → 3.0.0-alpha.21

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 (263) hide show
  1. package/README.md +172 -6
  2. package/bin/cli.js +0 -0
  3. package/dist/src/commands/agent.d.ts.map +1 -1
  4. package/dist/src/commands/agent.js +43 -27
  5. package/dist/src/commands/agent.js.map +1 -1
  6. package/dist/src/commands/analyze.d.ts +19 -0
  7. package/dist/src/commands/analyze.d.ts.map +1 -0
  8. package/dist/src/commands/analyze.js +1823 -0
  9. package/dist/src/commands/analyze.js.map +1 -0
  10. package/dist/src/commands/claims.d.ts +10 -0
  11. package/dist/src/commands/claims.d.ts.map +1 -0
  12. package/dist/src/commands/claims.js +288 -0
  13. package/dist/src/commands/claims.js.map +1 -0
  14. package/dist/src/commands/completions.d.ts +10 -0
  15. package/dist/src/commands/completions.d.ts.map +1 -0
  16. package/dist/src/commands/completions.js +539 -0
  17. package/dist/src/commands/completions.js.map +1 -0
  18. package/dist/src/commands/config.js +2 -2
  19. package/dist/src/commands/config.js.map +1 -1
  20. package/dist/src/commands/daemon.d.ts +8 -0
  21. package/dist/src/commands/daemon.d.ts.map +1 -0
  22. package/dist/src/commands/daemon.js +545 -0
  23. package/dist/src/commands/daemon.js.map +1 -0
  24. package/dist/src/commands/deployment.d.ts +10 -0
  25. package/dist/src/commands/deployment.d.ts.map +1 -0
  26. package/dist/src/commands/deployment.js +289 -0
  27. package/dist/src/commands/deployment.js.map +1 -0
  28. package/dist/src/commands/doctor.d.ts +10 -0
  29. package/dist/src/commands/doctor.d.ts.map +1 -0
  30. package/dist/src/commands/doctor.js +429 -0
  31. package/dist/src/commands/doctor.js.map +1 -0
  32. package/dist/src/commands/embeddings.d.ts +18 -0
  33. package/dist/src/commands/embeddings.d.ts.map +1 -0
  34. package/dist/src/commands/embeddings.js +616 -0
  35. package/dist/src/commands/embeddings.js.map +1 -0
  36. package/dist/src/commands/hive-mind.d.ts.map +1 -1
  37. package/dist/src/commands/hive-mind.js +252 -35
  38. package/dist/src/commands/hive-mind.js.map +1 -1
  39. package/dist/src/commands/hooks.d.ts.map +1 -1
  40. package/dist/src/commands/hooks.js +326 -2
  41. package/dist/src/commands/hooks.js.map +1 -1
  42. package/dist/src/commands/index.d.ts +13 -0
  43. package/dist/src/commands/index.d.ts.map +1 -1
  44. package/dist/src/commands/index.js +52 -1
  45. package/dist/src/commands/index.js.map +1 -1
  46. package/dist/src/commands/mcp.js +4 -4
  47. package/dist/src/commands/mcp.js.map +1 -1
  48. package/dist/src/commands/memory.d.ts.map +1 -1
  49. package/dist/src/commands/memory.js +236 -170
  50. package/dist/src/commands/memory.js.map +1 -1
  51. package/dist/src/commands/migrate.js +1 -1
  52. package/dist/src/commands/migrate.js.map +1 -1
  53. package/dist/src/commands/neural.d.ts +10 -0
  54. package/dist/src/commands/neural.d.ts.map +1 -0
  55. package/dist/src/commands/neural.js +224 -0
  56. package/dist/src/commands/neural.js.map +1 -0
  57. package/dist/src/commands/performance.d.ts +10 -0
  58. package/dist/src/commands/performance.d.ts.map +1 -0
  59. package/dist/src/commands/performance.js +262 -0
  60. package/dist/src/commands/performance.js.map +1 -0
  61. package/dist/src/commands/plugins.d.ts +10 -0
  62. package/dist/src/commands/plugins.d.ts.map +1 -0
  63. package/dist/src/commands/plugins.js +280 -0
  64. package/dist/src/commands/plugins.js.map +1 -0
  65. package/dist/src/commands/process.d.ts.map +1 -1
  66. package/dist/src/commands/process.js +95 -20
  67. package/dist/src/commands/process.js.map +1 -1
  68. package/dist/src/commands/providers.d.ts +10 -0
  69. package/dist/src/commands/providers.d.ts.map +1 -0
  70. package/dist/src/commands/providers.js +232 -0
  71. package/dist/src/commands/providers.js.map +1 -0
  72. package/dist/src/commands/route.d.ts +16 -0
  73. package/dist/src/commands/route.d.ts.map +1 -0
  74. package/dist/src/commands/route.js +603 -0
  75. package/dist/src/commands/route.js.map +1 -0
  76. package/dist/src/commands/security.d.ts +10 -0
  77. package/dist/src/commands/security.d.ts.map +1 -0
  78. package/dist/src/commands/security.js +261 -0
  79. package/dist/src/commands/security.js.map +1 -0
  80. package/dist/src/commands/start.js +2 -2
  81. package/dist/src/commands/start.js.map +1 -1
  82. package/dist/src/commands/status.d.ts.map +1 -1
  83. package/dist/src/commands/status.js +26 -2
  84. package/dist/src/commands/status.js.map +1 -1
  85. package/dist/src/commands/swarm.js +6 -6
  86. package/dist/src/commands/swarm.js.map +1 -1
  87. package/dist/src/index.d.ts +4 -2
  88. package/dist/src/index.d.ts.map +1 -1
  89. package/dist/src/index.js +63 -5
  90. package/dist/src/index.js.map +1 -1
  91. package/dist/src/init/claudemd-generator.d.ts.map +1 -1
  92. package/dist/src/init/claudemd-generator.js +218 -362
  93. package/dist/src/init/claudemd-generator.js.map +1 -1
  94. package/dist/src/init/executor.d.ts.map +1 -1
  95. package/dist/src/init/executor.js +5 -0
  96. package/dist/src/init/executor.js.map +1 -1
  97. package/dist/src/init/settings-generator.d.ts.map +1 -1
  98. package/dist/src/init/settings-generator.js +22 -12
  99. package/dist/src/init/settings-generator.js.map +1 -1
  100. package/dist/src/mcp-client.d.ts.map +1 -1
  101. package/dist/src/mcp-client.js +17 -1
  102. package/dist/src/mcp-client.js.map +1 -1
  103. package/dist/src/mcp-server.d.ts.map +1 -1
  104. package/dist/src/mcp-server.js +5 -0
  105. package/dist/src/mcp-server.js.map +1 -1
  106. package/dist/src/mcp-tools/agent-tools.d.ts +1 -1
  107. package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
  108. package/dist/src/mcp-tools/agent-tools.js +350 -14
  109. package/dist/src/mcp-tools/agent-tools.js.map +1 -1
  110. package/dist/src/mcp-tools/analyze-tools.d.ts +38 -0
  111. package/dist/src/mcp-tools/analyze-tools.d.ts.map +1 -0
  112. package/dist/src/mcp-tools/analyze-tools.js +317 -0
  113. package/dist/src/mcp-tools/analyze-tools.js.map +1 -0
  114. package/dist/src/mcp-tools/config-tools.d.ts +1 -1
  115. package/dist/src/mcp-tools/config-tools.d.ts.map +1 -1
  116. package/dist/src/mcp-tools/config-tools.js +262 -15
  117. package/dist/src/mcp-tools/config-tools.js.map +1 -1
  118. package/dist/src/mcp-tools/hive-mind-tools.d.ts +8 -0
  119. package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -0
  120. package/dist/src/mcp-tools/hive-mind-tools.js +447 -0
  121. package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -0
  122. package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
  123. package/dist/src/mcp-tools/hooks-tools.js +80 -15
  124. package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
  125. package/dist/src/mcp-tools/index.d.ts +6 -0
  126. package/dist/src/mcp-tools/index.d.ts.map +1 -1
  127. package/dist/src/mcp-tools/index.js +6 -0
  128. package/dist/src/mcp-tools/index.js.map +1 -1
  129. package/dist/src/mcp-tools/memory-tools.d.ts +1 -1
  130. package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
  131. package/dist/src/mcp-tools/memory-tools.js +157 -9
  132. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  133. package/dist/src/mcp-tools/session-tools.d.ts +8 -0
  134. package/dist/src/mcp-tools/session-tools.d.ts.map +1 -0
  135. package/dist/src/mcp-tools/session-tools.js +315 -0
  136. package/dist/src/mcp-tools/session-tools.js.map +1 -0
  137. package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
  138. package/dist/src/mcp-tools/swarm-tools.js +37 -2
  139. package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
  140. package/dist/src/mcp-tools/task-tools.d.ts +8 -0
  141. package/dist/src/mcp-tools/task-tools.d.ts.map +1 -0
  142. package/dist/src/mcp-tools/task-tools.js +302 -0
  143. package/dist/src/mcp-tools/task-tools.js.map +1 -0
  144. package/dist/src/mcp-tools/workflow-tools.d.ts +8 -0
  145. package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -0
  146. package/dist/src/mcp-tools/workflow-tools.js +481 -0
  147. package/dist/src/mcp-tools/workflow-tools.js.map +1 -0
  148. package/dist/src/output.d.ts +16 -0
  149. package/dist/src/output.d.ts.map +1 -1
  150. package/dist/src/output.js +42 -0
  151. package/dist/src/output.js.map +1 -1
  152. package/dist/src/ruvector/ast-analyzer.d.ts +67 -0
  153. package/dist/src/ruvector/ast-analyzer.d.ts.map +1 -0
  154. package/dist/src/ruvector/ast-analyzer.js +277 -0
  155. package/dist/src/ruvector/ast-analyzer.js.map +1 -0
  156. package/dist/src/ruvector/coverage-router.d.ts +145 -0
  157. package/dist/src/ruvector/coverage-router.d.ts.map +1 -0
  158. package/dist/src/ruvector/coverage-router.js +451 -0
  159. package/dist/src/ruvector/coverage-router.js.map +1 -0
  160. package/dist/src/ruvector/coverage-tools.d.ts +33 -0
  161. package/dist/src/ruvector/coverage-tools.d.ts.map +1 -0
  162. package/dist/src/ruvector/coverage-tools.js +157 -0
  163. package/dist/src/ruvector/coverage-tools.js.map +1 -0
  164. package/dist/src/ruvector/diff-classifier.d.ts +175 -0
  165. package/dist/src/ruvector/diff-classifier.d.ts.map +1 -0
  166. package/dist/src/ruvector/diff-classifier.js +662 -0
  167. package/dist/src/ruvector/diff-classifier.js.map +1 -0
  168. package/dist/src/ruvector/graph-analyzer.d.ts +174 -0
  169. package/dist/src/ruvector/graph-analyzer.d.ts.map +1 -0
  170. package/dist/src/ruvector/graph-analyzer.js +878 -0
  171. package/dist/src/ruvector/graph-analyzer.js.map +1 -0
  172. package/dist/src/ruvector/index.d.ts +27 -0
  173. package/dist/src/ruvector/index.d.ts.map +1 -0
  174. package/dist/src/ruvector/index.js +49 -0
  175. package/dist/src/ruvector/index.js.map +1 -0
  176. package/dist/src/ruvector/q-learning-router.d.ts +211 -0
  177. package/dist/src/ruvector/q-learning-router.d.ts.map +1 -0
  178. package/dist/src/ruvector/q-learning-router.js +681 -0
  179. package/dist/src/ruvector/q-learning-router.js.map +1 -0
  180. package/dist/src/ruvector/vector-db.d.ts +69 -0
  181. package/dist/src/ruvector/vector-db.d.ts.map +1 -0
  182. package/dist/src/ruvector/vector-db.js +243 -0
  183. package/dist/src/ruvector/vector-db.js.map +1 -0
  184. package/dist/src/services/index.d.ts +7 -0
  185. package/dist/src/services/index.d.ts.map +1 -0
  186. package/dist/src/services/index.js +6 -0
  187. package/dist/src/services/index.js.map +1 -0
  188. package/dist/src/services/worker-daemon.d.ts +153 -0
  189. package/dist/src/services/worker-daemon.d.ts.map +1 -0
  190. package/dist/src/services/worker-daemon.js +567 -0
  191. package/dist/src/services/worker-daemon.js.map +1 -0
  192. package/dist/src/suggest.d.ts +53 -0
  193. package/dist/src/suggest.d.ts.map +1 -0
  194. package/dist/src/suggest.js +200 -0
  195. package/dist/src/suggest.js.map +1 -0
  196. package/dist/tsconfig.tsbuildinfo +1 -1
  197. package/package.json +28 -6
  198. package/.agentic-flow/intelligence.json +0 -16
  199. package/.claude-flow/metrics/agent-metrics.json +0 -1
  200. package/.claude-flow/metrics/performance.json +0 -87
  201. package/.claude-flow/metrics/task-metrics.json +0 -10
  202. package/__tests__/README.md +0 -140
  203. package/__tests__/TEST_SUMMARY.md +0 -144
  204. package/__tests__/cli.test.ts +0 -558
  205. package/__tests__/commands.test.ts +0 -726
  206. package/__tests__/config-adapter.test.ts +0 -362
  207. package/__tests__/config-loading.test.ts +0 -106
  208. package/__tests__/coverage/.tmp/coverage-0.json +0 -1
  209. package/__tests__/coverage/.tmp/coverage-1.json +0 -1
  210. package/__tests__/coverage/.tmp/coverage-2.json +0 -1
  211. package/__tests__/coverage/.tmp/coverage-3.json +0 -1
  212. package/__tests__/coverage/.tmp/coverage-4.json +0 -1
  213. package/__tests__/coverage/.tmp/coverage-5.json +0 -1
  214. package/__tests__/mcp-client.test.ts +0 -480
  215. package/__tests__/p1-commands.test.ts +0 -1064
  216. package/docs/CONFIG_LOADING.md +0 -236
  217. package/docs/IMPLEMENTATION_COMPLETE.md +0 -421
  218. package/docs/MCP_CLIENT_GUIDE.md +0 -620
  219. package/docs/REFACTORING_SUMMARY.md +0 -247
  220. package/src/commands/agent.ts +0 -941
  221. package/src/commands/config.ts +0 -452
  222. package/src/commands/hive-mind.ts +0 -762
  223. package/src/commands/hooks.ts +0 -2603
  224. package/src/commands/index.ts +0 -115
  225. package/src/commands/init.ts +0 -597
  226. package/src/commands/mcp.ts +0 -753
  227. package/src/commands/memory.ts +0 -1063
  228. package/src/commands/migrate.ts +0 -447
  229. package/src/commands/process.ts +0 -617
  230. package/src/commands/session.ts +0 -891
  231. package/src/commands/start.ts +0 -457
  232. package/src/commands/status.ts +0 -705
  233. package/src/commands/swarm.ts +0 -648
  234. package/src/commands/task.ts +0 -792
  235. package/src/commands/workflow.ts +0 -742
  236. package/src/config-adapter.ts +0 -210
  237. package/src/index.ts +0 -383
  238. package/src/infrastructure/in-memory-repositories.ts +0 -310
  239. package/src/init/claudemd-generator.ts +0 -631
  240. package/src/init/executor.ts +0 -756
  241. package/src/init/helpers-generator.ts +0 -628
  242. package/src/init/index.ts +0 -60
  243. package/src/init/mcp-generator.ts +0 -83
  244. package/src/init/settings-generator.ts +0 -274
  245. package/src/init/statusline-generator.ts +0 -211
  246. package/src/init/types.ts +0 -447
  247. package/src/mcp-client.ts +0 -227
  248. package/src/mcp-server.ts +0 -571
  249. package/src/mcp-tools/agent-tools.ts +0 -92
  250. package/src/mcp-tools/config-tools.ts +0 -88
  251. package/src/mcp-tools/hooks-tools.ts +0 -1819
  252. package/src/mcp-tools/index.ts +0 -12
  253. package/src/mcp-tools/memory-tools.ts +0 -89
  254. package/src/mcp-tools/swarm-tools.ts +0 -69
  255. package/src/mcp-tools/types.ts +0 -33
  256. package/src/output.ts +0 -593
  257. package/src/parser.ts +0 -417
  258. package/src/prompt.ts +0 -619
  259. package/src/types.ts +0 -287
  260. package/tmp.json +0 -0
  261. package/tsconfig.json +0 -16
  262. package/tsconfig.tsbuildinfo +0 -1
  263. package/vitest.config.ts +0 -13
@@ -0,0 +1,662 @@
1
+ /**
2
+ * Diff Classifier for Change Analysis
3
+ */
4
+ const DEFAULT_CONFIG = {
5
+ maxDiffSize: 10000,
6
+ classifyByImpact: true,
7
+ detectRefactoring: true,
8
+ minConfidence: 0.5,
9
+ };
10
+ const CLASSIFICATION_PATTERNS = {
11
+ feature: [/^feat/, /add.*feature/, /implement/, /new.*functionality/i],
12
+ bugfix: [/^fix/, /bug/, /patch/, /resolve.*issue/i, /hotfix/i],
13
+ refactor: [/^refactor/, /restructure/, /reorganize/, /cleanup/i, /rename/i],
14
+ docs: [/^docs?/, /documentation/, /readme/i, /comment/i, /\.md$/i],
15
+ test: [/^test/, /spec/, /\.test\.[jt]sx?$/, /\.spec\.[jt]sx?$/, /__tests__/],
16
+ config: [/^config/, /\.config\./, /package\.json/, /tsconfig/, /\.env/],
17
+ style: [/^style/, /format/, /lint/, /prettier/, /eslint/],
18
+ };
19
+ const IMPACT_KEYWORDS = {
20
+ security: 3, auth: 3, payment: 3, database: 2, api: 2, core: 2,
21
+ util: 1, helper: 1, test: 0, mock: 0, fixture: 0,
22
+ };
23
+ export class DiffClassifier {
24
+ config;
25
+ ruvectorEngine = null;
26
+ useNative = false;
27
+ classificationCache = new Map();
28
+ constructor(config = {}) {
29
+ this.config = { ...DEFAULT_CONFIG, ...config };
30
+ }
31
+ async initialize() {
32
+ try {
33
+ // @ruvector/diff is optional - gracefully fallback if not installed
34
+ const ruvector = await import('@ruvector/diff').catch(() => null);
35
+ if (ruvector) {
36
+ this.ruvectorEngine = ruvector.createDiffClassifier?.(this.config);
37
+ this.useNative = !!this.ruvectorEngine;
38
+ }
39
+ }
40
+ catch {
41
+ this.useNative = false;
42
+ }
43
+ }
44
+ parseDiff(diffContent) {
45
+ const files = [];
46
+ const fileBlocks = diffContent.split(/^diff --git/m).filter(Boolean);
47
+ for (const block of fileBlocks) {
48
+ const pathMatch = block.match(/a\/(.+?)\s+b\/(.+)/);
49
+ if (!pathMatch)
50
+ continue;
51
+ const path = pathMatch[2];
52
+ const hunks = this.parseHunks(block);
53
+ const additions = hunks.reduce((sum, h) => sum + h.changes.filter(c => c.type === 'add').length, 0);
54
+ const deletions = hunks.reduce((sum, h) => sum + h.changes.filter(c => c.type === 'remove').length, 0);
55
+ const classification = this.classifyFile(path, hunks);
56
+ files.push({ path, hunks, additions, deletions, classification });
57
+ }
58
+ return files;
59
+ }
60
+ classify(files) {
61
+ const overall = this.computeOverallClassification(files);
62
+ const stats = {
63
+ totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
64
+ totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),
65
+ filesChanged: files.length,
66
+ avgConfidence: files.length > 0 ? files.reduce((sum, f) => sum + f.classification.confidence, 0) / files.length : 0,
67
+ };
68
+ return { files, overall, stats, timestamp: Date.now() };
69
+ }
70
+ classifyCommitMessage(message) {
71
+ const lowerMessage = message.toLowerCase();
72
+ for (const [type, patterns] of Object.entries(CLASSIFICATION_PATTERNS)) {
73
+ for (const pattern of patterns) {
74
+ if (pattern.test(lowerMessage))
75
+ return type;
76
+ }
77
+ }
78
+ return 'unknown';
79
+ }
80
+ getStats() {
81
+ return { useNative: this.useNative, cacheSize: this.classificationCache.size };
82
+ }
83
+ clearCache() { this.classificationCache.clear(); }
84
+ parseHunks(block) {
85
+ const hunks = [];
86
+ const hunkMatches = block.matchAll(/@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@([^\n]*)\n([\s\S]*?)(?=@@|$)/g);
87
+ for (const match of hunkMatches) {
88
+ const oldStart = parseInt(match[1], 10);
89
+ const oldLines = parseInt(match[2] || '1', 10);
90
+ const newStart = parseInt(match[3], 10);
91
+ const newLines = parseInt(match[4] || '1', 10);
92
+ const content = match[6] || '';
93
+ const changes = this.parseChanges(content, newStart);
94
+ hunks.push({ oldStart, oldLines, newStart, newLines, content, changes });
95
+ }
96
+ return hunks;
97
+ }
98
+ parseChanges(content, startLine) {
99
+ const changes = [];
100
+ const lines = content.split('\n');
101
+ let lineNumber = startLine;
102
+ for (const line of lines) {
103
+ if (line.startsWith('+')) {
104
+ changes.push({ type: 'add', lineNumber, content: line.substring(1) });
105
+ lineNumber++;
106
+ }
107
+ else if (line.startsWith('-')) {
108
+ changes.push({ type: 'remove', lineNumber: -1, content: line.substring(1) });
109
+ }
110
+ else if (line.startsWith(' ') || line === '') {
111
+ changes.push({ type: 'context', lineNumber, content: line.substring(1) || '' });
112
+ lineNumber++;
113
+ }
114
+ }
115
+ return changes;
116
+ }
117
+ classifyFile(path, hunks) {
118
+ const cacheKey = this.getCacheKey(path, hunks);
119
+ const cached = this.classificationCache.get(cacheKey);
120
+ if (cached)
121
+ return cached;
122
+ const primary = this.determinePrimaryClassification(path, hunks);
123
+ const secondary = this.determineSecondaryClassifications(path, hunks, primary);
124
+ const confidence = this.calculateConfidence(path, hunks, primary);
125
+ const impactLevel = this.determineImpactLevel(path, hunks);
126
+ const suggestedReviewers = this.suggestReviewers(path, primary, impactLevel);
127
+ const testingStrategy = this.determineTestingStrategy(path, primary, impactLevel);
128
+ const riskFactors = this.identifyRiskFactors(path, hunks, impactLevel);
129
+ const classification = { primary, secondary, confidence, impactLevel, suggestedReviewers, testingStrategy, riskFactors };
130
+ this.classificationCache.set(cacheKey, classification);
131
+ return classification;
132
+ }
133
+ getCacheKey(path, hunks) {
134
+ const hunkSummary = hunks.map(h => h.oldStart + ':' + h.newStart).join(',');
135
+ return path + ':' + hunkSummary;
136
+ }
137
+ determinePrimaryClassification(path, hunks) {
138
+ for (const [type, patterns] of Object.entries(CLASSIFICATION_PATTERNS)) {
139
+ for (const pattern of patterns) {
140
+ if (pattern.test(path))
141
+ return type;
142
+ }
143
+ }
144
+ const allContent = hunks.flatMap(h => h.changes.map(c => c.content)).join('\n').toLowerCase();
145
+ if (/function|class|interface|type\s+\w+/.test(allContent) && hunks.some(h => h.changes.filter(c => c.type === 'add').length > 10))
146
+ return 'feature';
147
+ if (/fix|bug|issue|error|exception/.test(allContent))
148
+ return 'bugfix';
149
+ if (this.config.detectRefactoring && this.isRefactoring(hunks))
150
+ return 'refactor';
151
+ return 'unknown';
152
+ }
153
+ isRefactoring(hunks) {
154
+ let totalAdds = 0, totalRemoves = 0;
155
+ for (const hunk of hunks) {
156
+ for (const change of hunk.changes) {
157
+ if (change.type === 'add')
158
+ totalAdds++;
159
+ else if (change.type === 'remove')
160
+ totalRemoves++;
161
+ }
162
+ }
163
+ const ratio = totalAdds > 0 ? totalRemoves / totalAdds : 0;
164
+ return ratio > 0.7 && ratio < 1.4 && totalAdds > 5;
165
+ }
166
+ determineSecondaryClassifications(path, hunks, primary) {
167
+ const secondary = [];
168
+ for (const [type, patterns] of Object.entries(CLASSIFICATION_PATTERNS)) {
169
+ if (type === primary)
170
+ continue;
171
+ for (const pattern of patterns) {
172
+ if (pattern.test(path)) {
173
+ secondary.push(type);
174
+ break;
175
+ }
176
+ }
177
+ }
178
+ return secondary.slice(0, 3);
179
+ }
180
+ calculateConfidence(path, hunks, primary) {
181
+ let confidence = 0.5;
182
+ for (const patterns of Object.values(CLASSIFICATION_PATTERNS)) {
183
+ for (const pattern of patterns) {
184
+ if (pattern.test(path)) {
185
+ confidence += 0.2;
186
+ break;
187
+ }
188
+ }
189
+ }
190
+ const totalChanges = hunks.reduce((sum, h) => sum + h.changes.length, 0);
191
+ if (totalChanges > 10)
192
+ confidence += 0.1;
193
+ if (totalChanges > 50)
194
+ confidence += 0.1;
195
+ if (primary !== 'unknown')
196
+ confidence += 0.1;
197
+ return Math.min(1, confidence);
198
+ }
199
+ determineImpactLevel(path, hunks) {
200
+ let score = 0;
201
+ const lowerPath = path.toLowerCase();
202
+ for (const [keyword, weight] of Object.entries(IMPACT_KEYWORDS)) {
203
+ if (lowerPath.includes(keyword))
204
+ score = Math.max(score, weight);
205
+ }
206
+ const totalChanges = hunks.reduce((sum, h) => sum + h.changes.filter(c => c.type !== 'context').length, 0);
207
+ if (totalChanges > 100)
208
+ score = Math.max(score, 2);
209
+ if (totalChanges > 300)
210
+ score = Math.max(score, 3);
211
+ if (score >= 3)
212
+ return 'critical';
213
+ if (score >= 2)
214
+ return 'high';
215
+ if (score >= 1)
216
+ return 'medium';
217
+ return 'low';
218
+ }
219
+ suggestReviewers(path, primary, impact) {
220
+ const reviewers = [];
221
+ const typeReviewers = { feature: ['tech-lead', 'product-owner'], bugfix: ['qa-engineer', 'developer'], refactor: ['senior-developer', 'architect'], docs: ['tech-writer', 'developer'], test: ['qa-engineer', 'developer'], config: ['devops', 'tech-lead'], style: ['developer'], unknown: ['developer'] };
222
+ reviewers.push(...(typeReviewers[primary] || typeReviewers.unknown));
223
+ if (impact === 'critical' || impact === 'high')
224
+ reviewers.push('security-reviewer');
225
+ if (/security|auth/.test(path))
226
+ reviewers.push('security-team');
227
+ if (/database|migration/.test(path))
228
+ reviewers.push('dba');
229
+ return [...new Set(reviewers)].slice(0, 4);
230
+ }
231
+ determineTestingStrategy(path, primary, impact) {
232
+ const strategies = [];
233
+ if (primary !== 'test')
234
+ strategies.push('unit-tests');
235
+ if (primary === 'feature')
236
+ strategies.push('integration-tests');
237
+ if (impact === 'high' || impact === 'critical') {
238
+ strategies.push('regression-tests');
239
+ strategies.push('e2e-tests');
240
+ }
241
+ if (/api|endpoint|route|handler/.test(path))
242
+ strategies.push('api-contract-tests');
243
+ if (/security|auth|crypto/.test(path))
244
+ strategies.push('security-audit');
245
+ return strategies.slice(0, 5);
246
+ }
247
+ identifyRiskFactors(path, hunks, impact) {
248
+ const risks = [];
249
+ const totalChanges = hunks.reduce((sum, h) => sum + h.changes.length, 0);
250
+ if (totalChanges > 200)
251
+ risks.push('Large change set - increased review time needed');
252
+ if (impact === 'critical')
253
+ risks.push('Critical system component - requires careful review');
254
+ if (impact === 'high')
255
+ risks.push('High-impact area - monitor after deployment');
256
+ if (/security|auth/.test(path))
257
+ risks.push('Security-sensitive code');
258
+ if (/database|migration/.test(path))
259
+ risks.push('Database changes - ensure backup strategy');
260
+ if (/config|env/.test(path))
261
+ risks.push('Configuration changes - verify all environments');
262
+ const allContent = hunks.flatMap(h => h.changes.map(c => c.content)).join('\n');
263
+ if (/TODO|FIXME|HACK/.test(allContent))
264
+ risks.push('Contains TODO/FIXME comments');
265
+ if (/password|secret|key|token/i.test(allContent))
266
+ risks.push('Potential secrets in code');
267
+ return risks.slice(0, 5);
268
+ }
269
+ computeOverallClassification(files) {
270
+ if (files.length === 0)
271
+ return { primary: 'unknown', secondary: [], confidence: 0, impactLevel: 'low', suggestedReviewers: [], testingStrategy: [], riskFactors: [] };
272
+ const primaryCounts = {};
273
+ for (const file of files) {
274
+ const p = file.classification.primary;
275
+ primaryCounts[p] = (primaryCounts[p] || 0) + 1;
276
+ }
277
+ let primary = 'unknown';
278
+ let maxCount = 0;
279
+ for (const [type, count] of Object.entries(primaryCounts)) {
280
+ if (count > maxCount) {
281
+ maxCount = count;
282
+ primary = type;
283
+ }
284
+ }
285
+ const secondaryCounts = {};
286
+ for (const file of files) {
287
+ for (const s of file.classification.secondary) {
288
+ secondaryCounts[s] = (secondaryCounts[s] || 0) + 1;
289
+ }
290
+ }
291
+ const secondary = Object.entries(secondaryCounts).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([type]) => type);
292
+ const confidence = files.reduce((sum, f) => sum + f.classification.confidence, 0) / files.length;
293
+ const impactOrder = ['low', 'medium', 'high', 'critical'];
294
+ let impactLevel = 'low';
295
+ for (const file of files) {
296
+ if (impactOrder.indexOf(file.classification.impactLevel) > impactOrder.indexOf(impactLevel))
297
+ impactLevel = file.classification.impactLevel;
298
+ }
299
+ const reviewers = [...new Set(files.flatMap(f => f.classification.suggestedReviewers))].slice(0, 5);
300
+ const testingStrategy = [...new Set(files.flatMap(f => f.classification.testingStrategy))].slice(0, 5);
301
+ const riskFactors = [...new Set(files.flatMap(f => f.classification.riskFactors))].slice(0, 5);
302
+ return { primary, secondary, confidence, impactLevel, suggestedReviewers: reviewers, testingStrategy, riskFactors };
303
+ }
304
+ }
305
+ export function createDiffClassifier(config) {
306
+ return new DiffClassifier(config);
307
+ }
308
+ // ============================================================================
309
+ // Optimized Git Diff Functions
310
+ // ============================================================================
311
+ // Cache for diff results (TTL-based)
312
+ const diffCache = new Map();
313
+ const CACHE_TTL_MS = 5000; // 5 seconds - short TTL since diffs change frequently
314
+ /**
315
+ * Get git diff statistics using SINGLE combined command (optimized)
316
+ * Replaces two separate git commands with one
317
+ */
318
+ export function getGitDiffNumstat(ref = 'HEAD') {
319
+ // Check cache first
320
+ const cacheKey = `numstat:${ref}`;
321
+ const cached = diffCache.get(cacheKey);
322
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
323
+ return cached.files;
324
+ }
325
+ const { execSync } = require('child_process');
326
+ try {
327
+ // OPTIMIZATION: Single combined command instead of two separate calls
328
+ // Uses --diff-filter to get status and --numstat in one pass
329
+ const output = execSync(`git diff --numstat --diff-filter=ACDMRTUXB ${ref} && echo "---STATUS---" && git diff --name-status ${ref}`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 } // 10MB buffer for large diffs
330
+ );
331
+ const [numstatPart, statusPart] = output.split('---STATUS---');
332
+ // Parse status (usually smaller, parse first)
333
+ const statusMap = new Map();
334
+ if (statusPart) {
335
+ for (const line of statusPart.trim().split('\n')) {
336
+ if (!line)
337
+ continue;
338
+ const [status, ...pathParts] = line.split('\t');
339
+ const path = pathParts[pathParts.length - 1] || pathParts[0];
340
+ if (path)
341
+ statusMap.set(path, status.charAt(0));
342
+ }
343
+ }
344
+ // Parse numstat
345
+ const files = [];
346
+ if (numstatPart) {
347
+ for (const line of numstatPart.trim().split('\n')) {
348
+ if (!line)
349
+ continue;
350
+ const [addStr, delStr, path] = line.split('\t');
351
+ if (!path)
352
+ continue;
353
+ const additions = addStr === '-' ? 0 : parseInt(addStr, 10) || 0;
354
+ const deletions = delStr === '-' ? 0 : parseInt(delStr, 10) || 0;
355
+ const binary = addStr === '-' && delStr === '-';
356
+ const statusChar = statusMap.get(path) || 'M';
357
+ let status = 'modified';
358
+ switch (statusChar) {
359
+ case 'A':
360
+ status = 'added';
361
+ break;
362
+ case 'D':
363
+ status = 'deleted';
364
+ break;
365
+ case 'R':
366
+ status = 'renamed';
367
+ break;
368
+ default: status = 'modified';
369
+ }
370
+ files.push({ path, status, additions, deletions, hunks: 1, binary });
371
+ }
372
+ }
373
+ // Cache the result
374
+ diffCache.set(cacheKey, { files, timestamp: Date.now() });
375
+ return files;
376
+ }
377
+ catch {
378
+ return [];
379
+ }
380
+ }
381
+ /**
382
+ * Async version of getGitDiffNumstat for non-blocking operation
383
+ */
384
+ export async function getGitDiffNumstatAsync(ref = 'HEAD') {
385
+ // Check cache first
386
+ const cacheKey = `numstat:${ref}`;
387
+ const cached = diffCache.get(cacheKey);
388
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
389
+ return cached.files;
390
+ }
391
+ const { exec } = require('child_process');
392
+ const { promisify } = require('util');
393
+ const execAsync = promisify(exec);
394
+ try {
395
+ const { stdout } = await execAsync(`git diff --numstat --diff-filter=ACDMRTUXB ${ref} && echo "---STATUS---" && git diff --name-status ${ref}`, { maxBuffer: 10 * 1024 * 1024 });
396
+ const [numstatPart, statusPart] = stdout.split('---STATUS---');
397
+ const statusMap = new Map();
398
+ if (statusPart) {
399
+ for (const line of statusPart.trim().split('\n')) {
400
+ if (!line)
401
+ continue;
402
+ const [status, ...pathParts] = line.split('\t');
403
+ const path = pathParts[pathParts.length - 1] || pathParts[0];
404
+ if (path)
405
+ statusMap.set(path, status.charAt(0));
406
+ }
407
+ }
408
+ const files = [];
409
+ if (numstatPart) {
410
+ for (const line of numstatPart.trim().split('\n')) {
411
+ if (!line)
412
+ continue;
413
+ const [addStr, delStr, path] = line.split('\t');
414
+ if (!path)
415
+ continue;
416
+ const additions = addStr === '-' ? 0 : parseInt(addStr, 10) || 0;
417
+ const deletions = delStr === '-' ? 0 : parseInt(delStr, 10) || 0;
418
+ const binary = addStr === '-' && delStr === '-';
419
+ const statusChar = statusMap.get(path) || 'M';
420
+ let status = 'modified';
421
+ switch (statusChar) {
422
+ case 'A':
423
+ status = 'added';
424
+ break;
425
+ case 'D':
426
+ status = 'deleted';
427
+ break;
428
+ case 'R':
429
+ status = 'renamed';
430
+ break;
431
+ default: status = 'modified';
432
+ }
433
+ files.push({ path, status, additions, deletions, hunks: 1, binary });
434
+ }
435
+ }
436
+ diffCache.set(cacheKey, { files, timestamp: Date.now() });
437
+ return files;
438
+ }
439
+ catch {
440
+ return [];
441
+ }
442
+ }
443
+ /**
444
+ * Clear the diff cache (call when git state changes)
445
+ */
446
+ export function clearDiffCache() {
447
+ diffCache.clear();
448
+ }
449
+ /**
450
+ * Assess risk for a single file
451
+ */
452
+ export function assessFileRisk(file) {
453
+ const reasons = [];
454
+ let score = 0;
455
+ // Size-based risk
456
+ const totalChanges = file.additions + file.deletions;
457
+ if (totalChanges > 300) {
458
+ score += 30;
459
+ reasons.push('Large change size (>300 lines)');
460
+ }
461
+ else if (totalChanges > 100) {
462
+ score += 15;
463
+ reasons.push('Medium change size (>100 lines)');
464
+ }
465
+ // Path-based risk
466
+ const lowerPath = file.path.toLowerCase();
467
+ if (/security|auth|crypto|password/.test(lowerPath)) {
468
+ score += 40;
469
+ reasons.push('Security-sensitive file');
470
+ }
471
+ if (/payment|billing|transaction/.test(lowerPath)) {
472
+ score += 35;
473
+ reasons.push('Payment-related file');
474
+ }
475
+ if (/database|migration|schema/.test(lowerPath)) {
476
+ score += 25;
477
+ reasons.push('Database-related file');
478
+ }
479
+ if (/core|main|index/.test(lowerPath)) {
480
+ score += 15;
481
+ reasons.push('Core module');
482
+ }
483
+ if (/config|env|settings/.test(lowerPath)) {
484
+ score += 20;
485
+ reasons.push('Configuration file');
486
+ }
487
+ // Status-based risk
488
+ if (file.status === 'deleted') {
489
+ score += 10;
490
+ reasons.push('File deleted');
491
+ }
492
+ // Binary file risk
493
+ if (file.binary) {
494
+ score += 5;
495
+ reasons.push('Binary file');
496
+ }
497
+ let risk = 'low';
498
+ if (score >= 60)
499
+ risk = 'critical';
500
+ else if (score >= 40)
501
+ risk = 'high';
502
+ else if (score >= 20)
503
+ risk = 'medium';
504
+ return { file: file.path, risk, score: Math.min(100, score), reasons };
505
+ }
506
+ /**
507
+ * Assess overall risk from files and file risks
508
+ */
509
+ export function assessOverallRisk(files, fileRisks) {
510
+ const breakdown = { low: 0, medium: 0, high: 0, critical: 0 };
511
+ let totalScore = 0;
512
+ for (const fr of fileRisks) {
513
+ breakdown[fr.risk]++;
514
+ totalScore += fr.score;
515
+ }
516
+ const avgScore = fileRisks.length > 0 ? totalScore / fileRisks.length : 0;
517
+ // Weight more heavily towards high/critical files
518
+ const weightedScore = avgScore + (breakdown.critical * 15) + (breakdown.high * 10);
519
+ let overall = 'low';
520
+ if (weightedScore >= 60 || breakdown.critical > 0)
521
+ overall = 'critical';
522
+ else if (weightedScore >= 40 || breakdown.high > 1)
523
+ overall = 'high';
524
+ else if (weightedScore >= 20 || breakdown.medium > 2)
525
+ overall = 'medium';
526
+ return { overall, score: Math.min(100, Math.round(weightedScore)), breakdown };
527
+ }
528
+ // Singleton classifier instance for reuse
529
+ let classifierInstance = null;
530
+ function getClassifier() {
531
+ if (!classifierInstance) {
532
+ classifierInstance = new DiffClassifier();
533
+ }
534
+ return classifierInstance;
535
+ }
536
+ /**
537
+ * Classify a diff based on files (uses singleton classifier)
538
+ */
539
+ export function classifyDiff(files) {
540
+ const classifier = getClassifier();
541
+ const fileDiffs = files.map(f => ({
542
+ path: f.path,
543
+ hunks: [],
544
+ additions: f.additions,
545
+ deletions: f.deletions,
546
+ classification: classifier['classifyFile'](f.path, []),
547
+ }));
548
+ return classifier['computeOverallClassification'](fileDiffs);
549
+ }
550
+ /**
551
+ * Suggest reviewers based on files and risks
552
+ */
553
+ export function suggestReviewers(files, fileRisks) {
554
+ const reviewers = new Set();
555
+ for (const file of files) {
556
+ const lowerPath = file.path.toLowerCase();
557
+ if (/security|auth|crypto/.test(lowerPath))
558
+ reviewers.add('security-team');
559
+ if (/database|migration/.test(lowerPath))
560
+ reviewers.add('dba');
561
+ if (/api|endpoint|route/.test(lowerPath))
562
+ reviewers.add('api-owner');
563
+ if (/test|spec/.test(lowerPath))
564
+ reviewers.add('qa-engineer');
565
+ if (/config|deploy|ci/.test(lowerPath))
566
+ reviewers.add('devops');
567
+ if (/ui|component|style/.test(lowerPath))
568
+ reviewers.add('frontend-lead');
569
+ if (/model|service|repository/.test(lowerPath))
570
+ reviewers.add('backend-lead');
571
+ }
572
+ // Add based on risk
573
+ const hasHighRisk = fileRisks.some(fr => fr.risk === 'high' || fr.risk === 'critical');
574
+ if (hasHighRisk) {
575
+ reviewers.add('tech-lead');
576
+ reviewers.add('senior-developer');
577
+ }
578
+ // Default reviewer
579
+ if (reviewers.size === 0) {
580
+ reviewers.add('developer');
581
+ }
582
+ return Array.from(reviewers).slice(0, 5);
583
+ }
584
+ // Analysis result cache
585
+ const analysisCache = new Map();
586
+ const ANALYSIS_CACHE_TTL_MS = 3000; // 3 seconds
587
+ /**
588
+ * Analyze a diff with full analysis (optimized with caching)
589
+ */
590
+ export async function analyzeDiff(options) {
591
+ const ref = options.ref || 'HEAD';
592
+ // Check analysis cache (unless skipCache is true)
593
+ if (!options.skipCache) {
594
+ const cached = analysisCache.get(ref);
595
+ if (cached && Date.now() - cached.timestamp < ANALYSIS_CACHE_TTL_MS) {
596
+ return cached.result;
597
+ }
598
+ }
599
+ // Use async git diff for non-blocking operation
600
+ const files = await getGitDiffNumstatAsync(ref);
601
+ // Parallel file risk assessment for large diffs
602
+ const fileRisks = files.length > 20
603
+ ? await Promise.all(files.map(f => Promise.resolve(assessFileRisk(f))))
604
+ : files.map(assessFileRisk);
605
+ const risk = assessOverallRisk(files, fileRisks);
606
+ const classification = classifyDiff(files);
607
+ const recommendedReviewers = suggestReviewers(files, fileRisks);
608
+ const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
609
+ const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
610
+ const result = {
611
+ ref,
612
+ timestamp: Date.now(),
613
+ files,
614
+ risk,
615
+ classification,
616
+ summary: `${files.length} files changed (+${totalAdditions}/-${totalDeletions}), ${risk.overall} risk`,
617
+ fileRisks,
618
+ recommendedReviewers,
619
+ };
620
+ // Cache the result
621
+ analysisCache.set(ref, { result, timestamp: Date.now() });
622
+ return result;
623
+ }
624
+ /**
625
+ * Synchronous version of analyzeDiff for backward compatibility
626
+ */
627
+ export function analyzeDiffSync(options) {
628
+ const ref = options.ref || 'HEAD';
629
+ // Check analysis cache
630
+ const cached = analysisCache.get(ref);
631
+ if (cached && Date.now() - cached.timestamp < ANALYSIS_CACHE_TTL_MS) {
632
+ return cached.result;
633
+ }
634
+ const files = getGitDiffNumstat(ref);
635
+ const fileRisks = files.map(assessFileRisk);
636
+ const risk = assessOverallRisk(files, fileRisks);
637
+ const classification = classifyDiff(files);
638
+ const recommendedReviewers = suggestReviewers(files, fileRisks);
639
+ const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
640
+ const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
641
+ const result = {
642
+ ref,
643
+ timestamp: Date.now(),
644
+ files,
645
+ risk,
646
+ classification,
647
+ summary: `${files.length} files changed (+${totalAdditions}/-${totalDeletions}), ${risk.overall} risk`,
648
+ fileRisks,
649
+ recommendedReviewers,
650
+ };
651
+ analysisCache.set(ref, { result, timestamp: Date.now() });
652
+ return result;
653
+ }
654
+ /**
655
+ * Clear all diff-related caches
656
+ */
657
+ export function clearAllDiffCaches() {
658
+ diffCache.clear();
659
+ analysisCache.clear();
660
+ classifierInstance?.clearCache();
661
+ }
662
+ //# sourceMappingURL=diff-classifier.js.map