repogenome 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/cli.js +1998 -0
  4. package/package.json +51 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1998 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command as Command5 } from "commander";
5
+ import { createRequire as createRequire2 } from "module";
6
+
7
+ // src/banner.ts
8
+ import chalk from "chalk";
9
+ import { createRequire } from "module";
10
+ var _MUGM = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
11
+ var require2 = createRequire(import.meta.url);
12
+ var { version } = require2("../package.json");
13
+ function printBanner() {
14
+ const g = chalk.green;
15
+ const w = chalk.white.bold;
16
+ const d = chalk.gray;
17
+ console.log();
18
+ console.log(w(" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"));
19
+ console.log(w(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D"));
20
+ console.log(g(" \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 "));
21
+ console.log(w(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D "));
22
+ console.log(w(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"));
23
+ console.log(d(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
24
+ console.log();
25
+ console.log(d(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
26
+ console.log(d(" git for software architecture \xB7 snapshot \xB7 drift \xB7 blame \xB7 compare"));
27
+ console.log();
28
+ console.log(g(" \u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8"));
29
+ console.log(d(" \u2572 \u2571 \u2572 \u2571 \u2572 \u2571 \u2572 \u2571 \u2572 \u2571 \u2572 \u2571 \u2572 \u2571 \u2572 \u2571 \u2572 \u2571 \u2572 \u2571 \u2572 "));
30
+ console.log(w(" \u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9\u2500\u254C\u2500\u25C8\u2500\u254C\u2500\u25C9"));
31
+ console.log();
32
+ console.log(d(` v${version} \xB7 MIT \xB7 npx repogenome init`));
33
+ console.log();
34
+ }
35
+
36
+ // src/commands/init.ts
37
+ import { Command } from "commander";
38
+ import { mkdir, writeFile, access } from "fs/promises";
39
+ import { join as join5, resolve } from "path";
40
+ import { createInterface } from "readline";
41
+ import chalk3 from "chalk";
42
+ import ora from "ora";
43
+
44
+ // src/scanner/walker.ts
45
+ import { readdir } from "fs/promises";
46
+ import { join, relative } from "path";
47
+ var _MUGM2 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
48
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
49
+ "node_modules",
50
+ ".git",
51
+ "dist",
52
+ "build",
53
+ "coverage",
54
+ ".next",
55
+ ".nuxt",
56
+ ".cache",
57
+ "__pycache__",
58
+ ".tox",
59
+ "vendor",
60
+ "target",
61
+ ".gradle",
62
+ "bin",
63
+ "obj"
64
+ ]);
65
+ function isHidden(name) {
66
+ return name.startsWith(".") && name !== ".";
67
+ }
68
+ async function walkFiles(rootPath) {
69
+ const results = [];
70
+ async function walk(dir) {
71
+ let entries;
72
+ try {
73
+ entries = await readdir(dir, { withFileTypes: true });
74
+ } catch {
75
+ return;
76
+ }
77
+ for (const entry of entries) {
78
+ if (SKIP_DIRS.has(entry.name) || isHidden(entry.name)) continue;
79
+ const fullPath = join(dir, entry.name);
80
+ if (entry.isDirectory()) {
81
+ await walk(fullPath);
82
+ } else if (entry.isFile()) {
83
+ results.push(relative(rootPath, fullPath).replace(/\\/g, "/"));
84
+ }
85
+ }
86
+ }
87
+ await walk(rootPath);
88
+ return results;
89
+ }
90
+
91
+ // src/scanner/classifier.ts
92
+ import { basename, extname, dirname } from "path";
93
+ var _MUGM3 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
94
+ var EXT_LANG_MAP = {
95
+ ".ts": "typescript",
96
+ ".tsx": "typescript",
97
+ ".js": "javascript",
98
+ ".jsx": "javascript",
99
+ ".mjs": "javascript",
100
+ ".cjs": "javascript",
101
+ ".py": "python",
102
+ ".go": "go",
103
+ ".java": "java",
104
+ ".kt": "kotlin",
105
+ ".kts": "kotlin"
106
+ };
107
+ function classifyFile(relativePath) {
108
+ const ext = extname(relativePath).toLowerCase();
109
+ const language = EXT_LANG_MAP[ext];
110
+ if (!language) return null;
111
+ return {
112
+ path: relativePath,
113
+ name: basename(relativePath, ext),
114
+ extension: ext,
115
+ directory: dirname(relativePath).replace(/\\/g, "/"),
116
+ language
117
+ };
118
+ }
119
+ function classifyFiles(paths) {
120
+ return paths.flatMap((p) => {
121
+ const info = classifyFile(p);
122
+ return info ? [info] : [];
123
+ });
124
+ }
125
+
126
+ // src/scanner/naming.ts
127
+ var _MUGM4 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
128
+ var KNOWN_PREFIXES = ["get", "set", "create", "update", "delete", "fetch", "handle", "build", "make", "find", "load", "save", "send", "parse", "format"];
129
+ var KNOWN_SUFFIXES = [".service", ".repository", ".controller", ".handler", ".middleware", ".factory", ".manager", ".helper", ".util", ".model", ".entity", ".dto", ".spec", ".test", ".config", ".module", ".resolver", ".guard", ".pipe", ".decorator"];
130
+ function detectCasing(name) {
131
+ if (/^[a-z][a-zA-Z0-9]*$/.test(name) && /[A-Z]/.test(name)) return "camelCase";
132
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(name)) return "PascalCase";
133
+ if (/^[a-z][a-z0-9_]*$/.test(name) && name.includes("_")) return "snake_case";
134
+ if (/^[a-z][a-z0-9-]*$/.test(name) && name.includes("-")) return "kebab-case";
135
+ return null;
136
+ }
137
+ function increment(map, key) {
138
+ map[key] = (map[key] ?? 0) + 1;
139
+ }
140
+ function extractNamingSignals(files) {
141
+ const prefixes = {};
142
+ const suffixes = {};
143
+ const casing = {};
144
+ for (const file of files) {
145
+ const name = file.name.toLowerCase();
146
+ for (const prefix of KNOWN_PREFIXES) {
147
+ if (name.startsWith(prefix)) {
148
+ increment(prefixes, prefix);
149
+ break;
150
+ }
151
+ }
152
+ const fullName = file.name + file.extension;
153
+ for (const suffix of KNOWN_SUFFIXES) {
154
+ if (fullName.toLowerCase().includes(suffix)) {
155
+ increment(suffixes, suffix);
156
+ break;
157
+ }
158
+ }
159
+ const casingStyle = detectCasing(file.name);
160
+ if (casingStyle) increment(casing, casingStyle);
161
+ }
162
+ return { prefixes, suffixes, casing };
163
+ }
164
+
165
+ // src/scanner/imports.ts
166
+ import { readFile } from "fs/promises";
167
+ import { join as join2 } from "path";
168
+ var _MUGM5 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
169
+ var PATTERNS = {
170
+ typescript: [
171
+ /import\s+(?:[\w*{},\s]+)\s+from\s+['"]([^'"]+)['"]/g,
172
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
173
+ ],
174
+ javascript: [
175
+ /import\s+(?:[\w*{},\s]+)\s+from\s+['"]([^'"]+)['"]/g,
176
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
177
+ ],
178
+ python: [
179
+ /^import\s+([\w.]+)/gm,
180
+ /^from\s+([\w.]+)\s+import/gm
181
+ ],
182
+ go: [
183
+ /import\s+"([^"]+)"/g,
184
+ /^\s+"([^"]+)"/gm
185
+ ],
186
+ java: [
187
+ /^import\s+([\w.]+);/gm
188
+ ],
189
+ kotlin: [
190
+ /^import\s+([\w.]+)/gm
191
+ ]
192
+ };
193
+ async function extractImports(rootPath, files) {
194
+ const result = {};
195
+ await Promise.all(
196
+ files.map(async (file) => {
197
+ const patterns = PATTERNS[file.language];
198
+ if (!patterns) return;
199
+ let content;
200
+ try {
201
+ content = await readFile(join2(rootPath, file.path), "utf-8");
202
+ } catch {
203
+ return;
204
+ }
205
+ const imports = /* @__PURE__ */ new Set();
206
+ for (const pattern of patterns) {
207
+ pattern.lastIndex = 0;
208
+ let match;
209
+ while ((match = pattern.exec(content)) !== null) {
210
+ if (match[1]) imports.add(match[1]);
211
+ }
212
+ }
213
+ if (imports.size > 0) {
214
+ result[file.path] = [...imports];
215
+ }
216
+ })
217
+ );
218
+ return result;
219
+ }
220
+
221
+ // src/scanner/structure.ts
222
+ var _MUGM6 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
223
+ function analyzeDirectoryStructure(files) {
224
+ const dirMap = /* @__PURE__ */ new Map();
225
+ for (const file of files) {
226
+ const dir = file.directory || ".";
227
+ if (!dirMap.has(dir)) dirMap.set(dir, { files: [] });
228
+ dirMap.get(dir).files.push(file);
229
+ }
230
+ for (const dir of [...dirMap.keys()]) {
231
+ const parts = dir.split("/");
232
+ for (let i = 1; i < parts.length; i++) {
233
+ const parent = parts.slice(0, i).join("/");
234
+ if (!dirMap.has(parent)) dirMap.set(parent, { files: [] });
235
+ }
236
+ }
237
+ const infos = [];
238
+ for (const [path, { files: dirFiles }] of dirMap) {
239
+ const depth = path === "." ? 0 : path.split("/").length;
240
+ const languages = [...new Set(dirFiles.map((f) => f.language))];
241
+ const isMixed = languages.length > 1 || dirFiles.length > 0 && hasMultiplePurposes(dirFiles);
242
+ infos.push({
243
+ path,
244
+ depth,
245
+ fileCount: dirFiles.length,
246
+ languages,
247
+ isMixed
248
+ });
249
+ }
250
+ return infos.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));
251
+ }
252
+ function hasMultiplePurposes(files) {
253
+ const ROLE_MARKERS = [".service", ".controller", ".repository", ".model", ".handler", ".middleware"];
254
+ const roles = /* @__PURE__ */ new Set();
255
+ for (const file of files) {
256
+ const fullName = (file.name + file.extension).toLowerCase();
257
+ for (const marker of ROLE_MARKERS) {
258
+ if (fullName.includes(marker)) {
259
+ roles.add(marker);
260
+ break;
261
+ }
262
+ }
263
+ }
264
+ return roles.size > 1;
265
+ }
266
+
267
+ // src/scanner/identifiers.ts
268
+ import { readFile as readFile2 } from "fs/promises";
269
+ import { join as join3 } from "path";
270
+ var _MUGM7 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
271
+ var PATTERNS2 = {
272
+ typescript: [
273
+ /class\s+(\w+)/g,
274
+ /function\s+(\w+)/g,
275
+ /(?:const|let|var)\s+(\w+)\s*=/g,
276
+ /interface\s+(\w+)/g,
277
+ /type\s+(\w+)\s*=/g,
278
+ /enum\s+(\w+)/g
279
+ ],
280
+ javascript: [
281
+ /class\s+(\w+)/g,
282
+ /function\s+(\w+)/g,
283
+ /(?:const|let|var)\s+(\w+)\s*=/g
284
+ ],
285
+ python: [
286
+ /^class\s+(\w+)/gm,
287
+ /^def\s+(\w+)/gm,
288
+ /^(\w+)\s*=/gm
289
+ ],
290
+ go: [
291
+ /^func\s+(\w+)/gm,
292
+ /^type\s+(\w+)/gm,
293
+ /^var\s+(\w+)/gm
294
+ ],
295
+ java: [
296
+ /class\s+(\w+)/g,
297
+ /interface\s+(\w+)/g,
298
+ /(?:public|private|protected)\s+\w+\s+(\w+)\s*\(/g
299
+ ],
300
+ kotlin: [
301
+ /class\s+(\w+)/g,
302
+ /fun\s+(\w+)/g,
303
+ /(?:val|var)\s+(\w+)/g
304
+ ]
305
+ };
306
+ var GENERIC_WORDS = /* @__PURE__ */ new Set([
307
+ "index",
308
+ "main",
309
+ "app",
310
+ "i",
311
+ "j",
312
+ "k",
313
+ "x",
314
+ "y",
315
+ "z",
316
+ "err",
317
+ "error",
318
+ "result",
319
+ "res",
320
+ "req",
321
+ "ctx",
322
+ "next",
323
+ "true",
324
+ "false",
325
+ "null",
326
+ "undefined"
327
+ ]);
328
+ async function extractIdentifiers(rootPath, files) {
329
+ const result = {};
330
+ await Promise.all(
331
+ files.map(async (file) => {
332
+ const patterns = PATTERNS2[file.language];
333
+ if (!patterns) return;
334
+ let content;
335
+ try {
336
+ content = await readFile2(join3(rootPath, file.path), "utf-8");
337
+ } catch {
338
+ return;
339
+ }
340
+ const identifiers = /* @__PURE__ */ new Set();
341
+ for (const pattern of patterns) {
342
+ pattern.lastIndex = 0;
343
+ let match;
344
+ while ((match = pattern.exec(content)) !== null) {
345
+ const name = match[1];
346
+ if (name && name.length > 1 && !GENERIC_WORDS.has(name.toLowerCase())) {
347
+ identifiers.add(name);
348
+ }
349
+ }
350
+ }
351
+ if (identifiers.size > 0) {
352
+ result[file.path] = [...identifiers];
353
+ }
354
+ })
355
+ );
356
+ return result;
357
+ }
358
+
359
+ // src/scanner/index.ts
360
+ var _MUGM8 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
361
+ async function scan(rootPath) {
362
+ const allPaths = await walkFiles(rootPath);
363
+ const files = classifyFiles(allPaths);
364
+ const [imports, identifiers] = await Promise.all([
365
+ extractImports(rootPath, files),
366
+ extractIdentifiers(rootPath, files)
367
+ ]);
368
+ const naming = extractNamingSignals(files);
369
+ const directories = analyzeDirectoryStructure(files);
370
+ return { files, directories, naming, imports, identifiers };
371
+ }
372
+
373
+ // src/analyzer/index.ts
374
+ import { readFile as readFile3 } from "fs/promises";
375
+ import { join as join4 } from "path";
376
+
377
+ // src/analyzer/architecture.ts
378
+ var _MUGM9 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
379
+ var LAYER_KEYWORDS = /* @__PURE__ */ new Set(["controller", "controllers", "service", "services", "repository", "repositories", "model", "models", "middleware", "route", "routes", "handler", "handlers", "resolver", "resolvers"]);
380
+ var MODULAR_INDICATORS = /* @__PURE__ */ new Set(["auth", "billing", "payments", "notifications", "users", "orders", "products", "admin", "core", "shared", "accounts", "inventory", "catalog", "reporting"]);
381
+ function basename2(p) {
382
+ return p.split("/").pop().toLowerCase();
383
+ }
384
+ function parentOf(p) {
385
+ const parts = p.split("/");
386
+ return parts.length > 1 ? parts.slice(0, -1).join("/") : "";
387
+ }
388
+ function detectArchitecture(directories) {
389
+ const maxDepth = Math.max(0, ...directories.map((d) => d.depth));
390
+ const byParent = /* @__PURE__ */ new Map();
391
+ for (const dir of directories) {
392
+ const parent = parentOf(dir.path);
393
+ if (!byParent.has(parent)) byParent.set(parent, []);
394
+ byParent.get(parent).push(basename2(dir.path));
395
+ }
396
+ for (const [parent, children] of byParent) {
397
+ const domainChildren = children.filter((c) => MODULAR_INDICATORS.has(c));
398
+ if (domainChildren.length >= 2) {
399
+ const domainWithLayers = domainChildren.filter((dc) => {
400
+ const subChildren = byParent.get(parent ? `${parent}/${dc}` : dc) ?? [];
401
+ return subChildren.some((sc) => LAYER_KEYWORDS.has(sc));
402
+ });
403
+ if (domainWithLayers.length >= 1) {
404
+ const confidence = Math.min(0.99, 0.4 + domainChildren.length * 0.1);
405
+ return { style: "modular", layers: domainChildren, depth: maxDepth, confidence };
406
+ }
407
+ }
408
+ }
409
+ for (const [, children] of byParent) {
410
+ const layerChildren = children.filter((c) => LAYER_KEYWORDS.has(c));
411
+ if (layerChildren.length >= 2) {
412
+ const confidence = Math.min(0.99, 0.5 + layerChildren.length * 0.1);
413
+ return { style: "layered", layers: layerChildren, depth: maxDepth, confidence };
414
+ }
415
+ }
416
+ return { style: "flat", layers: [], depth: maxDepth, confidence: 0.5 };
417
+ }
418
+
419
+ // src/analyzer/patterns.ts
420
+ var _MUGM10 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
421
+ var PATTERN_RULES = {
422
+ repository: {
423
+ fileNameMatches: [/\.repository\./i, /Repository\./],
424
+ identifierMatches: [/Repository$/]
425
+ },
426
+ service: {
427
+ fileNameMatches: [/\.service\./i, /Service\./],
428
+ identifierMatches: [/Service$/]
429
+ },
430
+ controller: {
431
+ fileNameMatches: [/\.controller\./i, /Controller\./],
432
+ identifierMatches: [/Controller$/]
433
+ },
434
+ factory: {
435
+ fileNameMatches: [/\.factory\./i, /Factory\./],
436
+ identifierMatches: [/Factory$/, /^create[A-Z]/]
437
+ },
438
+ middleware: {
439
+ fileNameMatches: [/\.middleware\./i, /Middleware\./],
440
+ identifierMatches: [/Middleware$/],
441
+ dirMatches: [/middleware/i]
442
+ },
443
+ handler: {
444
+ fileNameMatches: [/\.handler\./i, /Handler\./],
445
+ identifierMatches: [/Handler$/]
446
+ },
447
+ manager: {
448
+ fileNameMatches: [/\.manager\./i, /Manager\./],
449
+ identifierMatches: [/Manager$/]
450
+ }
451
+ };
452
+ function detectPatterns(files, identifiers) {
453
+ const results = {};
454
+ for (const [patternName, rule] of Object.entries(PATTERN_RULES)) {
455
+ const matchedFiles = /* @__PURE__ */ new Set();
456
+ for (const file of files) {
457
+ const fullName = file.name + file.extension;
458
+ const nameMatch = rule.fileNameMatches.some((r) => r.test(fullName));
459
+ const dirMatch = rule.dirMatches?.some((r) => r.test(file.directory)) ?? false;
460
+ if (nameMatch || dirMatch) {
461
+ matchedFiles.add(file.path);
462
+ continue;
463
+ }
464
+ const fileIdentifiers = identifiers[file.path] ?? [];
465
+ const identifierMatch = fileIdentifiers.some((id) => rule.identifierMatches.some((r) => r.test(id)));
466
+ if (identifierMatch) matchedFiles.add(file.path);
467
+ }
468
+ const count = matchedFiles.size;
469
+ const detected = count > 0;
470
+ const confidence = detected ? Math.min(0.99, 0.4 + count / Math.max(files.length, 1) * 3) : 0;
471
+ results[patternName] = { detected, confidence: parseFloat(confidence.toFixed(2)), count, files: [...matchedFiles] };
472
+ }
473
+ return results;
474
+ }
475
+
476
+ // src/analyzer/naming.ts
477
+ var _MUGM11 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
478
+ function topKeys(map, n = 5) {
479
+ return Object.entries(map).sort((a, b) => b[1] - a[1]).slice(0, n).map(([k]) => k);
480
+ }
481
+ function dominantKey(map) {
482
+ let best = "";
483
+ let max = 0;
484
+ for (const [k, v] of Object.entries(map)) {
485
+ if (v > max) {
486
+ max = v;
487
+ best = k;
488
+ }
489
+ }
490
+ return best;
491
+ }
492
+ function analyzeNaming(signals) {
493
+ return {
494
+ prefixes: topKeys(signals.prefixes),
495
+ suffixes: topKeys(signals.suffixes),
496
+ casing: dominantKey(signals.casing) || "camelCase"
497
+ };
498
+ }
499
+
500
+ // src/analyzer/concepts.ts
501
+ var _MUGM12 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
502
+ var GENERIC_WORDS2 = /* @__PURE__ */ new Set([
503
+ "index",
504
+ "main",
505
+ "app",
506
+ "utils",
507
+ "util",
508
+ "helpers",
509
+ "helper",
510
+ "common",
511
+ "base",
512
+ "abstract",
513
+ "shared",
514
+ "types",
515
+ "type",
516
+ "interface",
517
+ "config",
518
+ "constant",
519
+ "constants",
520
+ "enum",
521
+ "enums",
522
+ "default",
523
+ "test",
524
+ "spec",
525
+ "mock",
526
+ "stub",
527
+ "fixture",
528
+ "error",
529
+ "errors",
530
+ "exception",
531
+ "logger",
532
+ "log",
533
+ "debug",
534
+ "console",
535
+ "init",
536
+ "setup",
537
+ "boot",
538
+ "start",
539
+ "run",
540
+ "get",
541
+ "set",
542
+ "create",
543
+ "update",
544
+ "delete",
545
+ "fetch",
546
+ "handle",
547
+ "build",
548
+ "make",
549
+ "find",
550
+ "load",
551
+ "save",
552
+ "send",
553
+ "parse",
554
+ "format",
555
+ "validate",
556
+ "dto",
557
+ "vo",
558
+ "entity",
559
+ "module",
560
+ "component",
561
+ "service",
562
+ "controller",
563
+ "repository",
564
+ "middleware",
565
+ "handler",
566
+ "factory",
567
+ "manager",
568
+ "provider",
569
+ "resolver",
570
+ "guard",
571
+ "pipe",
572
+ "decorator",
573
+ "mixin",
574
+ "plugin"
575
+ ]);
576
+ function splitIntoWords(identifier) {
577
+ return identifier.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_\-./]/g, " ").split(" ").map((w) => w.toLowerCase()).filter((w) => w.length > 2);
578
+ }
579
+ function extractConcepts(identifiers, directories, topN = 20) {
580
+ const freq = {};
581
+ for (const dir of directories) {
582
+ const parts = dir.split("/");
583
+ for (const part of parts) {
584
+ const words = splitIntoWords(part);
585
+ for (const word of words) {
586
+ if (!GENERIC_WORDS2.has(word) && word.length > 2) {
587
+ freq[word] = (freq[word] ?? 0) + 3;
588
+ }
589
+ }
590
+ }
591
+ }
592
+ for (const ids of Object.values(identifiers)) {
593
+ for (const id of ids) {
594
+ const words = splitIntoWords(id);
595
+ for (const word of words) {
596
+ if (!GENERIC_WORDS2.has(word) && word.length > 2) {
597
+ freq[word] = (freq[word] ?? 0) + 1;
598
+ }
599
+ }
600
+ }
601
+ }
602
+ return Object.entries(freq).sort((a, b) => b[1] - a[1]).slice(0, topN).map(([word]) => word);
603
+ }
604
+
605
+ // src/analyzer/dependencies.ts
606
+ var _MUGM13 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
607
+ var DB_CLIENTS = ["prisma", "mongoose", "sequelize", "typeorm", "knex", "pg", "mysql", "sqlite", "mongodb", "redis", "drizzle", "@prisma/client", "pg-promise"];
608
+ var LAYER_ORDER = {
609
+ controller: 0,
610
+ controllers: 0,
611
+ route: 0,
612
+ routes: 0,
613
+ resolver: 0,
614
+ resolvers: 0,
615
+ service: 1,
616
+ services: 1,
617
+ repository: 2,
618
+ repositories: 2,
619
+ model: 3,
620
+ models: 3,
621
+ entity: 3,
622
+ entities: 3
623
+ };
624
+ function getLayer(filePath) {
625
+ const parts = filePath.toLowerCase().split("/");
626
+ for (const part of parts) {
627
+ if (part in LAYER_ORDER) return LAYER_ORDER[part];
628
+ }
629
+ return -1;
630
+ }
631
+ function dirOf(filePath) {
632
+ const parts = filePath.split("/");
633
+ return parts.length > 1 ? parts[0] : ".";
634
+ }
635
+ function analyzeDependencies(files, imports) {
636
+ let directDbAccess = 0;
637
+ let crossLayerViolations = 0;
638
+ for (const file of files) {
639
+ const fileImports = imports[file.path] ?? [];
640
+ const fileLayer = getLayer(file.path);
641
+ for (const imp of fileImports) {
642
+ const impLower = imp.toLowerCase();
643
+ const isDbClient = DB_CLIENTS.some((db) => impLower.includes(db));
644
+ if (isDbClient && fileLayer !== 2 && fileLayer !== 3) {
645
+ directDbAccess++;
646
+ }
647
+ if (fileLayer === 0) {
648
+ const impLayer = getLayer(imp);
649
+ if (impLayer === 2) crossLayerViolations++;
650
+ }
651
+ }
652
+ }
653
+ const adjMap = {};
654
+ for (const file of files) {
655
+ const fileDir = dirOf(file.path);
656
+ adjMap[fileDir] = adjMap[fileDir] ?? [];
657
+ for (const imp of imports[file.path] ?? []) {
658
+ if (imp.startsWith(".")) {
659
+ const parts = imp.replace(/^\.\//, "").split("/");
660
+ if (parts.length > 1) {
661
+ const targetDir = parts[0];
662
+ if (targetDir !== fileDir && !adjMap[fileDir].includes(targetDir)) {
663
+ adjMap[fileDir].push(targetDir);
664
+ }
665
+ }
666
+ }
667
+ }
668
+ }
669
+ const circularImports = countCycles(adjMap);
670
+ return { directDbAccess, crossLayerViolations, circularImports };
671
+ }
672
+ function countCycles(graph) {
673
+ const visited = /* @__PURE__ */ new Set();
674
+ const inStack = /* @__PURE__ */ new Set();
675
+ let cycles = 0;
676
+ function dfs(node) {
677
+ visited.add(node);
678
+ inStack.add(node);
679
+ for (const neighbor of graph[node] ?? []) {
680
+ if (!visited.has(neighbor)) {
681
+ dfs(neighbor);
682
+ } else if (inStack.has(neighbor)) {
683
+ cycles++;
684
+ }
685
+ }
686
+ inStack.delete(node);
687
+ }
688
+ for (const node of Object.keys(graph)) {
689
+ if (!visited.has(node)) dfs(node);
690
+ }
691
+ return cycles;
692
+ }
693
+
694
+ // src/analyzer/index.ts
695
+ var _MUGM14 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
696
+ async function computeSizes(rootPath, signals) {
697
+ const lineCounts = await Promise.all(
698
+ signals.files.map(async (f) => {
699
+ try {
700
+ const content = await readFile3(join4(rootPath, f.path), "utf-8");
701
+ return { path: f.path, lines: content.split("\n").length };
702
+ } catch {
703
+ return { path: f.path, lines: 0 };
704
+ }
705
+ })
706
+ );
707
+ const totalLines = lineCounts.reduce((sum, f) => sum + f.lines, 0);
708
+ const avgFileLines = signals.files.length > 0 ? Math.round(totalLines / signals.files.length) : 0;
709
+ const largestFiles = [...lineCounts].sort((a, b) => b.lines - a.lines).slice(0, 5).map((f) => f.path);
710
+ return { totalFiles: signals.files.length, avgFileLines, largestFiles };
711
+ }
712
+ async function analyze(signals, rootPath = ".") {
713
+ const architecture = detectArchitecture(signals.directories);
714
+ const patterns = detectPatterns(signals.files, signals.identifiers);
715
+ const naming = analyzeNaming(signals.naming);
716
+ const concepts = extractConcepts(
717
+ signals.identifiers,
718
+ signals.directories.map((d) => d.path)
719
+ );
720
+ const dependencies = analyzeDependencies(signals.files, signals.imports);
721
+ const sizes = await computeSizes(rootPath, signals);
722
+ return {
723
+ version: 1,
724
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
725
+ architecture,
726
+ patterns,
727
+ naming,
728
+ concepts,
729
+ dependencies,
730
+ sizes
731
+ };
732
+ }
733
+
734
+ // src/output/initReport.ts
735
+ import chalk2 from "chalk";
736
+ var _MUGM15 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
737
+ var SEP = chalk2.gray("\u2500".repeat(51));
738
+ function label(text) {
739
+ return chalk2.gray(text.padEnd(18));
740
+ }
741
+ function val(text) {
742
+ return chalk2.white(text);
743
+ }
744
+ function printInitReport(genome) {
745
+ const { architecture, patterns, naming, concepts, dependencies, sizes } = genome;
746
+ console.log();
747
+ console.log(chalk2.green.bold("RepoGenome Init"));
748
+ console.log(SEP);
749
+ console.log();
750
+ console.log(chalk2.gray(`Scanning ${sizes.totalFiles} files across ${genome.architecture.layers.length || 1} ${architecture.style === "modular" ? "modules" : "layers"}...`));
751
+ console.log();
752
+ console.log(chalk2.green.bold("Genome Captured"));
753
+ console.log();
754
+ const layerStr = architecture.layers.length > 0 ? `${architecture.layers.join(" \u2192 ")} (${architecture.layers.length} layers)` : `depth ${architecture.depth}`;
755
+ console.log(`${label("Architecture:")}${val(`${capitalise(architecture.style)} \u2014 ${layerStr}`)}`);
756
+ const detectedPatterns = Object.entries(patterns).filter(([, p]) => p.detected).map(([name, p]) => `${capitalise(name)} (${p.count})`).join(", ");
757
+ console.log(`${label("Patterns:")}${val(detectedPatterns || "none detected")}`);
758
+ const namingParts = [naming.casing];
759
+ if (naming.prefixes.length) namingParts.push(naming.prefixes.map((p) => `${p}*`).join(" \xB7 "));
760
+ if (naming.suffixes.length) namingParts.push(naming.suffixes.join(" \xB7 "));
761
+ console.log(`${label("Naming:")}${val(namingParts.join(" \xB7 "))}`);
762
+ console.log(`${label("Concepts:")}${val(concepts.slice(0, 8).join(" \xB7 ") || "none")}`);
763
+ console.log(`${label("Direct DB Access:")}${val(String(dependencies.directDbAccess) + " files")}`);
764
+ console.log(`${label("Cross-layer:")}${val(dependencies.crossLayerViolations + " violations")}`);
765
+ console.log(`${label("Circular Imports:")}${val(String(dependencies.circularImports))}`);
766
+ console.log(`${label("Avg file size:")}${val(sizes.avgFileLines + " lines")}`);
767
+ console.log();
768
+ console.log(SEP);
769
+ console.log(chalk2.green(" Baseline saved to ") + chalk2.white(".repogenome/genome.json"));
770
+ console.log(chalk2.gray(" Commit this file to track drift over time."));
771
+ console.log();
772
+ console.log(chalk2.gray(" Tip: add .repogenome/history/ to .gitignore"));
773
+ console.log();
774
+ }
775
+ function capitalise(s) {
776
+ return s.charAt(0).toUpperCase() + s.slice(1);
777
+ }
778
+
779
+ // src/output/json.ts
780
+ var _MUGM16 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
781
+ function buildJsonEnvelope(command, data) {
782
+ return {
783
+ version: 1,
784
+ command,
785
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
786
+ data
787
+ };
788
+ }
789
+ function printJson(command, data) {
790
+ console.log(JSON.stringify(buildJsonEnvelope(command, data), null, 2));
791
+ }
792
+
793
+ // src/commands/init.ts
794
+ var _MUGM17 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
795
+ async function fileExists(p) {
796
+ try {
797
+ await access(p);
798
+ return true;
799
+ } catch {
800
+ return false;
801
+ }
802
+ }
803
+ async function prompt(question) {
804
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
805
+ return new Promise((resolve5) => rl.question(question, (ans) => {
806
+ rl.close();
807
+ resolve5(ans.trim());
808
+ }));
809
+ }
810
+ function initCommand() {
811
+ return new Command("init").description("Scan the repository and create a baseline genome").option("--path <dir>", "Scan a subdirectory instead of root", ".").option("--lang <language>", "Hint the primary language (auto-detected by default)").option("--format <fmt>", "Output format: terminal | json", "terminal").option("--silent", "No output, just write the file").action(async (options) => {
812
+ const rootPath = resolve(options.path);
813
+ const outputDir = join5(rootPath, ".repogenome");
814
+ const outputFile = join5(outputDir, "genome.json");
815
+ const silent = options.silent ?? false;
816
+ const isJson = options.format === "json";
817
+ try {
818
+ if (await fileExists(outputFile)) {
819
+ if (silent) {
820
+ } else {
821
+ const answer = await prompt(chalk3.yellow(" Baseline already exists. Overwrite? (y/n) "));
822
+ if (answer.toLowerCase() !== "y") {
823
+ console.log(chalk3.gray(" Aborted."));
824
+ process.exit(0);
825
+ }
826
+ }
827
+ }
828
+ const spinner = silent || isJson ? null : ora({ text: chalk3.gray("Scanning files\u2026"), color: "green" }).start();
829
+ const signals = await scan(rootPath);
830
+ if (signals.files.length === 0) {
831
+ spinner?.fail(chalk3.red("No supported files found. Nothing to scan."));
832
+ process.exit(3);
833
+ }
834
+ if (spinner) spinner.text = chalk3.gray("Building genome\u2026");
835
+ const genome = await analyze(signals, rootPath);
836
+ spinner?.stop();
837
+ await mkdir(outputDir, { recursive: true });
838
+ await writeFile(outputFile, JSON.stringify(genome, null, 2), "utf-8");
839
+ if (!silent) {
840
+ if (isJson) {
841
+ printJson("init", genome);
842
+ } else {
843
+ printInitReport(genome);
844
+ }
845
+ }
846
+ } catch (err) {
847
+ const message = err instanceof Error ? err.message : String(err);
848
+ console.error(chalk3.red(` Error: ${message}`));
849
+ process.exit(3);
850
+ }
851
+ });
852
+ }
853
+
854
+ // src/commands/audit.ts
855
+ import { Command as Command2 } from "commander";
856
+ import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir2, access as access2 } from "fs/promises";
857
+ import { join as join6, resolve as resolve2 } from "path";
858
+ import chalk5 from "chalk";
859
+ import ora2 from "ora";
860
+
861
+ // src/diff/architecture.ts
862
+ var _MUGM18 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
863
+ function diffArchitecture(a, b) {
864
+ const details = [];
865
+ let score = 0;
866
+ if (a.architecture.style !== b.architecture.style) {
867
+ score += 0.6;
868
+ details.push(`architecture style changed: ${a.architecture.style} \u2192 ${b.architecture.style}`);
869
+ }
870
+ const aLayers = new Set(a.architecture.layers);
871
+ const bLayers = new Set(b.architecture.layers);
872
+ const added = b.architecture.layers.filter((l) => !aLayers.has(l));
873
+ const removed = a.architecture.layers.filter((l) => !bLayers.has(l));
874
+ if (added.length) {
875
+ score += Math.min(0.2, added.length * 0.07);
876
+ details.push(`layers added: ${added.join(", ")}`);
877
+ }
878
+ if (removed.length) {
879
+ score += Math.min(0.2, removed.length * 0.07);
880
+ details.push(`layers removed: ${removed.join(", ")}`);
881
+ }
882
+ const depthDelta = Math.abs(b.architecture.depth - a.architecture.depth);
883
+ if (depthDelta > 0) {
884
+ score += Math.min(0.1, depthDelta * 0.03);
885
+ details.push(`nesting depth changed: ${a.architecture.depth} \u2192 ${b.architecture.depth}`);
886
+ }
887
+ return { score: Math.min(1, parseFloat(score.toFixed(3))), details };
888
+ }
889
+
890
+ // src/diff/patterns.ts
891
+ var _MUGM19 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
892
+ function diffPatterns(a, b) {
893
+ const details = [];
894
+ const added = [];
895
+ const removed = [];
896
+ const changed = [];
897
+ let score = 0;
898
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(a.patterns), ...Object.keys(b.patterns)]);
899
+ for (const key of allKeys) {
900
+ const pa = a.patterns[key];
901
+ const pb = b.patterns[key];
902
+ if (!pa?.detected && pb?.detected) {
903
+ added.push(key);
904
+ score += 0.15;
905
+ details.push(`+ ${key} pattern introduced (confidence: ${pb.confidence}, ${pb.count} files)`);
906
+ } else if (pa?.detected && !pb?.detected) {
907
+ removed.push(key);
908
+ score += 0.2;
909
+ details.push(`- ${key} pattern removed`);
910
+ } else if (pa?.detected && pb?.detected) {
911
+ const drop = pa.confidence - pb.confidence;
912
+ if (drop > 0.2) {
913
+ changed.push(key);
914
+ score += drop * 0.3;
915
+ details.push(`~ ${key} pattern weakening ${(pa.confidence * 100).toFixed(0)}% \u2192 ${(pb.confidence * 100).toFixed(0)}% presence`);
916
+ }
917
+ }
918
+ }
919
+ return {
920
+ dimension: { score: Math.min(1, parseFloat(score.toFixed(3))), details },
921
+ added,
922
+ removed,
923
+ changed
924
+ };
925
+ }
926
+
927
+ // src/diff/naming.ts
928
+ var _MUGM20 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
929
+ function diffNaming(a, b) {
930
+ const details = [];
931
+ const changed = [];
932
+ let score = 0;
933
+ if (a.naming.casing !== b.naming.casing) {
934
+ score += 0.5;
935
+ details.push(`casing changed: ${a.naming.casing} \u2192 ${b.naming.casing}`);
936
+ changed.push(`casing: ${a.naming.casing} \u2192 ${b.naming.casing}`);
937
+ }
938
+ const aPreSet = new Set(a.naming.prefixes);
939
+ const bPreSet = new Set(b.naming.prefixes);
940
+ const newPrefixes = b.naming.prefixes.filter((p) => !aPreSet.has(p));
941
+ const gonePrefixes = a.naming.prefixes.filter((p) => !bPreSet.has(p));
942
+ if (newPrefixes.length) {
943
+ score += Math.min(0.2, newPrefixes.length * 0.05);
944
+ details.push(`new prefixes: ${newPrefixes.join(", ")}`);
945
+ changed.push(`new prefixes: ${newPrefixes.join(", ")}`);
946
+ }
947
+ if (gonePrefixes.length) {
948
+ score += Math.min(0.15, gonePrefixes.length * 0.05);
949
+ details.push(`removed prefixes: ${gonePrefixes.join(", ")}`);
950
+ changed.push(`removed prefixes: ${gonePrefixes.join(", ")}`);
951
+ }
952
+ const aSufSet = new Set(a.naming.suffixes);
953
+ const bSufSet = new Set(b.naming.suffixes);
954
+ const newSuffixes = b.naming.suffixes.filter((s) => !aSufSet.has(s));
955
+ const goneSuffixes = a.naming.suffixes.filter((s) => !bSufSet.has(s));
956
+ if (newSuffixes.length) {
957
+ score += Math.min(0.2, newSuffixes.length * 0.05);
958
+ details.push(`new suffixes: ${newSuffixes.join(", ")}`);
959
+ changed.push(`new suffixes: ${newSuffixes.join(", ")}`);
960
+ }
961
+ if (goneSuffixes.length) {
962
+ score += Math.min(0.15, goneSuffixes.length * 0.05);
963
+ details.push(`removed suffixes: ${goneSuffixes.join(", ")}`);
964
+ changed.push(`removed suffixes: ${goneSuffixes.join(", ")}`);
965
+ }
966
+ return { dimension: { score: Math.min(1, parseFloat(score.toFixed(3))), details }, changed };
967
+ }
968
+
969
+ // src/diff/concepts.ts
970
+ var _MUGM21 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
971
+ function diffConcepts(a, b) {
972
+ const aSet = new Set(a.concepts);
973
+ const bSet = new Set(b.concepts);
974
+ const added = b.concepts.filter((c) => !aSet.has(c));
975
+ const removed = a.concepts.filter((c) => !bSet.has(c));
976
+ const details = [];
977
+ if (added.length) details.push(`+ concepts: ${added.slice(0, 5).join(", ")}`);
978
+ if (removed.length) details.push(`- concepts: ${removed.slice(0, 5).join(", ")}`);
979
+ const total = Math.max(a.concepts.length, b.concepts.length, 1);
980
+ const score = Math.min(1, (added.length + removed.length) / total);
981
+ return { dimension: { score: parseFloat(score.toFixed(3)), details }, added, removed };
982
+ }
983
+
984
+ // src/diff/dependencies.ts
985
+ var _MUGM22 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
986
+ function scoreDelta(before, after, label2, details, changed) {
987
+ const delta = after - before;
988
+ if (delta === 0) return 0;
989
+ const direction = delta > 0 ? "increasing" : "decreasing";
990
+ details.push(`${label2} ${direction}: ${before} \u2192 ${after}`);
991
+ changed.push(`${label2}: ${before} \u2192 ${after}`);
992
+ return delta > 0 ? Math.min(1, delta * 0.15) : 0;
993
+ }
994
+ function diffDependencies(a, b) {
995
+ const depDetails = [];
996
+ const violDetails = [];
997
+ const changed = [];
998
+ let depScore = 0;
999
+ let violScore = 0;
1000
+ depScore += scoreDelta(a.dependencies.directDbAccess, b.dependencies.directDbAccess, "direct DB access", depDetails, changed);
1001
+ depScore += scoreDelta(a.dependencies.circularImports, b.dependencies.circularImports, "circular imports", depDetails, changed);
1002
+ violScore += scoreDelta(a.dependencies.crossLayerViolations, b.dependencies.crossLayerViolations, "cross-layer violations", violDetails, changed);
1003
+ return {
1004
+ depDimension: { score: Math.min(1, parseFloat(depScore.toFixed(3))), details: depDetails },
1005
+ violationDimension: { score: Math.min(1, parseFloat(violScore.toFixed(3))), details: violDetails },
1006
+ changed
1007
+ };
1008
+ }
1009
+
1010
+ // src/diff/scorer.ts
1011
+ var _MUGM23 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1012
+ var WEIGHTS = {
1013
+ architecture: 0.2,
1014
+ patterns: 0.25,
1015
+ naming: 0.15,
1016
+ concepts: 0.15,
1017
+ dependencies: 0.15,
1018
+ violations: 0.1
1019
+ };
1020
+ function scoreDrift(dimensions) {
1021
+ const weighted = dimensions.architecture.score * WEIGHTS.architecture + dimensions.patterns.score * WEIGHTS.patterns + dimensions.naming.score * WEIGHTS.naming + dimensions.concepts.score * WEIGHTS.concepts + dimensions.dependencies.score * WEIGHTS.dependencies + dimensions.layerViolations.score * WEIGHTS.violations;
1022
+ return parseFloat((weighted * 100).toFixed(1));
1023
+ }
1024
+
1025
+ // src/diff/risk.ts
1026
+ var _MUGM24 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1027
+ function classifyRisk(drift) {
1028
+ if (drift < 10) return "Low";
1029
+ if (drift <= 30) return "Medium";
1030
+ return "High";
1031
+ }
1032
+
1033
+ // src/diff/index.ts
1034
+ var _MUGM25 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1035
+ function diff(a, b) {
1036
+ const architecture = diffArchitecture(a, b);
1037
+ const patternResult = diffPatterns(a, b);
1038
+ const namingResult = diffNaming(a, b);
1039
+ const conceptResult = diffConcepts(a, b);
1040
+ const depResult = diffDependencies(a, b);
1041
+ const dimensions = {
1042
+ architecture,
1043
+ patterns: patternResult.dimension,
1044
+ naming: namingResult.dimension,
1045
+ concepts: conceptResult.dimension,
1046
+ dependencies: depResult.depDimension,
1047
+ layerViolations: depResult.violationDimension
1048
+ };
1049
+ const overallDrift = scoreDrift(dimensions);
1050
+ const riskLevel = classifyRisk(overallDrift);
1051
+ return {
1052
+ overallDrift,
1053
+ dimensions,
1054
+ added: { patterns: patternResult.added, concepts: conceptResult.added },
1055
+ removed: { patterns: patternResult.removed, concepts: conceptResult.removed },
1056
+ changed: {
1057
+ patterns: patternResult.changed,
1058
+ naming: namingResult.changed,
1059
+ dependencies: depResult.changed
1060
+ },
1061
+ riskLevel
1062
+ };
1063
+ }
1064
+
1065
+ // src/output/auditReport.ts
1066
+ import chalk4 from "chalk";
1067
+ var _MUGM26 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1068
+ var SEP2 = chalk4.gray("\u2500".repeat(51));
1069
+ function stabilityBar(pct) {
1070
+ const filled = Math.round(pct / 5);
1071
+ const empty = 20 - filled;
1072
+ return chalk4.green("\u2588".repeat(filled)) + chalk4.gray("\u2591".repeat(empty));
1073
+ }
1074
+ function riskColor(level) {
1075
+ if (level === "High") return chalk4.red(level);
1076
+ if (level === "Medium") return chalk4.yellow(level);
1077
+ return chalk4.green(level);
1078
+ }
1079
+ function printAuditReport(baseline, current, result) {
1080
+ const stability = parseFloat((100 - result.overallDrift).toFixed(1));
1081
+ console.log();
1082
+ console.log(chalk4.green.bold("RepoGenome Audit"));
1083
+ console.log(SEP2);
1084
+ console.log(chalk4.gray(`Baseline: commit ${baseline.commit ?? "unknown"} (${baseline.timestamp.slice(0, 10)})`));
1085
+ console.log(chalk4.gray(`Current: commit ${current.commit ?? "unknown"} (${current.timestamp.slice(0, 10)})`));
1086
+ console.log();
1087
+ const arrow = result.overallDrift > 0 ? chalk4.red(" \u25BC") : chalk4.green(" \u25B2");
1088
+ console.log(`${chalk4.white.bold("Genome Stability:")} ${chalk4.white.bold(stability + "%")}${arrow} from 100%`);
1089
+ console.log(` ${stabilityBar(stability)}`);
1090
+ console.log();
1091
+ const hasChanges = result.added.patterns.length || result.removed.patterns.length || result.changed.patterns.length || result.added.concepts.length || result.removed.concepts.length || result.changed.naming.length || result.changed.dependencies.length || result.dimensions.architecture.details.length;
1092
+ if (hasChanges) {
1093
+ console.log(chalk4.white.bold("Changes Detected:"));
1094
+ console.log();
1095
+ for (const d of result.dimensions.architecture.details) {
1096
+ console.log(` ${chalk4.yellow("~")} ${d}`);
1097
+ }
1098
+ for (const p of result.added.patterns) {
1099
+ const pat = current.patterns[p];
1100
+ console.log(` ${chalk4.green("+")} ${p} pattern introduced (confidence: ${pat?.confidence ?? "?"}, ${pat?.count ?? 0} files)`);
1101
+ }
1102
+ for (const p of result.removed.patterns) {
1103
+ console.log(` ${chalk4.red("-")} ${p} pattern removed`);
1104
+ }
1105
+ for (const p of result.changed.patterns) {
1106
+ const a = baseline.patterns[p];
1107
+ const b = current.patterns[p];
1108
+ console.log(` ${chalk4.yellow("~")} ${p} pattern weakening ${((a?.confidence ?? 0) * 100).toFixed(0)}% \u2192 ${((b?.confidence ?? 0) * 100).toFixed(0)}% presence`);
1109
+ }
1110
+ for (const d of result.dimensions.dependencies.details) {
1111
+ const sym = d.includes("increasing") ? chalk4.red("+") : chalk4.green("-");
1112
+ console.log(` ${sym} ${d}`);
1113
+ }
1114
+ for (const d of result.dimensions.layerViolations.details) {
1115
+ console.log(` ${chalk4.red("+")} ${d}`);
1116
+ }
1117
+ if (result.added.concepts.length) {
1118
+ console.log(` ${chalk4.yellow("~")} concept vocabulary expanding: ${result.added.concepts.slice(0, 4).join(", ")}`);
1119
+ }
1120
+ console.log();
1121
+ } else {
1122
+ console.log(chalk4.green(" No architectural changes detected."));
1123
+ console.log();
1124
+ }
1125
+ console.log(`${chalk4.gray("Layer Violations:")} ${baseline.dependencies.crossLayerViolations} \u2192 ${current.dependencies.crossLayerViolations}`);
1126
+ console.log(`${chalk4.gray("Circular Imports:")} ${baseline.dependencies.circularImports} \u2192 ${current.dependencies.circularImports}`);
1127
+ console.log(`${chalk4.gray("Direct DB Access:")} ${baseline.dependencies.directDbAccess} \u2192 ${current.dependencies.directDbAccess} files`);
1128
+ console.log();
1129
+ console.log(`${chalk4.white("Risk Level:")} ${riskColor(result.riskLevel)}`);
1130
+ console.log();
1131
+ if (result.overallDrift > 0) {
1132
+ console.log(chalk4.gray(` Tip: Run ${chalk4.white("repogenome blame")} to see which directories are driving this drift.`));
1133
+ }
1134
+ console.log();
1135
+ }
1136
+
1137
+ // src/output/html.ts
1138
+ import { writeFile as writeFile2 } from "fs/promises";
1139
+ var _MUGM27 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1140
+ function riskClass(level) {
1141
+ if (level === "High") return "high";
1142
+ if (level === "Medium") return "medium";
1143
+ return "low";
1144
+ }
1145
+ function stabilityBar2(stability) {
1146
+ const pct = Math.max(0, Math.min(100, stability));
1147
+ const color = pct >= 90 ? "#4ade80" : pct >= 70 ? "#facc15" : "#f87171";
1148
+ return `<div class="bar-wrap"><div class="bar" style="width:${pct}%;background:${color}"></div></div>`;
1149
+ }
1150
+ function patternRows(a, b, added, removed, changed) {
1151
+ const rows = [];
1152
+ for (const p of added) {
1153
+ const pat = b.patterns[p];
1154
+ rows.push(`<tr>
1155
+ <td>${p}</td>
1156
+ <td><span class="badge added">added</span></td>
1157
+ <td>${(pat?.confidence ?? 0 * 100).toFixed(0)}%</td>
1158
+ <td>${pat?.count ?? 0}</td>
1159
+ </tr>`);
1160
+ }
1161
+ for (const p of removed) {
1162
+ const pat = a.patterns[p];
1163
+ rows.push(`<tr>
1164
+ <td>${p}</td>
1165
+ <td><span class="badge removed">removed</span></td>
1166
+ <td>${(pat?.confidence ?? 0 * 100).toFixed(0)}%</td>
1167
+ <td>${pat?.count ?? 0}</td>
1168
+ </tr>`);
1169
+ }
1170
+ for (const p of changed) {
1171
+ const pa = a.patterns[p];
1172
+ const pb = b.patterns[p];
1173
+ rows.push(`<tr>
1174
+ <td>${p}</td>
1175
+ <td><span class="badge changed">weakened</span></td>
1176
+ <td>${((pa?.confidence ?? 0) * 100).toFixed(0)}% \u2192 ${((pb?.confidence ?? 0) * 100).toFixed(0)}%</td>
1177
+ <td>${pb?.count ?? 0}</td>
1178
+ </tr>`);
1179
+ }
1180
+ if (rows.length === 0) {
1181
+ rows.push('<tr><td colspan="4" class="meta">No pattern changes detected</td></tr>');
1182
+ }
1183
+ return rows.join("\n");
1184
+ }
1185
+ var CSS = `
1186
+ *{box-sizing:border-box;margin:0;padding:0}
1187
+ body{font-family:system-ui,-apple-system,sans-serif;background:#0f1117;color:#d4d4d8;max-width:920px;margin:2rem auto;padding:0 1.5rem;line-height:1.6}
1188
+ h1{color:#4ade80;font-size:1.4rem;font-weight:700;margin-bottom:.25rem}
1189
+ h2{font-size:1rem;font-weight:600;color:#a1a1aa;text-transform:uppercase;letter-spacing:.08em;margin:2rem 0 .75rem}
1190
+ .meta{color:#52525b;font-size:.82rem}
1191
+ .drift{font-size:3rem;font-weight:800;margin:.5rem 0}
1192
+ .low{color:#4ade80}.medium{color:#facc15}.high{color:#f87171}
1193
+ .bar-wrap{background:#1e1e2e;border-radius:4px;height:10px;overflow:hidden;margin:.5rem 0 1rem}
1194
+ .bar{height:100%;border-radius:4px;transition:width .3s}
1195
+ table{width:100%;border-collapse:collapse;font-size:.9rem;margin-bottom:1.5rem}
1196
+ th{text-align:left;padding:.5rem .75rem;border-bottom:2px solid #27272a;color:#71717a;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em}
1197
+ td{padding:.5rem .75rem;border-bottom:1px solid #1c1c24}
1198
+ tr:hover td{background:#17171f}
1199
+ .badge{display:inline-block;padding:.15rem .5rem;border-radius:4px;font-size:.78rem;font-weight:600}
1200
+ .added{background:#14532d;color:#4ade80}.removed{background:#450a0a;color:#f87171}.changed{background:#422006;color:#fdba74}
1201
+ .risk-tag{display:inline-block;padding:.3rem .9rem;border-radius:6px;font-size:1.1rem;font-weight:700}
1202
+ .risk-low{background:#14532d;color:#4ade80}.risk-medium{background:#422006;color:#facc15}.risk-high{background:#450a0a;color:#f87171}
1203
+ .concept-list{display:flex;flex-wrap:wrap;gap:.4rem;margin:.5rem 0 1.5rem}
1204
+ .concept{background:#18181b;border:1px solid #3f3f46;padding:.2rem .6rem;border-radius:4px;font-size:.85rem}
1205
+ .concept.add{border-color:#16a34a;color:#4ade80}.concept.rem{border-color:#dc2626;color:#f87171}
1206
+ hr{border:none;border-top:1px solid #27272a;margin:1.5rem 0}
1207
+ footer{margin-top:3rem;color:#3f3f46;font-size:.78rem;text-align:center}
1208
+ `;
1209
+ function buildHtml(title, body) {
1210
+ return `<!DOCTYPE html>
1211
+ <html lang="en">
1212
+ <head>
1213
+ <meta charset="UTF-8">
1214
+ <meta name="viewport" content="width=device-width,initial-scale=1">
1215
+ <meta name="author" content="Muhammad Usman" data-gh="MuhammadUsmanGM" data-sig="MUGM-b4e2">
1216
+ <title>${escHtml(title)}</title>
1217
+ <style>${CSS}</style>
1218
+ </head>
1219
+ <body>
1220
+ ${body}
1221
+ <footer>Generated by <strong>RepoGenome</strong> &mdash; ${escHtml((/* @__PURE__ */ new Date()).toISOString())}</footer>
1222
+ <!-- MuhammadUsmanGM &#x200B;&#x200C;&#x200B; MUGM-b4e2 -->
1223
+ </body>
1224
+ </html>`;
1225
+ }
1226
+ function escHtml(s) {
1227
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1228
+ }
1229
+ function generateAuditHtml(baseline, current, result) {
1230
+ const stability = parseFloat((100 - result.overallDrift).toFixed(1));
1231
+ const rc = riskClass(result.riskLevel);
1232
+ const addedConcepts = result.added.concepts.slice(0, 12).map((c) => `<span class="concept add">+${escHtml(c)}</span>`).join("");
1233
+ const removedConcepts = result.removed.concepts.slice(0, 12).map((c) => `<span class="concept rem">-${escHtml(c)}</span>`).join("");
1234
+ const archDetails = result.dimensions.architecture.details.map((d) => `<tr><td>${escHtml(d)}</td></tr>`).join("");
1235
+ const depDetails = [
1236
+ ...result.dimensions.dependencies.details,
1237
+ ...result.dimensions.layerViolations.details
1238
+ ].map((d) => `<tr><td>${escHtml(d)}</td></tr>`).join("");
1239
+ const body = `
1240
+ <h1>RepoGenome Audit Report</h1>
1241
+ <p class="meta">Baseline: ${escHtml(baseline.timestamp.slice(0, 10))} &nbsp;|&nbsp; Current: ${escHtml(current.timestamp.slice(0, 10))}</p>
1242
+ <hr>
1243
+
1244
+ <h2>Genome Stability</h2>
1245
+ <div class="drift ${rc}">${stability}%</div>
1246
+ ${stabilityBar2(stability)}
1247
+ <p class="meta">Overall drift: <strong>${result.overallDrift}%</strong></p>
1248
+
1249
+ <h2>Patterns</h2>
1250
+ <table>
1251
+ <thead><tr><th>Pattern</th><th>Status</th><th>Confidence</th><th>Files</th></tr></thead>
1252
+ <tbody>${patternRows(baseline, current, result.added.patterns, result.removed.patterns, result.changed.patterns)}</tbody>
1253
+ </table>
1254
+
1255
+ ${addedConcepts || removedConcepts ? `
1256
+ <h2>Concepts</h2>
1257
+ <div class="concept-list">${addedConcepts}${removedConcepts}</div>
1258
+ ` : ""}
1259
+
1260
+ ${archDetails || depDetails ? `
1261
+ <h2>Architecture &amp; Dependencies</h2>
1262
+ <table><tbody>${archDetails}${depDetails}</tbody></table>
1263
+ ` : ""}
1264
+
1265
+ <h2>Risk Level</h2>
1266
+ <p><span class="risk-tag risk-${rc}">${escHtml(result.riskLevel)}</span></p>
1267
+
1268
+ <hr>
1269
+ <h2>Metrics</h2>
1270
+ <table>
1271
+ <thead><tr><th>Metric</th><th>Baseline</th><th>Current</th></tr></thead>
1272
+ <tbody>
1273
+ <tr><td>Total Files</td><td>${baseline.sizes.totalFiles}</td><td>${current.sizes.totalFiles}</td></tr>
1274
+ <tr><td>Avg File Lines</td><td>${baseline.sizes.avgFileLines}</td><td>${current.sizes.avgFileLines}</td></tr>
1275
+ <tr><td>Cross-Layer Violations</td><td>${baseline.dependencies.crossLayerViolations}</td><td>${current.dependencies.crossLayerViolations}</td></tr>
1276
+ <tr><td>Circular Imports</td><td>${baseline.dependencies.circularImports}</td><td>${current.dependencies.circularImports}</td></tr>
1277
+ <tr><td>Direct DB Access</td><td>${baseline.dependencies.directDbAccess}</td><td>${current.dependencies.directDbAccess}</td></tr>
1278
+ </tbody>
1279
+ </table>`;
1280
+ return buildHtml("RepoGenome Audit Report", body);
1281
+ }
1282
+ function generateCompareHtml(a, b, result, ref1, ref2) {
1283
+ const rc = riskClass(result.riskLevel);
1284
+ const addedConcepts = result.added.concepts.slice(0, 12).map((c) => `<span class="concept add">+${escHtml(c)}</span>`).join("");
1285
+ const removedConcepts = result.removed.concepts.slice(0, 12).map((c) => `<span class="concept rem">-${escHtml(c)}</span>`).join("");
1286
+ const archDetails = result.dimensions.architecture.details.map((d) => `<tr><td>${escHtml(d)}</td></tr>`).join("");
1287
+ const depDetails = [
1288
+ ...result.dimensions.dependencies.details,
1289
+ ...result.dimensions.layerViolations.details
1290
+ ].map((d) => `<tr><td>${escHtml(d)}</td></tr>`).join("");
1291
+ const body = `
1292
+ <h1>RepoGenome Compare Report</h1>
1293
+ <p class="meta">${escHtml(ref1)} &nbsp;\u2192&nbsp; ${escHtml(ref2)}</p>
1294
+ <hr>
1295
+
1296
+ <h2>Genome Drift</h2>
1297
+ <div class="drift ${rc}">${result.overallDrift}%</div>
1298
+ ${stabilityBar2(100 - result.overallDrift)}
1299
+ <p class="meta">Architecture style: <strong>${escHtml(a.architecture.style)}</strong> \u2192 <strong>${escHtml(b.architecture.style)}</strong></p>
1300
+
1301
+ <h2>Patterns</h2>
1302
+ <table>
1303
+ <thead><tr><th>Pattern</th><th>Status</th><th>Confidence</th><th>Files</th></tr></thead>
1304
+ <tbody>${patternRows(a, b, result.added.patterns, result.removed.patterns, result.changed.patterns)}</tbody>
1305
+ </table>
1306
+
1307
+ ${addedConcepts || removedConcepts ? `
1308
+ <h2>Concepts</h2>
1309
+ <div class="concept-list">${addedConcepts}${removedConcepts}</div>
1310
+ ` : ""}
1311
+
1312
+ ${archDetails || depDetails ? `
1313
+ <h2>Architecture &amp; Dependencies</h2>
1314
+ <table><tbody>${archDetails}${depDetails}</tbody></table>
1315
+ ` : ""}
1316
+
1317
+ <h2>Risk Level</h2>
1318
+ <p><span class="risk-tag risk-${rc}">${escHtml(result.riskLevel)}</span></p>`;
1319
+ return buildHtml(`RepoGenome Compare: ${ref1} \u2192 ${ref2}`, body);
1320
+ }
1321
+ async function writeHtmlReport(html, outputPath) {
1322
+ await writeFile2(outputPath, html, "utf-8");
1323
+ }
1324
+
1325
+ // src/commands/audit.ts
1326
+ var _MUGM28 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1327
+ async function fileExists2(p) {
1328
+ try {
1329
+ await access2(p);
1330
+ return true;
1331
+ } catch {
1332
+ return false;
1333
+ }
1334
+ }
1335
+ function parseGenomeFile(raw) {
1336
+ try {
1337
+ return JSON.parse(raw);
1338
+ } catch {
1339
+ console.error(chalk5.red(" Error: genome.json is corrupted or invalid JSON. Run `repogenome init` to regenerate it."));
1340
+ process.exit(3);
1341
+ }
1342
+ }
1343
+ function auditCommand() {
1344
+ return new Command2("audit").description("Compare the current repository state against the baseline genome").option("--threshold <n>", "Exit with code 1 if drift exceeds n%").option("--format <fmt>", "Output format: terminal | json | html", "terminal").option("--output <file>", "Output file path (for --format html)", "repogenome-report.html").option("--save", "Save snapshot to history (default: true)", true).option("--no-save", "Skip writing snapshot to history").option("--baseline <file>", "Use a specific genome file instead of default").action(async (options) => {
1345
+ const rootPath = resolve2(".");
1346
+ const repoDir = join6(rootPath, ".repogenome");
1347
+ const baselineFile = options.baseline ? resolve2(options.baseline) : join6(repoDir, "genome.json");
1348
+ if (!await fileExists2(baselineFile)) {
1349
+ console.error(chalk5.red(" Error: No baseline found. Run `repogenome init` first."));
1350
+ process.exit(2);
1351
+ }
1352
+ const isJson = options.format === "json";
1353
+ const isHtml = options.format === "html";
1354
+ const spinner = isJson || isHtml ? null : ora2({ text: chalk5.gray("Scanning files\u2026"), color: "green" }).start();
1355
+ if (options.threshold !== void 0 && isNaN(parseFloat(options.threshold))) {
1356
+ console.error(chalk5.red(` Error: --threshold must be a number, got "${options.threshold}"`));
1357
+ process.exit(3);
1358
+ }
1359
+ try {
1360
+ const baselineRaw = await readFile5(baselineFile, "utf-8");
1361
+ const baseline = parseGenomeFile(baselineRaw);
1362
+ if (spinner) spinner.text = chalk5.gray("Analyzing current state\u2026");
1363
+ const signals = await scan(rootPath);
1364
+ if (signals.files.length === 0) {
1365
+ spinner?.fail(chalk5.red("No supported files found."));
1366
+ process.exit(3);
1367
+ }
1368
+ const current = await analyze(signals, rootPath);
1369
+ spinner?.stop();
1370
+ const result = diff(baseline, current);
1371
+ if (isJson) {
1372
+ printJson("audit", { baseline, current, diff: result });
1373
+ } else if (isHtml) {
1374
+ const html = generateAuditHtml(baseline, current, result);
1375
+ const outPath = resolve2(options.output ?? "repogenome-report.html");
1376
+ await writeHtmlReport(html, outPath);
1377
+ console.log(chalk5.green(` HTML report written to ${outPath}`));
1378
+ } else {
1379
+ printAuditReport(baseline, current, result);
1380
+ }
1381
+ if (options.save !== false) {
1382
+ await mkdir2(join6(repoDir, "history"), { recursive: true });
1383
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1384
+ await writeFile3(
1385
+ join6(repoDir, "history", `${ts}.json`),
1386
+ JSON.stringify(current, null, 2),
1387
+ "utf-8"
1388
+ );
1389
+ }
1390
+ if (options.threshold !== void 0) {
1391
+ const threshold = parseFloat(options.threshold);
1392
+ if (result.overallDrift > threshold) {
1393
+ if (!isJson) console.error(chalk5.red(` Drift ${result.overallDrift}% exceeds threshold ${threshold}%`));
1394
+ process.exit(1);
1395
+ }
1396
+ }
1397
+ } catch (err) {
1398
+ spinner?.stop();
1399
+ const message = err instanceof Error ? err.message : String(err);
1400
+ console.error(chalk5.red(` Error: ${message}`));
1401
+ process.exit(3);
1402
+ }
1403
+ });
1404
+ }
1405
+
1406
+ // src/commands/compare.ts
1407
+ import { Command as Command3 } from "commander";
1408
+ import { readdir as readdir2, readFile as readFile6 } from "fs/promises";
1409
+ import { join as join8, resolve as resolve3 } from "path";
1410
+ import chalk7 from "chalk";
1411
+ import ora3 from "ora";
1412
+
1413
+ // src/output/compareReport.ts
1414
+ import chalk6 from "chalk";
1415
+ var _MUGM29 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1416
+ var SEP3 = chalk6.gray("\u2500".repeat(51));
1417
+ function printCompareReport(a, b, result, ref1, ref2) {
1418
+ console.log();
1419
+ console.log(chalk6.green.bold("RepoGenome Compare"));
1420
+ console.log(SEP3);
1421
+ console.log(`${chalk6.white(ref1)} ${chalk6.gray("\u2192")} ${chalk6.white(ref2)}`);
1422
+ console.log();
1423
+ const driftStr = result.overallDrift > 0 ? chalk6.red(`+${result.overallDrift}%`) : chalk6.green(`${result.overallDrift}%`);
1424
+ console.log(`${chalk6.white("Genome Drift:")} ${driftStr}`);
1425
+ console.log();
1426
+ const hasPatternChanges = result.added.patterns.length || result.removed.patterns.length || result.changed.patterns.length;
1427
+ if (hasPatternChanges) {
1428
+ console.log(chalk6.white.bold("Patterns:"));
1429
+ for (const p of result.removed.patterns) {
1430
+ console.log(` ${chalk6.red("-")} ${p} pattern removed from ${ref2}`);
1431
+ }
1432
+ for (const p of result.added.patterns) {
1433
+ const pat = b.patterns[p];
1434
+ console.log(` ${chalk6.green("+")} ${p} pattern introduced (${pat?.count ?? 0} files)`);
1435
+ }
1436
+ for (const p of result.changed.patterns) {
1437
+ const pa = a.patterns[p];
1438
+ const pb = b.patterns[p];
1439
+ console.log(` ${chalk6.yellow("~")} ${p} pattern weakening ${((pa?.confidence ?? 0) * 100).toFixed(0)}% \u2192 ${((pb?.confidence ?? 0) * 100).toFixed(0)}%`);
1440
+ }
1441
+ console.log();
1442
+ }
1443
+ if (result.added.concepts.length || result.removed.concepts.length) {
1444
+ console.log(chalk6.white.bold("Concepts:"));
1445
+ if (result.added.concepts.length) console.log(` ${chalk6.green("+")} ${result.added.concepts.slice(0, 6).join(" \xB7 ")}`);
1446
+ if (result.removed.concepts.length) console.log(` ${chalk6.red("-")} ${result.removed.concepts.slice(0, 6).join(" \xB7 ")}`);
1447
+ console.log();
1448
+ }
1449
+ const archDetails = result.dimensions.architecture.details;
1450
+ const depDetails = [
1451
+ ...result.dimensions.dependencies.details,
1452
+ ...result.dimensions.layerViolations.details
1453
+ ];
1454
+ if (archDetails.length || depDetails.length) {
1455
+ console.log(chalk6.white.bold("Layer Changes:"));
1456
+ for (const d of archDetails) console.log(` ${chalk6.yellow("~")} ${d}`);
1457
+ for (const d of depDetails) console.log(` ${chalk6.red("!")} ${d}`);
1458
+ console.log();
1459
+ }
1460
+ const riskColor2 = result.riskLevel === "High" ? chalk6.red : result.riskLevel === "Medium" ? chalk6.yellow : chalk6.green;
1461
+ console.log(`${chalk6.white("Risk Level:")} ${riskColor2(result.riskLevel)}`);
1462
+ console.log();
1463
+ if (result.overallDrift > 0) {
1464
+ const newVocab = [...result.added.patterns, ...result.added.concepts.slice(0, 3)];
1465
+ if (newVocab.length) {
1466
+ console.log(chalk6.gray(` Recommendation: ${ref2} introduces new architectural vocabulary`));
1467
+ console.log(chalk6.gray(` (${newVocab.join(", ")}) that diverges from ${ref1}. Consider aligning before merge.`));
1468
+ } else {
1469
+ console.log(chalk6.gray(` Recommendation: ${ref2} shows ${result.overallDrift}% drift from ${ref1}.`));
1470
+ }
1471
+ console.log();
1472
+ }
1473
+ }
1474
+
1475
+ // src/git/index.ts
1476
+ import { execSync } from "child_process";
1477
+ import { mkdtempSync } from "fs";
1478
+ import { tmpdir } from "os";
1479
+ import { join as join7 } from "path";
1480
+ var _MUGM30 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1481
+ function git(cmd, cwd = ".") {
1482
+ try {
1483
+ return execSync(`git ${cmd}`, { cwd, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
1484
+ } catch (err) {
1485
+ const msg = err instanceof Error ? err.message : String(err);
1486
+ if (msg.includes("not a git repository")) throw new Error("Not a git repository.");
1487
+ if (msg.includes("not found") || msg.includes("command not found")) throw new Error("git is not installed or not in PATH.");
1488
+ throw new Error(`git error: ${msg.split("\n")[0]}`);
1489
+ }
1490
+ }
1491
+ function isGitRepo(cwd = ".") {
1492
+ try {
1493
+ git("rev-parse --git-dir", cwd);
1494
+ return true;
1495
+ } catch {
1496
+ return false;
1497
+ }
1498
+ }
1499
+ function resolveRef(ref, cwd = ".") {
1500
+ try {
1501
+ return git(`rev-parse --verify ${ref}`, cwd);
1502
+ } catch {
1503
+ throw new Error(`Git ref not found: "${ref}"`);
1504
+ }
1505
+ }
1506
+ function checkoutToTemp(ref, cwd = ".") {
1507
+ const resolved = resolveRef(ref, cwd);
1508
+ const tempDir = mkdtempSync(join7(tmpdir(), "repogenome-"));
1509
+ git(`worktree add --detach "${tempDir}" ${resolved}`, cwd);
1510
+ return tempDir;
1511
+ }
1512
+ function cleanupTemp(worktreePath, cwd = ".") {
1513
+ try {
1514
+ git(`worktree remove --force "${worktreePath}"`, cwd);
1515
+ } catch {
1516
+ }
1517
+ }
1518
+
1519
+ // src/commands/compare.ts
1520
+ var _MUGM31 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1521
+ async function loadHistoryGenome(historyDir, ref) {
1522
+ let entries;
1523
+ try {
1524
+ entries = await readdir2(historyDir);
1525
+ } catch {
1526
+ throw new Error(`No history found at ${historyDir}. Run repogenome audit first to build history.`);
1527
+ }
1528
+ const jsonFiles = entries.filter((e) => e.endsWith(".json")).sort();
1529
+ if (!jsonFiles.length) throw new Error("No history snapshots found. Run repogenome audit to build history.");
1530
+ let target;
1531
+ if (ref === "latest" || ref === "HEAD") {
1532
+ target = jsonFiles[jsonFiles.length - 1];
1533
+ } else if (ref === "oldest" || ref === "BASE") {
1534
+ target = jsonFiles[0];
1535
+ } else {
1536
+ const match = jsonFiles.find((f) => f.includes(ref));
1537
+ if (!match) throw new Error(`No history snapshot matching "${ref}". Available: ${jsonFiles.join(", ")}`);
1538
+ target = match;
1539
+ }
1540
+ const raw = await readFile6(join8(historyDir, target), "utf-8");
1541
+ try {
1542
+ return JSON.parse(raw);
1543
+ } catch {
1544
+ throw new Error(`History snapshot "${target}" is corrupted or invalid JSON.`);
1545
+ }
1546
+ }
1547
+ async function scanRef(ref, cwd) {
1548
+ const tempPath = checkoutToTemp(ref, cwd);
1549
+ try {
1550
+ const signals = await scan(tempPath);
1551
+ return await analyze(signals, tempPath);
1552
+ } finally {
1553
+ cleanupTemp(tempPath, cwd);
1554
+ }
1555
+ }
1556
+ function compareCommand() {
1557
+ return new Command3("compare").description("Compare the genomes of two git references").argument("<ref1>", "First git reference (branch, commit, tag)").argument("<ref2>", "Second git reference (branch, commit, tag)").option("--format <fmt>", "Output format: terminal | json | html", "terminal").option("--output <file>", "Output file path (for --format html)", "repogenome-report.html").option("--no-checkout", "Use pre-saved history snapshots instead of live checkout").action(async (ref1, ref2, options) => {
1558
+ const rootPath = resolve3(".");
1559
+ const historyDir = join8(rootPath, ".repogenome", "history");
1560
+ const isJson = options.format === "json";
1561
+ const isHtml = options.format === "html";
1562
+ const noCheckout = options.checkout === false;
1563
+ const spinner = isJson || isHtml ? null : ora3({ text: chalk7.gray(`Comparing ${ref1} \u2192 ${ref2}\u2026`), color: "green" }).start();
1564
+ try {
1565
+ let genomeA;
1566
+ let genomeB;
1567
+ if (noCheckout) {
1568
+ spinner && (spinner.text = chalk7.gray("Loading history snapshots\u2026"));
1569
+ [genomeA, genomeB] = await Promise.all([
1570
+ loadHistoryGenome(historyDir, ref1),
1571
+ loadHistoryGenome(historyDir, ref2)
1572
+ ]);
1573
+ } else {
1574
+ if (!isGitRepo(rootPath)) {
1575
+ spinner?.fail(chalk7.red("Not a git repository."));
1576
+ process.exit(3);
1577
+ }
1578
+ try {
1579
+ resolveRef(ref1, rootPath);
1580
+ resolveRef(ref2, rootPath);
1581
+ } catch (err) {
1582
+ spinner?.fail(chalk7.red(err instanceof Error ? err.message : String(err)));
1583
+ process.exit(3);
1584
+ }
1585
+ spinner && (spinner.text = chalk7.gray(`Checking out ${ref1}\u2026`));
1586
+ genomeA = await scanRef(ref1, rootPath);
1587
+ spinner && (spinner.text = chalk7.gray(`Checking out ${ref2}\u2026`));
1588
+ genomeB = await scanRef(ref2, rootPath);
1589
+ }
1590
+ spinner?.stop();
1591
+ const result = diff(genomeA, genomeB);
1592
+ if (isJson) {
1593
+ printJson("compare", { ref1, ref2, diff: result });
1594
+ } else if (isHtml) {
1595
+ const html = generateCompareHtml(genomeA, genomeB, result, ref1, ref2);
1596
+ const outPath = resolve3(options.output ?? "repogenome-report.html");
1597
+ await writeHtmlReport(html, outPath);
1598
+ console.log(chalk7.green(` HTML report written to ${outPath}`));
1599
+ } else {
1600
+ printCompareReport(genomeA, genomeB, result, ref1, ref2);
1601
+ }
1602
+ } catch (err) {
1603
+ spinner?.stop();
1604
+ console.error(chalk7.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
1605
+ process.exit(3);
1606
+ }
1607
+ });
1608
+ }
1609
+
1610
+ // src/commands/blame.ts
1611
+ import { Command as Command4 } from "commander";
1612
+ import { readFile as readFile7, access as access3 } from "fs/promises";
1613
+ import { join as join10, resolve as resolve4 } from "path";
1614
+ import chalk9 from "chalk";
1615
+ import ora4 from "ora";
1616
+
1617
+ // src/blame/directoryScanner.ts
1618
+ import { readdir as readdir3 } from "fs/promises";
1619
+ import { statSync } from "fs";
1620
+ import { join as join9 } from "path";
1621
+ var _MUGM32 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1622
+ var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".repogenome", ".cache"]);
1623
+ async function scanDirectories(rootPath) {
1624
+ let entries;
1625
+ try {
1626
+ entries = await readdir3(rootPath);
1627
+ } catch {
1628
+ return [];
1629
+ }
1630
+ const dirs = entries.filter((e) => {
1631
+ if (e.startsWith(".") || SKIP.has(e)) return false;
1632
+ try {
1633
+ return statSync(join9(rootPath, e)).isDirectory();
1634
+ } catch {
1635
+ return false;
1636
+ }
1637
+ });
1638
+ const results = [];
1639
+ for (const name of dirs) {
1640
+ const dirPath = join9(rootPath, name);
1641
+ try {
1642
+ const signals = await scan(dirPath);
1643
+ if (signals.files.length === 0) continue;
1644
+ const genome = await analyze(signals, dirPath);
1645
+ results.push({ name, path: dirPath, genome });
1646
+ } catch {
1647
+ }
1648
+ }
1649
+ return results;
1650
+ }
1651
+
1652
+ // src/blame/fileBlame.ts
1653
+ var _MUGM33 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1654
+ var IMPACT_ORDER = { high: 0, medium: 1, low: 2 };
1655
+ function buildFileBlame(baseline, current, result) {
1656
+ const raw = [];
1657
+ for (const p of result.added.patterns) {
1658
+ for (const f of current.patterns[p]?.files ?? []) {
1659
+ raw.push({ file: f, reason: `introduced ${p} pattern`, impact: "high" });
1660
+ }
1661
+ }
1662
+ for (const p of result.removed.patterns) {
1663
+ const baseFiles = baseline.patterns[p]?.files ?? [];
1664
+ const currFiles = new Set(current.patterns[p]?.files ?? []);
1665
+ for (const f of baseFiles) {
1666
+ if (!currFiles.has(f)) {
1667
+ raw.push({ file: f, reason: `removed from ${p} pattern`, impact: "high" });
1668
+ }
1669
+ }
1670
+ }
1671
+ for (const p of result.changed.patterns) {
1672
+ const baseFiles = new Set(baseline.patterns[p]?.files ?? []);
1673
+ const currFiles = new Set(current.patterns[p]?.files ?? []);
1674
+ for (const f of baseFiles) {
1675
+ if (!currFiles.has(f)) {
1676
+ raw.push({ file: f, reason: `weakened ${p} pattern`, impact: "medium" });
1677
+ }
1678
+ }
1679
+ for (const f of currFiles) {
1680
+ if (!baseFiles.has(f)) {
1681
+ raw.push({ file: f, reason: `strengthened ${p} pattern`, impact: "low" });
1682
+ }
1683
+ }
1684
+ }
1685
+ const seen = /* @__PURE__ */ new Map();
1686
+ for (const e of raw) {
1687
+ const existing = seen.get(e.file);
1688
+ if (!existing || IMPACT_ORDER[e.impact] < IMPACT_ORDER[existing.impact]) {
1689
+ seen.set(e.file, e);
1690
+ }
1691
+ }
1692
+ return [...seen.values()].sort((a, b) => IMPACT_ORDER[a.impact] - IMPACT_ORDER[b.impact]);
1693
+ }
1694
+
1695
+ // src/blame/conceptGrouper.ts
1696
+ var _MUGM34 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1697
+ var GENERIC = /* @__PURE__ */ new Set(["src", "lib", "app", "api", "web", "cli", "the", "and", "for", "new"]);
1698
+ function extractWords(dirName) {
1699
+ return dirName.toLowerCase().split(/[-_/]/).filter((w) => w.length > 2 && !GENERIC.has(w));
1700
+ }
1701
+ function groupByConcept(dirs) {
1702
+ const conceptMap = /* @__PURE__ */ new Map();
1703
+ for (const dir of dirs) {
1704
+ for (const word of extractWords(dir.name)) {
1705
+ if (!conceptMap.has(word)) conceptMap.set(word, []);
1706
+ conceptMap.get(word).push(dir.name);
1707
+ }
1708
+ }
1709
+ const groups = [];
1710
+ const grouped = /* @__PURE__ */ new Set();
1711
+ for (const [concept, dirNames] of conceptMap) {
1712
+ if (dirNames.length < 2) continue;
1713
+ const totalContribution = dirs.filter((d) => dirNames.includes(d.name)).reduce((sum, d) => sum + d.contribution, 0);
1714
+ groups.push({
1715
+ concept,
1716
+ directories: dirNames,
1717
+ totalContribution: parseFloat(totalContribution.toFixed(1))
1718
+ });
1719
+ for (const n of dirNames) grouped.add(n);
1720
+ }
1721
+ for (const dir of dirs) {
1722
+ if (!grouped.has(dir.name)) {
1723
+ groups.push({
1724
+ concept: dir.name,
1725
+ directories: [dir.name],
1726
+ totalContribution: dir.contribution
1727
+ });
1728
+ }
1729
+ }
1730
+ return groups.sort((a, b) => b.totalContribution - a.totalContribution);
1731
+ }
1732
+
1733
+ // src/blame/index.ts
1734
+ var _MUGM35 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1735
+ function topLevelDir(filePath) {
1736
+ return filePath.replace(/\\/g, "/").split("/")[0];
1737
+ }
1738
+ function attributeDrift(baseline, current, result, dirScans) {
1739
+ const scores = /* @__PURE__ */ new Map();
1740
+ for (const d of dirScans) {
1741
+ scores.set(d.name, { points: 0, addedPatterns: [], removedPatterns: [], addedConcepts: [], removedConcepts: [], violations: 0 });
1742
+ }
1743
+ const addTo = (dir, delta) => {
1744
+ const s = scores.get(dir);
1745
+ if (s) s.points += delta;
1746
+ };
1747
+ for (const p of result.added.patterns) {
1748
+ for (const f of current.patterns[p]?.files ?? []) {
1749
+ const dir = topLevelDir(f);
1750
+ const s = scores.get(dir);
1751
+ if (s) {
1752
+ addTo(dir, 1);
1753
+ if (!s.addedPatterns.includes(p)) s.addedPatterns.push(p);
1754
+ }
1755
+ }
1756
+ }
1757
+ for (const p of result.removed.patterns) {
1758
+ for (const f of baseline.patterns[p]?.files ?? []) {
1759
+ const dir = topLevelDir(f);
1760
+ const s = scores.get(dir);
1761
+ if (s) {
1762
+ addTo(dir, 1);
1763
+ if (!s.removedPatterns.includes(p)) s.removedPatterns.push(p);
1764
+ }
1765
+ }
1766
+ }
1767
+ for (const p of result.changed.patterns) {
1768
+ const baseSet = new Set(baseline.patterns[p]?.files ?? []);
1769
+ const currSet = new Set(current.patterns[p]?.files ?? []);
1770
+ for (const f of baseSet) {
1771
+ if (!currSet.has(f)) {
1772
+ const dir = topLevelDir(f);
1773
+ const s = scores.get(dir);
1774
+ if (s) {
1775
+ addTo(dir, 0.5);
1776
+ if (!s.removedPatterns.includes(p)) s.removedPatterns.push(p);
1777
+ }
1778
+ }
1779
+ }
1780
+ }
1781
+ for (const concept of result.added.concepts) {
1782
+ for (const d of dirScans) {
1783
+ if (d.genome.concepts.includes(concept)) {
1784
+ const s = scores.get(d.name);
1785
+ addTo(d.name, 0.4);
1786
+ if (!s.addedConcepts.includes(concept)) s.addedConcepts.push(concept);
1787
+ }
1788
+ }
1789
+ }
1790
+ for (const concept of result.removed.concepts) {
1791
+ for (const d of dirScans) {
1792
+ if (d.name.toLowerCase().includes(concept.toLowerCase())) {
1793
+ const s = scores.get(d.name);
1794
+ addTo(d.name, 0.4);
1795
+ if (!s.removedConcepts.includes(concept)) s.removedConcepts.push(concept);
1796
+ }
1797
+ }
1798
+ }
1799
+ const violationDelta = current.dependencies.crossLayerViolations - baseline.dependencies.crossLayerViolations;
1800
+ if (violationDelta > 0) {
1801
+ const active = dirScans.filter((d) => Object.values(d.genome.patterns).some((p) => p.detected));
1802
+ const share = violationDelta / Math.max(active.length, 1);
1803
+ for (const d of active) {
1804
+ const s = scores.get(d.name);
1805
+ addTo(d.name, share * 0.3);
1806
+ s.violations += Math.ceil(share);
1807
+ }
1808
+ }
1809
+ const totalPoints = [...scores.values()].reduce((sum, s) => sum + s.points, 0);
1810
+ return dirScans.map((d) => {
1811
+ const s = scores.get(d.name) ?? { points: 0, addedPatterns: [], removedPatterns: [], addedConcepts: [], removedConcepts: [], violations: 0 };
1812
+ const contribution = totalPoints > 0 ? parseFloat((s.points / totalPoints * 100).toFixed(1)) : 0;
1813
+ return {
1814
+ name: d.name,
1815
+ contribution,
1816
+ drift: parseFloat((contribution / 100 * result.overallDrift).toFixed(1)),
1817
+ addedPatterns: s.addedPatterns,
1818
+ removedPatterns: s.removedPatterns,
1819
+ addedConcepts: s.addedConcepts,
1820
+ removedConcepts: s.removedConcepts,
1821
+ violations: s.violations
1822
+ };
1823
+ }).sort((a, b) => b.contribution - a.contribution);
1824
+ }
1825
+ function runBlame(baseline, current, result, dirScans, topN = 10) {
1826
+ const dirs = attributeDrift(baseline, current, result, dirScans).slice(0, topN);
1827
+ const conceptGroups = groupByConcept(dirs);
1828
+ const files = buildFileBlame(baseline, current, result);
1829
+ return {
1830
+ dirs,
1831
+ conceptGroups,
1832
+ files,
1833
+ topDir: dirs[0]?.name ?? "",
1834
+ totalAttributed: parseFloat(dirs.reduce((sum, d) => sum + d.contribution, 0).toFixed(1))
1835
+ };
1836
+ }
1837
+
1838
+ // src/output/blameReport.ts
1839
+ import chalk8 from "chalk";
1840
+ var _MUGM36 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1841
+ var SEP4 = chalk8.gray("\u2500".repeat(51));
1842
+ function driftBar(contribution) {
1843
+ const filled = Math.round(contribution / 5);
1844
+ const empty = 20 - filled;
1845
+ return chalk8.red("\u2588".repeat(Math.min(filled, 20))) + chalk8.gray("\u2591".repeat(Math.max(empty, 0)));
1846
+ }
1847
+ function formatTags(added, removed) {
1848
+ const parts = [];
1849
+ for (const p of added) parts.push(chalk8.green(`+${p}`));
1850
+ for (const p of removed) parts.push(chalk8.red(`-${p}`));
1851
+ return parts.slice(0, 4).join(" ");
1852
+ }
1853
+ function printBlameReport(result, mode, overallDrift) {
1854
+ console.log();
1855
+ console.log(chalk8.green.bold("RepoGenome Blame"));
1856
+ console.log(SEP4);
1857
+ console.log(chalk8.gray(`Overall Drift: ${overallDrift}%`));
1858
+ console.log();
1859
+ if (mode === "directory" || mode === "concept") {
1860
+ if (mode === "directory") {
1861
+ printDirectoryView(result.dirs, result.topDir);
1862
+ } else {
1863
+ printConceptView(result.conceptGroups);
1864
+ }
1865
+ } else {
1866
+ printFileView(result.files);
1867
+ }
1868
+ if (result.topDir) {
1869
+ console.log();
1870
+ console.log(chalk8.gray(` Tip: Run ${chalk8.white(`repogenome blame --by file`)} for file-level attribution.`));
1871
+ console.log(chalk8.gray(` Run ${chalk8.white(`repogenome blame --by concept`)} for concept grouping.`));
1872
+ }
1873
+ console.log();
1874
+ }
1875
+ function printDirectoryView(dirs, topDir) {
1876
+ if (dirs.length === 0) {
1877
+ console.log(chalk8.gray(" No drift attributed \u2014 genome is stable or no directories found."));
1878
+ return;
1879
+ }
1880
+ console.log(chalk8.white.bold("Drift Attribution by Directory"));
1881
+ console.log();
1882
+ const nameWidth = Math.min(30, Math.max(...dirs.map((d) => d.name.length)) + 2);
1883
+ for (const dir of dirs) {
1884
+ const name = dir.name.padEnd(nameWidth);
1885
+ const bar = driftBar(dir.contribution);
1886
+ const pct = chalk8.white(`${dir.contribution.toFixed(1).padStart(5)}%`);
1887
+ const tags = formatTags(dir.addedPatterns, dir.removedPatterns);
1888
+ const marker = dir.name === topDir ? chalk8.yellow(" \u25C4") : "";
1889
+ console.log(` ${chalk8.cyan(name)} ${bar} ${pct} ${tags}${marker}`);
1890
+ }
1891
+ }
1892
+ function printConceptView(groups) {
1893
+ if (groups.length === 0) {
1894
+ console.log(chalk8.gray(" No concept groups found."));
1895
+ return;
1896
+ }
1897
+ console.log(chalk8.white.bold("Drift Attribution by Concept"));
1898
+ console.log();
1899
+ for (const g of groups.slice(0, 10)) {
1900
+ const label2 = g.concept.padEnd(20);
1901
+ const bar = driftBar(g.totalContribution);
1902
+ const pct = chalk8.white(`${g.totalContribution.toFixed(1).padStart(5)}%`);
1903
+ const dirs = chalk8.gray(`(${g.directories.join(", ")})`);
1904
+ console.log(` ${chalk8.cyan(label2)} ${bar} ${pct} ${dirs}`);
1905
+ }
1906
+ }
1907
+ function printFileView(files) {
1908
+ if (files.length === 0) {
1909
+ console.log(chalk8.gray(" No file-level changes detected \u2014 drift may be in naming or concepts."));
1910
+ return;
1911
+ }
1912
+ console.log(chalk8.white.bold("Top Changed Files"));
1913
+ console.log();
1914
+ const impactIcon = (impact) => {
1915
+ if (impact === "high") return chalk8.red("[+]");
1916
+ if (impact === "medium") return chalk8.yellow("[~]");
1917
+ return chalk8.green("[\xB7]");
1918
+ };
1919
+ for (const entry of files.slice(0, 15)) {
1920
+ const icon = impactIcon(entry.impact);
1921
+ const file = chalk8.white(entry.file.padEnd(50));
1922
+ const reason = chalk8.gray(entry.reason);
1923
+ console.log(` ${icon} ${file} ${reason}`);
1924
+ }
1925
+ }
1926
+
1927
+ // src/commands/blame.ts
1928
+ var _MUGM37 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1929
+ async function fileExists3(p) {
1930
+ try {
1931
+ await access3(p);
1932
+ return true;
1933
+ } catch {
1934
+ return false;
1935
+ }
1936
+ }
1937
+ function parseGenomeFile2(raw) {
1938
+ try {
1939
+ return JSON.parse(raw);
1940
+ } catch {
1941
+ console.error(chalk9.red(" Error: genome.json is corrupted or invalid JSON. Run `repogenome init` to regenerate it."));
1942
+ process.exit(3);
1943
+ }
1944
+ }
1945
+ function blameCommand() {
1946
+ return new Command4("blame").description("Identify which directories and files are driving the most architectural drift").option("--by <mode>", "Attribution mode: directory | file | concept", "directory").option("--top <n>", "Show top N contributors", "10").option("--format <fmt>", "Output format: terminal | json", "terminal").option("--baseline <file>", "Path to a custom baseline genome file").action(async (options) => {
1947
+ const rootPath = resolve4(".");
1948
+ const repoDir = join10(rootPath, ".repogenome");
1949
+ const baselineFile = options.baseline ? resolve4(options.baseline) : join10(repoDir, "genome.json");
1950
+ if (!await fileExists3(baselineFile)) {
1951
+ console.error(chalk9.red(" Error: No baseline found. Run `repogenome init` first."));
1952
+ process.exit(2);
1953
+ }
1954
+ const mode = options.by ?? "directory";
1955
+ const topN = parseInt(options.top ?? "10", 10);
1956
+ const isJson = options.format === "json";
1957
+ const spinner = isJson ? null : ora4({ text: chalk9.gray("Scanning current state\u2026"), color: "green" }).start();
1958
+ try {
1959
+ const baselineRaw = await readFile7(baselineFile, "utf-8");
1960
+ const baseline = parseGenomeFile2(baselineRaw);
1961
+ if (spinner) spinner.text = chalk9.gray("Analyzing current state\u2026");
1962
+ const signals = await scan(rootPath);
1963
+ if (signals.files.length === 0) {
1964
+ spinner?.fail(chalk9.red("No supported files found."));
1965
+ process.exit(3);
1966
+ }
1967
+ const current = await analyze(signals, rootPath);
1968
+ const result = diff(baseline, current);
1969
+ if (spinner) spinner.text = chalk9.gray("Scanning directories\u2026");
1970
+ const dirScans = await scanDirectories(rootPath);
1971
+ spinner?.stop();
1972
+ const blameResult = runBlame(baseline, current, result, dirScans, topN);
1973
+ if (isJson) {
1974
+ printJson("blame", { overallDrift: result.overallDrift, blame: blameResult });
1975
+ } else {
1976
+ printBlameReport(blameResult, mode, result.overallDrift);
1977
+ }
1978
+ } catch (err) {
1979
+ spinner?.stop();
1980
+ console.error(chalk9.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
1981
+ process.exit(3);
1982
+ }
1983
+ });
1984
+ }
1985
+
1986
+ // src/cli.ts
1987
+ var _MUGM38 = Object.freeze({ b: 1299540065, g: "MuhammadUsmanGM" });
1988
+ var _require = createRequire2(import.meta.url);
1989
+ var { version: version2 } = _require("../package.json");
1990
+ var program = new Command5();
1991
+ var isMachineOutput = process.argv.includes("--format") && process.argv[process.argv.indexOf("--format") + 1] === "json";
1992
+ if (!isMachineOutput) printBanner();
1993
+ program.name("repogenome").description("Capture the architectural DNA of a codebase and track drift over time").version(version2);
1994
+ program.addCommand(initCommand());
1995
+ program.addCommand(auditCommand());
1996
+ program.addCommand(compareCommand());
1997
+ program.addCommand(blameCommand());
1998
+ program.parse();