vaspera 2.8.0 → 2.9.2

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 (303) hide show
  1. package/CHANGELOG.md +109 -7
  2. package/README.md +111 -7
  3. package/dist/__tests__/agents/adversary/tactics/api.test.d.ts +5 -0
  4. package/dist/__tests__/agents/adversary/tactics/api.test.d.ts.map +1 -0
  5. package/dist/__tests__/agents/adversary/tactics/api.test.js +369 -0
  6. package/dist/__tests__/agents/adversary/tactics/api.test.js.map +1 -0
  7. package/dist/__tests__/agents/adversary/tactics/llm.test.d.ts +5 -0
  8. package/dist/__tests__/agents/adversary/tactics/llm.test.d.ts.map +1 -0
  9. package/dist/__tests__/agents/adversary/tactics/llm.test.js +409 -0
  10. package/dist/__tests__/agents/adversary/tactics/llm.test.js.map +1 -0
  11. package/dist/__tests__/agents/adversary/tactics/registry.test.d.ts +7 -0
  12. package/dist/__tests__/agents/adversary/tactics/registry.test.d.ts.map +1 -0
  13. package/dist/__tests__/agents/adversary/tactics/registry.test.js +74 -0
  14. package/dist/__tests__/agents/adversary/tactics/registry.test.js.map +1 -0
  15. package/dist/__tests__/agents/adversary/tactics/web-app.test.d.ts +7 -0
  16. package/dist/__tests__/agents/adversary/tactics/web-app.test.d.ts.map +1 -0
  17. package/dist/__tests__/agents/adversary/tactics/web-app.test.js +374 -0
  18. package/dist/__tests__/agents/adversary/tactics/web-app.test.js.map +1 -0
  19. package/dist/__tests__/compliance-bundle.test.d.ts +9 -0
  20. package/dist/__tests__/compliance-bundle.test.d.ts.map +1 -0
  21. package/dist/__tests__/compliance-bundle.test.js +344 -0
  22. package/dist/__tests__/compliance-bundle.test.js.map +1 -0
  23. package/dist/__tests__/healthcare-compliance.test.d.ts +9 -0
  24. package/dist/__tests__/healthcare-compliance.test.d.ts.map +1 -0
  25. package/dist/__tests__/healthcare-compliance.test.js +233 -0
  26. package/dist/__tests__/healthcare-compliance.test.js.map +1 -0
  27. package/dist/action/diff-mode.d.ts +124 -8
  28. package/dist/action/diff-mode.d.ts.map +1 -1
  29. package/dist/action/diff-mode.js +384 -65
  30. package/dist/action/diff-mode.js.map +1 -1
  31. package/dist/action/diff-mode.test.js +3 -3
  32. package/dist/action/diff-mode.test.js.map +1 -1
  33. package/dist/action/pr-comment.test.js +1 -0
  34. package/dist/action/pr-comment.test.js.map +1 -1
  35. package/dist/action/sarif-upload.test.js +1 -0
  36. package/dist/action/sarif-upload.test.js.map +1 -1
  37. package/dist/agents/adversary/config.d.ts +25 -4
  38. package/dist/agents/adversary/config.d.ts.map +1 -1
  39. package/dist/agents/adversary/config.js +38 -8
  40. package/dist/agents/adversary/config.js.map +1 -1
  41. package/dist/agents/adversary/index.d.ts +7 -0
  42. package/dist/agents/adversary/index.d.ts.map +1 -1
  43. package/dist/agents/adversary/index.js +83 -1
  44. package/dist/agents/adversary/index.js.map +1 -1
  45. package/dist/agents/adversary/reporting/compliance-mapper.d.ts +108 -0
  46. package/dist/agents/adversary/reporting/compliance-mapper.d.ts.map +1 -0
  47. package/dist/agents/adversary/reporting/compliance-mapper.js +391 -0
  48. package/dist/agents/adversary/reporting/compliance-mapper.js.map +1 -0
  49. package/dist/agents/adversary/reporting/index.d.ts +10 -0
  50. package/dist/agents/adversary/reporting/index.d.ts.map +1 -0
  51. package/dist/agents/adversary/reporting/index.js +10 -0
  52. package/dist/agents/adversary/reporting/index.js.map +1 -0
  53. package/dist/agents/adversary/reporting/poc-generator.d.ts +44 -0
  54. package/dist/agents/adversary/reporting/poc-generator.d.ts.map +1 -0
  55. package/dist/agents/adversary/reporting/poc-generator.js +308 -0
  56. package/dist/agents/adversary/reporting/poc-generator.js.map +1 -0
  57. package/dist/agents/adversary/tactics/api.d.ts +13 -0
  58. package/dist/agents/adversary/tactics/api.d.ts.map +1 -0
  59. package/dist/agents/adversary/tactics/api.js +815 -0
  60. package/dist/agents/adversary/tactics/api.js.map +1 -0
  61. package/dist/agents/adversary/tactics/auth.d.ts +13 -0
  62. package/dist/agents/adversary/tactics/auth.d.ts.map +1 -0
  63. package/dist/agents/adversary/tactics/auth.js +676 -0
  64. package/dist/agents/adversary/tactics/auth.js.map +1 -0
  65. package/dist/agents/adversary/tactics/index.d.ts +129 -0
  66. package/dist/agents/adversary/tactics/index.d.ts.map +1 -0
  67. package/dist/agents/adversary/tactics/index.js +199 -0
  68. package/dist/agents/adversary/tactics/index.js.map +1 -0
  69. package/dist/agents/adversary/tactics/infra.d.ts +13 -0
  70. package/dist/agents/adversary/tactics/infra.d.ts.map +1 -0
  71. package/dist/agents/adversary/tactics/infra.js +827 -0
  72. package/dist/agents/adversary/tactics/infra.js.map +1 -0
  73. package/dist/agents/adversary/tactics/injection.d.ts +12 -0
  74. package/dist/agents/adversary/tactics/injection.d.ts.map +1 -0
  75. package/dist/agents/adversary/tactics/injection.js +549 -0
  76. package/dist/agents/adversary/tactics/injection.js.map +1 -0
  77. package/dist/agents/adversary/tactics/llm.d.ts +13 -0
  78. package/dist/agents/adversary/tactics/llm.d.ts.map +1 -0
  79. package/dist/agents/adversary/tactics/llm.js +767 -0
  80. package/dist/agents/adversary/tactics/llm.js.map +1 -0
  81. package/dist/agents/adversary/tactics/web-app.d.ts +13 -0
  82. package/dist/agents/adversary/tactics/web-app.d.ts.map +1 -0
  83. package/dist/agents/adversary/tactics/web-app.js +717 -0
  84. package/dist/agents/adversary/tactics/web-app.js.map +1 -0
  85. package/dist/agents/adversary/types.d.ts +66 -10
  86. package/dist/agents/adversary/types.d.ts.map +1 -1
  87. package/dist/agents/zero-day-hunter.d.ts +1 -1
  88. package/dist/agents/zero-day-hunter.d.ts.map +1 -1
  89. package/dist/analysis/data-flow.d.ts +154 -0
  90. package/dist/analysis/data-flow.d.ts.map +1 -0
  91. package/dist/analysis/data-flow.js +393 -0
  92. package/dist/analysis/data-flow.js.map +1 -0
  93. package/dist/analysis/index.d.ts +9 -0
  94. package/dist/analysis/index.d.ts.map +1 -0
  95. package/dist/analysis/index.js +9 -0
  96. package/dist/analysis/index.js.map +1 -0
  97. package/dist/badge-service/index.d.ts +144 -0
  98. package/dist/badge-service/index.d.ts.map +1 -0
  99. package/dist/badge-service/index.js +206 -0
  100. package/dist/badge-service/index.js.map +1 -0
  101. package/dist/certification/types.d.ts +1 -1
  102. package/dist/certification/types.d.ts.map +1 -1
  103. package/dist/certification/types.js.map +1 -1
  104. package/dist/commands/certification/certify.d.ts.map +1 -1
  105. package/dist/commands/certification/certify.js +18 -4
  106. package/dist/commands/certification/certify.js.map +1 -1
  107. package/dist/compliance/attestation.d.ts +39 -0
  108. package/dist/compliance/attestation.d.ts.map +1 -0
  109. package/dist/compliance/attestation.js +364 -0
  110. package/dist/compliance/attestation.js.map +1 -0
  111. package/dist/compliance/cfr42-part2.d.ts +42 -0
  112. package/dist/compliance/cfr42-part2.d.ts.map +1 -0
  113. package/dist/compliance/cfr42-part2.js +408 -0
  114. package/dist/compliance/cfr42-part2.js.map +1 -0
  115. package/dist/compliance/compliance-bundle.d.ts +100 -0
  116. package/dist/compliance/compliance-bundle.d.ts.map +1 -0
  117. package/dist/compliance/compliance-bundle.js +210 -0
  118. package/dist/compliance/compliance-bundle.js.map +1 -0
  119. package/dist/compliance/healthcare-bundle.d.ts +68 -0
  120. package/dist/compliance/healthcare-bundle.d.ts.map +1 -0
  121. package/dist/compliance/healthcare-bundle.js +104 -0
  122. package/dist/compliance/healthcare-bundle.js.map +1 -0
  123. package/dist/compliance/hipaa.d.ts.map +1 -1
  124. package/dist/compliance/hipaa.js +14 -11
  125. package/dist/compliance/hipaa.js.map +1 -1
  126. package/dist/compliance/index.d.ts +10 -2
  127. package/dist/compliance/index.d.ts.map +1 -1
  128. package/dist/compliance/index.js +9 -3
  129. package/dist/compliance/index.js.map +1 -1
  130. package/dist/compliance/mapper.d.ts.map +1 -1
  131. package/dist/compliance/mapper.js +3 -17
  132. package/dist/compliance/mapper.js.map +1 -1
  133. package/dist/compliance/nist-800-53.d.ts +22 -6
  134. package/dist/compliance/nist-800-53.d.ts.map +1 -1
  135. package/dist/compliance/nist-800-53.js +264 -272
  136. package/dist/compliance/nist-800-53.js.map +1 -1
  137. package/dist/compliance/report.d.ts +31 -2
  138. package/dist/compliance/report.d.ts.map +1 -1
  139. package/dist/compliance/report.js +255 -4
  140. package/dist/compliance/report.js.map +1 -1
  141. package/dist/compliance/types.d.ts +1 -1
  142. package/dist/compliance/types.d.ts.map +1 -1
  143. package/dist/config/flags.d.ts +12 -12
  144. package/dist/cost/index.d.ts +1 -1
  145. package/dist/cost/index.d.ts.map +1 -1
  146. package/dist/cost/index.js +1 -1
  147. package/dist/cost/index.js.map +1 -1
  148. package/dist/cost/tracker.d.ts +64 -0
  149. package/dist/cost/tracker.d.ts.map +1 -1
  150. package/dist/cost/tracker.js +165 -0
  151. package/dist/cost/tracker.js.map +1 -1
  152. package/dist/eval/fixtures/healthcare/audit-gaps.d.ts +28 -0
  153. package/dist/eval/fixtures/healthcare/audit-gaps.d.ts.map +1 -0
  154. package/dist/eval/fixtures/healthcare/audit-gaps.js +90 -0
  155. package/dist/eval/fixtures/healthcare/audit-gaps.js.map +1 -0
  156. package/dist/eval/fixtures/healthcare/consent-bypass.d.ts +31 -0
  157. package/dist/eval/fixtures/healthcare/consent-bypass.d.ts.map +1 -0
  158. package/dist/eval/fixtures/healthcare/consent-bypass.js +61 -0
  159. package/dist/eval/fixtures/healthcare/consent-bypass.js.map +1 -0
  160. package/dist/eval/fixtures/healthcare/phi-in-logs.d.ts +24 -0
  161. package/dist/eval/fixtures/healthcare/phi-in-logs.d.ts.map +1 -0
  162. package/dist/eval/fixtures/healthcare/phi-in-logs.js +41 -0
  163. package/dist/eval/fixtures/healthcare/phi-in-logs.js.map +1 -0
  164. package/dist/evidence/collector.d.ts +21 -0
  165. package/dist/evidence/collector.d.ts.map +1 -0
  166. package/dist/evidence/collector.js +340 -0
  167. package/dist/evidence/collector.js.map +1 -0
  168. package/dist/evidence/index.d.ts +11 -0
  169. package/dist/evidence/index.d.ts.map +1 -0
  170. package/dist/evidence/index.js +12 -0
  171. package/dist/evidence/index.js.map +1 -0
  172. package/dist/evidence/store.d.ts +39 -0
  173. package/dist/evidence/store.d.ts.map +1 -0
  174. package/dist/evidence/store.js +173 -0
  175. package/dist/evidence/store.js.map +1 -0
  176. package/dist/evidence/types.d.ts +175 -0
  177. package/dist/evidence/types.d.ts.map +1 -0
  178. package/dist/evidence/types.js +9 -0
  179. package/dist/evidence/types.js.map +1 -0
  180. package/dist/exporters/checkmarx.d.ts +18 -0
  181. package/dist/exporters/checkmarx.d.ts.map +1 -0
  182. package/dist/exporters/checkmarx.js +203 -0
  183. package/dist/exporters/checkmarx.js.map +1 -0
  184. package/dist/exporters/index.d.ts +22 -0
  185. package/dist/exporters/index.d.ts.map +1 -0
  186. package/dist/exporters/index.js +41 -0
  187. package/dist/exporters/index.js.map +1 -0
  188. package/dist/exporters/snyk.d.ts +18 -0
  189. package/dist/exporters/snyk.d.ts.map +1 -0
  190. package/dist/exporters/snyk.js +119 -0
  191. package/dist/exporters/snyk.js.map +1 -0
  192. package/dist/exporters/sonarqube.d.ts +18 -0
  193. package/dist/exporters/sonarqube.d.ts.map +1 -0
  194. package/dist/exporters/sonarqube.js +125 -0
  195. package/dist/exporters/sonarqube.js.map +1 -0
  196. package/dist/exporters/types.d.ts +190 -0
  197. package/dist/exporters/types.d.ts.map +1 -0
  198. package/dist/exporters/types.js +9 -0
  199. package/dist/exporters/types.js.map +1 -0
  200. package/dist/frontier/index.d.ts +12 -0
  201. package/dist/frontier/index.d.ts.map +1 -0
  202. package/dist/frontier/index.js +12 -0
  203. package/dist/frontier/index.js.map +1 -0
  204. package/dist/frontier/orchestrator.d.ts +73 -0
  205. package/dist/frontier/orchestrator.d.ts.map +1 -0
  206. package/dist/frontier/orchestrator.js +312 -0
  207. package/dist/frontier/orchestrator.js.map +1 -0
  208. package/dist/frontier/providers/stub.d.ts +32 -0
  209. package/dist/frontier/providers/stub.d.ts.map +1 -0
  210. package/dist/frontier/providers/stub.js +66 -0
  211. package/dist/frontier/providers/stub.js.map +1 -0
  212. package/dist/frontier/types.d.ts +318 -0
  213. package/dist/frontier/types.d.ts.map +1 -0
  214. package/dist/frontier/types.js +27 -0
  215. package/dist/frontier/types.js.map +1 -0
  216. package/dist/history/index.d.ts +13 -0
  217. package/dist/history/index.d.ts.map +1 -0
  218. package/dist/history/index.js +15 -0
  219. package/dist/history/index.js.map +1 -0
  220. package/dist/history/store.d.ts +74 -0
  221. package/dist/history/store.d.ts.map +1 -0
  222. package/dist/history/store.js +399 -0
  223. package/dist/history/store.js.map +1 -0
  224. package/dist/history/types.d.ts +282 -0
  225. package/dist/history/types.d.ts.map +1 -0
  226. package/dist/history/types.js +41 -0
  227. package/dist/history/types.js.map +1 -0
  228. package/dist/history/verify.d.ts +44 -0
  229. package/dist/history/verify.d.ts.map +1 -0
  230. package/dist/history/verify.js +230 -0
  231. package/dist/history/verify.js.map +1 -0
  232. package/dist/index.d.ts.map +1 -1
  233. package/dist/index.js +515 -19
  234. package/dist/index.js.map +1 -1
  235. package/dist/multimodel/index.d.ts +1 -0
  236. package/dist/multimodel/index.d.ts.map +1 -1
  237. package/dist/multimodel/index.js +2 -0
  238. package/dist/multimodel/index.js.map +1 -1
  239. package/dist/multimodel/leaderboard.d.ts +116 -0
  240. package/dist/multimodel/leaderboard.d.ts.map +1 -0
  241. package/dist/multimodel/leaderboard.js +262 -0
  242. package/dist/multimodel/leaderboard.js.map +1 -0
  243. package/dist/observability/otel.d.ts.map +1 -1
  244. package/dist/observability/otel.js +1 -3
  245. package/dist/observability/otel.js.map +1 -1
  246. package/dist/plugins/loader.js +1 -1
  247. package/dist/plugins/loader.js.map +1 -1
  248. package/dist/scanners/agent/agent-chain-analysis.d.ts +152 -0
  249. package/dist/scanners/agent/agent-chain-analysis.d.ts.map +1 -0
  250. package/dist/scanners/agent/agent-chain-analysis.js +438 -0
  251. package/dist/scanners/agent/agent-chain-analysis.js.map +1 -0
  252. package/dist/scanners/agent/payloads/index.d.ts +2 -1
  253. package/dist/scanners/agent/payloads/index.d.ts.map +1 -1
  254. package/dist/scanners/agent/payloads/index.js +25 -6
  255. package/dist/scanners/agent/payloads/index.js.map +1 -1
  256. package/dist/scanners/agent/prompt-injection-fuzzer.d.ts.map +1 -1
  257. package/dist/scanners/agent/prompt-injection-fuzzer.js +14 -0
  258. package/dist/scanners/agent/prompt-injection-fuzzer.js.map +1 -1
  259. package/dist/scanners/agent/types.d.ts +5 -5
  260. package/dist/scanners/agent/types.d.ts.map +1 -1
  261. package/dist/scanners/agent/types.js.map +1 -1
  262. package/dist/scanners/cache.d.ts +156 -0
  263. package/dist/scanners/cache.d.ts.map +1 -0
  264. package/dist/scanners/cache.js +462 -0
  265. package/dist/scanners/cache.js.map +1 -0
  266. package/dist/scanners/dependencies.js +4 -4
  267. package/dist/scanners/dependencies.js.map +1 -1
  268. package/dist/scanners/gosec.d.ts.map +1 -1
  269. package/dist/scanners/gosec.js +47 -9
  270. package/dist/scanners/gosec.js.map +1 -1
  271. package/dist/scanners/healthcare.d.ts +29 -0
  272. package/dist/scanners/healthcare.d.ts.map +1 -0
  273. package/dist/scanners/healthcare.js +526 -0
  274. package/dist/scanners/healthcare.js.map +1 -0
  275. package/dist/scanners/index.d.ts +1 -0
  276. package/dist/scanners/index.d.ts.map +1 -1
  277. package/dist/scanners/index.js +33 -0
  278. package/dist/scanners/index.js.map +1 -1
  279. package/dist/scanners/index.test.js +6 -6
  280. package/dist/scanners/index.test.js.map +1 -1
  281. package/dist/scanners/secrets.js +4 -4
  282. package/dist/scanners/secrets.js.map +1 -1
  283. package/dist/scanners/semgrep.js +5 -5
  284. package/dist/scanners/semgrep.js.map +1 -1
  285. package/dist/scanners/types.d.ts +1 -1
  286. package/dist/scanners/types.d.ts.map +1 -1
  287. package/dist/scanners/types.js +1 -0
  288. package/dist/scanners/types.js.map +1 -1
  289. package/dist/scanners/typescript.test.js +1 -1
  290. package/dist/scanners/typescript.test.js.map +1 -1
  291. package/dist/telemetry/index.d.ts +10 -0
  292. package/dist/telemetry/index.d.ts.map +1 -0
  293. package/dist/telemetry/index.js +10 -0
  294. package/dist/telemetry/index.js.map +1 -0
  295. package/dist/telemetry/registry.d.ts +178 -0
  296. package/dist/telemetry/registry.d.ts.map +1 -0
  297. package/dist/telemetry/registry.js +297 -0
  298. package/dist/telemetry/registry.js.map +1 -0
  299. package/dist/telemetry/usage.d.ts +197 -0
  300. package/dist/telemetry/usage.d.ts.map +1 -0
  301. package/dist/telemetry/usage.js +252 -0
  302. package/dist/telemetry/usage.js.map +1 -0
  303. package/package.json +2 -6
