@hyperdrive.bot/gut 0.1.8 → 0.1.10

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 (111) hide show
  1. package/README.md +1048 -1
  2. package/dist/base-command.d.ts +22 -0
  3. package/dist/base-command.js +99 -0
  4. package/dist/commands/add.d.ts +14 -0
  5. package/dist/commands/add.js +70 -0
  6. package/dist/commands/affected.d.ts +23 -0
  7. package/dist/commands/affected.js +323 -0
  8. package/dist/commands/audit.d.ts +33 -0
  9. package/dist/commands/audit.js +594 -0
  10. package/dist/commands/back.d.ts +6 -0
  11. package/dist/commands/back.js +29 -0
  12. package/dist/commands/checkout.d.ts +14 -0
  13. package/dist/commands/checkout.js +124 -0
  14. package/dist/commands/commit.d.ts +11 -0
  15. package/dist/commands/commit.js +107 -0
  16. package/dist/commands/context.d.ts +6 -0
  17. package/dist/commands/context.js +32 -0
  18. package/dist/commands/contexts.d.ts +7 -0
  19. package/dist/commands/contexts.js +88 -0
  20. package/dist/commands/deps.d.ts +10 -0
  21. package/dist/commands/deps.js +100 -0
  22. package/dist/commands/entity/add.d.ts +16 -0
  23. package/dist/commands/entity/add.js +103 -0
  24. package/dist/commands/entity/clone-all.d.ts +18 -0
  25. package/dist/commands/entity/clone-all.js +166 -0
  26. package/dist/commands/entity/clone.d.ts +17 -0
  27. package/dist/commands/entity/clone.js +132 -0
  28. package/dist/commands/entity/list.d.ts +11 -0
  29. package/dist/commands/entity/list.js +80 -0
  30. package/dist/commands/entity/remove.d.ts +12 -0
  31. package/dist/commands/entity/remove.js +54 -0
  32. package/dist/commands/extract.d.ts +35 -0
  33. package/dist/commands/extract.js +483 -0
  34. package/dist/commands/focus.d.ts +19 -0
  35. package/dist/commands/focus.js +137 -0
  36. package/dist/commands/graph.d.ts +18 -0
  37. package/dist/commands/graph.js +273 -0
  38. package/dist/commands/init.d.ts +11 -0
  39. package/dist/commands/init.js +75 -0
  40. package/dist/commands/insights.d.ts +21 -0
  41. package/dist/commands/insights.js +465 -0
  42. package/dist/commands/patterns.d.ts +40 -0
  43. package/dist/commands/patterns.js +405 -0
  44. package/dist/commands/pull.d.ts +11 -0
  45. package/dist/commands/pull.js +121 -0
  46. package/dist/commands/push.d.ts +11 -0
  47. package/dist/commands/push.js +97 -0
  48. package/dist/commands/quick-setup.d.ts +20 -0
  49. package/dist/commands/quick-setup.js +417 -0
  50. package/dist/commands/recent.d.ts +9 -0
  51. package/dist/commands/recent.js +51 -0
  52. package/dist/commands/related.d.ts +23 -0
  53. package/dist/commands/related.js +255 -0
  54. package/dist/commands/repos.d.ts +17 -0
  55. package/dist/commands/repos.js +184 -0
  56. package/dist/commands/stack.d.ts +10 -0
  57. package/dist/commands/stack.js +78 -0
  58. package/dist/commands/status.d.ts +13 -0
  59. package/dist/commands/status.js +193 -0
  60. package/dist/commands/sync.d.ts +11 -0
  61. package/dist/commands/sync.js +139 -0
  62. package/dist/commands/ticket/focus.d.ts +20 -0
  63. package/dist/commands/ticket/focus.js +217 -0
  64. package/dist/commands/ticket/get.d.ts +15 -0
  65. package/dist/commands/ticket/get.js +168 -0
  66. package/dist/commands/ticket/hint.d.ts +16 -0
  67. package/dist/commands/ticket/hint.js +147 -0
  68. package/dist/commands/ticket/index.d.ts +10 -0
  69. package/dist/commands/ticket/index.js +60 -0
  70. package/dist/commands/ticket/list.d.ts +13 -0
  71. package/dist/commands/ticket/list.js +120 -0
  72. package/dist/commands/ticket/sync.d.ts +14 -0
  73. package/dist/commands/ticket/sync.js +85 -0
  74. package/dist/commands/ticket/update.d.ts +17 -0
  75. package/dist/commands/ticket/update.js +142 -0
  76. package/dist/commands/unfocus.d.ts +6 -0
  77. package/dist/commands/unfocus.js +19 -0
  78. package/dist/commands/used-by.d.ts +13 -0
  79. package/dist/commands/used-by.js +110 -0
  80. package/dist/commands/workspace.d.ts +22 -0
  81. package/dist/commands/workspace.js +372 -0
  82. package/dist/index.d.ts +14 -0
  83. package/dist/index.js +16 -0
  84. package/dist/models/entity.model.d.ts +234 -0
  85. package/dist/models/entity.model.js +1 -0
  86. package/dist/models/ticket.model.d.ts +117 -0
  87. package/dist/models/ticket.model.js +43 -0
  88. package/dist/services/auth.service.d.ts +15 -0
  89. package/dist/services/auth.service.js +26 -0
  90. package/dist/services/config.service.d.ts +34 -0
  91. package/dist/services/config.service.js +234 -0
  92. package/dist/services/entity.service.d.ts +20 -0
  93. package/dist/services/entity.service.js +127 -0
  94. package/dist/services/focus.service.d.ts +71 -0
  95. package/dist/services/focus.service.js +614 -0
  96. package/dist/services/git.service.d.ts +39 -0
  97. package/dist/services/git.service.js +188 -0
  98. package/dist/services/gut-api.service.d.ts +53 -0
  99. package/dist/services/gut-api.service.js +99 -0
  100. package/dist/services/ticket.service.d.ts +84 -0
  101. package/dist/services/ticket.service.js +207 -0
  102. package/dist/utils/display.d.ts +26 -0
  103. package/dist/utils/display.js +145 -0
  104. package/dist/utils/filesystem.d.ts +32 -0
  105. package/dist/utils/filesystem.js +198 -0
  106. package/dist/utils/index.d.ts +13 -0
  107. package/dist/utils/index.js +14 -0
  108. package/dist/utils/validation.d.ts +22 -0
  109. package/dist/utils/validation.js +192 -0
  110. package/oclif.manifest.json +2006 -0
  111. package/package.json +11 -2
