@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.
- package/README.md +1048 -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 +18 -0
- package/dist/commands/entity/clone-all.js +166 -0
- package/dist/commands/entity/clone.d.ts +17 -0
- package/dist/commands/entity/clone.js +132 -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 +2006 -0
- 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
|
+
}
|