@@ -1,20 +1,107 @@
1
1
  /**
2
- * Diff Mode - Changed Files Detection
2
+ * Differential PR Mode
3
3
  *
4
- * Detects which files have changed in a PR or push event
5
- * to enable incremental scanning.
4
+ * Enhanced diff mode for incremental scanning with:
5
+ * - Git-based change detection (git diff --name-only)
6
+ * - Integration with scanner cache
7
+ * - Support for PR base SHA comparison
8
+ * - Incremental certification support
6
9
  *
7
10
  * @module action/diff-mode
8
11
  */
9
12
  import type { ChangedFile, GitHubContext } from "./types.js";
10
13
  /**
11
- * Get changed files from GitHub API
14
+ * Result of git diff operation
15
+ */
16
+ export interface GitDiffResult {
17
+ /** Files that changed */
18
+ changedFiles: string[];
19
+ /** Base commit SHA */
20
+ baseSha: string;
21
+ /** Head commit SHA */
22
+ headSha: string;
23
+ /** Whether diff detection was successful */
24
+ success: boolean;
25
+ /** Error message if unsuccessful */
26
+ error?: string;
27
+ /** Total additions */
28
+ additions: number;
29
+ /** Total deletions */
30
+ deletions: number;
31
+ }
32
+ /**
33
+ * File info with hash for caching
34
+ */
35
+ export interface FileInfo {
36
+ /** Relative file path */
37
+ path: string;
38
+ /** SHA-256 hash of file contents */
39
+ hash: string;
40
+ /** File size in bytes */
41
+ size: number;
42
+ /** Last modified time */
43
+ mtime: Date;
44
+ }
45
+ /**
46
+ * Incremental scan configuration
47
+ */
48
+ export interface IncrementalScanConfig {
49
+ /** Project path */
50
+ projectPath: string;
51
+ /** Base SHA for comparison (e.g., PR base branch) */
52
+ baseSha?: string;
53
+ /** Head SHA (default: HEAD) */
54
+ headSha?: string;
55
+ /** Previous certification ID to build upon */
56
+ previousCertificationId?: string;
57
+ /** Files to force include even if unchanged */
58
+ forceInclude?: string[];
59
+ /** Files to exclude from scanning */
60
+ exclude?: string[];
61
+ /** Whether to scan security-critical files regardless of changes */
62
+ alwaysScanSecurityFiles?: boolean;
63
+ }
64
+ /**
65
+ * Incremental scan result
66
+ */
67
+ export interface IncrementalScanResult {
68
+ /** Files that need scanning */
69
+ filesToScan: FileInfo[];
70
+ /** Files that are unchanged (cached) */
71
+ unchangedFiles: string[];
72
+ /** Files that were removed */
73
+ removedFiles: string[];
74
+ /** Git diff information */
75
+ diff: GitDiffResult;
76
+ /** Configuration used */
77
+ config: IncrementalScanConfig;
78
+ /** Whether incremental mode was used */
79
+ isIncremental: boolean;
80
+ /** Estimated time savings from cache */
81
+ estimatedSavingsPercent: number;
82
+ }
83
+ /**
84
+ * Get changed files using git diff
12
85
  *
13
- * @param octokit - GitHub API client
14
- * @param context - GitHub context
15
- * @returns Array of changed files
86
+ * Uses `git diff --name-only $BASE_SHA...HEAD` for accurate change detection.
87
+ *
88
+ * @param projectPath - Path to git repository
89
+ * @param baseSha - Base commit SHA (e.g., PR base branch)
90
+ * @param headSha - Head commit SHA (default: HEAD)
16
91
  */
