@vreko/cli 3.0.1

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 (98) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +45 -0
  3. package/dist/CeremonyView-LQS7FTMK.js +134 -0
  4. package/dist/CeremonyView-LQS7FTMK.js.map +1 -0
  5. package/dist/InitApp-7K5DTYSW.js +1479 -0
  6. package/dist/InitApp-7K5DTYSW.js.map +1 -0
  7. package/dist/SkippedTestDetector-PJSKSOZR.js +7 -0
  8. package/dist/SkippedTestDetector-PJSKSOZR.js.map +1 -0
  9. package/dist/TuiApp-FX23XQBK.js +8 -0
  10. package/dist/TuiApp-FX23XQBK.js.map +1 -0
  11. package/dist/analysis-ABEO6RTN.js +8 -0
  12. package/dist/analysis-ABEO6RTN.js.map +1 -0
  13. package/dist/auth-XNBEBNPY.js +7669 -0
  14. package/dist/auth-XNBEBNPY.js.map +1 -0
  15. package/dist/ceremony-M7CXVBVA.js +45 -0
  16. package/dist/ceremony-M7CXVBVA.js.map +1 -0
  17. package/dist/chunk-A3QSZJPD.js +3147 -0
  18. package/dist/chunk-A3QSZJPD.js.map +1 -0
  19. package/dist/chunk-ASGZ5B6C.js +3969 -0
  20. package/dist/chunk-ASGZ5B6C.js.map +1 -0
  21. package/dist/chunk-DMXC2JTC.js +58 -0
  22. package/dist/chunk-DMXC2JTC.js.map +1 -0
  23. package/dist/chunk-EEBSK2IH.js +161 -0
  24. package/dist/chunk-EEBSK2IH.js.map +1 -0
  25. package/dist/chunk-EWOJGXRX.js +22 -0
  26. package/dist/chunk-EWOJGXRX.js.map +1 -0
  27. package/dist/chunk-F7GEJLP7.js +2389 -0
  28. package/dist/chunk-F7GEJLP7.js.map +1 -0
  29. package/dist/chunk-GOYL3F4T.js +605 -0
  30. package/dist/chunk-GOYL3F4T.js.map +1 -0
  31. package/dist/chunk-GRMRYWYS.js +17 -0
  32. package/dist/chunk-GRMRYWYS.js.map +1 -0
  33. package/dist/chunk-GSUGROXB.js +1951 -0
  34. package/dist/chunk-GSUGROXB.js.map +1 -0
  35. package/dist/chunk-H7773ONB.js +50 -0
  36. package/dist/chunk-H7773ONB.js.map +1 -0
  37. package/dist/chunk-HFQHU5LC.js +445 -0
  38. package/dist/chunk-HFQHU5LC.js.map +1 -0
  39. package/dist/chunk-IVHUBLJD.js +318 -0
  40. package/dist/chunk-IVHUBLJD.js.map +1 -0
  41. package/dist/chunk-KJWKY4L4.js +14 -0
  42. package/dist/chunk-KJWKY4L4.js.map +1 -0
  43. package/dist/chunk-MJVY2XUN.js +1793 -0
  44. package/dist/chunk-MJVY2XUN.js.map +1 -0
  45. package/dist/chunk-QWZVCJII.js +1797 -0
  46. package/dist/chunk-QWZVCJII.js.map +1 -0
  47. package/dist/chunk-VTSNRV3V.js +3237 -0
  48. package/dist/chunk-VTSNRV3V.js.map +1 -0
  49. package/dist/chunk-W5B4GTXR.js +1466 -0
  50. package/dist/chunk-W5B4GTXR.js.map +1 -0
  51. package/dist/chunk-WZEZLVOW.js +4995 -0
  52. package/dist/chunk-WZEZLVOW.js.map +1 -0
  53. package/dist/chunk-YPTTIXKC.js +199 -0
  54. package/dist/chunk-YPTTIXKC.js.map +1 -0
  55. package/dist/chunk-Z55UGM6X.js +6360 -0
  56. package/dist/chunk-Z55UGM6X.js.map +1 -0
  57. package/dist/chunk-ZIIRQODJ.js +110 -0
  58. package/dist/chunk-ZIIRQODJ.js.map +1 -0
  59. package/dist/chunk-ZSUQ4FMB.js +77 -0
  60. package/dist/chunk-ZSUQ4FMB.js.map +1 -0
  61. package/dist/client-JMTSZS3V.js +10 -0
  62. package/dist/client-JMTSZS3V.js.map +1 -0
  63. package/dist/deprecated-snap.js +19 -0
  64. package/dist/deprecated-snap.js.map +1 -0
  65. package/dist/dist-2KWBZFLA.js +14 -0
  66. package/dist/dist-2KWBZFLA.js.map +1 -0
  67. package/dist/dist-5ZYKNNU3.js +7 -0
  68. package/dist/dist-5ZYKNNU3.js.map +1 -0
  69. package/dist/dist-CP3RFHPI.js +11 -0
  70. package/dist/dist-CP3RFHPI.js.map +1 -0
  71. package/dist/gecko-53ITAGG6.js +56 -0
  72. package/dist/gecko-53ITAGG6.js.map +1 -0
  73. package/dist/guards-QAFC64NO.js +7 -0
  74. package/dist/guards-QAFC64NO.js.map +1 -0
  75. package/dist/index.js +57785 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/init-command-246JIVXM.js +7 -0
  78. package/dist/init-command-246JIVXM.js.map +1 -0
  79. package/dist/init-core-KAI7LCXZ.js +12 -0
  80. package/dist/init-core-KAI7LCXZ.js.map +1 -0
  81. package/dist/init-scan-RZNYDTUV.js +1919 -0
  82. package/dist/init-scan-RZNYDTUV.js.map +1 -0
  83. package/dist/local-service-adapter-6KNN6WQL.js +8 -0
  84. package/dist/local-service-adapter-6KNN6WQL.js.map +1 -0
  85. package/dist/secure-credentials-JXWAQLS2.js +306 -0
  86. package/dist/secure-credentials-JXWAQLS2.js.map +1 -0
  87. package/dist/tui-TPJPUS2R.js +111 -0
  88. package/dist/tui-TPJPUS2R.js.map +1 -0
  89. package/dist/vreko-dir-O3RLG7PI.js +8 -0
  90. package/dist/vreko-dir-O3RLG7PI.js.map +1 -0
  91. package/package.json +132 -0
  92. package/scripts/check-banned-words.ts +152 -0
  93. package/scripts/hooks/posttooluse-file-notify.sh +108 -0
  94. package/scripts/hooks/pretooluse-fragile-guard.sh +82 -0
  95. package/scripts/post-install-notice.js +24 -0
  96. package/scripts/postinstall.mjs +84 -0
  97. package/scripts/preuninstall.mjs +34 -0
  98. package/scripts/verify-jsx-transform.mjs +55 -0
