@hyperdrive.bot/gut 0.1.6 → 0.1.9
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.
- package/README.md +1 -1
- package/dist/base-command.d.ts +22 -0
- package/dist/base-command.js +99 -0
- package/dist/commands/add.d.ts +14 -0
- package/dist/commands/add.js +70 -0
- package/dist/commands/affected.d.ts +23 -0
- package/dist/commands/affected.js +323 -0
- package/dist/commands/audit.d.ts +33 -0
- package/dist/commands/audit.js +594 -0
- package/dist/commands/back.d.ts +6 -0
- package/dist/commands/back.js +29 -0
- package/dist/commands/checkout.d.ts +14 -0
- package/dist/commands/checkout.js +124 -0
- package/dist/commands/commit.d.ts +11 -0
- package/dist/commands/commit.js +107 -0
- package/dist/commands/context.d.ts +6 -0
- package/dist/commands/context.js +32 -0
- package/dist/commands/contexts.d.ts +7 -0
- package/dist/commands/contexts.js +88 -0
- package/dist/commands/deps.d.ts +10 -0
- package/dist/commands/deps.js +100 -0
- package/dist/commands/entity/add.d.ts +16 -0
- package/dist/commands/entity/add.js +103 -0
- package/dist/commands/entity/clone-all.d.ts +17 -0
- package/dist/commands/entity/clone-all.js +127 -0
- package/dist/commands/entity/clone.d.ts +15 -0
- package/dist/commands/entity/clone.js +106 -0
- package/dist/commands/entity/list.d.ts +11 -0
- package/dist/commands/entity/list.js +80 -0
- package/dist/commands/entity/remove.d.ts +12 -0
- package/dist/commands/entity/remove.js +54 -0
- package/dist/commands/extract.d.ts +35 -0
- package/dist/commands/extract.js +483 -0
- package/dist/commands/focus.d.ts +19 -0
- package/dist/commands/focus.js +137 -0
- package/dist/commands/graph.d.ts +18 -0
- package/dist/commands/graph.js +273 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.js +75 -0
- package/dist/commands/insights.d.ts +21 -0
- package/dist/commands/insights.js +465 -0
- package/dist/commands/patterns.d.ts +40 -0
- package/dist/commands/patterns.js +405 -0
- package/dist/commands/pull.d.ts +11 -0
- package/dist/commands/pull.js +121 -0
- package/dist/commands/push.d.ts +11 -0
- package/dist/commands/push.js +97 -0
- package/dist/commands/quick-setup.d.ts +20 -0
- package/dist/commands/quick-setup.js +417 -0
- package/dist/commands/recent.d.ts +9 -0
- package/dist/commands/recent.js +51 -0
- package/dist/commands/related.d.ts +23 -0
- package/dist/commands/related.js +255 -0
- package/dist/commands/repos.d.ts +17 -0
- package/dist/commands/repos.js +184 -0
- package/dist/commands/stack.d.ts +10 -0
- package/dist/commands/stack.js +78 -0
- package/dist/commands/status.d.ts +13 -0
- package/dist/commands/status.js +193 -0
- package/dist/commands/sync.d.ts +11 -0
- package/dist/commands/sync.js +139 -0
- package/dist/commands/ticket/focus.d.ts +20 -0
- package/dist/commands/ticket/focus.js +217 -0
- package/dist/commands/ticket/get.d.ts +15 -0
- package/dist/commands/ticket/get.js +168 -0
- package/dist/commands/ticket/hint.d.ts +16 -0
- package/dist/commands/ticket/hint.js +147 -0
- package/dist/commands/ticket/index.d.ts +10 -0
- package/dist/commands/ticket/index.js +60 -0
- package/dist/commands/ticket/list.d.ts +13 -0
- package/dist/commands/ticket/list.js +120 -0
- package/dist/commands/ticket/sync.d.ts +14 -0
- package/dist/commands/ticket/sync.js +85 -0
- package/dist/commands/ticket/update.d.ts +17 -0
- package/dist/commands/ticket/update.js +142 -0
- package/dist/commands/unfocus.d.ts +6 -0
- package/dist/commands/unfocus.js +19 -0
- package/dist/commands/used-by.d.ts +13 -0
- package/dist/commands/used-by.js +110 -0
- package/dist/commands/workspace.d.ts +22 -0
- package/dist/commands/workspace.js +372 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +16 -0
- package/dist/models/entity.model.d.ts +234 -0
- package/dist/models/entity.model.js +1 -0
- package/dist/models/ticket.model.d.ts +117 -0
- package/dist/models/ticket.model.js +43 -0
- package/dist/services/auth.service.d.ts +15 -0
- package/dist/services/auth.service.js +26 -0
- package/dist/services/config.service.d.ts +34 -0
- package/dist/services/config.service.js +234 -0
- package/dist/services/entity.service.d.ts +20 -0
- package/dist/services/entity.service.js +127 -0
- package/dist/services/focus.service.d.ts +71 -0
- package/dist/services/focus.service.js +614 -0
- package/dist/services/git.service.d.ts +39 -0
- package/dist/services/git.service.js +188 -0
- package/dist/services/gut-api.service.d.ts +53 -0
- package/dist/services/gut-api.service.js +99 -0
- package/dist/services/ticket.service.d.ts +84 -0
- package/dist/services/ticket.service.js +207 -0
- package/dist/utils/display.d.ts +26 -0
- package/dist/utils/display.js +145 -0
- package/dist/utils/filesystem.d.ts +32 -0
- package/dist/utils/filesystem.js +198 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.js +14 -0
- package/dist/utils/validation.d.ts +22 -0
- package/dist/utils/validation.js +192 -0
- package/oclif.manifest.json +2008 -0
- package/package.json +11 -2
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import Table from 'cli-table3';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import { BaseCommand } from '../base-command.js';
|
|
9
|
+
export default class Insights extends BaseCommand {
|
|
10
|
+
static description = 'Display analytics and insights about the workspace';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> <%= command.id %>',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> --detailed',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --format json',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
detailed: Flags.boolean({
|
|
18
|
+
char: 'd',
|
|
19
|
+
default: false,
|
|
20
|
+
description: 'Show detailed statistics for each entity',
|
|
21
|
+
}),
|
|
22
|
+
format: Flags.string({
|
|
23
|
+
char: 'f',
|
|
24
|
+
default: 'table',
|
|
25
|
+
description: 'Output format',
|
|
26
|
+
options: ['table', 'json', 'summary'],
|
|
27
|
+
}),
|
|
28
|
+
'include-ignored': Flags.boolean({
|
|
29
|
+
default: false,
|
|
30
|
+
description: 'Include gitignored files in statistics',
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
async run() {
|
|
34
|
+
const { flags } = await this.parse(Insights);
|
|
35
|
+
const { detailed, format, 'include-ignored': includeIgnored } = flags;
|
|
36
|
+
const spinner = ora('Analyzing workspace').start();
|
|
37
|
+
try {
|
|
38
|
+
const entities = await this.entityService.listEntities();
|
|
39
|
+
if (entities.length === 0) {
|
|
40
|
+
spinner.fail('No entities found in workspace');
|
|
41
|
+
this.log(chalk.yellow('Initialize your workspace with: gut init'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const entityStats = [];
|
|
45
|
+
for (const entity of entities) {
|
|
46
|
+
const stats = await this.analyzeEntity(entity, includeIgnored);
|
|
47
|
+
entityStats.push({ entity, stats });
|
|
48
|
+
}
|
|
49
|
+
const workspaceStats = this.calculateWorkspaceStats(entityStats);
|
|
50
|
+
spinner.succeed('Analysis complete');
|
|
51
|
+
switch (format) {
|
|
52
|
+
case 'json': {
|
|
53
|
+
this.printJSON(workspaceStats, entityStats);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'summary': {
|
|
57
|
+
this.printSummary(workspaceStats);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
default: {
|
|
61
|
+
this.printTable(workspaceStats, entityStats, detailed);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
spinner.fail();
|
|
68
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async analyzeEntity(entity, includeIgnored) {
|
|
72
|
+
const stats = {
|
|
73
|
+
branches: 0,
|
|
74
|
+
contributors: 0,
|
|
75
|
+
files: 0,
|
|
76
|
+
languages: new Map(),
|
|
77
|
+
lastCommit: 'N/A',
|
|
78
|
+
lines: 0,
|
|
79
|
+
size: '0B',
|
|
80
|
+
uncommitted: 0,
|
|
81
|
+
};
|
|
82
|
+
const entityPath = path.resolve(entity.path);
|
|
83
|
+
if (!fs.existsSync(entityPath)) {
|
|
84
|
+
return stats;
|
|
85
|
+
}
|
|
86
|
+
// Count files and lines
|
|
87
|
+
const fileStats = this.countFiles(entityPath, includeIgnored);
|
|
88
|
+
stats.files = fileStats.files;
|
|
89
|
+
stats.lines = fileStats.lines;
|
|
90
|
+
stats.size = this.formatSize(fileStats.size);
|
|
91
|
+
stats.languages = fileStats.languages;
|
|
92
|
+
// Git statistics
|
|
93
|
+
if (fs.existsSync(path.join(entityPath, '.git'))) {
|
|
94
|
+
try {
|
|
95
|
+
// Last commit
|
|
96
|
+
const lastCommit = execSync('git log -1 --format=%ar', {
|
|
97
|
+
cwd: entityPath,
|
|
98
|
+
encoding: 'utf8',
|
|
99
|
+
stdio: 'pipe',
|
|
100
|
+
}).trim();
|
|
101
|
+
stats.lastCommit = lastCommit;
|
|
102
|
+
// Contributors
|
|
103
|
+
const contributors = execSync('git shortlog -sn', {
|
|
104
|
+
cwd: entityPath,
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
stdio: 'pipe',
|
|
107
|
+
}).split('\n').filter(l => l.trim()).length;
|
|
108
|
+
stats.contributors = contributors;
|
|
109
|
+
// Branches
|
|
110
|
+
const branches = execSync('git branch -a', {
|
|
111
|
+
cwd: entityPath,
|
|
112
|
+
encoding: 'utf8',
|
|
113
|
+
stdio: 'pipe',
|
|
114
|
+
}).split('\n').filter(l => l.trim()).length;
|
|
115
|
+
stats.branches = branches;
|
|
116
|
+
// Uncommitted changes
|
|
117
|
+
const status = execSync('git status --porcelain', {
|
|
118
|
+
cwd: entityPath,
|
|
119
|
+
encoding: 'utf8',
|
|
120
|
+
stdio: 'pipe',
|
|
121
|
+
}).split('\n').filter(l => l.trim()).length;
|
|
122
|
+
stats.uncommitted = status;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Ignore git errors
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return stats;
|
|
129
|
+
}
|
|
130
|
+
calculateWorkspaceStats(entityStats) {
|
|
131
|
+
let totalFiles = 0;
|
|
132
|
+
let totalLines = 0;
|
|
133
|
+
let totalSize = 0;
|
|
134
|
+
const allLanguages = new Map();
|
|
135
|
+
let mostActive = '';
|
|
136
|
+
let mostActiveCommit = new Date(0);
|
|
137
|
+
let leastActive = '';
|
|
138
|
+
let leastActiveCommit = new Date();
|
|
139
|
+
let largestEntity = '';
|
|
140
|
+
let largestSize = 0;
|
|
141
|
+
let smallestEntity = '';
|
|
142
|
+
let smallestSize = Infinity;
|
|
143
|
+
for (const { entity, stats } of entityStats) {
|
|
144
|
+
totalFiles += stats.files;
|
|
145
|
+
totalLines += stats.lines;
|
|
146
|
+
// Parse size back to bytes for total
|
|
147
|
+
const sizeMatch = stats.size.match(/(\d+\.?\d*)([BKMG])/);
|
|
148
|
+
if (sizeMatch) {
|
|
149
|
+
const value = Number.parseFloat(sizeMatch[1]);
|
|
150
|
+
const unit = sizeMatch[2];
|
|
151
|
+
const multipliers = {
|
|
152
|
+
B: 1, G: 1024 * 1024 * 1024, K: 1024, M: 1024 * 1024,
|
|
153
|
+
};
|
|
154
|
+
totalSize += value * multipliers[unit];
|
|
155
|
+
}
|
|
156
|
+
// Merge languages
|
|
157
|
+
for (const [lang, count] of stats.languages.entries()) {
|
|
158
|
+
allLanguages.set(lang, (allLanguages.get(lang) || 0) + count);
|
|
159
|
+
}
|
|
160
|
+
// Track most/least active
|
|
161
|
+
if (stats.lastCommit !== 'N/A') {
|
|
162
|
+
const commitDate = this.parseRelativeDate(stats.lastCommit);
|
|
163
|
+
if (commitDate > mostActiveCommit) {
|
|
164
|
+
mostActiveCommit = commitDate;
|
|
165
|
+
mostActive = entity.name;
|
|
166
|
+
}
|
|
167
|
+
if (commitDate < leastActiveCommit) {
|
|
168
|
+
leastActiveCommit = commitDate;
|
|
169
|
+
leastActive = entity.name;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Track largest/smallest
|
|
173
|
+
const entitySize = stats.files;
|
|
174
|
+
if (entitySize > largestSize) {
|
|
175
|
+
largestSize = entitySize;
|
|
176
|
+
largestEntity = entity.name;
|
|
177
|
+
}
|
|
178
|
+
if (entitySize < smallestSize && entitySize > 0) {
|
|
179
|
+
smallestSize = entitySize;
|
|
180
|
+
smallestEntity = entity.name;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
languages: allLanguages,
|
|
185
|
+
largestEntity,
|
|
186
|
+
leastActive,
|
|
187
|
+
mostActive,
|
|
188
|
+
smallestEntity,
|
|
189
|
+
totalEntities: entityStats.length,
|
|
190
|
+
totalFiles,
|
|
191
|
+
totalLines,
|
|
192
|
+
totalSize: this.formatSize(totalSize),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
countFiles(dir, includeIgnored) {
|
|
196
|
+
const result = {
|
|
197
|
+
files: 0,
|
|
198
|
+
languages: new Map(),
|
|
199
|
+
lines: 0,
|
|
200
|
+
size: 0,
|
|
201
|
+
};
|
|
202
|
+
const shouldIgnore = (file) => {
|
|
203
|
+
if (includeIgnored)
|
|
204
|
+
return false;
|
|
205
|
+
const ignoredPatterns = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];
|
|
206
|
+
return ignoredPatterns.some(pattern => file.includes(pattern));
|
|
207
|
+
};
|
|
208
|
+
const walkDir = (currentDir) => {
|
|
209
|
+
try {
|
|
210
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
213
|
+
if (shouldIgnore(fullPath))
|
|
214
|
+
continue;
|
|
215
|
+
if (entry.isDirectory()) {
|
|
216
|
+
walkDir(fullPath);
|
|
217
|
+
}
|
|
218
|
+
else if (entry.isFile()) {
|
|
219
|
+
result.files++;
|
|
220
|
+
const stats = fs.statSync(fullPath);
|
|
221
|
+
result.size += stats.size;
|
|
222
|
+
const ext = path.extname(fullPath).toLowerCase();
|
|
223
|
+
const lang = this.getLanguageFromExt(ext);
|
|
224
|
+
if (lang) {
|
|
225
|
+
result.languages.set(lang, (result.languages.get(lang) || 0) + 1);
|
|
226
|
+
}
|
|
227
|
+
// Count lines for text files
|
|
228
|
+
if (this.isTextFile(fullPath)) {
|
|
229
|
+
try {
|
|
230
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
231
|
+
result.lines += content.split('\n').length;
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Ignore read errors
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Ignore permission errors
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
walkDir(dir);
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
formatSize(bytes) {
|
|
248
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
249
|
+
let size = bytes;
|
|
250
|
+
let unitIndex = 0;
|
|
251
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
252
|
+
size /= 1024;
|
|
253
|
+
unitIndex++;
|
|
254
|
+
}
|
|
255
|
+
return `${size.toFixed(1)}${units[unitIndex]}`;
|
|
256
|
+
}
|
|
257
|
+
getLanguageFromExt(ext) {
|
|
258
|
+
const langMap = {
|
|
259
|
+
'.bash': 'Shell',
|
|
260
|
+
'.c': 'C',
|
|
261
|
+
'.cpp': 'C++',
|
|
262
|
+
'.cs': 'C#',
|
|
263
|
+
'.css': 'CSS',
|
|
264
|
+
'.dart': 'Dart',
|
|
265
|
+
'.go': 'Go',
|
|
266
|
+
'.html': 'HTML',
|
|
267
|
+
'.java': 'Java',
|
|
268
|
+
'.js': 'JavaScript',
|
|
269
|
+
'.json': 'JSON',
|
|
270
|
+
'.jsx': 'JavaScript',
|
|
271
|
+
'.kt': 'Kotlin',
|
|
272
|
+
'.less': 'Less',
|
|
273
|
+
'.m': 'Objective-C',
|
|
274
|
+
'.md': 'Markdown',
|
|
275
|
+
'.php': 'PHP',
|
|
276
|
+
'.py': 'Python',
|
|
277
|
+
'.r': 'R',
|
|
278
|
+
'.rb': 'Ruby',
|
|
279
|
+
'.rs': 'Rust',
|
|
280
|
+
'.sass': 'Sass',
|
|
281
|
+
'.scala': 'Scala',
|
|
282
|
+
'.scss': 'SCSS',
|
|
283
|
+
'.sh': 'Shell',
|
|
284
|
+
'.sql': 'SQL',
|
|
285
|
+
'.svelte': 'Svelte',
|
|
286
|
+
'.swift': 'Swift',
|
|
287
|
+
'.ts': 'TypeScript',
|
|
288
|
+
'.tsx': 'TypeScript',
|
|
289
|
+
'.vue': 'Vue',
|
|
290
|
+
'.xml': 'XML',
|
|
291
|
+
'.yaml': 'YAML',
|
|
292
|
+
'.yml': 'YAML',
|
|
293
|
+
};
|
|
294
|
+
return langMap[ext] || null;
|
|
295
|
+
}
|
|
296
|
+
isTextFile(filePath) {
|
|
297
|
+
const textExts = [
|
|
298
|
+
'.js',
|
|
299
|
+
'.jsx',
|
|
300
|
+
'.ts',
|
|
301
|
+
'.tsx',
|
|
302
|
+
'.json',
|
|
303
|
+
'.md',
|
|
304
|
+
'.txt',
|
|
305
|
+
'.yml',
|
|
306
|
+
'.yaml',
|
|
307
|
+
'.xml',
|
|
308
|
+
'.html',
|
|
309
|
+
'.css',
|
|
310
|
+
'.scss',
|
|
311
|
+
'.sass',
|
|
312
|
+
'.py',
|
|
313
|
+
'.rb',
|
|
314
|
+
'.go',
|
|
315
|
+
'.rs',
|
|
316
|
+
'.java',
|
|
317
|
+
'.c',
|
|
318
|
+
'.cpp',
|
|
319
|
+
'.h',
|
|
320
|
+
'.php',
|
|
321
|
+
'.sql',
|
|
322
|
+
'.sh',
|
|
323
|
+
'.bash',
|
|
324
|
+
'.env',
|
|
325
|
+
'.gitignore',
|
|
326
|
+
];
|
|
327
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
328
|
+
return textExts.includes(ext);
|
|
329
|
+
}
|
|
330
|
+
parseRelativeDate(relativeDate) {
|
|
331
|
+
const now = new Date();
|
|
332
|
+
const match = relativeDate.match(/(\d+)\s+(\w+)\s+ago/);
|
|
333
|
+
if (!match)
|
|
334
|
+
return now;
|
|
335
|
+
const value = Number.parseInt(match[1], 10);
|
|
336
|
+
const unit = match[2];
|
|
337
|
+
const date = new Date(now);
|
|
338
|
+
switch (unit) {
|
|
339
|
+
case 'day':
|
|
340
|
+
case 'days': {
|
|
341
|
+
date.setDate(date.getDate() - value);
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case 'hour':
|
|
345
|
+
case 'hours': {
|
|
346
|
+
date.setHours(date.getHours() - value);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case 'minute':
|
|
350
|
+
case 'minutes': {
|
|
351
|
+
date.setMinutes(date.getMinutes() - value);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case 'month':
|
|
355
|
+
case 'months': {
|
|
356
|
+
date.setMonth(date.getMonth() - value);
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case 'second':
|
|
360
|
+
case 'seconds': {
|
|
361
|
+
date.setSeconds(date.getSeconds() - value);
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
case 'week':
|
|
365
|
+
case 'weeks': {
|
|
366
|
+
date.setDate(date.getDate() - (value * 7));
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case 'year':
|
|
370
|
+
case 'years': {
|
|
371
|
+
date.setFullYear(date.getFullYear() - value);
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return date;
|
|
376
|
+
}
|
|
377
|
+
printJSON(workspaceStats, entityStats) {
|
|
378
|
+
const output = {
|
|
379
|
+
entities: entityStats.map(({ entity, stats }) => ({
|
|
380
|
+
name: entity.name,
|
|
381
|
+
path: entity.path,
|
|
382
|
+
stats: {
|
|
383
|
+
...stats,
|
|
384
|
+
languages: Object.fromEntries(stats.languages),
|
|
385
|
+
},
|
|
386
|
+
type: entity.type,
|
|
387
|
+
})),
|
|
388
|
+
workspace: {
|
|
389
|
+
...workspaceStats,
|
|
390
|
+
languages: Object.fromEntries(workspaceStats.languages),
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
this.log(JSON.stringify(output, null, 2));
|
|
394
|
+
}
|
|
395
|
+
printSummary(workspaceStats) {
|
|
396
|
+
this.log('');
|
|
397
|
+
this.log(chalk.bold.blue('Workspace Summary'));
|
|
398
|
+
this.log(chalk.dim('─'.repeat(40)));
|
|
399
|
+
this.log(`Entities: ${workspaceStats.totalEntities}`);
|
|
400
|
+
this.log(`Files: ${workspaceStats.totalFiles.toLocaleString()}`);
|
|
401
|
+
this.log(`Lines: ${workspaceStats.totalLines.toLocaleString()}`);
|
|
402
|
+
this.log(`Size: ${workspaceStats.totalSize}`);
|
|
403
|
+
if (workspaceStats.languages.size > 0) {
|
|
404
|
+
const topLang = [...workspaceStats.languages.entries()]
|
|
405
|
+
.sort((a, b) => b[1] - a[1])[0];
|
|
406
|
+
this.log(`Primary Language: ${topLang[0]}`);
|
|
407
|
+
}
|
|
408
|
+
this.log(`Most Active: ${workspaceStats.mostActive || 'N/A'}`);
|
|
409
|
+
this.log(`Largest: ${workspaceStats.largestEntity || 'N/A'}`);
|
|
410
|
+
}
|
|
411
|
+
printTable(workspaceStats, entityStats, detailed) {
|
|
412
|
+
// Workspace summary
|
|
413
|
+
this.log('');
|
|
414
|
+
this.log(chalk.bold.blue('📊 Workspace Insights'));
|
|
415
|
+
this.log('');
|
|
416
|
+
const summaryTable = new Table({
|
|
417
|
+
head: [chalk.cyan('Metric'), chalk.cyan('Value')],
|
|
418
|
+
style: { border: [], head: [] },
|
|
419
|
+
});
|
|
420
|
+
summaryTable.push(['Total Entities', workspaceStats.totalEntities.toString()], ['Total Files', workspaceStats.totalFiles.toLocaleString()], ['Total Lines', workspaceStats.totalLines.toLocaleString()], ['Total Size', workspaceStats.totalSize], ['Most Active', workspaceStats.mostActive || 'N/A'], ['Largest Entity', workspaceStats.largestEntity || 'N/A']);
|
|
421
|
+
this.log(summaryTable.toString());
|
|
422
|
+
// Language distribution
|
|
423
|
+
if (workspaceStats.languages.size > 0) {
|
|
424
|
+
this.log('');
|
|
425
|
+
this.log(chalk.bold('Language Distribution:'));
|
|
426
|
+
const sortedLangs = [...workspaceStats.languages.entries()]
|
|
427
|
+
.sort((a, b) => b[1] - a[1])
|
|
428
|
+
.slice(0, 5);
|
|
429
|
+
for (const [lang, count] of sortedLangs) {
|
|
430
|
+
const percentage = ((count / workspaceStats.totalFiles) * 100).toFixed(1);
|
|
431
|
+
const bar = '█'.repeat(Math.floor(Number.parseFloat(percentage) / 2));
|
|
432
|
+
this.log(` ${lang.padEnd(12)} ${bar} ${percentage}%`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Entity details
|
|
436
|
+
if (detailed) {
|
|
437
|
+
this.log('');
|
|
438
|
+
this.log(chalk.bold('Entity Details:'));
|
|
439
|
+
const entityTable = new Table({
|
|
440
|
+
head: [
|
|
441
|
+
chalk.cyan('Entity'),
|
|
442
|
+
chalk.cyan('Files'),
|
|
443
|
+
chalk.cyan('Lines'),
|
|
444
|
+
chalk.cyan('Size'),
|
|
445
|
+
chalk.cyan('Last Commit'),
|
|
446
|
+
chalk.cyan('Changes'),
|
|
447
|
+
],
|
|
448
|
+
style: { border: [], head: [] },
|
|
449
|
+
});
|
|
450
|
+
for (const { entity, stats } of entityStats) {
|
|
451
|
+
entityTable.push([
|
|
452
|
+
entity.name,
|
|
453
|
+
stats.files.toString(),
|
|
454
|
+
stats.lines.toLocaleString(),
|
|
455
|
+
stats.size,
|
|
456
|
+
stats.lastCommit,
|
|
457
|
+
stats.uncommitted > 0 ? chalk.yellow(stats.uncommitted.toString()) : '0',
|
|
458
|
+
]);
|
|
459
|
+
}
|
|
460
|
+
this.log(entityTable.toString());
|
|
461
|
+
}
|
|
462
|
+
this.log('');
|
|
463
|
+
this.log(chalk.dim('Tip: Use --detailed flag for per-entity statistics'));
|
|
464
|
+
}
|
|
465
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command.js';
|
|
2
|
+
export default class Patterns extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
entity: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
mode: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
private analyzeCollaborationPatterns;
|
|
13
|
+
private analyzeCrossEntityPatterns;
|
|
14
|
+
private analyzeEntityPatterns;
|
|
15
|
+
private analyzeFilePatterns;
|
|
16
|
+
private analyzeModePatterns;
|
|
17
|
+
private analyzePatterns;
|
|
18
|
+
private analyzeRelationshipPatterns;
|
|
19
|
+
private analyzeTypePatterns;
|
|
20
|
+
private calculateTypicalDuration;
|
|
21
|
+
private displayEntityPatterns;
|
|
22
|
+
private displayModePatterns;
|
|
23
|
+
private displayPatterns;
|
|
24
|
+
private displayProductivityPatterns;
|
|
25
|
+
private displayTemporalPatterns;
|
|
26
|
+
private displayTypePatterns;
|
|
27
|
+
private findDependencyChains;
|
|
28
|
+
private findSharedSystems;
|
|
29
|
+
private findSimilarClusters;
|
|
30
|
+
private findWorkflowSimilarities;
|
|
31
|
+
private generateProductivityPatterns;
|
|
32
|
+
private generateTemporalPatterns;
|
|
33
|
+
private getCommonModes;
|
|
34
|
+
private getCommonTools;
|
|
35
|
+
private getOptimalDuration;
|
|
36
|
+
private getSuccessIndicators;
|
|
37
|
+
private getSuccessMetrics;
|
|
38
|
+
private getTransitionPatterns;
|
|
39
|
+
private getTypicalActivities;
|
|
40
|
+
}
|