@@ -0,0 +1,417 @@
1
+ import { Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
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 QuickSetup extends BaseCommand {
10
+ static description = 'Automated workspace setup based on common patterns';
11
+ static examples = [
12
+ '<%= config.bin %> <%= command.id %>',
13
+ '<%= config.bin %> <%= command.id %> --auto',
14
+ '<%= config.bin %> <%= command.id %> --profile monorepo',
15
+ '<%= config.bin %> <%= command.id %> --scan-depth 3',
16
+ ];
17
+ static flags = {
18
+ auto: Flags.boolean({
19
+ char: 'a',
20
+ default: false,
21
+ description: 'Auto-detect and configure without prompts',
22
+ }),
23
+ 'clone-missing': Flags.boolean({
24
+ default: false,
25
+ description: 'Clone missing repositories if found',
26
+ }),
27
+ profile: Flags.string({
28
+ char: 'p',
29
+ description: 'Use specific setup profile',
30
+ options: ['monorepo', 'microservices', 'fullstack', 'library'],
31
+ }),
32
+ 'scan-depth': Flags.integer({
33
+ default: 2,
34
+ description: 'Directory depth to scan for entities',
35
+ }),
36
+ };
37
+ profiles = [
38
+ {
39
+ description: 'Monorepo with packages or apps',
40
+ name: 'monorepo',
41
+ patterns: {
42
+ client: ['clients/*/package.json', 'entities/clients/*/package.json'],
43
+ company: ['companies/*/package.json', 'entities/companies/*/package.json'],
44
+ delivery: ['packages/*/package.json', 'apps/*/package.json'],
45
+ initiative: ['initiatives/*/package.json', 'entities/initiatives/*/package.json'],
46
+ module: ['packages/*/package.json', 'libs/*/package.json'],
47
+ prospect: ['prospects/*/package.json', 'entities/prospects/*/package.json'],
48
+ service: ['services/*/package.json', 'apis/*/package.json'],
49
+ system: ['systems/*/package.json', 'shared/*/package.json'],
50
+ tool: ['tools/*/package.json', 'scripts/*/package.json'],
51
+ },
52
+ repositories: {
53
+ pattern: /package\.json/,
54
+ transform(match) {
55
+ const pkg = JSON.parse(fs.readFileSync(match, 'utf8'));
56
+ return pkg.repository?.url || pkg.repository || '';
57
+ },
58
+ },
59
+ },
60
+ {
61
+ description: 'Microservices architecture',
62
+ name: 'microservices',
63
+ patterns: {
64
+ client: ['clients/*/package.json'],
65
+ company: ['companies/*/package.json'],
66
+ delivery: ['frontend/package.json', 'web/package.json', 'mobile/package.json'],
67
+ initiative: ['initiatives/*/package.json'],
68
+ module: ['shared/*/package.json', 'common/*/package.json'],
69
+ prospect: ['prospects/*/package.json'],
70
+ service: ['services/*/package.json', 'services/*/pom.xml', 'services/*/go.mod'],
71
+ system: ['shared/*/package.json', 'common/*/package.json'],
72
+ tool: ['infrastructure/*/package.json', 'deployment/*/'],
73
+ },
74
+ },
75
+ {
76
+ description: 'Full-stack application',
77
+ name: 'fullstack',
78
+ patterns: {
79
+ client: ['clients/*/package.json'],
80
+ company: ['companies/*/package.json'],
81
+ delivery: ['frontend/package.json', 'client/package.json', 'web/package.json'],
82
+ initiative: ['initiatives/*/package.json'],
83
+ module: ['shared/package.json', 'common/package.json'],
84
+ prospect: ['prospects/*/package.json'],
85
+ service: ['backend/package.json', 'server/package.json', 'api/package.json'],
86
+ system: ['shared/package.json', 'common/package.json'],
87
+ tool: ['scripts/package.json', 'tools/package.json'],
88
+ },
89
+ },
90
+ {
91
+ description: 'Library or package development',
92
+ name: 'library',
93
+ patterns: {
94
+ client: [],
95
+ company: [],
96
+ delivery: ['examples/*/package.json', 'demo/*/package.json'],
97
+ initiative: [],
98
+ module: ['src/package.json', 'lib/package.json', 'package.json'],
99
+ prospect: [],
100
+ service: [],
101
+ system: ['package.json'],
102
+ tool: ['scripts/package.json', 'tools/package.json'],
103
+ },
104
+ },
105
+ ];
106
+ async run() {
107
+ const { flags } = await this.parse(QuickSetup);
108
+ const { auto, 'clone-missing': cloneMissing, profile, 'scan-depth': scanDepth } = flags;
109
+ const spinner = ora();
110
+ try {
111
+ // Check if workspace is already initialized
112
+ const config = await this.configService.loadConfig();
113
+ if (config.entities && Object.keys(config.entities).length > 0) {
114
+ const { proceed } = await inquirer.prompt([{
115
+ default: false,
116
+ message: 'Workspace already has entities configured. Continue setup?',
117
+ name: 'proceed',
118
+ type: 'confirm',
119
+ }]);
120
+ if (!proceed) {
121
+ this.log(chalk.yellow('Setup cancelled'));
122
+ return;
123
+ }
124
+ }
125
+ spinner.start('Scanning workspace structure');
126
+ // Detect workspace structure
127
+ const detectedEntities = await this.scanWorkspace(scanDepth);
128
+ spinner.succeed(`Found ${detectedEntities.length} potential entities`);
129
+ // Select or detect profile
130
+ let selectedProfile;
131
+ if (profile) {
132
+ selectedProfile = this.profiles.find(p => p.name === profile);
133
+ if (!selectedProfile) {
134
+ this.error(`Profile '${profile}' not found`);
135
+ }
136
+ }
137
+ else if (auto) {
138
+ selectedProfile = this.detectProfile(detectedEntities);
139
+ this.log(chalk.blue(`Auto-detected profile: ${selectedProfile.name}`));
140
+ }
141
+ else {
142
+ const { selectedProfileName } = await inquirer.prompt([{
143
+ choices: this.profiles.map(p => ({
144
+ name: `${p.name} - ${p.description}`,
145
+ value: p.name,
146
+ })),
147
+ message: 'Select workspace profile:',
148
+ name: 'selectedProfileName',
149
+ type: 'list',
150
+ }]);
151
+ selectedProfile = this.profiles.find(p => p.name === selectedProfileName);
152
+ }
153
+ // Categorize entities
154
+ const categorizedEntities = this.categorizeEntities(detectedEntities, selectedProfile);
155
+ // Show and confirm entities
156
+ if (!auto) {
157
+ this.printDetectedEntities(categorizedEntities);
158
+ const { confirmSetup } = await inquirer.prompt([{
159
+ default: true,
160
+ message: 'Proceed with this configuration?',
161
+ name: 'confirmSetup',
162
+ type: 'confirm',
163
+ }]);
164
+ if (!confirmSetup) {
165
+ this.log(chalk.yellow('Setup cancelled'));
166
+ return;
167
+ }
168
+ }
169
+ // Initialize workspace if needed
170
+ if (!config.workspace) {
171
+ spinner.start('Initializing workspace');
172
+ await this.configService.initWorkspace(process.cwd());
173
+ spinner.succeed('Workspace initialized');
174
+ }
175
+ // Add entities
176
+ spinner.start('Adding entities to configuration');
177
+ let addedCount = 0;
178
+ let skippedCount = 0;
179
+ for (const [type, entities] of Object.entries(categorizedEntities)) {
180
+ for (const entity of entities) {
181
+ if (!entity.name || !entity.path) {
182
+ continue;
183
+ }
184
+ const existing = await this.entityService.getEntity(entity.name);
185
+ if (existing) {
186
+ skippedCount++;
187
+ continue;
188
+ }
189
+ await this.entityService.addEntity(entity.name, type, entity.path, entity.repository);
190
+ addedCount++;
191
+ }
192
+ }
193
+ spinner.succeed(`Added ${addedCount} entities (${skippedCount} skipped)`);
194
+ // Clone missing repositories if requested
195
+ if (cloneMissing) {
196
+ const missingEntities = await this.findMissingEntities();
197
+ if (missingEntities.length > 0) {
198
+ spinner.start(`Cloning ${missingEntities.length} missing repositories`);
199
+ for (const entity of missingEntities) {
200
+ if (entity.repository) {
201
+ try {
202
+ execSync(`git clone ${entity.repository} ${entity.path}`, {
203
+ stdio: 'pipe',
204
+ });
205
+ }
206
+ catch {
207
+ // Continue on error
208
+ }
209
+ }
210
+ }
211
+ spinner.succeed('Cloning complete');
212
+ }
213
+ }
214
+ // Set initial focus
215
+ if (addedCount > 0) {
216
+ const allEntities = await this.entityService.listEntities();
217
+ const mainEntity = allEntities.find(e => e.name.includes('main') || e.name.includes('core') || e.name.includes('app')) || allEntities[0];
218
+ if (mainEntity) {
219
+ await this.focusService.setFocus([mainEntity.name]);
220
+ this.log(chalk.green(`āœ“ Initial focus set to: ${mainEntity.name}`));
221
+ }
222
+ }
223
+ // Print summary
224
+ this.printSetupSummary(addedCount, skippedCount);
225
+ // Show next steps
226
+ this.printNextSteps();
227
+ }
228
+ catch (error) {
229
+ spinner.fail();
230
+ this.error(error instanceof Error ? error.message : String(error));
231
+ }
232
+ }
233
+ categorizeEntities(entities, profile) {
234
+ const categorized = {
235
+ client: [],
236
+ company: [],
237
+ delivery: [],
238
+ initiative: [],
239
+ module: [],
240
+ prospect: [],
241
+ service: [],
242
+ system: [],
243
+ tool: [],
244
+ };
245
+ for (const entity of entities) {
246
+ let matched = false;
247
+ // Try to match against profile patterns
248
+ for (const [type, patterns] of Object.entries(profile.patterns)) {
249
+ for (const pattern of patterns) {
250
+ const glob = pattern.replaceAll('*', '[^/]+');
251
+ const regex = new RegExp(`^${glob}$`);
252
+ if (regex.test(entity.path + '/package.json')) {
253
+ categorized[type].push(entity);
254
+ matched = true;
255
+ break;
256
+ }
257
+ }
258
+ if (matched)
259
+ break;
260
+ }
261
+ // If no match, categorize based on path/name
262
+ if (!matched) {
263
+ const pathLower = (entity.path || '').toLowerCase();
264
+ if (pathLower.includes('frontend') || pathLower.includes('client')
265
+ || pathLower.includes('web') || pathLower.includes('app')) {
266
+ categorized.delivery.push(entity);
267
+ }
268
+ else if (pathLower.includes('service') || pathLower.includes('api')
269
+ || pathLower.includes('backend') || pathLower.includes('server')) {
270
+ categorized.service.push(entity);
271
+ }
272
+ else if (pathLower.includes('lib') || pathLower.includes('shared')
273
+ || pathLower.includes('common') || pathLower.includes('core')) {
274
+ categorized.module.push(entity);
275
+ }
276
+ else if (pathLower.includes('tool') || pathLower.includes('script')
277
+ || pathLower.includes('util') || pathLower.includes('infra')) {
278
+ categorized.tool.push(entity);
279
+ }
280
+ else {
281
+ // Default to module
282
+ categorized.module.push(entity);
283
+ }
284
+ }
285
+ }
286
+ return categorized;
287
+ }
288
+ detectProfile(entities) {
289
+ const pathPatterns = entities.map(e => e.path || '');
290
+ // Check for monorepo patterns
291
+ const hasPackages = pathPatterns.some(p => p.startsWith('packages/'));
292
+ const hasApps = pathPatterns.some(p => p.startsWith('apps/'));
293
+ if (hasPackages || hasApps) {
294
+ return this.profiles[0]; // monorepo
295
+ }
296
+ // Check for microservices patterns
297
+ const hasServices = pathPatterns.some(p => p.startsWith('services/'));
298
+ if (hasServices) {
299
+ return this.profiles[1]; // microservices
300
+ }
301
+ // Check for fullstack patterns
302
+ const hasFrontend = pathPatterns.some(p => p.includes('frontend') || p.includes('client') || p.includes('web'));
303
+ const hasBackend = pathPatterns.some(p => p.includes('backend') || p.includes('server') || p.includes('api'));
304
+ if (hasFrontend && hasBackend) {
305
+ return this.profiles[2]; // fullstack
306
+ }
307
+ // Default to library
308
+ return this.profiles[3];
309
+ }
310
+ async findMissingEntities() {
311
+ const entities = await this.entityService.listEntities();
312
+ return entities.filter(e => !fs.existsSync(e.path));
313
+ }
314
+ printDetectedEntities(categorized) {
315
+ this.log('');
316
+ this.log(chalk.bold('Detected Entities:'));
317
+ this.log('');
318
+ for (const [type, entities] of Object.entries(categorized)) {
319
+ if (entities.length === 0)
320
+ continue;
321
+ this.log(chalk.cyan(` ${type}:`));
322
+ for (const e of entities) {
323
+ const repo = e.repository ? chalk.dim(` (${e.repository})`) : '';
324
+ this.log(` - ${e.name} ${chalk.dim(`[${e.path}]`)}${repo}`);
325
+ }
326
+ }
327
+ }
328
+ printNextSteps() {
329
+ this.log('');
330
+ this.log(chalk.bold('Next Steps:'));
331
+ this.log('');
332
+ this.log(' 1. List all entities:');
333
+ this.log(chalk.dim(' gut entity list'));
334
+ this.log('');
335
+ this.log(' 2. Set focus on specific entities:');
336
+ this.log(chalk.dim(' gut focus [entity-name]'));
337
+ this.log('');
338
+ this.log(' 3. Check status across focused entities:');
339
+ this.log(chalk.dim(' gut status'));
340
+ this.log('');
341
+ this.log(' 4. View workspace insights:');
342
+ this.log(chalk.dim(' gut insights'));
343
+ this.log('');
344
+ this.log(' 5. Clone missing repositories:');
345
+ this.log(chalk.dim(' gut entity clone-all'));
346
+ }
347
+ printSetupSummary(added, skipped) {
348
+ this.log('');
349
+ this.log(chalk.bold.green('āœ“ Setup Complete!'));
350
+ this.log('');
351
+ this.log(chalk.dim('Summary:'));
352
+ this.log(` • Entities added: ${added}`);
353
+ this.log(` • Entities skipped: ${skipped}`);
354
+ this.log(` • Total entities: ${added + skipped}`);
355
+ }
356
+ async scanWorkspace(maxDepth) {
357
+ const entities = [];
358
+ const seen = new Set();
359
+ const scan = (dir, depth) => {
360
+ if (depth > maxDepth)
361
+ return;
362
+ try {
363
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
364
+ for (const entry of entries) {
365
+ if (!entry.isDirectory())
366
+ continue;
367
+ const { name } = entry;
368
+ if (name.startsWith('.') || name === 'node_modules' || name === 'dist' || name === 'build') {
369
+ continue;
370
+ }
371
+ const fullPath = path.join(dir, name);
372
+ const relativePath = path.relative(process.cwd(), fullPath);
373
+ // Check for entity indicators
374
+ const indicators = [
375
+ 'package.json',
376
+ 'pom.xml',
377
+ 'go.mod',
378
+ 'Cargo.toml',
379
+ 'requirements.txt',
380
+ 'Gemfile',
381
+ 'build.gradle',
382
+ ];
383
+ const hasIndicator = indicators.some(ind => fs.existsSync(path.join(fullPath, ind)));
384
+ if (hasIndicator && !seen.has(relativePath)) {
385
+ seen.add(relativePath);
386
+ // Try to determine name and repository
387
+ let entityName = name;
388
+ let repository;
389
+ const pkgPath = path.join(fullPath, 'package.json');
390
+ if (fs.existsSync(pkgPath)) {
391
+ try {
392
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
393
+ entityName = pkg.name || name;
394
+ repository = pkg.repository?.url || pkg.repository;
395
+ }
396
+ catch {
397
+ // Use directory name
398
+ }
399
+ }
400
+ entities.push({
401
+ name: entityName,
402
+ path: relativePath,
403
+ repository,
404
+ });
405
+ }
406
+ // Recurse
407
+ scan(fullPath, depth + 1);
408
+ }
409
+ }
410
+ catch {
411
+ // Ignore permission errors
412
+ }
413
+ };
414
+ scan(process.cwd(), 0);
415
+ return entities;
416
+ }
417
+ }
@@ -0,0 +1,9 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class Recent extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,51 @@
1
+ import { Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { BaseCommand } from '../base-command.js';
4
+ export default class Recent extends BaseCommand {
5
+ static description = 'Show recent focus history';
6
+ static examples = [
7
+ '<%= config.bin %> <%= command.id %>',
8
+ '<%= config.bin %> <%= command.id %> --limit 10',
9
+ ];
10
+ static flags = {
11
+ limit: Flags.integer({
12
+ char: 'l',
13
+ default: 5,
14
+ description: 'Number of recent items to show',
15
+ }),
16
+ };
17
+ async run() {
18
+ const { flags } = await this.parse(Recent);
19
+ const { configService } = this;
20
+ const history = configService.getHistory();
21
+ const focusedEntities = await this.focusService.getFocusedEntities();
22
+ const currentFocusNames = focusedEntities.map(e => e.name);
23
+ if (history.length === 0) {
24
+ this.log(chalk.yellow('No focus history available'));
25
+ return;
26
+ }
27
+ this.log(chalk.bold('\nšŸ“œ Recent Focus History'));
28
+ this.log(chalk.dim('─'.repeat(50)));
29
+ const limit = Math.min(flags.limit, history.length);
30
+ for (let i = 0; i < limit; i++) {
31
+ const entry = history[i];
32
+ const isCurrent = i === 0
33
+ && currentFocusNames.length === entry.entities.length
34
+ && currentFocusNames.every(e => entry.entities.includes(e));
35
+ const marker = isCurrent ? chalk.green('ā–ø') : chalk.dim('•');
36
+ const label = isCurrent ? chalk.green(' (current)') : '';
37
+ const timestamp = new Date(entry.timestamp).toLocaleString();
38
+ this.log(`\n${marker} ${chalk.bold(entry.entities.join(', '))}${label}`);
39
+ this.log(` ${chalk.dim('Time:')} ${timestamp}`);
40
+ if (i === 0 && isCurrent) {
41
+ this.log(` ${chalk.dim('Status:')} ${chalk.green('Active')}`);
42
+ }
43
+ }
44
+ this.log(chalk.dim('\n─'.repeat(50)));
45
+ this.log(`${chalk.dim('Total history entries:')} ${history.length}`);
46
+ if (history.length > limit) {
47
+ this.log(chalk.dim(`Showing ${limit} of ${history.length} entries`));
48
+ }
49
+ this.log(chalk.dim('\nTip: Use "gut back" to switch to previous focus'));
50
+ }
51
+ }
@@ -0,0 +1,23 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class Related extends BaseCommand {
3
+ static args: {
4
+ entity: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ detailed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ threshold: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ private analyzeRelation;
15
+ private calculateScore;
16
+ private extractDependencies;
17
+ private findDependencyRelations;
18
+ private findGitReferences;
19
+ private findSharedFiles;
20
+ private getFileList;
21
+ private isSimilarFile;
22
+ private printRelations;
23
+ }