@@ -0,0 +1,1919 @@
1
+ #!/usr/bin/env node
2
+ import { __name, __require } from './chunk-EWOJGXRX.js';
3
+ import { EventEmitter } from 'events';
4
+ import { execFile } from 'child_process';
5
+ import { promisify } from 'util';
6
+ import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { homedir } from 'os';
9
+
10
+ process.env.VREKO_CLI='true';process.env.NODE_NO_WARNINGS='1';
11
+ var __defProp = Object.defineProperty;
12
+ var __name2 = /* @__PURE__ */ __name((target, value) => __defProp(target, "name", {
13
+ value,
14
+ configurable: true
15
+ }), "__name");
16
+ var __require2 = /* @__PURE__ */ ((x) => typeof __require !== "undefined" ? __require : typeof Proxy !== "undefined" ? new Proxy(x, {
17
+ get: /* @__PURE__ */ __name((a, b) => (typeof __require !== "undefined" ? __require : a)[b], "get")
18
+ }) : x)(function(x) {
19
+ if (typeof __require !== "undefined") return __require.apply(this, arguments);
20
+ throw Error('Dynamic require of "' + x + '" is not supported');
21
+ });
22
+ var DiscoveryEmitter = class extends EventEmitter {
23
+ static {
24
+ __name(this, "DiscoveryEmitter");
25
+ }
26
+ static {
27
+ __name2(this, "DiscoveryEmitter");
28
+ }
29
+ emitDiscovery(discovery) {
30
+ if (discovery.confidence > 0.85) {
31
+ this.emit("discovery", discovery);
32
+ }
33
+ }
34
+ };
35
+ function createDiscoveryEmitter() {
36
+ return new DiscoveryEmitter();
37
+ }
38
+ __name(createDiscoveryEmitter, "createDiscoveryEmitter");
39
+ __name2(createDiscoveryEmitter, "createDiscoveryEmitter");
40
+ var execFileAsync = promisify(execFile);
41
+ var ARTIFACT_BASENAMES = /* @__PURE__ */ new Set([
42
+ "pnpm-lock.yaml",
43
+ "yarn.lock",
44
+ "package-lock.json",
45
+ "bun.lockb",
46
+ "Gemfile.lock",
47
+ "Cargo.lock",
48
+ "poetry.lock",
49
+ "go.sum",
50
+ "composer.lock",
51
+ "package.json"
52
+ ]);
53
+ var ARTIFACT_BUILD_SEGMENTS = /* @__PURE__ */ new Set([
54
+ "dist",
55
+ ".next",
56
+ "__generated__",
57
+ ".turbo",
58
+ ".cache"
59
+ ]);
60
+ function isArtifactFile(filePath) {
61
+ const parts = filePath.split("/");
62
+ const basename = parts[parts.length - 1];
63
+ if (ARTIFACT_BASENAMES.has(basename)) {
64
+ return true;
65
+ }
66
+ if (basename.endsWith(".d.ts") || basename.endsWith(".gen.ts") || basename.endsWith(".generated.ts") || basename.endsWith(".gen.js") || basename.endsWith(".generated.js") || basename.endsWith(".min.js") || basename.endsWith(".min.css")) {
67
+ return true;
68
+ }
69
+ for (const segment of parts.slice(0, -1)) {
70
+ if (ARTIFACT_BUILD_SEGMENTS.has(segment)) {
71
+ return true;
72
+ }
73
+ }
74
+ return false;
75
+ }
76
+ __name(isArtifactFile, "isArtifactFile");
77
+ __name2(isArtifactFile, "isArtifactFile");
78
+ function topLevelDir(filePath) {
79
+ const parts = filePath.split("/");
80
+ return parts.length > 1 ? parts[0] : ".";
81
+ }
82
+ __name(topLevelDir, "topLevelDir");
83
+ __name2(topLevelDir, "topLevelDir");
84
+ function withinHours(a, b, hours) {
85
+ return Math.abs(a.getTime() - b.getTime()) <= hours * 60 * 60 * 1e3;
86
+ }
87
+ __name(withinHours, "withinHours");
88
+ __name2(withinHours, "withinHours");
89
+ async function analyzeGitLog(repoPath, emitter) {
90
+ const signals = {
91
+ avgCommitsPerDay: 0,
92
+ commitFrequencyVariance: 0,
93
+ largeCommitRatio: 0,
94
+ diffusion: {
95
+ avgFilesTouchedPerCommit: 0,
96
+ crossDirectoryDiffusionRate: 0,
97
+ rollbackAdjacentCommitRate: 0,
98
+ largeSessionClusterRate: 0,
99
+ maxSingleCommitSpread: 0
100
+ },
101
+ rollbackCorrelation: {
102
+ resetAfterLargeCommitRate: 0,
103
+ resetAfterBranchSwitchRate: 0,
104
+ resetAfterLateNightRate: 0,
105
+ resetAfterCrossPackageRate: 0,
106
+ branchSpecificRollbackDensity: /* @__PURE__ */ new Map(),
107
+ medianTimeToRecovery: 0,
108
+ topRecoveryTrigger: "large-commit"
109
+ },
110
+ fileChurnRanking: [],
111
+ coChangeGraph: [],
112
+ hotspotFiles: [],
113
+ contributorCount: 0,
114
+ busFactorEstimate: 0,
115
+ mergeConflictFrequency: 0
116
+ };
117
+ try {
118
+ const { stdout: logStdout } = await execFileAsync(
119
+ "git",
120
+ // --no-merges: merge commits produce empty numstat output - file changes
121
+ // were already counted in the original feature commits, so merge commits
122
+ // only add noise and cause fileChurnRanking to appear empty on repos with
123
+ // dense merge-commit histories (e.g. large open-source monorepos on canary branches).
124
+ //
125
+ // Omit --all: scanning every ref (tags, remote branches) on large repos like
126
+ // Next.js (~34k commits) exhausts the 10-second timeout and SIGTERM-kills the
127
+ // child process, causing the catch block to swallow the error and return all-zero
128
+ // signals. HEAD-reachable history is sufficient for behavioral intelligence - the
129
+ // developer's active branch is what matters, not all historical branches.
130
+ [
131
+ "log",
132
+ "--format=COMMIT:%H %aI %P|%s",
133
+ "--numstat",
134
+ "--no-merges",
135
+ "-n",
136
+ "10000"
137
+ ],
138
+ {
139
+ cwd: repoPath,
140
+ maxBuffer: 20 * 1024 * 1024,
141
+ // 60 s: gives large monorepos (Next.js-scale, ~34k commits HEAD-reachable)
142
+ // enough headroom while still bounding the scan. The scan result is cached
143
+ // for 24 hours so a one-time 30-60s wait is acceptable on first run.
144
+ timeout: 6e4
145
+ }
146
+ );
147
+ const commits = parseLogOutput(logStdout);
148
+ if (commits.length === 0) {
149
+ return signals;
150
+ }
151
+ const dates = commits.map((c) => c.date.getTime()).sort((a, b) => a - b);
152
+ const timeSpanMs = dates[dates.length - 1] - dates[0];
153
+ const days = Math.max(timeSpanMs / (24 * 60 * 60 * 1e3), 1);
154
+ signals.avgCommitsPerDay = commits.length / days;
155
+ const commitsByDay = /* @__PURE__ */ new Map();
156
+ for (const c of commits) {
157
+ const dayKey = c.date.toISOString().slice(0, 10);
158
+ commitsByDay.set(dayKey, (commitsByDay.get(dayKey) || 0) + 1);
159
+ }
160
+ const dailyCounts = [
161
+ ...commitsByDay.values()
162
+ ];
163
+ const mean = dailyCounts.reduce((a, b) => a + b, 0) / Math.max(dailyCounts.length, 1);
164
+ signals.commitFrequencyVariance = Math.sqrt(dailyCounts.reduce((sum, c) => sum + (c - mean) ** 2, 0) / Math.max(dailyCounts.length, 1));
165
+ const largeCommits = commits.filter((c) => c.files.length >= 10);
166
+ signals.largeCommitRatio = largeCommits.length / commits.length * 100;
167
+ signals.diffusion = computeDiffusion(commits);
168
+ signals.rollbackCorrelation = computeRollbackCorrelation(commits);
169
+ signals.fileChurnRanking = computeFileChurn(commits);
170
+ signals.coChangeGraph = computeCoChangeGraph(commits);
171
+ signals.hotspotFiles = signals.fileChurnRanking.filter((f) => f.revertCount > 0 || f.changeCount > 5).sort((a, b) => {
172
+ const hasRevertA = a.revertCount > 0 ? 1 : 0;
173
+ const hasRevertB = b.revertCount > 0 ? 1 : 0;
174
+ if (hasRevertB !== hasRevertA) return hasRevertB - hasRevertA;
175
+ if (a.revertCount > 0 && b.revertCount > 0) {
176
+ return b.revertCount / b.changeCount - a.revertCount / a.changeCount;
177
+ }
178
+ return b.changeCount - a.changeCount;
179
+ }).slice(0, 10).map((f) => f.path);
180
+ const { stdout: shortlogOut } = await execFileAsync("git", [
181
+ "shortlog",
182
+ "-sn",
183
+ "--all"
184
+ ], {
185
+ cwd: repoPath,
186
+ maxBuffer: 1024 * 1024,
187
+ timeout: 1e4
188
+ });
189
+ const contributors = shortlogOut.split("\n").filter(Boolean);
190
+ signals.contributorCount = contributors.length;
191
+ signals.busFactorEstimate = Math.max(1, Math.min(signals.contributorCount, Math.ceil(signals.contributorCount * 0.3)));
192
+ const mergeCommits = commits.filter((c) => c.parentCount > 1);
193
+ signals.mergeConflictFrequency = commits.length > 0 ? mergeCommits.length / commits.length * 100 : 0;
194
+ if (emitter && signals.coChangeGraph.length > 0) {
195
+ const topPair = signals.coChangeGraph[0];
196
+ if (topPair.confidence > 0.9) {
197
+ emitter.emit("discovery", {
198
+ source: "gitlog",
199
+ confidence: topPair.confidence,
200
+ message: `${topPair.fileA} and ${topPair.fileB} change together ${Math.round(topPair.confidence * 100)}% of the time`,
201
+ detailMessage: "Vreko will track this relationship",
202
+ relatedInsightId: "co-change-instability"
203
+ });
204
+ }
205
+ }
206
+ if (emitter && signals.avgCommitsPerDay > 10) {
207
+ emitter.emit("discovery", {
208
+ source: "gitlog",
209
+ confidence: 0.88,
210
+ message: "High commit frequency detected",
211
+ detailMessage: "Vreko can group these logically"
212
+ });
213
+ }
214
+ } catch (_error) {
215
+ }
216
+ return signals;
217
+ }
218
+ __name(analyzeGitLog, "analyzeGitLog");
219
+ __name2(analyzeGitLog, "analyzeGitLog");
220
+ function parseLogOutput(stdout) {
221
+ const commits = [];
222
+ const lines = stdout.split("\n");
223
+ let current = null;
224
+ for (const line of lines) {
225
+ if (line.startsWith("COMMIT:")) {
226
+ if (current) {
227
+ commits.push(current);
228
+ }
229
+ const rest = line.slice(7);
230
+ const pipeIdx = rest.indexOf("|");
231
+ if (pipeIdx === -1) {
232
+ continue;
233
+ }
234
+ const metaPart = rest.slice(0, pipeIdx).trim();
235
+ const subject = rest.slice(pipeIdx + 1);
236
+ const parts = metaPart.split(/\s+/);
237
+ if (parts.length < 2) {
238
+ continue;
239
+ }
240
+ const sha = parts[0];
241
+ const dateStr = parts[1];
242
+ const parentHashes = parts.slice(2);
243
+ const date = new Date(dateStr);
244
+ if (Number.isNaN(date.getTime())) {
245
+ continue;
246
+ }
247
+ current = {
248
+ sha,
249
+ date,
250
+ parentCount: parentHashes.length,
251
+ files: [],
252
+ isRevert: subject.toLowerCase().startsWith("revert"),
253
+ subject
254
+ };
255
+ } else if (current && line.trim()) {
256
+ const parts = line.split(" ");
257
+ if (parts.length >= 3) {
258
+ const added = parts[0] === "-" ? 0 : Number.parseInt(parts[0], 10) || 0;
259
+ const deleted = parts[1] === "-" ? 0 : Number.parseInt(parts[1], 10) || 0;
260
+ current.files.push({
261
+ path: parts[2],
262
+ added,
263
+ deleted
264
+ });
265
+ }
266
+ }
267
+ }
268
+ if (current) {
269
+ commits.push(current);
270
+ }
271
+ return commits;
272
+ }
273
+ __name(parseLogOutput, "parseLogOutput");
274
+ __name2(parseLogOutput, "parseLogOutput");
275
+ function computeDiffusion(commits) {
276
+ if (commits.length === 0) {
277
+ return {
278
+ avgFilesTouchedPerCommit: 0,
279
+ crossDirectoryDiffusionRate: 0,
280
+ rollbackAdjacentCommitRate: 0,
281
+ largeSessionClusterRate: 0,
282
+ maxSingleCommitSpread: 0
283
+ };
284
+ }
285
+ const fileCounts = commits.map((c) => c.files.length);
286
+ const avgFilesTouchedPerCommit = fileCounts.reduce((a, b) => a + b, 0) / commits.length;
287
+ const crossDirCommits = commits.filter((c) => {
288
+ const dirs = new Set(c.files.map((f) => topLevelDir(f.path)));
289
+ return dirs.size >= 2;
290
+ });
291
+ const crossDirectoryDiffusionRate = crossDirCommits.length / commits.length * 100;
292
+ let rollbackAdjacentCount = 0;
293
+ const sortedByDate = [
294
+ ...commits
295
+ ].sort((a, b) => a.date.getTime() - b.date.getTime());
296
+ for (let i = 0; i < sortedByDate.length; i++) {
297
+ for (let j = i + 1; j < sortedByDate.length; j++) {
298
+ if (!withinHours(sortedByDate[i].date, sortedByDate[j].date, 6)) {
299
+ break;
300
+ }
301
+ if (sortedByDate[j].isRevert) {
302
+ rollbackAdjacentCount++;
303
+ break;
304
+ }
305
+ }
306
+ }
307
+ const rollbackAdjacentCommitRate = rollbackAdjacentCount / commits.length * 100;
308
+ let clusterCount = 0;
309
+ for (let i = 0; i < sortedByDate.length; i++) {
310
+ let windowEnd = i;
311
+ while (windowEnd + 1 < sortedByDate.length && withinHours(sortedByDate[i].date, sortedByDate[windowEnd + 1].date, 2)) {
312
+ windowEnd++;
313
+ }
314
+ if (windowEnd - i + 1 >= 5) {
315
+ clusterCount++;
316
+ i = windowEnd;
317
+ }
318
+ }
319
+ const largeSessionClusterRate = clusterCount / Math.max(commits.length / 5, 1) * 100;
320
+ const maxSingleCommitSpread = Math.max(...fileCounts, 0);
321
+ return {
322
+ avgFilesTouchedPerCommit,
323
+ crossDirectoryDiffusionRate,
324
+ rollbackAdjacentCommitRate,
325
+ largeSessionClusterRate,
326
+ maxSingleCommitSpread
327
+ };
328
+ }
329
+ __name(computeDiffusion, "computeDiffusion");
330
+ __name2(computeDiffusion, "computeDiffusion");
331
+ function computeRollbackCorrelation(commits) {
332
+ const result = {
333
+ resetAfterLargeCommitRate: 0,
334
+ resetAfterBranchSwitchRate: 0,
335
+ resetAfterLateNightRate: 0,
336
+ resetAfterCrossPackageRate: 0,
337
+ branchSpecificRollbackDensity: /* @__PURE__ */ new Map(),
338
+ medianTimeToRecovery: 0,
339
+ topRecoveryTrigger: "large-commit"
340
+ };
341
+ const sorted = [
342
+ ...commits
343
+ ].sort((a, b) => a.date.getTime() - b.date.getTime());
344
+ const reverts = sorted.filter((c) => c.isRevert);
345
+ if (reverts.length === 0) {
346
+ return result;
347
+ }
348
+ let afterLargeCount = 0;
349
+ let afterLateNightCount = 0;
350
+ let afterCrossPackageCount = 0;
351
+ const recoveryTimes = [];
352
+ for (const revert of reverts) {
353
+ const precedingIdx = sorted.findIndex((c) => c.sha === revert.sha) - 1;
354
+ if (precedingIdx < 0) {
355
+ continue;
356
+ }
357
+ for (let i = precedingIdx; i >= 0; i--) {
358
+ const preceding = sorted[i];
359
+ if (!withinHours(preceding.date, revert.date, 6)) {
360
+ break;
361
+ }
362
+ const timeDiff = (revert.date.getTime() - preceding.date.getTime()) / (1e3 * 60);
363
+ recoveryTimes.push(timeDiff);
364
+ if (preceding.files.length >= 5) {
365
+ afterLargeCount++;
366
+ }
367
+ if (preceding.date.getHours() >= 22 || preceding.date.getHours() < 5) {
368
+ afterLateNightCount++;
369
+ }
370
+ const dirs = new Set(preceding.files.map((f) => topLevelDir(f.path)));
371
+ if (dirs.size >= 2) {
372
+ afterCrossPackageCount++;
373
+ }
374
+ break;
375
+ }
376
+ }
377
+ const total = Math.max(reverts.length, 1);
378
+ result.resetAfterLargeCommitRate = afterLargeCount / total * 100;
379
+ result.resetAfterLateNightRate = afterLateNightCount / total * 100;
380
+ result.resetAfterCrossPackageRate = afterCrossPackageCount / total * 100;
381
+ if (recoveryTimes.length > 0) {
382
+ recoveryTimes.sort((a, b) => a - b);
383
+ const mid = Math.floor(recoveryTimes.length / 2);
384
+ result.medianTimeToRecovery = recoveryTimes.length % 2 === 0 ? (recoveryTimes[mid - 1] + recoveryTimes[mid]) / 2 : recoveryTimes[mid];
385
+ }
386
+ const triggers = {
387
+ "large-commit": afterLargeCount,
388
+ "late-night": afterLateNightCount,
389
+ "cross-package": afterCrossPackageCount,
390
+ "branch-switch": 0
391
+ };
392
+ result.topRecoveryTrigger = Object.entries(triggers).sort((a, b) => b[1] - a[1])[0][0];
393
+ return result;
394
+ }
395
+ __name(computeRollbackCorrelation, "computeRollbackCorrelation");
396
+ __name2(computeRollbackCorrelation, "computeRollbackCorrelation");
397
+ function computeFileChurn(commits) {
398
+ const churnMap = /* @__PURE__ */ new Map();
399
+ for (const commit of commits) {
400
+ for (const file of commit.files) {
401
+ if (isArtifactFile(file.path)) continue;
402
+ const entry = churnMap.get(file.path) || {
403
+ changeCount: 0,
404
+ totalLines: 0,
405
+ revertCount: 0,
406
+ lastChanged: commit.date
407
+ };
408
+ entry.changeCount++;
409
+ entry.totalLines += file.added + file.deleted;
410
+ if (commit.isRevert) {
411
+ entry.revertCount++;
412
+ }
413
+ if (commit.date > entry.lastChanged) {
414
+ entry.lastChanged = commit.date;
415
+ }
416
+ churnMap.set(file.path, entry);
417
+ }
418
+ }
419
+ return [
420
+ ...churnMap.entries()
421
+ ].map(([path, data]) => ({
422
+ path,
423
+ changeCount: data.changeCount,
424
+ avgLinesChanged: data.changeCount > 0 ? data.totalLines / data.changeCount : 0,
425
+ revertCount: data.revertCount,
426
+ lastChanged: data.lastChanged.toISOString()
427
+ })).sort((a, b) => b.changeCount - a.changeCount).slice(0, 50);
428
+ }
429
+ __name(computeFileChurn, "computeFileChurn");
430
+ __name2(computeFileChurn, "computeFileChurn");
431
+ function computeCoChangeGraph(commits) {
432
+ const pairCounts = /* @__PURE__ */ new Map();
433
+ const fileCounts = /* @__PURE__ */ new Map();
434
+ for (const commit of commits) {
435
+ const paths = commit.files.map((f) => f.path);
436
+ for (const p of paths) {
437
+ fileCounts.set(p, (fileCounts.get(p) || 0) + 1);
438
+ }
439
+ if (paths.length < 2 || paths.length > 20) {
440
+ continue;
441
+ }
442
+ for (let i = 0; i < paths.length; i++) {
443
+ for (let j = i + 1; j < paths.length; j++) {
444
+ const key = [
445
+ paths[i],
446
+ paths[j]
447
+ ].sort().join("|||");
448
+ pairCounts.set(key, (pairCounts.get(key) || 0) + 1);
449
+ }
450
+ }
451
+ }
452
+ const edges = [];
453
+ for (const [key, count] of pairCounts) {
454
+ if (count < 3) {
455
+ continue;
456
+ }
457
+ const [fileA, fileB] = key.split("|||");
458
+ if (isArtifactFile(fileA) && isArtifactFile(fileB)) {
459
+ continue;
460
+ }
461
+ const maxChanges = Math.max(fileCounts.get(fileA) || 1, fileCounts.get(fileB) || 1);
462
+ edges.push({
463
+ fileA,
464
+ fileB,
465
+ coChangeCount: count,
466
+ confidence: count / maxChanges
467
+ });
468
+ }
469
+ return edges.sort((a, b) => b.confidence - a.confidence).slice(0, 20);
470
+ }
471
+ __name(computeCoChangeGraph, "computeCoChangeGraph");
472
+ __name2(computeCoChangeGraph, "computeCoChangeGraph");
473
+ function generateInsights(signals, baseline) {
474
+ const insights = [];
475
+ if (signals.reflog && signals.reflog.resetToHEADCount > 5) {
476
+ const rc = signals.gitlog?.rollbackCorrelation;
477
+ const correlationDetail = rc?.topRecoveryTrigger ? ` Most correlate with ${rc.topRecoveryTrigger.replace(/-/g, " ")} changes.` : "";
478
+ const comparison = baseline ? `${(signals.reflog.resetToHEADCount / Math.max(baseline.baselines.avgResetToHEADRate, 1)).toFixed(1)}x the average for repos like yours` : void 0;
479
+ insights.push({
480
+ id: "high-reset-rate",
481
+ severity: signals.reflog.resetToHEADCount > 15 ? "critical" : "warning",
482
+ observation: `${signals.reflog.resetToHEADCount} recovery events in recent history`,
483
+ whyItMatters: `Frequent resets indicate volatile development sessions.${correlationDetail}`,
484
+ whatWeWillDo: "Increase snapshot density during high-diffusion sessions",
485
+ comparison
486
+ });
487
+ }
488
+ if (signals.gitlog) {
489
+ const fragileFiles = signals.gitlog.fileChurnRanking.filter((f) => f.changeCount >= 20 && f.revertCount >= 3);
490
+ for (const file of fragileFiles.slice(0, 2)) {
491
+ const isWeaklyTested = signals.structure?.testAdjacency.weaklyTestedHotspots.some((h) => file.path.startsWith(h));
492
+ insights.push({
493
+ id: `fragile-hotspot-${file.path}`,
494
+ severity: "warning",
495
+ observation: `${file.path} changed ${file.changeCount}x, reverted ${file.revertCount}x${isWeaklyTested ? ". Weakly test-adjacent" : ""}`,
496
+ whyItMatters: "High-churn files with reverts are the most likely to need recovery",
497
+ whatWeWillDo: "Watch this file as a fragile hotspot with enhanced monitoring"
498
+ });
499
+ }
500
+ }
501
+ if (signals.gitlog && signals.gitlog.diffusion.crossDirectoryDiffusionRate > 30) {
502
+ const rc = signals.gitlog.rollbackCorrelation;
503
+ const comparison = baseline ? `${(signals.gitlog.diffusion.crossDirectoryDiffusionRate / Math.max(baseline.baselines.avgCrossDirectoryDiffusionRate, 1)).toFixed(1)}x the average` : void 0;
504
+ insights.push({
505
+ id: "cross-directory-blast",
506
+ severity: "warning",
507
+ observation: `${Math.round(signals.gitlog.diffusion.crossDirectoryDiffusionRate)}% of commits span multiple directories`,
508
+ whyItMatters: `Wide-reaching changes are harder to roll back safely. ${rc.resetAfterCrossPackageRate > 10 ? `${Math.round(rc.resetAfterCrossPackageRate)}% of reverts follow cross-package changes.` : ""}`.trim(),
509
+ whatWeWillDo: "Track cross-directory blast radius and auto-snapshot before high-diffusion commits",
510
+ comparison
511
+ });
512
+ }
513
+ if (signals.gitlog) {
514
+ const strongPairs = signals.gitlog.coChangeGraph.filter((e) => e.confidence > 0.7);
515
+ if (strongPairs.length > 0) {
516
+ const top = strongPairs[0];
517
+ insights.push({
518
+ id: "co-change-instability",
519
+ severity: "info",
520
+ observation: `${top.fileA} and ${top.fileB} co-change at ${Math.round(top.confidence * 100)}% rate`,
521
+ whyItMatters: "Tightly coupled files that change together may break if one is modified alone",
522
+ whatWeWillDo: "Track co-change relationships and warn if one file changes without the other"
523
+ });
524
+ }
525
+ }
526
+ if (signals.reflog && signals.reflog.lateNightRatio > 0.2) {
527
+ insights.push({
528
+ id: "temporal-risk",
529
+ severity: "notable",
530
+ observation: `${Math.round(signals.reflog.lateNightRatio * 100)}% of activity occurs during late-night hours`,
531
+ whyItMatters: "Late-night development sessions correlate with higher error rates",
532
+ whatWeWillDo: "Increase snapshot frequency during late-night sessions"
533
+ });
534
+ }
535
+ if (signals.structure) {
536
+ const weakPackages = signals.structure.testAdjacency.packageTestAdjacency.filter((p) => p.churnScore > 2 && p.testRatio < 0.1);
537
+ if (weakPackages.length > 0) {
538
+ insights.push({
539
+ id: "weak-test-adjacency",
540
+ severity: "notable",
541
+ observation: `${weakPackages.length} high-churn package(s) with minimal test coverage`,
542
+ whyItMatters: "Code that changes frequently without tests is most likely to break unexpectedly",
543
+ whatWeWillDo: "Prioritize snapshot protection for untested hotspots"
544
+ });
545
+ }
546
+ }
547
+ if (signals.reflog && signals.reflog.branchAbandonmentRate > 30) {
548
+ insights.push({
549
+ id: "branch-abandonment",
550
+ severity: "info",
551
+ observation: `${Math.round(signals.reflog.branchAbandonmentRate)}% of branches are never merged`,
552
+ whyItMatters: "Abandoned branches may indicate experimental work that could benefit from snapshot protection",
553
+ whatWeWillDo: "Track branch lifecycle patterns to identify recovery-prone experiments"
554
+ });
555
+ }
556
+ if (signals.gitlog && signals.gitlog.busFactorEstimate < 2) {
557
+ const singleAuthorFiles = signals.gitlog.fileChurnRanking.filter((f) => f.changeCount >= 10);
558
+ if (singleAuthorFiles.length > 0) {
559
+ insights.push({
560
+ id: "bus-factor-risk",
561
+ severity: "notable",
562
+ observation: `Low bus factor: ${singleAuthorFiles.length} high-churn files maintained by a single contributor`,
563
+ whyItMatters: "Single-contributor files are at higher risk if that contributor is unavailable",
564
+ whatWeWillDo: "Enhanced snapshot protection for single-contributor hotspots"
565
+ });
566
+ }
567
+ }
568
+ if (baseline && signals.reflog) {
569
+ if (signals.reflog.forceResetRate > baseline.baselines.avgForceResetRate * 1.5) {
570
+ insights.push({
571
+ id: "above-baseline-resets",
572
+ severity: "info",
573
+ observation: "Force reset rate above typical for your repo type",
574
+ whyItMatters: "Higher-than-average reset frequency suggests more volatile workflows",
575
+ whatWeWillDo: "Calibrate protection levels against aggregate baselines",
576
+ comparison: `${(signals.reflog.forceResetRate / Math.max(baseline.baselines.avgForceResetRate, 0.1)).toFixed(1)}x the average`
577
+ });
578
+ }
579
+ }
580
+ if (signals.gitlog && signals.topology) {
581
+ const highFanInPaths = new Map(signals.topology.highFanInFiles.map((f) => [
582
+ f.path,
583
+ f.importedByCount
584
+ ]));
585
+ for (const hotspotPath of signals.gitlog.hotspotFiles) {
586
+ const importedByCount = highFanInPaths.get(hotspotPath);
587
+ if (importedByCount !== void 0) {
588
+ const churnEntry = signals.gitlog.fileChurnRanking.find((f) => f.path === hotspotPath);
589
+ const changeCount = churnEntry?.changeCount ?? 0;
590
+ const revertCount = churnEntry?.revertCount ?? 0;
591
+ insights.push({
592
+ id: "fused-structural-temporal-hotspot",
593
+ severity: "critical",
594
+ observation: `\`${hotspotPath}\` changed ${changeCount} times, reverted ${revertCount} times, and is imported by ${importedByCount} files`,
595
+ whyItMatters: "This file is both structurally critical (high blast radius) and behaviorally unstable (frequent changes and rollbacks). Changes here cascade across your codebase.",
596
+ whatWeWillDo: "Vreko will apply maximum protection density and provide structural context to AI tools before they modify this file."
597
+ });
598
+ break;
599
+ }
600
+ }
601
+ }
602
+ const locked = selectLockedInsight(signals);
603
+ return {
604
+ insights: insights.slice(0, 7),
605
+ locked
606
+ };
607
+ }
608
+ __name(generateInsights, "generateInsights");
609
+ __name2(generateInsights, "generateInsights");
610
+ function selectLockedInsight(signals) {
611
+ if (signals.reflog && signals.reflog.lateNightRatio > 0.2) {
612
+ return {
613
+ id: "temporal-risk-windows",
614
+ teaser: "Identify your personal risk windows -- the times and patterns where recovery is most likely",
615
+ requirement: "Requires observed coding sessions to validate temporal patterns",
616
+ unlockCondition: {
617
+ type: "days_observed",
618
+ days: 3
619
+ }
620
+ };
621
+ }
622
+ if (signals.gitlog && signals.gitlog.busFactorEstimate < 2) {
623
+ return {
624
+ id: "collaboration-risk",
625
+ teaser: "Detect when your coding patterns diverge from your stable baseline",
626
+ requirement: "Requires observed coding sessions to establish baseline patterns",
627
+ unlockCondition: {
628
+ type: "days_observed",
629
+ days: 5
630
+ }
631
+ };
632
+ }
633
+ return {
634
+ id: "session-risk-windows",
635
+ teaser: "Identify your personal risk windows",
636
+ requirement: "Requires observed coding sessions",
637
+ unlockCondition: {
638
+ type: "days_observed",
639
+ days: 3
640
+ }
641
+ };
642
+ }
643
+ __name(selectLockedInsight, "selectLockedInsight");
644
+ __name2(selectLockedInsight, "selectLockedInsight");
645
+ var execFileAsync2 = promisify(execFile);
646
+ function parseDateFromLine(line) {
647
+ const match = line.match(/\{([^}]+)\}/);
648
+ if (!match) {
649
+ return null;
650
+ }
651
+ const d = new Date(match[1]);
652
+ return Number.isNaN(d.getTime()) ? null : d;
653
+ }
654
+ __name(parseDateFromLine, "parseDateFromLine");
655
+ __name2(parseDateFromLine, "parseDateFromLine");
656
+ function parseBranchFromLine(line) {
657
+ const match = line.match(/refs\/heads\/([^@]+)@/);
658
+ return match ? match[1] : null;
659
+ }
660
+ __name(parseBranchFromLine, "parseBranchFromLine");
661
+ __name2(parseBranchFromLine, "parseBranchFromLine");
662
+ async function scanReflog(repoPath, emitter) {
663
+ const signals = {
664
+ forceResetRate: 0,
665
+ rebaseFrequency: 0,
666
+ branchAbandonmentRate: 0,
667
+ contextSwitchRate: 0,
668
+ peakActivityWindows: [],
669
+ weekendActivityRatio: 0,
670
+ lateNightRatio: 0,
671
+ resetToHEADCount: 0,
672
+ reflogChurnByBranch: /* @__PURE__ */ new Map(),
673
+ avgTimeBetweenResets: 0
674
+ };
675
+ try {
676
+ const { stdout } = await execFileAsync2("git", [
677
+ "reflog",
678
+ "--all",
679
+ "--date=iso"
680
+ ], {
681
+ cwd: repoPath,
682
+ maxBuffer: 10 * 1024 * 1024,
683
+ timeout: 1e4
684
+ });
685
+ const lines = stdout.split("\n").filter(Boolean);
686
+ if (lines.length === 0) {
687
+ return signals;
688
+ }
689
+ const timestamps = [];
690
+ const resetTimestamps = [];
691
+ let resetCount = 0;
692
+ let forceResetCount = 0;
693
+ let rebaseCount = 0;
694
+ let checkoutCount = 0;
695
+ let weekendCount = 0;
696
+ let lateNightCount = 0;
697
+ const hourCounts = new Array(24).fill(0);
698
+ const branchesCreated = /* @__PURE__ */ new Set();
699
+ const branchesMerged = /* @__PURE__ */ new Set();
700
+ const branchChurn = /* @__PURE__ */ new Map();
701
+ for (const line of lines) {
702
+ const date = parseDateFromLine(line);
703
+ if (date) {
704
+ timestamps.push(date);
705
+ const hour = date.getHours();
706
+ hourCounts[hour]++;
707
+ const day = date.getDay();
708
+ if (day === 0 || day === 6) {
709
+ weekendCount++;
710
+ }
711
+ if (hour >= 22 || hour < 5) {
712
+ lateNightCount++;
713
+ }
714
+ }
715
+ const branch = parseBranchFromLine(line);
716
+ if (branch) {
717
+ branchChurn.set(branch, (branchChurn.get(branch) || 0) + 1);
718
+ }
719
+ if (line.includes("reset: moving to HEAD")) {
720
+ resetCount++;
721
+ if (date) {
722
+ resetTimestamps.push(date.getTime());
723
+ }
724
+ }
725
+ if (line.includes("reset: moving to") && !line.includes("reset: moving to HEAD")) {
726
+ forceResetCount++;
727
+ if (date) {
728
+ resetTimestamps.push(date.getTime());
729
+ }
730
+ }
731
+ if (line.includes("rebase")) {
732
+ rebaseCount++;
733
+ }
734
+ if (line.includes("checkout: moving from")) {
735
+ checkoutCount++;
736
+ }
737
+ if (line.includes("branch: Created from")) {
738
+ if (branch) {
739
+ branchesCreated.add(branch);
740
+ }
741
+ }
742
+ if (line.includes("merge")) {
743
+ const mergedMatch = line.match(/merge\s+(\S+)/);
744
+ if (mergedMatch) {
745
+ branchesMerged.add(mergedMatch[1].replace(/[:;]$/, ""));
746
+ }
747
+ }
748
+ }
749
+ const sortedTimestamps = timestamps.map((d) => d.getTime()).sort((a, b) => a - b);
750
+ const timeSpanMs = sortedTimestamps.length > 1 ? sortedTimestamps[sortedTimestamps.length - 1] - sortedTimestamps[0] : 7 * 24 * 60 * 60 * 1e3;
751
+ const weeks = Math.max(timeSpanMs / (7 * 24 * 60 * 60 * 1e3), 1);
752
+ const days = Math.max(timeSpanMs / (24 * 60 * 60 * 1e3), 1);
753
+ signals.resetToHEADCount = resetCount;
754
+ signals.forceResetRate = (forceResetCount + resetCount) / weeks;
755
+ signals.rebaseFrequency = rebaseCount / weeks;
756
+ signals.contextSwitchRate = checkoutCount / days;
757
+ const abandoned = [
758
+ ...branchesCreated
759
+ ].filter((b) => !branchesMerged.has(b));
760
+ signals.branchAbandonmentRate = branchesCreated.size > 0 ? abandoned.length / branchesCreated.size * 100 : 0;
761
+ signals.weekendActivityRatio = lines.length > 0 ? weekendCount / lines.length : 0;
762
+ signals.lateNightRatio = lines.length > 0 ? lateNightCount / lines.length : 0;
763
+ const meanActivity = lines.length / 24;
764
+ const stddev = Math.sqrt(hourCounts.reduce((sum, c) => sum + (c - meanActivity) ** 2, 0) / 24);
765
+ const threshold = meanActivity + stddev;
766
+ const peakWindows = [];
767
+ let windowStart = null;
768
+ for (let h = 0; h < 24; h++) {
769
+ if (hourCounts[h] > threshold) {
770
+ if (windowStart === null) {
771
+ windowStart = h;
772
+ }
773
+ } else {
774
+ if (windowStart !== null) {
775
+ peakWindows.push({
776
+ startHour: windowStart,
777
+ endHour: h
778
+ });
779
+ windowStart = null;
780
+ }
781
+ }
782
+ }
783
+ if (windowStart !== null) {
784
+ peakWindows.push({
785
+ startHour: windowStart,
786
+ endHour: 24
787
+ });
788
+ }
789
+ signals.peakActivityWindows = peakWindows;
790
+ signals.reflogChurnByBranch = branchChurn;
791
+ if (resetTimestamps.length > 1) {
792
+ const sorted = resetTimestamps.sort((a, b) => a - b);
793
+ let totalGap = 0;
794
+ for (let i = 1; i < sorted.length; i++) {
795
+ totalGap += sorted[i] - sorted[i - 1];
796
+ }
797
+ signals.avgTimeBetweenResets = totalGap / (sorted.length - 1) / (1e3 * 60);
798
+ }
799
+ if (emitter && resetCount > 5) {
800
+ emitter.emit("discovery", {
801
+ source: "reflog",
802
+ confidence: 0.9,
803
+ message: `Found ${resetCount} reset events in your history`,
804
+ detailMessage: "Vreko will learn your rollback patterns"
805
+ });
806
+ }
807
+ if (emitter && signals.lateNightRatio > 0.2) {
808
+ emitter.emit("discovery", {
809
+ source: "reflog",
810
+ confidence: 0.88,
811
+ message: `${Math.round(signals.lateNightRatio * 100)}% of git activity happens after 10pm`,
812
+ detailMessage: "Vreko will increase protection during late-night sessions"
813
+ });
814
+ }
815
+ } catch (_error) {
816
+ }
817
+ return signals;
818
+ }
819
+ __name(scanReflog, "scanReflog");
820
+ __name2(scanReflog, "scanReflog");
821
+ var execFileAsync3 = promisify(execFile);
822
+ function countFiles(dir, predicate, maxDepth = 4, currentDepth = 0) {
823
+ if (currentDepth >= maxDepth || !existsSync(dir)) {
824
+ return 0;
825
+ }
826
+ let count = 0;
827
+ try {
828
+ const entries = readdirSync(dir, {
829
+ withFileTypes: true
830
+ });
831
+ for (const entry of entries) {
832
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist" || entry.name === "build") {
833
+ continue;
834
+ }
835
+ if (entry.isFile() && predicate(entry.name)) {
836
+ count++;
837
+ } else if (entry.isDirectory()) {
838
+ count += countFiles(join(dir, entry.name), predicate, maxDepth, currentDepth + 1);
839
+ }
840
+ }
841
+ } catch {
842
+ }
843
+ return count;
844
+ }
845
+ __name(countFiles, "countFiles");
846
+ __name2(countFiles, "countFiles");
847
+ function detectTestFramework(repoPath) {
848
+ if (existsSync(join(repoPath, "vitest.config.ts")) || existsSync(join(repoPath, "vitest.config.js"))) {
849
+ return "vitest";
850
+ }
851
+ if (existsSync(join(repoPath, "jest.config.ts")) || existsSync(join(repoPath, "jest.config.js"))) {
852
+ return "jest";
853
+ }
854
+ if (existsSync(join(repoPath, ".mocharc.yml")) || existsSync(join(repoPath, ".mocharc.json"))) {
855
+ return "mocha";
856
+ }
857
+ if (existsSync(join(repoPath, "pytest.ini")) || existsSync(join(repoPath, "pyproject.toml"))) {
858
+ return "pytest";
859
+ }
860
+ try {
861
+ const pkg = JSON.parse(readFileSync(join(repoPath, "package.json"), "utf-8"));
862
+ const allDeps = {
863
+ ...pkg.dependencies,
864
+ ...pkg.devDependencies
865
+ };
866
+ if (allDeps.vitest) {
867
+ return "vitest";
868
+ }
869
+ if (allDeps.jest) {
870
+ return "jest";
871
+ }
872
+ if (allDeps.mocha) {
873
+ return "mocha";
874
+ }
875
+ } catch {
876
+ }
877
+ return null;
878
+ }
879
+ __name(detectTestFramework, "detectTestFramework");
880
+ __name2(detectTestFramework, "detectTestFramework");
881
+ function detectFramework(repoPath) {
882
+ if (existsSync(join(repoPath, "next.config.ts")) || existsSync(join(repoPath, "next.config.js")) || existsSync(join(repoPath, "next.config.mjs"))) {
883
+ return "next.js";
884
+ }
885
+ if (existsSync(join(repoPath, "nuxt.config.ts"))) {
886
+ return "nuxt";
887
+ }
888
+ if (existsSync(join(repoPath, "angular.json"))) {
889
+ return "angular";
890
+ }
891
+ if (existsSync(join(repoPath, "svelte.config.js"))) {
892
+ return "svelte";
893
+ }
894
+ if (existsSync(join(repoPath, "vite.config.ts")) || existsSync(join(repoPath, "vite.config.js"))) {
895
+ return "vite";
896
+ }
897
+ try {
898
+ const pkg = JSON.parse(readFileSync(join(repoPath, "package.json"), "utf-8"));
899
+ const allDeps = {
900
+ ...pkg.dependencies,
901
+ ...pkg.devDependencies
902
+ };
903
+ if (allDeps.next) {
904
+ return "next.js";
905
+ }
906
+ if (allDeps.react) {
907
+ return "react";
908
+ }
909
+ if (allDeps.vue) {
910
+ return "vue";
911
+ }
912
+ if (allDeps.express) {
913
+ return "express";
914
+ }
915
+ if (allDeps.hono) {
916
+ return "hono";
917
+ }
918
+ } catch {
919
+ }
920
+ return null;
921
+ }
922
+ __name(detectFramework, "detectFramework");
923
+ __name2(detectFramework, "detectFramework");
924
+ function detectPackageDirs(repoPath) {
925
+ const dirs = [];
926
+ const candidates = [
927
+ "packages",
928
+ "apps",
929
+ "libs",
930
+ "modules",
931
+ "services"
932
+ ];
933
+ for (const candidate of candidates) {
934
+ const candidatePath = join(repoPath, candidate);
935
+ if (!existsSync(candidatePath)) {
936
+ continue;
937
+ }
938
+ try {
939
+ const entries = readdirSync(candidatePath, {
940
+ withFileTypes: true
941
+ });
942
+ for (const entry of entries) {
943
+ if (entry.isDirectory() && existsSync(join(candidatePath, entry.name, "package.json"))) {
944
+ dirs.push(join(candidate, entry.name));
945
+ }
946
+ }
947
+ } catch {
948
+ }
949
+ }
950
+ if (dirs.length === 0) {
951
+ dirs.push(".");
952
+ }
953
+ return dirs;
954
+ }
955
+ __name(detectPackageDirs, "detectPackageDirs");
956
+ __name2(detectPackageDirs, "detectPackageDirs");
957
+ function computeTestAdjacency(repoPath, packageDirs, churnFiles) {
958
+ const isTestFile = /* @__PURE__ */ __name2((name) => name.endsWith(".test.ts") || name.endsWith(".test.tsx") || name.endsWith(".test.js") || name.endsWith(".spec.ts") || name.endsWith(".spec.tsx") || name.endsWith(".spec.js"), "isTestFile");
959
+ const isSourceFile = /* @__PURE__ */ __name2((name) => (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !isTestFile(name) && !name.endsWith(".d.ts") && !name.endsWith(".config.ts") && !name.endsWith(".config.js"), "isSourceFile");
960
+ const packageTestAdjacency = [];
961
+ const churnSet = new Set(churnFiles || []);
962
+ for (const pkgDir of packageDirs) {
963
+ const fullPath = join(repoPath, pkgDir);
964
+ const srcPath = existsSync(join(fullPath, "src")) ? join(fullPath, "src") : fullPath;
965
+ const hasTestDir = existsSync(join(fullPath, "__tests__")) || existsSync(join(fullPath, "test")) || existsSync(join(fullPath, "tests"));
966
+ const testFileCount = countFiles(fullPath, isTestFile);
967
+ const sourceFileCount = countFiles(srcPath, isSourceFile);
968
+ const testRatio = sourceFileCount > 0 ? testFileCount / sourceFileCount : 0;
969
+ let churnScore = 0;
970
+ if (churnSet.size > 0) {
971
+ for (const f of churnSet) {
972
+ if (f.startsWith(pkgDir)) {
973
+ churnScore++;
974
+ }
975
+ }
976
+ }
977
+ packageTestAdjacency.push({
978
+ packagePath: pkgDir,
979
+ hasTestDir: hasTestDir || testFileCount > 0,
980
+ testFileCount,
981
+ sourceFileCount,
982
+ testRatio,
983
+ churnScore
984
+ });
985
+ }
986
+ const weaklyTestedHotspots = packageTestAdjacency.filter((p) => p.churnScore > 2 && p.testRatio < 0.3).map((p) => p.packagePath);
987
+ const totalSource = packageTestAdjacency.reduce((s, p) => s + p.sourceFileCount, 0);
988
+ const overallTestAdjacencyScore = totalSource > 0 ? Math.min(100, packageTestAdjacency.reduce((s, p) => s + p.testRatio * p.sourceFileCount, 0) / totalSource * 100) : 0;
989
+ return {
990
+ packageTestAdjacency,
991
+ weaklyTestedHotspots,
992
+ overallTestAdjacencyScore
993
+ };
994
+ }
995
+ __name(computeTestAdjacency, "computeTestAdjacency");
996
+ __name2(computeTestAdjacency, "computeTestAdjacency");
997
+ function detectSensitiveSurfaces(repoPath) {
998
+ const envPatterns = [
999
+ ".env",
1000
+ ".env.local",
1001
+ ".env.development",
1002
+ ".env.production",
1003
+ ".env.staging"
1004
+ ];
1005
+ const hasEnvFiles = envPatterns.some((p) => existsSync(join(repoPath, p)));
1006
+ const credPatterns = [
1007
+ ".npmrc",
1008
+ ".pypirc",
1009
+ "docker-compose.yml",
1010
+ "docker-compose.yaml"
1011
+ ];
1012
+ const hasCredentialConfigs = credPatterns.some((p) => existsSync(join(repoPath, p)));
1013
+ let hasCISecrets = false;
1014
+ const workflowDir = join(repoPath, ".github", "workflows");
1015
+ if (existsSync(workflowDir)) {
1016
+ try {
1017
+ const files = readdirSync(workflowDir);
1018
+ for (const f of files) {
1019
+ try {
1020
+ const content = readFileSync(join(workflowDir, f), "utf-8");
1021
+ if (content.includes("secrets.")) {
1022
+ hasCISecrets = true;
1023
+ break;
1024
+ }
1025
+ } catch {
1026
+ }
1027
+ }
1028
+ } catch {
1029
+ }
1030
+ }
1031
+ const hasInfraState = existsSync(join(repoPath, "terraform.tfstate")) || existsSync(join(repoPath, ".terraform"));
1032
+ let sensitivePathCount = 0;
1033
+ if (hasEnvFiles) {
1034
+ sensitivePathCount += envPatterns.filter((p) => existsSync(join(repoPath, p))).length;
1035
+ }
1036
+ if (hasCredentialConfigs) {
1037
+ sensitivePathCount += credPatterns.filter((p) => existsSync(join(repoPath, p))).length;
1038
+ }
1039
+ if (hasCISecrets) {
1040
+ sensitivePathCount++;
1041
+ }
1042
+ if (hasInfraState) {
1043
+ sensitivePathCount++;
1044
+ }
1045
+ return {
1046
+ hasEnvFiles,
1047
+ hasCredentialConfigs,
1048
+ hasCISecrets,
1049
+ hasInfraState,
1050
+ sensitivePathCount
1051
+ };
1052
+ }
1053
+ __name(detectSensitiveSurfaces, "detectSensitiveSurfaces");
1054
+ __name2(detectSensitiveSurfaces, "detectSensitiveSurfaces");
1055
+ async function detectStructure(repoPath, emitter) {
1056
+ const isTurbo = existsSync(join(repoPath, "turbo.json"));
1057
+ const isNx = existsSync(join(repoPath, "nx.json"));
1058
+ const isLerna = existsSync(join(repoPath, "lerna.json"));
1059
+ const isPnpmWorkspace = existsSync(join(repoPath, "pnpm-workspace.yaml"));
1060
+ let repoType = "single";
1061
+ if (isTurbo) {
1062
+ repoType = "monorepo-turbo";
1063
+ } else if (isNx) {
1064
+ repoType = "monorepo-nx";
1065
+ } else if (isLerna) {
1066
+ repoType = "monorepo-lerna";
1067
+ } else if (isPnpmWorkspace) {
1068
+ repoType = "polyrepo";
1069
+ }
1070
+ let packageManager = null;
1071
+ if (existsSync(join(repoPath, "pnpm-lock.yaml"))) {
1072
+ packageManager = "pnpm";
1073
+ } else if (existsSync(join(repoPath, "bun.lockb"))) {
1074
+ packageManager = "bun";
1075
+ } else if (existsSync(join(repoPath, "yarn.lock"))) {
1076
+ packageManager = "yarn";
1077
+ } else if (existsSync(join(repoPath, "package-lock.json"))) {
1078
+ packageManager = "npm";
1079
+ }
1080
+ const configExtensions = [
1081
+ ".json",
1082
+ ".yaml",
1083
+ ".yml",
1084
+ ".toml",
1085
+ ".config.ts",
1086
+ ".config.js",
1087
+ ".config.mjs"
1088
+ ];
1089
+ let configFileCount = 0;
1090
+ try {
1091
+ const rootFiles = readdirSync(repoPath);
1092
+ for (const f of rootFiles) {
1093
+ if (configExtensions.some((ext) => f.endsWith(ext))) {
1094
+ configFileCount++;
1095
+ }
1096
+ }
1097
+ } catch {
1098
+ }
1099
+ let gitignoreComplexity = 0;
1100
+ try {
1101
+ const gitignore = readFileSync(join(repoPath, ".gitignore"), "utf-8");
1102
+ gitignoreComplexity = gitignore.split("\n").filter((l) => l.trim() && !l.startsWith("#")).length;
1103
+ } catch {
1104
+ }
1105
+ const [ageResult, countResult, branchResult] = await Promise.all([
1106
+ execFileAsync3("git", [
1107
+ "log",
1108
+ "--reverse",
1109
+ "--format=%aI",
1110
+ "-1"
1111
+ ], {
1112
+ cwd: repoPath,
1113
+ timeout: 5e3
1114
+ }).catch(() => ({
1115
+ stdout: ""
1116
+ })),
1117
+ execFileAsync3("git", [
1118
+ "rev-list",
1119
+ "--count",
1120
+ "--all"
1121
+ ], {
1122
+ cwd: repoPath,
1123
+ timeout: 5e3
1124
+ }).catch(() => ({
1125
+ stdout: "0"
1126
+ })),
1127
+ execFileAsync3("git", [
1128
+ "branch",
1129
+ "-a",
1130
+ "--format=%(refname:short) %(committerdate:iso)"
1131
+ ], {
1132
+ cwd: repoPath,
1133
+ timeout: 5e3
1134
+ }).catch(() => ({
1135
+ stdout: ""
1136
+ }))
1137
+ ]);
1138
+ let age = 0;
1139
+ if (ageResult.stdout.trim()) {
1140
+ const firstDate = new Date(ageResult.stdout.trim());
1141
+ age = Math.floor((Date.now() - firstDate.getTime()) / (24 * 60 * 60 * 1e3));
1142
+ }
1143
+ const totalCommits = Number.parseInt(countResult.stdout.trim(), 10) || 0;
1144
+ let activeBranches = 0;
1145
+ const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1e3;
1146
+ for (const line of branchResult.stdout.split("\n").filter(Boolean)) {
1147
+ const parts = line.trim().split(/\s+/);
1148
+ if (parts.length >= 2) {
1149
+ const branchDate = new Date(parts.slice(1).join(" "));
1150
+ if (!Number.isNaN(branchDate.getTime()) && branchDate.getTime() > thirtyDaysAgo) {
1151
+ activeBranches++;
1152
+ }
1153
+ }
1154
+ }
1155
+ const packageDirs = detectPackageDirs(repoPath);
1156
+ const testAdjacency = computeTestAdjacency(repoPath, packageDirs);
1157
+ const sensitiveSurfaces = detectSensitiveSurfaces(repoPath);
1158
+ const structure = {
1159
+ repoType,
1160
+ framework: detectFramework(repoPath),
1161
+ packageManager,
1162
+ hasCI: existsSync(join(repoPath, ".github", "workflows")) || existsSync(join(repoPath, ".gitlab-ci.yml")) || existsSync(join(repoPath, ".circleci")),
1163
+ hasDocker: existsSync(join(repoPath, "Dockerfile")) || existsSync(join(repoPath, "docker-compose.yml")),
1164
+ testFramework: detectTestFramework(repoPath),
1165
+ configFileCount,
1166
+ gitignoreComplexity,
1167
+ age,
1168
+ totalCommits,
1169
+ activeBranches,
1170
+ testAdjacency,
1171
+ sensitiveSurfaces
1172
+ };
1173
+ if (emitter && sensitiveSurfaces.hasEnvFiles) {
1174
+ emitter.emit("discovery", {
1175
+ source: "structure",
1176
+ confidence: 0.95,
1177
+ message: "Detected environment variables",
1178
+ detailMessage: "Vreko ignores .env files completely"
1179
+ });
1180
+ }
1181
+ if (emitter && testAdjacency.weaklyTestedHotspots.length > 0) {
1182
+ emitter.emit("discovery", {
1183
+ source: "structure",
1184
+ confidence: 0.87,
1185
+ message: `${testAdjacency.weaklyTestedHotspots.length} package(s) with high churn but low test coverage`,
1186
+ detailMessage: "Vreko will monitor these as fragile zones"
1187
+ });
1188
+ }
1189
+ return structure;
1190
+ }
1191
+ __name(detectStructure, "detectStructure");
1192
+ __name2(detectStructure, "detectStructure");
1193
+ var execFileAsync4 = promisify(execFile);
1194
+ function writeScanCache(path, cache) {
1195
+ writeFileSync(path, JSON.stringify(cache, null, 2));
1196
+ }
1197
+ __name(writeScanCache, "writeScanCache");
1198
+ __name2(writeScanCache, "writeScanCache");
1199
+ function readScanCache(path) {
1200
+ if (!existsSync(path)) {
1201
+ return null;
1202
+ }
1203
+ try {
1204
+ const data = JSON.parse(readFileSync(path, "utf-8"));
1205
+ if (!data.lastScannedHead || !data.lastScannedAt) {
1206
+ return null;
1207
+ }
1208
+ return data;
1209
+ } catch {
1210
+ return null;
1211
+ }
1212
+ }
1213
+ __name(readScanCache, "readScanCache");
1214
+ __name2(readScanCache, "readScanCache");
1215
+ async function computeDelta(cache, repoPath) {
1216
+ try {
1217
+ const { stdout: currentHead } = await execFileAsync4("git", [
1218
+ "rev-parse",
1219
+ "HEAD"
1220
+ ], {
1221
+ cwd: repoPath,
1222
+ timeout: 5e3
1223
+ });
1224
+ const head = currentHead.trim();
1225
+ if (head === cache.lastScannedHead) {
1226
+ return {
1227
+ needsFullScan: false
1228
+ };
1229
+ }
1230
+ try {
1231
+ await execFileAsync4("git", [
1232
+ "merge-base",
1233
+ "--is-ancestor",
1234
+ cache.lastScannedHead,
1235
+ head
1236
+ ], {
1237
+ cwd: repoPath,
1238
+ timeout: 5e3
1239
+ });
1240
+ const { stdout: countOut } = await execFileAsync4("git", [
1241
+ "rev-list",
1242
+ "--count",
1243
+ `${cache.lastScannedHead}..${head}`
1244
+ ], {
1245
+ cwd: repoPath,
1246
+ timeout: 5e3
1247
+ });
1248
+ const count = Number.parseInt(countOut.trim(), 10) || 0;
1249
+ return {
1250
+ needsFullScan: false,
1251
+ commitRange: {
1252
+ from: cache.lastScannedHead,
1253
+ to: head,
1254
+ count
1255
+ }
1256
+ };
1257
+ } catch {
1258
+ return {
1259
+ needsFullScan: true
1260
+ };
1261
+ }
1262
+ } catch {
1263
+ return {
1264
+ needsFullScan: true
1265
+ };
1266
+ }
1267
+ }
1268
+ __name(computeDelta, "computeDelta");
1269
+ __name2(computeDelta, "computeDelta");
1270
+ var ScanEventEmitter = class extends EventEmitter {
1271
+ static {
1272
+ __name(this, "ScanEventEmitter");
1273
+ }
1274
+ static {
1275
+ __name2(this, "ScanEventEmitter");
1276
+ }
1277
+ // --- DiscoveryEmitter compatibility ---
1278
+ emitDiscovery(discovery) {
1279
+ if (discovery.confidence > 0.85) {
1280
+ this.emit("discovery", discovery);
1281
+ }
1282
+ }
1283
+ // --- Scan-specific events ---
1284
+ emitProgress(event) {
1285
+ this.emit("progress", event);
1286
+ }
1287
+ emitFinding(event) {
1288
+ this.emit("finding", event);
1289
+ }
1290
+ emitComplete(profile) {
1291
+ this.emit("complete", profile);
1292
+ }
1293
+ onDiscovery(handler) {
1294
+ this.on("discovery", handler);
1295
+ return this;
1296
+ }
1297
+ onProgress(handler) {
1298
+ this.on("progress", handler);
1299
+ return this;
1300
+ }
1301
+ onFinding(handler) {
1302
+ this.on("finding", handler);
1303
+ return this;
1304
+ }
1305
+ onComplete(handler) {
1306
+ this.on("complete", handler);
1307
+ return this;
1308
+ }
1309
+ };
1310
+ function createScanEventEmitter() {
1311
+ return new ScanEventEmitter();
1312
+ }
1313
+ __name(createScanEventEmitter, "createScanEventEmitter");
1314
+ __name2(createScanEventEmitter, "createScanEventEmitter");
1315
+ var ACTIVE_SCAN_LOCKS = /* @__PURE__ */ new Set();
1316
+ var DEFAULT_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
1317
+ function resolveCachePath(workspaceHash) {
1318
+ const cacheDir = join(homedir(), ".vreko", "cache", "init-scan");
1319
+ mkdirSync(cacheDir, {
1320
+ recursive: true
1321
+ });
1322
+ return join(cacheDir, `${workspaceHash}.json`);
1323
+ }
1324
+ __name(resolveCachePath, "resolveCachePath");
1325
+ __name2(resolveCachePath, "resolveCachePath");
1326
+ function isCacheValid(cache, maxAgeMs = DEFAULT_CACHE_MAX_AGE_MS) {
1327
+ if (!cache) {
1328
+ return false;
1329
+ }
1330
+ const cachedAt = Date.parse(cache.lastScannedAt);
1331
+ if (!Number.isFinite(cachedAt)) {
1332
+ return false;
1333
+ }
1334
+ return Date.now() - cachedAt < maxAgeMs;
1335
+ }
1336
+ __name(isCacheValid, "isCacheValid");
1337
+ __name2(isCacheValid, "isCacheValid");
1338
+ function isScanInProgress(workspaceHash) {
1339
+ return ACTIVE_SCAN_LOCKS.has(workspaceHash);
1340
+ }
1341
+ __name(isScanInProgress, "isScanInProgress");
1342
+ __name2(isScanInProgress, "isScanInProgress");
1343
+ function acquireScanLock(workspaceHash) {
1344
+ if (ACTIVE_SCAN_LOCKS.has(workspaceHash)) {
1345
+ return null;
1346
+ }
1347
+ ACTIVE_SCAN_LOCKS.add(workspaceHash);
1348
+ let released = false;
1349
+ return () => {
1350
+ if (released) {
1351
+ return;
1352
+ }
1353
+ released = true;
1354
+ ACTIVE_SCAN_LOCKS.delete(workspaceHash);
1355
+ };
1356
+ }
1357
+ __name(acquireScanLock, "acquireScanLock");
1358
+ __name2(acquireScanLock, "acquireScanLock");
1359
+ function clamp(value, min = 0, max = 100) {
1360
+ return Math.max(min, Math.min(max, value));
1361
+ }
1362
+ __name(clamp, "clamp");
1363
+ __name2(clamp, "clamp");
1364
+ function computeRecoveryRisk(signals) {
1365
+ const reflog = signals.reflog;
1366
+ const gitlog = signals.gitlog;
1367
+ let score = 0;
1368
+ if (reflog) {
1369
+ score += Math.min(reflog.resetToHEADCount * 3, 40);
1370
+ score += Math.min(reflog.forceResetRate * 5, 15);
1371
+ }
1372
+ if (gitlog) {
1373
+ const rc = gitlog.rollbackCorrelation;
1374
+ score += Math.min(rc.resetAfterLargeCommitRate * 0.3, 15);
1375
+ score += Math.min(rc.resetAfterCrossPackageRate * 0.3, 10);
1376
+ const totalReverts = gitlog.fileChurnRanking.reduce((s, f) => s + f.revertCount, 0);
1377
+ score += Math.min(totalReverts * 2, 20);
1378
+ }
1379
+ return clamp(score);
1380
+ }
1381
+ __name(computeRecoveryRisk, "computeRecoveryRisk");
1382
+ __name2(computeRecoveryRisk, "computeRecoveryRisk");
1383
+ function computeChangeVolatility(signals) {
1384
+ const reflog = signals.reflog;
1385
+ const gitlog = signals.gitlog;
1386
+ let score = 0;
1387
+ if (reflog) {
1388
+ score += Math.min(reflog.forceResetRate * 4, 20);
1389
+ score += Math.min(reflog.rebaseFrequency * 2, 10);
1390
+ score += Math.min(reflog.branchAbandonmentRate * 0.2, 10);
1391
+ }
1392
+ if (gitlog) {
1393
+ const d = gitlog.diffusion;
1394
+ score += Math.min(d.crossDirectoryDiffusionRate * 0.5, 20);
1395
+ score += Math.min(gitlog.largeCommitRatio * 0.4, 15);
1396
+ score += Math.min(d.largeSessionClusterRate * 0.5, 15);
1397
+ score += Math.min(gitlog.commitFrequencyVariance * 2, 10);
1398
+ }
1399
+ return clamp(score);
1400
+ }
1401
+ __name(computeChangeVolatility, "computeChangeVolatility");
1402
+ __name2(computeChangeVolatility, "computeChangeVolatility");
1403
+ function computeWorkflowFragility(signals) {
1404
+ const gitlog = signals.gitlog;
1405
+ const structure = signals.structure;
1406
+ let score = 0;
1407
+ if (gitlog) {
1408
+ const fragileCount = gitlog.fileChurnRanking.filter((f) => f.revertCount > 0 && f.changeCount > 5).length;
1409
+ score += Math.min(fragileCount * 5, 25);
1410
+ const unstablePairs = gitlog.coChangeGraph.filter((e) => e.confidence > 0.7);
1411
+ score += Math.min(unstablePairs.length * 3, 15);
1412
+ score += Math.min(gitlog.hotspotFiles.length * 2, 10);
1413
+ }
1414
+ if (structure) {
1415
+ const testGap = 100 - structure.testAdjacency.overallTestAdjacencyScore;
1416
+ score += Math.min(testGap * 0.3, 20);
1417
+ score += Math.min(structure.testAdjacency.weaklyTestedHotspots.length * 5, 15);
1418
+ score += Math.min(structure.sensitiveSurfaces.sensitivePathCount * 3, 15);
1419
+ }
1420
+ return clamp(score);
1421
+ }
1422
+ __name(computeWorkflowFragility, "computeWorkflowFragility");
1423
+ __name2(computeWorkflowFragility, "computeWorkflowFragility");
1424
+ function computeComplexity(signals) {
1425
+ const structure = signals.structure;
1426
+ if (!structure) {
1427
+ return 0;
1428
+ }
1429
+ let score = 0;
1430
+ score += Math.min(structure.configFileCount * 2, 30);
1431
+ const packageCount = structure.testAdjacency.packageTestAdjacency.length;
1432
+ score += Math.min(packageCount * 3, 30);
1433
+ score += Math.min(structure.gitignoreComplexity, 20);
1434
+ score += structure.repoType.startsWith("monorepo") ? 20 : 0;
1435
+ return clamp(score);
1436
+ }
1437
+ __name(computeComplexity, "computeComplexity");
1438
+ __name2(computeComplexity, "computeComplexity");
1439
+ function computeCollaboration(signals) {
1440
+ const gitlog = signals.gitlog;
1441
+ if (!gitlog) {
1442
+ return 0;
1443
+ }
1444
+ let score = 0;
1445
+ score += Math.min(gitlog.contributorCount * 5, 30);
1446
+ score += Math.min(gitlog.mergeConflictFrequency * 0.5, 20);
1447
+ if (gitlog.busFactorEstimate <= 1) {
1448
+ score += 30;
1449
+ } else if (gitlog.busFactorEstimate <= 2) {
1450
+ score += 15;
1451
+ }
1452
+ return clamp(score);
1453
+ }
1454
+ __name(computeCollaboration, "computeCollaboration");
1455
+ __name2(computeCollaboration, "computeCollaboration");
1456
+ function calculateStructuralRisk(topology) {
1457
+ if (!topology) {
1458
+ return 0;
1459
+ }
1460
+ let score = 0;
1461
+ score += Math.min(30, topology.circularChainCount * 8);
1462
+ const highFanInRatio = topology.highFanInFiles.length / Math.max(topology.moduleCount, 1);
1463
+ score += Math.min(25, highFanInRatio * 500);
1464
+ score += Math.min(20, topology.ruleViolationCount * 3);
1465
+ const orphanRatio = topology.orphanFileCount / Math.max(topology.moduleCount, 1);
1466
+ score += Math.min(15, orphanRatio * 150);
1467
+ const edgeDensity = topology.edgeCount / Math.max(topology.moduleCount, 1);
1468
+ if (edgeDensity > 10) {
1469
+ score += 10;
1470
+ }
1471
+ return Math.min(100, Math.round(score));
1472
+ }
1473
+ __name(calculateStructuralRisk, "calculateStructuralRisk");
1474
+ __name2(calculateStructuralRisk, "calculateStructuralRisk");
1475
+ function computeAiExposure(repoPath, toolIdentity) {
1476
+ if (!repoPath) {
1477
+ return 0;
1478
+ }
1479
+ let score = 0;
1480
+ const aiIndicators = [
1481
+ ".cursor",
1482
+ ".github/copilot",
1483
+ ".codeium",
1484
+ ".continue",
1485
+ ".aider",
1486
+ ".windsurf",
1487
+ ".claude",
1488
+ ".augment",
1489
+ ".roo"
1490
+ ];
1491
+ for (const indicator of aiIndicators) {
1492
+ if (existsSync(join(repoPath, indicator))) {
1493
+ score += 15;
1494
+ }
1495
+ }
1496
+ if (toolIdentity && toolIdentity.confidence >= 0.5) {
1497
+ const TOOL_RISK_MULTIPLIERS = {
1498
+ devin: 1.4,
1499
+ "claude-code": 1.2,
1500
+ cursor: 1,
1501
+ "github-copilot": 0.9,
1502
+ windsurf: 1,
1503
+ augment: 1,
1504
+ cline: 1.1,
1505
+ roocode: 1.1,
1506
+ aider: 1
1507
+ };
1508
+ const multiplier = TOOL_RISK_MULTIPLIERS[toolIdentity.tool] ?? 1;
1509
+ score = Math.round(score * multiplier);
1510
+ }
1511
+ try {
1512
+ const { execSync } = __require2("child_process");
1513
+ const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
1514
+ cwd: repoPath,
1515
+ timeout: 5e3,
1516
+ encoding: "utf-8"
1517
+ }).trim();
1518
+ if (/^devin\/\d{10}-/.test(currentBranch)) {
1519
+ score += 10;
1520
+ }
1521
+ } catch {
1522
+ }
1523
+ return clamp(score);
1524
+ }
1525
+ __name(computeAiExposure, "computeAiExposure");
1526
+ __name2(computeAiExposure, "computeAiExposure");
1527
+ function buildTopDrivers(signals) {
1528
+ const drivers = [];
1529
+ if (signals.reflog && signals.reflog.resetToHEADCount > 3) {
1530
+ drivers.push({
1531
+ id: "repeated-recoveries",
1532
+ label: "Repeated recovery events",
1533
+ scoreImpact: Math.min(signals.reflog.resetToHEADCount * 3, 40),
1534
+ evidence: [
1535
+ `${signals.reflog.resetToHEADCount} reset-to-HEAD events detected`,
1536
+ signals.reflog.avgTimeBetweenResets > 0 ? `Average ${Math.round(signals.reflog.avgTimeBetweenResets)} minutes between resets` : "Multiple resets in short timeframe"
1537
+ ],
1538
+ protectiveAction: "Increase snapshot density during volatile periods"
1539
+ });
1540
+ }
1541
+ if (signals.gitlog) {
1542
+ const rc = signals.gitlog.rollbackCorrelation;
1543
+ if (rc.resetAfterLargeCommitRate > 20 || rc.resetAfterCrossPackageRate > 20) {
1544
+ drivers.push({
1545
+ id: "rollback-correlation",
1546
+ label: `Recoveries correlate with ${rc.topRecoveryTrigger.replace(/-/g, " ")} changes`,
1547
+ scoreImpact: Math.max(rc.resetAfterLargeCommitRate, rc.resetAfterCrossPackageRate) * 0.3,
1548
+ evidence: [
1549
+ rc.resetAfterLargeCommitRate > 0 ? `${Math.round(rc.resetAfterLargeCommitRate)}% of reverts follow large commits` : "",
1550
+ rc.resetAfterCrossPackageRate > 0 ? `${Math.round(rc.resetAfterCrossPackageRate)}% of reverts follow cross-package changes` : ""
1551
+ ].filter(Boolean),
1552
+ protectiveAction: "Auto-snapshot before high-diffusion commits"
1553
+ });
1554
+ }
1555
+ if (signals.gitlog.hotspotFiles.length > 0) {
1556
+ const topChurn = signals.gitlog.fileChurnRanking[0];
1557
+ drivers.push({
1558
+ id: "fragile-hotspots",
1559
+ label: "Fragile file hotspots detected",
1560
+ scoreImpact: Math.min(signals.gitlog.hotspotFiles.length * 5, 25),
1561
+ evidence: [
1562
+ topChurn ? `${topChurn.path} changed ${topChurn.changeCount} times (${topChurn.revertCount} reverts)` : "Multiple high-churn files with reverts"
1563
+ ],
1564
+ protectiveAction: "Watch fragile files with enhanced monitoring"
1565
+ });
1566
+ }
1567
+ if (signals.gitlog.diffusion.crossDirectoryDiffusionRate > 20) {
1568
+ drivers.push({
1569
+ id: "cross-directory-blast",
1570
+ label: "High cross-directory change diffusion",
1571
+ scoreImpact: Math.min(signals.gitlog.diffusion.crossDirectoryDiffusionRate * 0.5, 20),
1572
+ evidence: [
1573
+ `${Math.round(signals.gitlog.diffusion.crossDirectoryDiffusionRate)}% of commits span multiple directories`
1574
+ ],
1575
+ protectiveAction: "Track cross-directory blast radius"
1576
+ });
1577
+ }
1578
+ }
1579
+ if (signals.structure?.testAdjacency.weaklyTestedHotspots.length) {
1580
+ drivers.push({
1581
+ id: "weak-test-coverage",
1582
+ label: "Weakly-tested high-churn areas",
1583
+ scoreImpact: Math.min(signals.structure.testAdjacency.weaklyTestedHotspots.length * 5, 15),
1584
+ evidence: signals.structure.testAdjacency.weaklyTestedHotspots.map((p) => `${p} has high churn but low test coverage`),
1585
+ protectiveAction: "Prioritize snapshot protection for untested hotspots"
1586
+ });
1587
+ }
1588
+ return drivers.sort((a, b) => b.scoreImpact - a.scoreImpact).slice(0, 5);
1589
+ }
1590
+ __name(buildTopDrivers, "buildTopDrivers");
1591
+ __name2(buildTopDrivers, "buildTopDrivers");
1592
+ function buildRecommendedConfig(signals, recoveryRisk) {
1593
+ const protectionLevel = recoveryRisk > 70 ? "maximum" : recoveryRisk > 40 ? "enhanced" : "standard";
1594
+ const snapshotFrequency = recoveryRisk > 70 ? "aggressive" : recoveryRisk > 40 ? "balanced" : "conservative";
1595
+ const watchTargets = [];
1596
+ if (signals.gitlog) {
1597
+ for (const file of signals.gitlog.hotspotFiles.slice(0, 5)) {
1598
+ const churnEntry = signals.gitlog.fileChurnRanking.find((f) => f.path === file);
1599
+ watchTargets.push({
1600
+ path: file,
1601
+ fileCount: 1,
1602
+ reason: churnEntry?.revertCount ? "fragile detected" : "high churn"
1603
+ });
1604
+ }
1605
+ for (const edge of signals.gitlog.coChangeGraph.slice(0, 3)) {
1606
+ if (edge.confidence > 0.7) {
1607
+ const dir = edge.fileA.includes("/") ? edge.fileA.substring(0, edge.fileA.lastIndexOf("/")) : ".";
1608
+ if (!watchTargets.some((t) => t.path === dir)) {
1609
+ watchTargets.push({
1610
+ path: dir,
1611
+ fileCount: 2,
1612
+ reason: "co-change patterns"
1613
+ });
1614
+ }
1615
+ }
1616
+ }
1617
+ }
1618
+ if (signals.structure) {
1619
+ for (const hotspot of signals.structure.testAdjacency.weaklyTestedHotspots.slice(0, 3)) {
1620
+ if (!watchTargets.some((t) => t.path === hotspot)) {
1621
+ watchTargets.push({
1622
+ path: hotspot,
1623
+ fileCount: 0,
1624
+ reason: "weak test coverage"
1625
+ });
1626
+ }
1627
+ }
1628
+ }
1629
+ if (watchTargets.length === 0 && signals.gitlog?.fileChurnRanking.length) {
1630
+ const topChurned = [
1631
+ ...signals.gitlog.fileChurnRanking
1632
+ ].filter((f) => f.changeCount > 0).sort((a, b) => b.changeCount - a.changeCount).slice(0, 5);
1633
+ for (const file of topChurned) {
1634
+ watchTargets.push({
1635
+ path: file.path,
1636
+ fileCount: 1,
1637
+ reason: "frequently changed"
1638
+ });
1639
+ }
1640
+ }
1641
+ const enabledFeatures = [
1642
+ "real-time-protection"
1643
+ ];
1644
+ if (recoveryRisk > 50) {
1645
+ enabledFeatures.push("pre-commit-snapshot");
1646
+ }
1647
+ if (signals.gitlog?.coChangeGraph.length) {
1648
+ enabledFeatures.push("co-change-tracking");
1649
+ }
1650
+ return {
1651
+ protectionLevel,
1652
+ snapshotFrequency,
1653
+ watchTargets,
1654
+ enabledFeatures
1655
+ };
1656
+ }
1657
+ __name(buildRecommendedConfig, "buildRecommendedConfig");
1658
+ __name2(buildRecommendedConfig, "buildRecommendedConfig");
1659
+ function riskTier(score) {
1660
+ if (score > 75) {
1661
+ return "high";
1662
+ }
1663
+ if (score > 50) {
1664
+ return "elevated";
1665
+ }
1666
+ if (score > 25) {
1667
+ return "moderate";
1668
+ }
1669
+ return "low";
1670
+ }
1671
+ __name(riskTier, "riskTier");
1672
+ __name2(riskTier, "riskTier");
1673
+ function computeConfidence(signals) {
1674
+ let sources = 0;
1675
+ let weight = 0;
1676
+ if (signals.reflog) {
1677
+ sources++;
1678
+ weight += 0.4;
1679
+ }
1680
+ if (signals.gitlog) {
1681
+ sources++;
1682
+ weight += 0.4;
1683
+ }
1684
+ if (signals.structure) {
1685
+ sources++;
1686
+ weight += 0.2;
1687
+ }
1688
+ return sources === 0 ? 0.1 : weight;
1689
+ }
1690
+ __name(computeConfidence, "computeConfidence");
1691
+ __name2(computeConfidence, "computeConfidence");
1692
+ function buildTopFragileFiles(signals) {
1693
+ if (!signals.gitlog?.fileChurnRanking.length) {
1694
+ return [];
1695
+ }
1696
+ const withReverts = [
1697
+ ...signals.gitlog.fileChurnRanking
1698
+ ].filter((f) => f.changeCount >= 3 && f.revertCount > 0).sort((a, b) => b.revertCount / b.changeCount - a.revertCount / a.changeCount).slice(0, 20).map((f) => ({
1699
+ path: f.path,
1700
+ changeCount: f.changeCount,
1701
+ revertCount: f.revertCount
1702
+ }));
1703
+ if (withReverts.length > 0) {
1704
+ return withReverts;
1705
+ }
1706
+ return [
1707
+ ...signals.gitlog.fileChurnRanking
1708
+ ].filter((f) => f.changeCount > 0).sort((a, b) => b.changeCount - a.changeCount).slice(0, 5).map((f) => ({
1709
+ path: f.path,
1710
+ changeCount: f.changeCount,
1711
+ revertCount: 0
1712
+ }));
1713
+ }
1714
+ __name(buildTopFragileFiles, "buildTopFragileFiles");
1715
+ __name2(buildTopFragileFiles, "buildTopFragileFiles");
1716
+ function findTopFragileFile(signals) {
1717
+ if (!signals.gitlog?.fileChurnRanking.length) {
1718
+ return null;
1719
+ }
1720
+ const ranked = [
1721
+ ...signals.gitlog.fileChurnRanking
1722
+ ].filter((f) => f.changeCount >= 3 && f.revertCount > 0).sort((a, b) => b.revertCount / b.changeCount - a.revertCount / a.changeCount);
1723
+ return ranked.length > 0 ? ranked[0].path : null;
1724
+ }
1725
+ __name(findTopFragileFile, "findTopFragileFile");
1726
+ __name2(findTopFragileFile, "findTopFragileFile");
1727
+ var FRAGILITY_MIN_SAMPLE = 5;
1728
+ function confidenceWeight(changeCount) {
1729
+ return Math.min(changeCount / FRAGILITY_MIN_SAMPLE, 1);
1730
+ }
1731
+ __name(confidenceWeight, "confidenceWeight");
1732
+ __name2(confidenceWeight, "confidenceWeight");
1733
+ function isExcludedFromFragilityRanking(filePath) {
1734
+ return isArtifactFile(filePath);
1735
+ }
1736
+ __name(isExcludedFromFragilityRanking, "isExcludedFromFragilityRanking");
1737
+ __name2(isExcludedFromFragilityRanking, "isExcludedFromFragilityRanking");
1738
+ function buildFragilityArray(signals) {
1739
+ if (!signals.gitlog?.fileChurnRanking.length) {
1740
+ return [];
1741
+ }
1742
+ return signals.gitlog.fileChurnRanking.map((f) => {
1743
+ const excluded = isExcludedFromFragilityRanking(f.path);
1744
+ const revertRate = f.changeCount > 0 ? f.revertCount / f.changeCount : 0;
1745
+ const fragilityScore = excluded ? 0 : revertRate * confidenceWeight(f.changeCount);
1746
+ return {
1747
+ file: f.path,
1748
+ changeCount: f.changeCount,
1749
+ revertCount: f.revertCount,
1750
+ revertRate,
1751
+ fragilityScore,
1752
+ excluded
1753
+ };
1754
+ });
1755
+ }
1756
+ __name(buildFragilityArray, "buildFragilityArray");
1757
+ __name2(buildFragilityArray, "buildFragilityArray");
1758
+ function buildCoChangeArray(signals) {
1759
+ if (!signals.gitlog?.coChangeGraph.length) {
1760
+ return [];
1761
+ }
1762
+ return signals.gitlog.coChangeGraph.map((edge) => ({
1763
+ files: [
1764
+ edge.fileA,
1765
+ edge.fileB
1766
+ ],
1767
+ rate: edge.confidence,
1768
+ occurrences: edge.coChangeCount,
1769
+ generated: isArtifactFile(edge.fileA) && isArtifactFile(edge.fileB)
1770
+ }));
1771
+ }
1772
+ __name(buildCoChangeArray, "buildCoChangeArray");
1773
+ __name2(buildCoChangeArray, "buildCoChangeArray");
1774
+ function synthesize(signals, baseline, repoPath) {
1775
+ const { insights, locked } = generateInsights(signals, baseline);
1776
+ const recoveryRisk = computeRecoveryRisk(signals);
1777
+ const changeVolatility = computeChangeVolatility(signals);
1778
+ const workflowFragility = computeWorkflowFragility(signals);
1779
+ return {
1780
+ overallRisk: riskTier(recoveryRisk),
1781
+ confidence: computeConfidence(signals),
1782
+ primary: {
1783
+ recoveryRisk,
1784
+ changeVolatility,
1785
+ workflowFragility
1786
+ },
1787
+ secondary: {
1788
+ complexity: computeComplexity(signals),
1789
+ collaboration: computeCollaboration(signals),
1790
+ aiExposure: computeAiExposure(repoPath, void 0),
1791
+ structuralRisk: calculateStructuralRisk(signals.topology)
1792
+ },
1793
+ topDrivers: buildTopDrivers(signals),
1794
+ insights,
1795
+ lockedInsights: [
1796
+ locked
1797
+ ],
1798
+ recommendedConfig: buildRecommendedConfig(signals, recoveryRisk),
1799
+ topFragileFile: findTopFragileFile(signals),
1800
+ topFragileFiles: buildTopFragileFiles(signals),
1801
+ coChange: buildCoChangeArray(signals),
1802
+ fragility: buildFragilityArray(signals)
1803
+ };
1804
+ }
1805
+ __name(synthesize, "synthesize");
1806
+ __name2(synthesize, "synthesize");
1807
+ async function runTopologyScan(provider, repoPath, emitter) {
1808
+ const scanEmitter = emitter;
1809
+ scanEmitter?.emitProgress?.({
1810
+ stage: "topology",
1811
+ progress: 0,
1812
+ message: "Analyzing dependencies..."
1813
+ });
1814
+ try {
1815
+ const result = await provider.scan?.(repoPath);
1816
+ if (!result) {
1817
+ scanEmitter?.emitProgress?.({
1818
+ stage: "topology",
1819
+ progress: 100,
1820
+ message: "Topology scan returned no data"
1821
+ });
1822
+ return null;
1823
+ }
1824
+ for (const file of result.highFanInFiles.slice(0, 3)) {
1825
+ emitter?.emitDiscovery?.({
1826
+ source: "structure",
1827
+ confidence: 0.9,
1828
+ message: `${file.path} is imported by ${file.importedByCount} files`,
1829
+ detailMessage: "Vreko will treat this as a structural hotspot with enhanced blast radius monitoring",
1830
+ relatedInsightId: "fused-structural-temporal-hotspot"
1831
+ });
1832
+ }
1833
+ if (result.circularChainCount > 0) {
1834
+ emitter?.emitDiscovery?.({
1835
+ source: "structure",
1836
+ confidence: 0.95,
1837
+ message: `${result.circularChainCount} circular dependency chain(s) detected`,
1838
+ detailMessage: "Circular dependencies increase change risk - Vreko will monitor these chains closely"
1839
+ });
1840
+ }
1841
+ scanEmitter?.emitProgress?.({
1842
+ stage: "topology",
1843
+ progress: 100,
1844
+ message: `Topology: ${result.moduleCount} modules, ${result.edgeCount} edges in ${result.cruiseDurationMs}ms`
1845
+ });
1846
+ return result;
1847
+ } catch (_err) {
1848
+ scanEmitter?.emitProgress?.({
1849
+ stage: "topology",
1850
+ progress: 100,
1851
+ message: "Topology scan failed (non-fatal)"
1852
+ });
1853
+ return null;
1854
+ }
1855
+ }
1856
+ __name(runTopologyScan, "runTopologyScan");
1857
+ __name2(runTopologyScan, "runTopologyScan");
1858
+ async function runInitScan({ repoPath, emitter, topologyProvider }) {
1859
+ const scanEmitter = emitter;
1860
+ const [reflogSignals, gitlogSignals, structureSignals, topologySignals] = await Promise.all([
1861
+ scanReflog(repoPath, emitter),
1862
+ analyzeGitLog(repoPath, emitter),
1863
+ detectStructure(repoPath, emitter),
1864
+ topologyProvider?.scan ? runTopologyScan(topologyProvider, repoPath, emitter) : Promise.resolve(null)
1865
+ ]);
1866
+ scanEmitter?.emitProgress?.({
1867
+ stage: "synthesis",
1868
+ progress: 0,
1869
+ message: "Synthesizing risk profile..."
1870
+ });
1871
+ const profile = synthesize({
1872
+ reflog: reflogSignals,
1873
+ gitlog: gitlogSignals,
1874
+ structure: structureSignals,
1875
+ topology: topologySignals
1876
+ });
1877
+ scanEmitter?.emitProgress?.({
1878
+ stage: "synthesis",
1879
+ progress: 100,
1880
+ message: "Scan complete"
1881
+ });
1882
+ scanEmitter?.emitComplete?.(profile);
1883
+ return profile;
1884
+ }
1885
+ __name(runInitScan, "runInitScan");
1886
+ __name2(runInitScan, "runInitScan");
1887
+ async function runInitScan2(input, emitter) {
1888
+ if (typeof input !== "string") {
1889
+ return runInitScan({
1890
+ repoPath: input.repoPath,
1891
+ emitter: input.emitter,
1892
+ topologyProvider: input.topologyProvider
1893
+ });
1894
+ }
1895
+ const repoPath = input;
1896
+ const em = emitter ?? createDiscoveryEmitter();
1897
+ const [reflog, gitlog, structure] = await Promise.all([
1898
+ scanReflog(repoPath, em),
1899
+ analyzeGitLog(repoPath, em),
1900
+ detectStructure(repoPath, em)
1901
+ ]);
1902
+ const signals = {
1903
+ reflog,
1904
+ gitlog,
1905
+ structure,
1906
+ topology: null
1907
+ };
1908
+ const profile = synthesize(signals, void 0, repoPath);
1909
+ return {
1910
+ profile,
1911
+ emitter: em
1912
+ };
1913
+ }
1914
+ __name(runInitScan2, "runInitScan2");
1915
+ __name2(runInitScan2, "runInitScan");
1916
+
1917
+ export { DiscoveryEmitter, ScanEventEmitter, acquireScanLock, analyzeGitLog, computeDelta, createDiscoveryEmitter, createScanEventEmitter, detectStructure, generateInsights, isCacheValid, isScanInProgress, readScanCache, resolveCachePath, runInitScan2 as runInitScan, scanReflog, synthesize, writeScanCache };
1918
+ //# sourceMappingURL=init-scan-RZNYDTUV.js.map
1919
+ //# sourceMappingURL=init-scan-RZNYDTUV.js.map