17
- export declare function getChangedFiles(octokit: any, context: GitHubContext): Promise<ChangedFile[]>;
92
+ export declare function getGitDiff(projectPath: string, baseSha?: string, headSha?: string): Promise<GitDiffResult>;
93
+ /**
94
+ * Get file info with content hash
95
+ */
96
+ export declare function getFileInfo(projectPath: string, filePath: string): Promise<FileInfo | null>;
97
+ /**
98
+ * Get file info for multiple files in parallel
99
+ */
100
+ export declare function getFilesInfo(projectPath: string, filePaths: string[]): Promise<FileInfo[]>;
101
+ /**
102
+ * Check if a file should be scanned
103
+ */
104
+ export declare function isScannableFile(filename: string): boolean;
18
105
  /**
19
106
  * Filter changed files to only scannable ones
20
107
  */
@@ -23,12 +110,41 @@ export declare function filterScannableFiles(files: ChangedFile[]): ChangedFile[
23
110
  * Get the list of file paths to scan
24
111
  */
25
112
  export declare function getFilesToScan(changedFiles: ChangedFile[]): string[];
113
+ /**
114
+ * Configure incremental scanning
115
+ *
116
+ * Determines which files need scanning based on:
117
+ * 1. Git diff against base SHA
118
+ * 2. File content hashes (for cache validation)
119
+ * 3. Security-critical file detection
120
+ */
121
+ export declare function configureIncrementalScan(config: IncrementalScanConfig): Promise<IncrementalScanResult>;
122
+ /**
123
+ * Get changed files from GitHub API
124
+ *
125
+ * @param octokit - GitHub API client
126
+ * @param context - GitHub context
127
+ * @returns Array of changed files
128
+ */
129
+ export declare function getChangedFiles(octokit: any, context: GitHubContext): Promise<ChangedFile[]>;
26
130
  /**
27
131
  * Check if any security-relevant files changed
28
132
  */
29
133
  export declare function hasSecurityRelevantChanges(changedFiles: ChangedFile[]): boolean;
134
+ /**
135
+ * Get scan scope recommendation based on changes
136
+ */
137
+ export declare function getScanScopeRecommendation(changedFiles: ChangedFile[]): "full" | "incremental" | "security-only";
30
138
  /**
31
139
  * Generate summary of changed files
32
140
  */
33
141
  export declare function generateChangeSummary(changedFiles: ChangedFile[]): string;
142
+ /**
143
+ * Group files by directory for parallel processing
144
+ */
145
+ export declare function groupFilesByDirectory(files: FileInfo[]): Map<string, FileInfo[]>;
146
+ /**
147
+ * Prioritize files for scanning (security-critical first)
148
+ */
149
+ export declare function prioritizeFiles(files: FileInfo[]): FileInfo[];
34
150
  //# sourceMappingURL=diff-mode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"diff-mode.d.ts","sourceRoot":"","sources":["../../src/action/diff-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA6B7D;;;;;;GAMG;AACH,wBAAsB,eAAe,CAEnC,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,WAAW,EAAE,CAAC,CAmExB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAoBxE;AAWD;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CAEpE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,OAAO,CAsB/E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CA8BzE"}
1
+ {"version":3,"file":"diff-mode.d.ts","sourceRoot":"","sources":["../../src/action/diff-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAoD7D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,KAAK,EAAE,IAAI,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,oEAAoE;IACpE,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+BAA+B;IAC/B,WAAW,EAAE,QAAQ,EAAE,CAAC;IACxB,wCAAwC;IACxC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,8BAA8B;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,2BAA2B;IAC3B,IAAI,EAAE,aAAa,CAAC;IACpB,yBAAyB;IACzB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,wCAAwC;IACxC,aAAa,EAAE,OAAO,CAAC;IACvB,wCAAwC;IACxC,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAC9B,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,MAAe,GACvB,OAAO,CAAC,aAAa,CAAC,CA+ExB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAkB1B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAKrB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAsBzD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAMxE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CAEpE;AAED;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CAmEhC;AAkED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAEnC,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,WAAW,EAAE,CAAC,CAkExB;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,OAAO,CA0B/E;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,WAAW,EAAE,GAC1B,MAAM,GAAG,aAAa,GAAG,eAAe,CAkB1C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CA4CzE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,QAAQ,EAAE,GAChB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAWzB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAsB7D"}
@@ -1,11 +1,21 @@
1
1
  /**
2
- * Diff Mode - Changed Files Detection
2
+ * Differential PR Mode
3
3
  *
4
- * Detects which files have changed in a PR or push event
5
- * to enable incremental scanning.
4
+ * Enhanced diff mode for incremental scanning with:
5
+ * - Git-based change detection (git diff --name-only)
6
+ * - Integration with scanner cache
7
+ * - Support for PR base SHA comparison
8
+ * - Incremental certification support
6
9
  *
7
10
  * @module action/diff-mode
8
11
  */
12
+ import { exec } from "child_process";
13
+ import { promisify } from "util";
14
+ import { join, extname } from "path";
15
+ import { stat, readFile } from "fs/promises";
16
+ import { createHash } from "crypto";
17
+ import { logger } from "../logger.js";
18
+ const execAsync = promisify(exec);
9
19
  /**
10
20
  * File extensions to include in scans
11
21
  */
@@ -31,7 +41,285 @@ const SCANNABLE_EXTENSIONS = new Set([
31
41
  ".cs",
32
42
  ".vue",
33
43
  ".svelte",
44
+ ".tf", // Terraform
45
+ ".dockerfile", // Docker
34
46
  ]);
47
+ /**
48
+ * Paths to exclude from scanning
49
+ */
50
+ const EXCLUDED_PATHS = [
51
+ "node_modules/",
52
+ "dist/",
53
+ "build/",
54
+ "coverage/",
55
+ ".git/",
56
+ ".vaspera/",
57
+ ".next/",
58
+ ".turbo/",
59
+ "__pycache__/",
60
+ "vendor/",
61
+ "venv/",
62
+ ".venv/",
63
+ ];
64
+ /**
65
+ * Get changed files using git diff
66
+ *
67
+ * Uses `git diff --name-only $BASE_SHA...HEAD` for accurate change detection.
68
+ *
69
+ * @param projectPath - Path to git repository
70
+ * @param baseSha - Base commit SHA (e.g., PR base branch)
71
+ * @param headSha - Head commit SHA (default: HEAD)
72
+ */
73
+ export async function getGitDiff(projectPath, baseSha, headSha = "HEAD") {
74
+ const result = {
75
+ changedFiles: [],
76
+ baseSha: baseSha || "",
77
+ headSha,
78
+ success: false,
79
+ additions: 0,
80
+ deletions: 0,
81
+ };
82
+ try {
83
+ // Get base SHA if not provided (use default branch)
84
+ if (!baseSha) {
85
+ try {
86
+ const { stdout: defaultBranch } = await execAsync('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed "s@^refs/remotes/origin/@@" || echo "main"', { cwd: projectPath, timeout: 10000 });
87
+ baseSha = `origin/${defaultBranch.trim()}`;
88
+ result.baseSha = baseSha;
89
+ }
90
+ catch {
91
+ baseSha = "HEAD^";
92
+ result.baseSha = baseSha;
93
+ }
94
+ }
95
+ // Get list of changed files
96
+ const { stdout: diffOutput } = await execAsync(`git diff --name-only ${baseSha}...${headSha} 2>/dev/null || git diff --name-only ${baseSha} ${headSha}`, { cwd: projectPath, timeout: 30000 });
97
+ result.changedFiles = diffOutput
98
+ .trim()
99
+ .split("\n")
100
+ .filter((f) => f.length > 0);
101
+ // Get stats (additions/deletions)
102
+ try {
103
+ const { stdout: statsOutput } = await execAsync(`git diff --shortstat ${baseSha}...${headSha} 2>/dev/null || git diff --shortstat ${baseSha} ${headSha}`, { cwd: projectPath, timeout: 10000 });
104
+ const addMatch = statsOutput.match(/(\d+) insertions?/);
105
+ const delMatch = statsOutput.match(/(\d+) deletions?/);
106
+ result.additions = addMatch ? parseInt(addMatch[1], 10) : 0;
107
+ result.deletions = delMatch ? parseInt(delMatch[1], 10) : 0;
108
+ }
109
+ catch {
110
+ // Stats are optional
111
+ }
112
+ // Get actual head SHA
113
+ try {
114
+ const { stdout: actualHead } = await execAsync(`git rev-parse ${headSha}`, { cwd: projectPath, timeout: 5000 });
115
+ result.headSha = actualHead.trim();
116
+ }
117
+ catch {
118
+ // Keep original headSha
119
+ }
120
+ result.success = true;
121
+ logger.info("diff_mode.git_diff", {
122
+ baseSha: result.baseSha,
123
+ headSha: result.headSha,
124
+ changedFiles: result.changedFiles.length,
125
+ additions: result.additions,
126
+ deletions: result.deletions,
127
+ });
128
+ return result;
129
+ }
130
+ catch (error) {
131
+ result.error = String(error);
132
+ logger.warn("diff_mode.git_diff_failed", { error: result.error });
133
+ return result;
134
+ }
135
+ }
136
+ /**
137
+ * Get file info with content hash
138
+ */
139
+ export async function getFileInfo(projectPath, filePath) {
140
+ const fullPath = join(projectPath, filePath);
141
+ try {
142
+ const [fileStats, content] = await Promise.all([
143
+ stat(fullPath),
144
+ readFile(fullPath),
145
+ ]);
146
+ return {
147
+ path: filePath,
148
+ hash: createHash("sha256").update(content).digest("hex"),
149
+ size: fileStats.size,
150
+ mtime: fileStats.mtime,
151
+ };
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ /**
158
+ * Get file info for multiple files in parallel
159
+ */
160
+ export async function getFilesInfo(projectPath, filePaths) {
161
+ const results = await Promise.all(filePaths.map((path) => getFileInfo(projectPath, path)));
162
+ return results.filter((f) => f !== null);
163
+ }
164
+ /**
165
+ * Check if a file should be scanned
166
+ */
167
+ export function isScannableFile(filename) {
168
+ // Check excluded paths
169
+ for (const excluded of EXCLUDED_PATHS) {
170
+ if (filename.includes(excluded)) {
171
+ return false;
172
+ }
173
+ }
174
+ // Check extension
175
+ const ext = extname(filename).toLowerCase();
176
+ if (!SCANNABLE_EXTENSIONS.has(ext)) {
177
+ // Special cases
178
+ if (filename.endsWith("Dockerfile"))
179
+ return true;
180
+ if (filename === "docker-compose.yml" || filename === "docker-compose.yaml")
181
+ return true;
182
+ return false;
183
+ }
184
+ // Exclude minified/bundled files
185
+ if (filename.endsWith(".min.js") || filename.endsWith(".min.css"))
186
+ return false;
187
+ if (filename.endsWith(".bundle.js"))
188
+ return false;
189
+ return true;
190
+ }
191
+ /**
192
+ * Filter changed files to only scannable ones
193
+ */
194
+ export function filterScannableFiles(files) {
195
+ return files.filter((file) => {
196
+ // Exclude deleted files
197
+ if (file.status === "removed")
198
+ return false;
199
+ return isScannableFile(file.filename);
200
+ });
201
+ }
202
+ /**
203
+ * Get the list of file paths to scan
204
+ */
205
+ export function getFilesToScan(changedFiles) {
206
+ return filterScannableFiles(changedFiles).map((f) => f.filename);
207
+ }
208
+ /**
209
+ * Configure incremental scanning
210
+ *
211
+ * Determines which files need scanning based on:
212
+ * 1. Git diff against base SHA
213
+ * 2. File content hashes (for cache validation)
214
+ * 3. Security-critical file detection
215
+ */
216
+ export async function configureIncrementalScan(config) {
217
+ const { projectPath, baseSha, headSha, forceInclude, exclude, alwaysScanSecurityFiles } = config;
218
+ // Get git diff
219
+ const diff = await getGitDiff(projectPath, baseSha, headSha);
220
+ // Filter to scannable files
221
+ const scannableChanged = diff.changedFiles.filter(isScannableFile);
222
+ // Apply exclusions
223
+ let filesToProcess = scannableChanged;
224
+ if (exclude && exclude.length > 0) {
225
+ const excludePatterns = exclude.map((p) => new RegExp(p));
226
+ filesToProcess = filesToProcess.filter((f) => !excludePatterns.some((pattern) => pattern.test(f)));
227
+ }
228
+ // Add force-included files
229
+ if (forceInclude && forceInclude.length > 0) {
230
+ const additional = forceInclude.filter((f) => !filesToProcess.includes(f) && isScannableFile(f));
231
+ filesToProcess = [...filesToProcess, ...additional];
232
+ }
233
+ // Add security-critical files if requested
234
+ if (alwaysScanSecurityFiles) {
235
+ const securityFiles = await findSecurityCriticalFiles(projectPath);
236
+ const additional = securityFiles.filter((f) => !filesToProcess.includes(f));
237
+ filesToProcess = [...filesToProcess, ...additional];
238
+ }
239
+ // Get file info (with hashes)
240
+ const fileInfos = await getFilesInfo(projectPath, filesToProcess);
241
+ // Calculate removed files
242
+ const removedFiles = diff.changedFiles.filter((f) => {
243
+ // Try to stat the file - if it fails, it's removed
244
+ return !fileInfos.some((fi) => fi.path === f);
245
+ });
246
+ // Estimate savings
247
+ const totalProjectFiles = await estimateTotalFiles(projectPath);
248
+ const estimatedSavingsPercent = totalProjectFiles > 0
249
+ ? Math.round(((totalProjectFiles - fileInfos.length) / totalProjectFiles) * 100)
250
+ : 0;
251
+ const result = {
252
+ filesToScan: fileInfos,
253
+ unchangedFiles: [], // Will be populated by cache lookup
254
+ removedFiles,
255
+ diff,
256
+ config,
257
+ isIncremental: diff.success && fileInfos.length < totalProjectFiles,
258
+ estimatedSavingsPercent,
259
+ };
260
+ logger.info("diff_mode.incremental_config", {
261
+ filesToScan: fileInfos.length,
262
+ removedFiles: removedFiles.length,
263
+ isIncremental: result.isIncremental,
264
+ estimatedSavingsPercent,
265
+ });
266
+ return result;
267
+ }
268
+ /**
269
+ * Find security-critical files that should always be scanned
270
+ */
271
+ async function findSecurityCriticalFiles(projectPath) {
272
+ const patterns = [
273
+ "**/auth/**/*.{ts,tsx,js,jsx}",
274
+ "**/security/**/*.{ts,tsx,js,jsx}",
275
+ "**/middleware/**/*.{ts,tsx,js,jsx}",
276
+ "**/api/**/*.{ts,tsx,js,jsx}",
277
+ "**/*auth*.{ts,tsx,js,jsx}",
278
+ "**/*login*.{ts,tsx,js,jsx}",
279
+ "**/*session*.{ts,tsx,js,jsx}",
280
+ "**/*token*.{ts,tsx,js,jsx}",
281
+ "**/*password*.{ts,tsx,js,jsx}",
282
+ ".env.example",
283
+ "package.json",
284
+ "package-lock.json",
285
+ "yarn.lock",
286
+ "pnpm-lock.yaml",
287
+ ];
288
+ const files = [];
289
+ try {
290
+ // Use git ls-files to get tracked files matching patterns
291
+ for (const pattern of patterns) {
292
+ try {
293
+ const { stdout } = await execAsync(`git ls-files "${pattern}" 2>/dev/null || true`, { cwd: projectPath, timeout: 5000 });
294
+ const matches = stdout.trim().split("\n").filter((f) => f.length > 0);
295
+ files.push(...matches);
296
+ }
297
+ catch {
298
+ // Ignore pattern matching errors
299
+ }
300
+ }
301
+ }
302
+ catch {
303
+ // Git ls-files failed, return empty
304
+ }
305
+ // Deduplicate
306
+ return [...new Set(files)];
307
+ }
308
+ /**
309
+ * Estimate total number of scannable files in project
310
+ */
311
+ async function estimateTotalFiles(projectPath) {
312
+ try {
313
+ const { stdout } = await execAsync('git ls-files | grep -E "\\.(ts|tsx|js|jsx|py|rb|go|java|rs|sql|json|yaml|yml)$" | wc -l', { cwd: projectPath, timeout: 10000 });
314
+ return parseInt(stdout.trim(), 10) || 0;
315
+ }
316
+ catch {
317
+ return 0;
318
+ }
319
+ }
320
+ // ============================================================================
321
+ // GitHub API Integration (preserved from original)
322
+ // ============================================================================
35
323
  /**
36
324
  * Get changed files from GitHub API
37
325
  *
@@ -72,7 +360,6 @@ octokit, context) {
72
360
  }
73
361
  else {
74
362
  // Push event - compare with previous commit
75
- // For push events, compare HEAD^ with HEAD
76
363
  const base = `${context.sha}^`;
77
364
  const head = context.sha;
78
365
  try {
@@ -105,70 +392,51 @@ octokit, context) {
105
392
  }
106
393
  return changedFiles;
107
394
  }
108
- /**
109
- * Filter changed files to only scannable ones
110
- */
111
- export function filterScannableFiles(files) {
112
- return files.filter((file) => {
113
- // Exclude deleted files
114
- if (file.status === "removed")
115
- return false;
116
- // Check extension
117
- const ext = getExtension(file.filename);
118
- if (!SCANNABLE_EXTENSIONS.has(ext))
119
- return false;
120
- // Exclude common non-source paths
121
- if (file.filename.includes("node_modules/"))
122
- return false;
123
- if (file.filename.includes("dist/"))
124
- return false;
125
- if (file.filename.includes("build/"))
126
- return false;
127
- if (file.filename.includes("coverage/"))
128
- return false;
129
- if (file.filename.includes(".git/"))
130
- return false;
131
- if (file.filename.endsWith(".min.js"))
132
- return false;
133
- if (file.filename.endsWith(".bundle.js"))
134
- return false;
135
- return true;
136
- });
137
- }
138
- /**
139
- * Get file extension (including dot)
140
- */
141
- function getExtension(filename) {
142
- const lastDot = filename.lastIndexOf(".");
143
- if (lastDot === -1)
144
- return "";
145
- return filename.slice(lastDot).toLowerCase();
146
- }
147
- /**
148
- * Get the list of file paths to scan
149
- */
150
- export function getFilesToScan(changedFiles) {
151
- return filterScannableFiles(changedFiles).map((f) => f.filename);
152
- }
153
395
  /**
154
396
  * Check if any security-relevant files changed
155
397
  */
156
398
  export function hasSecurityRelevantChanges(changedFiles) {
157
- const securityFiles = [
158
- "package.json",
159
- "package-lock.json",
160
- "yarn.lock",
161
- "pnpm-lock.yaml",
162
- ".env",
163
- ".env.example",
164
- "auth",
165
- "security",
166
- "middleware",
167
- "api/",
168
- "routes/",
399
+ const securityPatterns = [
400
+ /package\.json$/,
401
+ /package-lock\.json$/,
402
+ /yarn\.lock$/,
403
+ /pnpm-lock\.yaml$/,
404
+ /\.env/,
405
+ /auth/i,
406
+ /security/i,
407
+ /middleware/i,
408
+ /api\//i,
409
+ /routes?\//i,
410
+ /session/i,
411
+ /token/i,
412
+ /password/i,
413
+ /crypt/i,
414
+ /jwt/i,
415
+ /oauth/i,
416
+ /supabase/i,
417
+ /prisma/i,
418
+ /schema\.prisma$/,
169
419
  ];
170
- return changedFiles.some((file) => securityFiles.some((pattern) => file.filename.includes(pattern) ||
171
- file.filename.toLowerCase().includes(pattern)));
420
+ return changedFiles.some((file) => securityPatterns.some((pattern) => pattern.test(file.filename)));
421
+ }
422
+ /**
423
+ * Get scan scope recommendation based on changes
424
+ */
425
+ export function getScanScopeRecommendation(changedFiles) {
426
+ // If no changes detected, suggest full scan
427
+ if (changedFiles.length === 0) {
428
+ return "full";
429
+ }
430
+ // If many files changed (>50), suggest full scan
431
+ if (changedFiles.length > 50) {
432
+ return "full";
433
+ }
434
+ // If security-relevant changes, suggest security-focused scan
435
+ if (hasSecurityRelevantChanges(changedFiles)) {
436
+ return "security-only";
437
+ }
438
+ // Otherwise, incremental scan
439
+ return "incremental";
172
440
  }
173
441
  /**
174
442
  * Generate summary of changed files
@@ -176,19 +444,25 @@ export function hasSecurityRelevantChanges(changedFiles) {
176
444
  export function generateChangeSummary(changedFiles) {
177
445
  const byStatus = new Map();
178
446
  const byExtension = new Map();
447
+ let totalAdditions = 0;
448
+ let totalDeletions = 0;
179
449
  for (const file of changedFiles) {
180
450
  byStatus.set(file.status, (byStatus.get(file.status) || 0) + 1);
181
- const ext = getExtension(file.filename) || "(no extension)";
451
+ const ext = extname(file.filename) || "(no extension)";
182
452
  byExtension.set(ext, (byExtension.get(ext) || 0) + 1);
453
+ totalAdditions += file.additions;
454
+ totalDeletions += file.deletions;
183
455
  }
184
456
  const lines = [
185
457
  `**Changed Files**: ${changedFiles.length}`,
458
+ `**Lines Changed**: +${totalAdditions} / -${totalDeletions}`,
186
459
  "",
187
460
  "| Status | Count |",
188
461
  "|--------|-------|",
189
462
  ];
190
463
  for (const [status, count] of byStatus) {
191
- lines.push(`| ${status} | ${count} |`);
464
+ const emoji = status === "added" ? "🆕" : status === "removed" ? "🗑️" : "📝";
465
+ lines.push(`| ${emoji} ${status} | ${count} |`);
192
466
  }
193
467
  lines.push("", "| Extension | Count |", "|-----------|-------|");
194
468
  // Sort by count descending
@@ -196,6 +470,51 @@ export function generateChangeSummary(changedFiles) {
196
470
  for (const [ext, count] of sortedExtensions.slice(0, 10)) {
197
471
  lines.push(`| ${ext} | ${count} |`);
198
472
  }
473
+ // Add scope recommendation
474
+ const scope = getScanScopeRecommendation(changedFiles);
475
+ lines.push("", `**Recommended Scope**: ${scope}`);
476
+ if (hasSecurityRelevantChanges(changedFiles)) {
477
+ lines.push("", "⚠️ **Security-relevant files detected** - recommend full security scan");
478
+ }
199
479
  return lines.join("\n");
200
480
  }
481
+ /**
482
+ * Group files by directory for parallel processing
483
+ */
484
+ export function groupFilesByDirectory(files) {
485
+ const groups = new Map();
486
+ for (const file of files) {
487
+ const dir = file.path.split("/").slice(0, -1).join("/") || ".";
488
+ const existing = groups.get(dir) || [];
489
+ existing.push(file);
490
+ groups.set(dir, existing);
491
+ }
492
+ return groups;
493
+ }
494
+ /**
495
+ * Prioritize files for scanning (security-critical first)
496
+ */
497
+ export function prioritizeFiles(files) {
498
+ const securityPatterns = [
499
+ /auth/i,
500
+ /security/i,
501
+ /middleware/i,
502
+ /api\//i,
503
+ /routes?\//i,
504
+ /session/i,
505
+ /token/i,
506
+ /password/i,
507
+ /\.env/,
508
+ /package\.json$/,
509
+ ];
510
+ return [...files].sort((a, b) => {
511
+ const aIsSecurity = securityPatterns.some((p) => p.test(a.path));
512
+ const bIsSecurity = securityPatterns.some((p) => p.test(b.path));
513
+ if (aIsSecurity && !bIsSecurity)
514
+ return -1;
515
+ if (!aIsSecurity && bIsSecurity)
516
+ return 1;
517
+ return 0;
518
+ });
519
+ }
201
520
  //# sourceMappingURL=diff-mode.js.map